Add README.md, BEDIENUNGSANLEITUNG.md, and setup/start scripts
README.md gives the precise, reproducible install path (tested the two scripts against the live instance - both idempotent, correctly detect already-registered credentials/models). BEDIENUNGSANLEITUNG.md covers daily usage: notebooks/sources, the three chat context modes (and why "nur Erkenntnisse" needs a transformation run first), podcasts, voice cloning, and troubleshooting for the issues actually hit during setup (num_ctx truncation, embedding GPU contention, ufw). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
778f820e27
commit
7838863dbf
4 changed files with 652 additions and 0 deletions
278
BEDIENUNGSANLEITUNG.md
Normal file
278
BEDIENUNGSANLEITUNG.md
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
# Bedienungsanleitung — Open Notebook (lokales Deployment)
|
||||||
|
|
||||||
|
Diese Anleitung deckt die **tägliche Benutzung** ab. Installation/Neuaufbau:
|
||||||
|
siehe [`README.md`](README.md). Technische Hintergründe und Architektur:
|
||||||
|
siehe [`CLAUDE.md`](CLAUDE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inhalt
|
||||||
|
|
||||||
|
1. [Zugriff](#1-zugriff)
|
||||||
|
2. [Notebooks und Quellen](#2-notebooks-und-quellen)
|
||||||
|
3. [Chat: die drei Kontext-Modi](#3-chat-die-drei-kontext-modi)
|
||||||
|
4. [Podcasts erzeugen](#4-podcasts-erzeugen)
|
||||||
|
5. [Stimmklonung](#5-stimmklonung)
|
||||||
|
6. [Modelle und Provider verwalten](#6-modelle-und-provider-verwalten)
|
||||||
|
7. [Dienste starten/stoppen](#7-dienste-startenstoppen)
|
||||||
|
8. [Troubleshooting](#8-troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Zugriff
|
||||||
|
|
||||||
|
- **Web-Oberfläche:** `http://localhost:8502`
|
||||||
|
- **REST-API** (für Automatisierung/Skripte): `http://localhost:5055`, Doku unter
|
||||||
|
`http://localhost:5055/docs`
|
||||||
|
|
||||||
|
Beide sind nur von diesem Rechner aus erreichbar (`127.0.0.1`), nicht aus dem Netzwerk.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Notebooks und Quellen
|
||||||
|
|
||||||
|
Ein **Notebook** bündelt Quellen, Notizen, Chats und Podcasts zu einem Thema. Neues Notebook
|
||||||
|
über „+ New Notebook" in der UI anlegen.
|
||||||
|
|
||||||
|
### Quellen hinzufügen
|
||||||
|
|
||||||
|
Unterstützt: hochgeladene Dateien (PDF, EPUB, TXT, DOCX, …), URLs, eingefügter Text.
|
||||||
|
|
||||||
|
**Wie lang darf eine Datei sein?** Es gibt kein hartes Limit für Upload/Verarbeitung — auch
|
||||||
|
sehr große PDFs/EPUBs werden vollständig extrahiert und in Textabschnitte („Chunks")
|
||||||
|
zerlegt, die für die Suche eingebettet werden. Eine Längenbegrenzung greift nur, wenn eine
|
||||||
|
Quelle im **„Volltext"-Kontextmodus** (siehe Abschnitt 3) oder für eine **Transformation**
|
||||||
|
verwendet wird — dort geht der komplette Text in einem Stück an das Sprachmodell. Praktischer
|
||||||
|
Richtwert bei `qwen3.5:27b` (98.304 Tokens Kontext, siehe Abschnitt 6): grob **180–350
|
||||||
|
Buchseiten**, abhängig von Textdichte. Wichtig: Die **Dateigröße in MB sagt nichts über die
|
||||||
|
Textmenge aus** — ein gescanntes PDF mit vielen Bildern kann riesig sein, aber nach der
|
||||||
|
Texterkennung kaum Inhalt liefern.
|
||||||
|
|
||||||
|
### Verarbeitungsstatus prüfen
|
||||||
|
|
||||||
|
Nach dem Hochladen läuft die Verarbeitung (Extraktion → Chunking → Embedding) im Hintergrund.
|
||||||
|
Bei großen Dateien kann das ein bis mehrere Minuten dauern. Status ist in der UI an der Quelle
|
||||||
|
sichtbar; per API: `GET /api/sources/{source_id}/status`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Chat: die drei Kontext-Modi
|
||||||
|
|
||||||
|
Beim Chatten mit einem Notebook lässt sich pro Quelle einstellen, wie viel davon in den
|
||||||
|
Chat-Kontext einfließt (Buttons/Auswahl in der UI, z. B. als Sammel-Aktion für alle Quellen):
|
||||||
|
|
||||||
|
| UI-Option | Bedeutung | Geschwindigkeit |
|
||||||
|
|---|---|---|
|
||||||
|
| **Alle aus dem Kontext ausschließen** | Quelle wird ignoriert | – |
|
||||||
|
| **Alle aufnehmen (nur Erkenntnisse)** | Nutzt die generierten „Insights" (Zusammenfassungen) der Quelle | schnell (Sekunden) |
|
||||||
|
| **Alle aufnehmen (vollständiger Inhalt)** | Kompletter Text der Quelle geht an das Sprachmodell | langsam bei langen Dokumenten (bis zu einigen Minuten) |
|
||||||
|
|
||||||
|
### Wichtig: „nur Erkenntnisse" braucht vorher eine Transformation
|
||||||
|
|
||||||
|
Der Modus „nur Erkenntnisse" ist **nur dann nützlich, wenn für die Quelle bereits eine
|
||||||
|
Zusammenfassung („Insight") erzeugt wurde**. Ohne das liefert er praktisch nichts (nur Titel/
|
||||||
|
Metadaten). Eine Zusammenfassung erzeugen:
|
||||||
|
|
||||||
|
- In der UI: bei der Quelle eine Transformation anwenden, z. B. **„Simple Summary"**
|
||||||
|
(für die meisten Fälle passend) oder „Dense Summary", „Key Insights", „Table of Contents".
|
||||||
|
- Per API:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5055/api/sources/{source_id}/insights \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"transformation_id": "<id aus GET /api/transformations>"}'
|
||||||
|
```
|
||||||
|
- Bei langen Dokumenten (z. B. ein ganzes Buch) dauert das einmalig **einige Minuten** (die
|
||||||
|
gesamte Quelle wird einmal durchs Sprachmodell geschickt) — danach ist „nur Erkenntnisse"
|
||||||
|
aber dauerhaft schnell nutzbar.
|
||||||
|
|
||||||
|
### Faustregel
|
||||||
|
|
||||||
|
- **Normale Fragen, Überblick, grobe Zusammenhänge** → „nur Erkenntnisse" (nach einmaliger
|
||||||
|
Zusammenfassung)
|
||||||
|
- **Sehr spezifische Detailfrage** (z. B. exaktes Zitat aus einem bestimmten Kapitel), die in
|
||||||
|
der Zusammenfassung fehlen könnte → „vollständiger Inhalt", dafür Geduld bei langen Quellen
|
||||||
|
- **Quelle für die aktuelle Frage irrelevant** → ausschließen (spart Zeit bei mehreren Quellen)
|
||||||
|
|
||||||
|
Für gezielte Detailsuche über viele/lange Quellen gibt es außerdem eine separate
|
||||||
|
**Such-/Ask-Funktion**, die per Embedding-Ähnlichkeitssuche automatisch nur die relevanten
|
||||||
|
Textstellen findet, statt ganze Dokumente einzubeziehen — meist die schnellste und treffsicherste
|
||||||
|
Option für Detailfragen in langen Werken.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Podcasts erzeugen
|
||||||
|
|
||||||
|
Open Notebook kann aus Notebook-Inhalten automatisch einen Podcast (Dialog zwischen ein oder
|
||||||
|
mehreren Sprechern) erzeugen — Text via Sprachmodell, Audio via TTS (hier: lokal, Chatterbox).
|
||||||
|
|
||||||
|
1. **Episode-Profil** wählen (bestimmt Sprecherzahl/-rollen, Anzahl Segmente, Textmodell):
|
||||||
|
`solo_expert` (1 Sprecher), `tech_discussion` (2 Sprecher), `business_analysis` (3 Sprecher).
|
||||||
|
2. Notebook/Quelle als Grundlage angeben.
|
||||||
|
3. Generierung starten — läuft asynchron (Outline → Transkript → Sprachsynthese pro Segment).
|
||||||
|
Bei mehreren Sprechern und langen Transkripten realistisch **mehrere Minuten**, da jedes
|
||||||
|
Dialogsegment einzeln per TTS synthetisiert wird.
|
||||||
|
4. Fertige Episode in der UI abspielbar/herunterladbar.
|
||||||
|
|
||||||
|
Alle Episode-/Sprecherprofile sind bereits auf lokale Modelle (`qwen3.5:27b` für Text,
|
||||||
|
Chatterbox für Sprache) umgestellt — keine zusätzlichen Kosten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Stimmklonung
|
||||||
|
|
||||||
|
Die TTS-Wrapper (`services/tts_server.py`) unterstützt Stimmklonung: Referenz-WAV rein, Stimme
|
||||||
|
raus. Aktuell hinterlegte Stimmen: `default` (eigene Stimme), `male_thorsten` und
|
||||||
|
`female_kerstin` (offen lizenzierte, synthetische Piper-Stimmen — bewusst **keine** ungefragt
|
||||||
|
geklonten echten Personen, siehe [`CLAUDE.md`](CLAUDE.md)).
|
||||||
|
|
||||||
|
### Eine weitere Stimme hinzufügen
|
||||||
|
|
||||||
|
1. Referenz-Aufnahme besorgen: **10–30 Sekunden**, eine Person, saubere Aufnahme, WAV-Format.
|
||||||
|
Rechtlich sauber ist entweder eine eigene Aufnahme, eine Aufnahme mit ausdrücklicher
|
||||||
|
Zustimmung der Person, oder eine offen für diesen Zweck lizenzierte Stimme (z. B. weitere
|
||||||
|
[Piper-Stimmen](https://huggingface.co/rhasspy/piper-voices)).
|
||||||
|
2. Datei nach `services/voices/<name>.wav` legen (z. B. `services/voices/elena.wav`).
|
||||||
|
3. TTS-Server neu starten (Stimmen werden nur beim Start eingelesen):
|
||||||
|
```bash
|
||||||
|
pkill -f "python tts_server.py"
|
||||||
|
./scripts/start_services.sh
|
||||||
|
```
|
||||||
|
4. Prüfen: `curl http://127.0.0.1:8901/health` sollte `<name>` in der `voices`-Liste zeigen.
|
||||||
|
5. In einem Sprecherprofil (Podcast) den betreffenden Sprecher auf `voice_id: "<name>"` setzen,
|
||||||
|
z. B. per API:
|
||||||
|
```bash
|
||||||
|
curl -X PUT http://127.0.0.1:5055/api/speaker-profiles/{id} \
|
||||||
|
-H "Content-Type: application/json" -d '{... "speakers": [{"name": "Elena", ..., "voice_id": "elena"}], ...}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Mit Piper lässt sich auch selbst eine Referenz-Stimme synthetisieren (statt eine echte Aufnahme
|
||||||
|
zu verwenden):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "Ein Beispielsatz auf Deutsch." | piper --model <stimme>.onnx --output_file services/voices/<name>.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
Weitere deutsche Piper-Stimmen: `huggingface.co/rhasspy/piper-voices/tree/main/de/de_DE`
|
||||||
|
(Lizenz jeweils im `MODEL_CARD` prüfen).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Modelle und Provider verwalten
|
||||||
|
|
||||||
|
Übersicht unter **Settings → AI Providers / Models** in der UI, oder per API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:5055/api/models | python3 -m json.tool # alle registrierten Modelle
|
||||||
|
curl http://127.0.0.1:5055/api/models/defaults | python3 -m json.tool # aktuelle Standardmodelle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Standardmodelle ändern
|
||||||
|
|
||||||
|
Sieben Rollen lassen sich unabhängig belegen: Chat, Transformation, großer Kontext, Embedding,
|
||||||
|
Tools, Text-to-Speech, Speech-to-Text.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X PUT http://127.0.0.1:5055/api/models/defaults \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"default_tools_model": "<model_id>"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Endpunkt aktualisiert nur die tatsächlich mitgeschickten Felder — nicht angegebene Felder
|
||||||
|
bleiben unverändert, es muss also nicht der komplette Satz aller sieben Modelle mitgeschickt
|
||||||
|
werden.
|
||||||
|
|
||||||
|
### Kostenübersicht der aktuell registrierten Modelle
|
||||||
|
|
||||||
|
| Modell | Provider | Kosten |
|
||||||
|
|---|---|---|
|
||||||
|
| `qwen3.5:27b` | Ollama (lokal) | 0 € (Standard für Chat/Tools/großer Kontext) |
|
||||||
|
| `nomic-embed-text` | Ollama (lokal) | 0 € (Standard-Embedding) |
|
||||||
|
| `qwen3-coder-30b-128k` | Ollama (lokal) | 0 € (optional, für Coding-Aufgaben) |
|
||||||
|
| Chatterbox TTS / faster-whisper | lokal | 0 € |
|
||||||
|
| `anthropic/claude-sonnet-5` | OpenRouter | ~2$/10$ pro Mio. Tokens (Prompt/Antwort) |
|
||||||
|
| `qwen/qwen3-max` | OpenRouter | ~0,78$/3,90$ pro Mio. Tokens — günstigere Cloud-Alternative |
|
||||||
|
| `google/gemini-3.5-flash` | OpenRouter | ~1,50$/9$ pro Mio. Tokens — 1M Tokens Kontext |
|
||||||
|
|
||||||
|
Weitere OpenRouter-Modelle lassen sich jederzeit über `POST /api/models` registrieren
|
||||||
|
(`provider: "openrouter"`) — aktuelle Preise: `https://openrouter.ai/api/v1/models`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Dienste starten/stoppen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open Notebook (Docker)
|
||||||
|
docker compose up -d
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# TTS/STT (lokale Hintergrundprozesse, nicht in Docker)
|
||||||
|
./scripts/start_services.sh
|
||||||
|
pkill -f "python tts_server.py"
|
||||||
|
pkill -f "python stt_server.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ollama selbst läuft als systemd-Dienst und muss normalerweise nicht manuell verwaltet werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl status ollama
|
||||||
|
ollama ps # aktuell geladene Modelle + GPU/CPU-Verteilung
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Troubleshooting
|
||||||
|
|
||||||
|
### „Chat gibt keine Antwort" (leere Antwort, kein Fehler)
|
||||||
|
|
||||||
|
Meist ein zu kleiner Kontext (`num_ctx`) am Ollama-Chat-Credential — siehe README, Abschnitt 2.6.
|
||||||
|
Prüfen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:5055/api/credentials | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
for c in json.load(sys.stdin):
|
||||||
|
if c['provider']=='ollama': print(c['name'], c['num_ctx'])"
|
||||||
|
```
|
||||||
|
|
||||||
|
Sollte `98304` (oder höher, dann ggf. auf GPU-Auslastung achten) zeigen, nicht `None`/`null`.
|
||||||
|
|
||||||
|
### Quelle hängt beim Verarbeiten / Einbetten fest (Endlos-Retry)
|
||||||
|
|
||||||
|
Meist Ressourcen-Engpass beim Embedding-Modell (GPU voll, Modell läuft teilweise auf CPU,
|
||||||
|
Timeout). Prüfen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ollama ps # PROCESSOR-Spalte: sollte "100% GPU" zeigen, nicht "xx%/yy% CPU/GPU"
|
||||||
|
```
|
||||||
|
|
||||||
|
Falls CPU-Anteil: Embedding-Modell ist zu groß für den freien GPU-Speicher. Standardmäßig ist
|
||||||
|
hier das kleine `nomic-embed-text` (274 MB) konfiguriert, das läuft normalerweise problemlos
|
||||||
|
komplett auf der GPU.
|
||||||
|
|
||||||
|
### Antwort dauert sehr lange (mehrere Minuten)
|
||||||
|
|
||||||
|
Meist normal bei „vollständiger Inhalt"-Modus auf langen Dokumenten (siehe Abschnitt 3) — das
|
||||||
|
Sprachmodell muss den kompletten Text lesen. Für Detailfragen auf lange Sicht besser die
|
||||||
|
Such-/Ask-Funktion oder „nur Erkenntnisse" nach einer Zusammenfassung nutzen.
|
||||||
|
|
||||||
|
### TTS/STT vom Container aus nicht erreichbar
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec open_notebook curl -sf http://host.docker.internal:8901/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Bei Timeout: `ufw`-Regeln fehlen (siehe README 2.4) oder die Server laufen nicht
|
||||||
|
(`./scripts/start_services.sh`).
|
||||||
|
|
||||||
|
### Podcast-Profil zeigt „Einrichtung erforderlich"
|
||||||
|
|
||||||
|
Das Episode- oder Sprecherprofil verweist auf ein Modell ohne konfiguriertes Credential
|
||||||
|
(z. B. nach einem Reset auf die mitgelieferten OpenAI-Standardprofile). Modell-IDs über
|
||||||
|
`PUT /api/episode-profiles/{id}` (`outline_llm`, `transcript_llm`) bzw.
|
||||||
|
`PUT /api/speaker-profiles/{id}` (`voice_model`) auf registrierte lokale/OpenRouter-Modelle
|
||||||
|
umstellen — siehe `scripts/setup_models.sh` für die IDs der Standardmodelle.
|
||||||
|
|
||||||
|
### Weitere technische Details
|
||||||
|
|
||||||
|
Siehe [`CLAUDE.md`](CLAUDE.md) — dort stehen alle bisher aufgetretenen Probleme mit exakter
|
||||||
|
Ursache (Codestellen, Bibliotheks-Bugs) dokumentiert.
|
||||||
206
README.md
Normal file
206
README.md
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
# Open Notebook (lokales Deployment)
|
||||||
|
|
||||||
|
Selbst gehostetes [Open Notebook](https://github.com/lfnovo/open-notebook) per Docker Compose —
|
||||||
|
KI-gestützter Notiz-/Recherche-Assistent mit Chat, Quellenverwaltung und Podcast-Erstellung.
|
||||||
|
|
||||||
|
**Nur zwei KI-Provider:**
|
||||||
|
- **Ollama** (lokal, auf dem Host, GPU 1+2 — RTX 3090) für Chat, Embedding, Tools
|
||||||
|
- **OpenRouter** (Cloud, einziger externer Provider) als Alternative/Ergänzung
|
||||||
|
|
||||||
|
Text-to-Speech und Speech-to-Text laufen ebenfalls lokal: [Chatterbox TTS](https://github.com/resemble-ai/chatterbox)
|
||||||
|
(mit Stimmklonung) und [faster-whisper](https://github.com/SYSTRAN/faster-whisper), beide über
|
||||||
|
schlanke selbstgeschriebene OpenAI-kompatible HTTP-Wrapper in `services/`.
|
||||||
|
|
||||||
|
Ausführliche Bedienung (Chat-Modi, Podcasts, Stimmen, Troubleshooting): siehe
|
||||||
|
[`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md). Architektur- und Betriebsdetails für
|
||||||
|
Weiterentwicklung: siehe [`CLAUDE.md`](CLAUDE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
| Komponente | Läuft wo | Zweck |
|
||||||
|
|---|---|---|
|
||||||
|
| `surrealdb` | Docker | Datenbank (nur `127.0.0.1:8000`) |
|
||||||
|
| `open_notebook` | Docker | Web-UI (`:8502`) + REST-API (`:5055`) |
|
||||||
|
| Ollama | Host (systemd) | Chat-/Embedding-Modelle, GPU 1+2 |
|
||||||
|
| `services/tts_server.py` | Host (Hintergrundprozess) | Chatterbox TTS, GPU 2, Port `8901` |
|
||||||
|
| `services/stt_server.py` | Host (Hintergrundprozess) | faster-whisper STT, GPU 2, Port `8902` |
|
||||||
|
|
||||||
|
Der `open_notebook`-Container erreicht Ollama und die TTS/STT-Wrapper über
|
||||||
|
`host.docker.internal` (Linux-Route via `extra_hosts: host-gateway`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Voraussetzungen
|
||||||
|
|
||||||
|
- **Docker** + Docker Compose (Plugin), Daemon läuft
|
||||||
|
- **Ollama** installiert und als Dienst aktiv (`systemctl status ollama`), erreichbar auf `11434`
|
||||||
|
- Empfohlene systemd-Umgebung für Multi-GPU-Setups mit dedizierter Desktop-GPU:
|
||||||
|
`CUDA_VISIBLE_DEVICES=1,2` (GPU 0 ausschließen), `OLLAMA_KEEP_ALIVE=-1`,
|
||||||
|
`OLLAMA_CONTEXT_LENGTH=131072`
|
||||||
|
- Benötigte Modelle: `qwen3.5:27b`, `nomic-embed-text` (weitere optional, siehe
|
||||||
|
`scripts/setup_models.sh`) — mit `ollama pull <modell>` laden
|
||||||
|
- **Python-Umgebungen für TTS/STT** (bereits vorhandene Installationen dieses Systems):
|
||||||
|
- Conda-Env `chatterbox` mit [chatterbox-tts](https://github.com/resemble-ai/chatterbox)
|
||||||
|
(`~/miniforge3/envs/chatterbox`, siehe `~/chatterbox-tts-cli/`)
|
||||||
|
- System-Python mit `faster-whisper`, `fastapi`, `uvicorn`
|
||||||
|
- **OPENROUTER_API_KEY** als Umgebungsvariable im Environment verfügbar (für die Secret-Generierung
|
||||||
|
in Schritt 2)
|
||||||
|
- **`ufw`** (falls als Firewall aktiv) — Standardrichtlinie „deny incoming“ wird vorausgesetzt
|
||||||
|
- **`openssl`**, **`ffmpeg`**, **`curl`**, **`python3`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Installation
|
||||||
|
|
||||||
|
### 2.1 Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/open-notebook # bzw. Zielverzeichnis
|
||||||
|
git status # falls schon vorhanden: prüfen statt neu klonen
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Secrets erzeugen (`.env`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ENC_KEY=$(openssl rand -base64 32)
|
||||||
|
DB_PASS=$(openssl rand -base64 24 | tr -d '/+=')
|
||||||
|
cat > .env <<EOF
|
||||||
|
OPEN_NOTEBOOK_ENCRYPTION_KEY=${ENC_KEY}
|
||||||
|
SURREAL_USER=root
|
||||||
|
SURREAL_PASSWORD=${DB_PASS}
|
||||||
|
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||||
|
EOF
|
||||||
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
|
`.env` wird nicht committed (siehe `.gitignore`). `.env.example` ist das Template.
|
||||||
|
|
||||||
|
### 2.3 Container starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
docker compose ps # beide Container sollten "Up" sein
|
||||||
|
```
|
||||||
|
|
||||||
|
Web-UI: `http://localhost:8502` · REST-API: `http://localhost:5055` (beide nur `127.0.0.1`).
|
||||||
|
|
||||||
|
### 2.4 Firewall öffnen für TTS/STT
|
||||||
|
|
||||||
|
Die TTS/STT-Wrapper laufen auf dem Host; der Docker-Container erreicht sie über die
|
||||||
|
Docker-Bridge, was bei aktiver `ufw`-Firewall (Standard: deny incoming) explizit erlaubt werden muss —
|
||||||
|
analog zur bestehenden Regel für Ollama (`11434/tcp`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 8901/tcp comment 'open-notebook TTS (chatterbox)'
|
||||||
|
sudo ufw allow 8902/tcp comment 'open-notebook STT (faster-whisper)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ohne diesen Schritt bleiben TTS/STT vom Container aus unerreichbar (Timeout), obwohl sie lokal
|
||||||
|
laufen und funktionieren.
|
||||||
|
|
||||||
|
### 2.5 TTS/STT-Server starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/start_services.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Startet `tts_server.py` und `stt_server.py` als Hintergrundprozesse auf GPU 2 (nicht GPU 0/1,
|
||||||
|
um Ollama und die Desktop-GPU nicht zu belasten). Nicht systemd-verwaltet — nach einem Reboot
|
||||||
|
erneut ausführen. Logs: `services/logs/`.
|
||||||
|
|
||||||
|
Für Stimmklonung mindestens eine Referenz-WAV in `services/voices/default.wav` ablegen (z. B.
|
||||||
|
Symlink auf eine eigene Sprachaufnahme, 10–30s, WAV) — siehe
|
||||||
|
[`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md#stimmklonung).
|
||||||
|
|
||||||
|
### 2.6 KI-Provider und Modelle registrieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup_models.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Richtet automatisch ein:
|
||||||
|
- Ollama-Credential (Chat + Embedding) mit `num_ctx=98304` — **wichtig**, siehe Warnkasten unten
|
||||||
|
- Kuratierte Ollama-Modelle: `qwen3.5:27b` (Standard für Chat/Tools/großer Kontext),
|
||||||
|
`nomic-embed-text` (Standard-Embedding), `qwen3-coder-30b-128k` (optional für Code)
|
||||||
|
- OpenRouter-API-Key aus `.env`/Environment in die verschlüsselte Datenbank migriert
|
||||||
|
- Kuratierte OpenRouter-Modelle (Claude Sonnet 5, Qwen3-Max, Gemini 3.5 Flash)
|
||||||
|
- Lokale TTS/STT-Wrapper als `openai_compatible`-Credentials + Standardmodelle
|
||||||
|
|
||||||
|
> **Warum `num_ctx` explizit gesetzt werden muss:** Ohne diese Einstellung begrenzt die
|
||||||
|
> zugrunde liegende Bibliothek (`esperanto`) jede Ollama-Chat-Anfrage stillschweigend auf
|
||||||
|
> 8192 Tokens Kontext — unabhängig vom eigentlichen Modell-Limit. Größere Chat-Kontexte
|
||||||
|
> (z. B. ganze Bücher im „Volltext"-Modus) werden dann abgeschnitten, und weil `qwen3.5:27b`
|
||||||
|
> ein „denkendes" Modell ist, kommt dabei oft eine **leere Antwort statt eines Fehlers**
|
||||||
|
> heraus. `98304` ist der größte Wert, der auf dieser Hardware (RTX 3090, 24 GB) noch
|
||||||
|
> vollständig auf der GPU bleibt (`ollama ps` → `100% GPU`). Details:
|
||||||
|
> [`CLAUDE.md`](CLAUDE.md).
|
||||||
|
|
||||||
|
### 2.7 Verifikation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:5055/api/models/providers | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung: `"available": ["openrouter", "ollama"]`. TTS/STT-Gesundheit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:8901/health # Chatterbox TTS
|
||||||
|
curl -s http://127.0.0.1:8902/health # faster-whisper STT
|
||||||
|
```
|
||||||
|
|
||||||
|
UI öffnen (`http://localhost:8502`), ein Notebook anlegen, eine Quelle hinzufügen und chatten —
|
||||||
|
siehe [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md) für die weitere Bedienung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Alltägliche Befehle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d # starten
|
||||||
|
docker compose down # stoppen (Daten bleiben erhalten)
|
||||||
|
docker compose logs -f open_notebook # Logs verfolgen
|
||||||
|
docker compose ps # Status
|
||||||
|
./scripts/start_services.sh # TTS/STT (re-)starten, z.B. nach Reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Verzeichnisstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
open-notebook/
|
||||||
|
├── docker-compose.yml # SurrealDB + open_notebook
|
||||||
|
├── .env # Secrets (gitignored)
|
||||||
|
├── .env.example # Template
|
||||||
|
├── surreal_data/ # DB-Daten (gitignored)
|
||||||
|
├── notebook_data/ # Notebook-Dateien/Uploads (gitignored)
|
||||||
|
├── services/
|
||||||
|
│ ├── tts_server.py # Chatterbox-Wrapper (OpenAI-API-kompatibel)
|
||||||
|
│ ├── stt_server.py # faster-whisper-Wrapper
|
||||||
|
│ ├── voices/ # Referenz-WAVs fürs Voice-Cloning (gitignored)
|
||||||
|
│ └── logs/ # (gitignored)
|
||||||
|
├── scripts/
|
||||||
|
│ ├── setup_models.sh # Provider/Modelle einrichten (idempotent)
|
||||||
|
│ └── start_services.sh # TTS/STT starten
|
||||||
|
├── README.md # diese Datei
|
||||||
|
├── BEDIENUNGSANLEITUNG.md # ausführliche Bedienungsanleitung (Deutsch)
|
||||||
|
└── CLAUDE.md # technische Architektur-Doku für Weiterentwicklung
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Bekannte Stolpersteine
|
||||||
|
|
||||||
|
Kurzreferenz — Details jeweils in [`CLAUDE.md`](CLAUDE.md):
|
||||||
|
|
||||||
|
- **Embedding-Modell muss klein sein.** `qwen3-embedding` (ein zweckentfremdetes 8B-LLM,
|
||||||
|
~13 GB geladen) konkurriert mit dem Chat-Modell um GPU-Speicher und fällt dann teilweise auf
|
||||||
|
die CPU zurück — einzelne Embeddings dauern dann 40+ Sekunden und reißen Timeouts.
|
||||||
|
`nomic-embed-text` (274 MB) ist der Standard hier.
|
||||||
|
- **`num_ctx` beim Ollama-Chat-Credential ist Pflicht** (siehe Warnkasten in 2.6) — sonst
|
||||||
|
leere Chat-Antworten bei größerem Kontext, ohne sichtbaren Fehler.
|
||||||
|
- **Neue Host-Ports brauchen eine `ufw`-Regel**, sonst kann der Container sie nicht erreichen
|
||||||
|
(Timeout, keine Fehlermeldung in Open Notebook selbst).
|
||||||
137
scripts/setup_models.sh
Executable file
137
scripts/setup_models.sh
Executable file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Registriert Ollama + OpenRouter + lokale TTS/STT-Wrapper als AI-Provider in
|
||||||
|
# Open Notebook und setzt sinnvolle Standardmodelle. Für einen frischen Stack
|
||||||
|
# gedacht (siehe README.md) — prüft vor dem Anlegen jeweils, ob Credential/
|
||||||
|
# Modell mit demselben Namen schon existiert, ist also im Wesentlichen
|
||||||
|
# wiederholbar, aber nicht für abweichende Konfigurationen gedacht.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
API="http://127.0.0.1:5055/api"
|
||||||
|
|
||||||
|
echo "Warte auf Open Notebook API..."
|
||||||
|
until curl -sf "${API}/models/providers" >/dev/null 2>&1; do sleep 2; done
|
||||||
|
|
||||||
|
# --- Hilfsfunktionen -------------------------------------------------------
|
||||||
|
|
||||||
|
credential_id_by_name() {
|
||||||
|
curl -s "${API}/credentials" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
name = sys.argv[1]
|
||||||
|
for c in json.load(sys.stdin):
|
||||||
|
if c['name'] == name:
|
||||||
|
print(c['id']); break
|
||||||
|
" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
model_id_by_name() {
|
||||||
|
curl -s "${API}/models" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
name = sys.argv[1]
|
||||||
|
for m in json.load(sys.stdin):
|
||||||
|
if m['name'] == name:
|
||||||
|
print(m['id']); break
|
||||||
|
" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_credential() {
|
||||||
|
# ensure_credential <name> <json-payload>
|
||||||
|
local name="$1" payload="$2" id
|
||||||
|
id=$(credential_id_by_name "$name")
|
||||||
|
if [ -n "$id" ]; then
|
||||||
|
echo "Credential '$name' existiert bereits ($id)" >&2
|
||||||
|
echo "$id"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
id=$(curl -s -X POST "${API}/credentials" -H "Content-Type: application/json" -d "$payload" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
|
||||||
|
echo "Credential '$name' angelegt ($id)" >&2
|
||||||
|
echo "$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_model() {
|
||||||
|
# ensure_model <name> <provider> <type> [credential_id]
|
||||||
|
local name="$1" provider="$2" type="$3" cred="${4:-}" id
|
||||||
|
id=$(model_id_by_name "$name")
|
||||||
|
if [ -n "$id" ]; then
|
||||||
|
echo "Modell '$name' existiert bereits ($id)" >&2
|
||||||
|
echo "$id"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local cred_json="null"
|
||||||
|
[ -n "$cred" ] && cred_json="\"$cred\""
|
||||||
|
id=$(curl -s -X POST "${API}/models" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\": \"$name\", \"provider\": \"$provider\", \"type\": \"$type\", \"credential\": ${cred_json}}" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
|
||||||
|
echo "Modell '$name' ($type) angelegt ($id)" >&2
|
||||||
|
echo "$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. OpenRouter: API-Key aus Environment in die DB migrieren -----------
|
||||||
|
|
||||||
|
echo "== OpenRouter =="
|
||||||
|
curl -s -X POST "${API}/credentials/migrate-from-env" >/dev/null
|
||||||
|
echo "OPENROUTER_API_KEY aus Environment migriert (falls gesetzt)"
|
||||||
|
|
||||||
|
# --- 2. Ollama-Credential (Chat/Embedding), num_ctx=98304 -----------------
|
||||||
|
# 98304 ist der größte Kontext, der bei qwen3.5:27b auf dieser Hardware noch
|
||||||
|
# vollständig auf einer GPU bleibt (100% GPU laut `ollama ps`) — 131072
|
||||||
|
# spillt auf die CPU und macht Antworten 5-6x langsamer. Ohne num_ctx
|
||||||
|
# begrenzt esperanto Ollama-Chat-Aufrufe fest auf 8192 Tokens.
|
||||||
|
echo "== Ollama (Chat + Embedding) =="
|
||||||
|
OLLAMA_CRED=$(ensure_credential "Ollama (local)" '{
|
||||||
|
"name": "Ollama (local)",
|
||||||
|
"provider": "ollama",
|
||||||
|
"modalities": ["language", "embedding"],
|
||||||
|
"base_url": "http://host.docker.internal:11434",
|
||||||
|
"num_ctx": 98304
|
||||||
|
}')
|
||||||
|
|
||||||
|
QWEN_CHAT=$(ensure_model "qwen3.5:27b" "ollama" "language" "$OLLAMA_CRED")
|
||||||
|
QWEN_CODER=$(ensure_model "qwen3-coder-30b-128k:latest" "ollama" "language" "$OLLAMA_CRED")
|
||||||
|
NOMIC_EMBED=$(ensure_model "nomic-embed-text" "ollama" "embedding" "$OLLAMA_CRED")
|
||||||
|
|
||||||
|
# --- 3. OpenRouter-Sprachmodelle (kuratiert, nicht alle ~400) --------------
|
||||||
|
echo "== OpenRouter Sprachmodelle =="
|
||||||
|
CLAUDE_SONNET=$(ensure_model "anthropic/claude-sonnet-5" "openrouter" "language")
|
||||||
|
QWEN_MAX=$(ensure_model "qwen/qwen3-max" "openrouter" "language")
|
||||||
|
GEMINI_FLASH=$(ensure_model "google/gemini-3.5-flash" "openrouter" "language")
|
||||||
|
|
||||||
|
# --- 4. Lokale TTS/STT-Wrapper (siehe services/) --------------------------
|
||||||
|
echo "== Lokales TTS (Chatterbox) =="
|
||||||
|
TTS_CRED=$(ensure_credential "Chatterbox TTS (local)" '{
|
||||||
|
"name": "Chatterbox TTS (local)",
|
||||||
|
"provider": "openai_compatible",
|
||||||
|
"modalities": ["text_to_speech"],
|
||||||
|
"base_url": "http://host.docker.internal:8901",
|
||||||
|
"api_key": "not-required"
|
||||||
|
}')
|
||||||
|
TTS_MODEL=$(ensure_model "chatterbox-tts" "openai_compatible" "text_to_speech" "$TTS_CRED")
|
||||||
|
|
||||||
|
echo "== Lokales STT (faster-whisper) =="
|
||||||
|
STT_CRED=$(ensure_credential "faster-whisper STT (local)" '{
|
||||||
|
"name": "faster-whisper STT (local)",
|
||||||
|
"provider": "openai_compatible",
|
||||||
|
"modalities": ["speech_to_text"],
|
||||||
|
"base_url": "http://host.docker.internal:8902",
|
||||||
|
"api_key": "not-required"
|
||||||
|
}')
|
||||||
|
STT_MODEL=$(ensure_model "faster-whisper-large-v3" "openai_compatible" "speech_to_text" "$STT_CRED")
|
||||||
|
|
||||||
|
# --- 5. Standardmodelle setzen ---------------------------------------------
|
||||||
|
# Alles lokal/kostenlos außer Tools-Modell optional auf ein OpenRouter-Modell
|
||||||
|
# umstellbar (siehe BEDIENUNGSANLEITUNG.md, Abschnitt "Modelle wechseln").
|
||||||
|
echo "== Standardmodelle setzen =="
|
||||||
|
curl -s -X PUT "${API}/models/defaults" -H "Content-Type: application/json" -d "{
|
||||||
|
\"default_chat_model\": \"$QWEN_CHAT\",
|
||||||
|
\"default_transformation_model\": \"$QWEN_CHAT\",
|
||||||
|
\"large_context_model\": \"$QWEN_CHAT\",
|
||||||
|
\"default_embedding_model\": \"$NOMIC_EMBED\",
|
||||||
|
\"default_tools_model\": \"$QWEN_CHAT\",
|
||||||
|
\"default_text_to_speech_model\": \"$TTS_MODEL\",
|
||||||
|
\"default_speech_to_text_model\": \"$STT_MODEL\"
|
||||||
|
}" | python3 -m json.tool
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Fertig. Registrierte, aber nicht als Standard gesetzte Zusatzmodelle:"
|
||||||
|
echo " - qwen3-coder-30b-128k:latest ($QWEN_CODER) — für reine Coding-Aufgaben"
|
||||||
|
echo " - anthropic/claude-sonnet-5 ($CLAUDE_SONNET), qwen/qwen3-max ($QWEN_MAX), google/gemini-3.5-flash ($GEMINI_FLASH) — OpenRouter-Alternativen"
|
||||||
31
scripts/start_services.sh
Executable file
31
scripts/start_services.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Startet die lokalen TTS/STT-Wrapper-Server (services/tts_server.py,
|
||||||
|
# services/stt_server.py) als Hintergrundprozesse auf GPU 2. Überspringt
|
||||||
|
# einen Service, falls er unter seinem Port bereits antwortet.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../services" && pwd)"
|
||||||
|
mkdir -p "$DIR/logs"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
start_if_needed() {
|
||||||
|
local name="$1" port="$2" cmd="$3"
|
||||||
|
if curl -sf -m 2 "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
|
||||||
|
echo "$name läuft bereits auf Port $port"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "Starte $name auf Port $port..."
|
||||||
|
eval "$cmd"
|
||||||
|
disown
|
||||||
|
for _ in $(seq 1 15); do
|
||||||
|
curl -sf -m 2 "http://127.0.0.1:${port}/health" >/dev/null 2>&1 && { echo "$name ist bereit."; return; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "WARNUNG: $name antwortet nach 30s nicht auf /health — siehe logs/${name}.log" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
start_if_needed "tts" 8901 \
|
||||||
|
'CUDA_VISIBLE_DEVICES=2 TTS_PORT=8901 nohup ~/miniforge3/envs/chatterbox/bin/python tts_server.py > logs/tts_server.log 2>&1 &'
|
||||||
|
|
||||||
|
start_if_needed "stt" 8902 \
|
||||||
|
'CUDA_VISIBLE_DEVICES=2 STT_PORT=8902 nohup python3 stt_server.py > logs/stt_server.log 2>&1 &'
|
||||||
Loading…
Add table
Add a link
Reference in a new issue