Audited README, BEDIENUNGSANLEITUNG and CLAUDE.md against what the repo and the running stack actually do. Findings, all fixed: - CLAUDE.md claimed the repo holds "only deployment config (docker-compose.yml, .env, .gitignore)". It has not been that for a while: services/ is our own source (TTS/STT servers + systemd units), scripts/ is tooling, and prompts/podcast/ + config/content_core.yaml are overlays bind-mounted over the image. Those overlays are the fragile part, so the section now says so and points at smoke_test.sh. - CLAUDE.md still described the image as a moving tag and argued against patching app code "because it would rot against pull_policy: always" — both obsolete since the digest pin. - README listed neither yt-dlp (needed by add_video_source.py) nor smoke_test.sh in the verification step, and section 2.3 still told you to `docker compose pull` as if the tag moved. - BEDIENUNGSANLEITUNG had nothing at all about updates — the very thing a user has to do periodically. New section 8 covers check_updates / update_stack / smoke_test and, importantly, why updating is not automatic (DB migration is one-way without a backup; the local adaptations can break silently without crashing). - Renumbered the ad-hoc "3a. Updates" in README into a real section 4, and the BEDIENUNGSANLEITUNG sections after the new 8 accordingly. Verified mechanically: every scripts//services//config//prompts/ path named in the docs exists, and all internal and cross-document anchors resolve (0 dead links — one was already broken before this change: README pointed at BEDIENUNGSANLEITUNG.md#stimmklonung instead of #5-stimmklonung). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
575 lines
25 KiB
Markdown
575 lines
25 KiB
Markdown
# 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. [Updates einspielen](#8-updates-einspielen)
|
||
9. [Suche über Chunks (Search/Ask)](#9-suche-über-chunks-searchask)
|
||
10. [Die API in eigenen Programmen nutzen](#10-die-api-in-eigenen-programmen-nutzen)
|
||
11. [Troubleshooting](#11-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.
|
||
|
||
### Episoden löschen räumt nur halb auf
|
||
|
||
**Der Mülleimer-Button löscht nicht alles.** Er entfernt die finale MP3 und den
|
||
Datenbankeintrag — der Ordner der Episode bleibt aber liegen, samt Einzelclips:
|
||
|
||
```
|
||
notebook_data/podcasts/episodes/<uuid>/
|
||
├── clips/ ← 1 MP3 pro Dialogsegment BLEIBT LIEGEN
|
||
├── outline.json BLEIBT LIEGEN
|
||
├── transcript.json BLEIBT LIEGEN
|
||
└── audio/<uuid>.mp3 ← nur diese wird gelöscht
|
||
```
|
||
|
||
Das ist eine Lücke im Open-Notebook-Code selbst (der Löschpfad ruft `unlink()` nur auf die eine
|
||
MP3-Datei auf), nicht an dieser Installation einstellbar. Ebenso hinterlässt jeder
|
||
fehlgeschlagene oder per Retry wiederholte Lauf einen Ordner, der nie aufgeräumt wird.
|
||
|
||
Zum Aufräumen dient:
|
||
|
||
```bash
|
||
./scripts/prune_podcast_data.py # zeigt nur an, was wegfiele (Trockenlauf)
|
||
./scripts/prune_podcast_data.py --yes # löscht wirklich
|
||
```
|
||
|
||
Es entfernt (a) Ordner ohne zugehörige Episode und (b) die `clips/` fertiger Episoden — die
|
||
Einzelclips sind nach dem Zusammenbau reines Zwischenprodukt, die Episode bleibt vollständig
|
||
abspielbar. Schutzmechanismen: bei einem laufenden Podcast-Job bricht es ab, und Ordner, die
|
||
jünger als 60 Minuten sind, bleiben unangetastet (`--min-age`). Mit `--keep-clips` werden nur
|
||
die verwaisten Ordner gelöscht.
|
||
|
||
---
|
||
|
||
## 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
|
||
systemctl --user restart open-notebook-tts
|
||
```
|
||
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 (auf dem Host, nicht in Docker — systemd-User-Dienste)
|
||
systemctl --user status open-notebook-tts open-notebook-stt
|
||
systemctl --user restart open-notebook-tts open-notebook-stt
|
||
systemctl --user stop open-notebook-tts open-notebook-stt
|
||
journalctl --user -u open-notebook-tts -f # Logs
|
||
```
|
||
|
||
TTS/STT starten nach einem Reboot **automatisch** mit. Nur bei der Ersteinrichtung (oder nach
|
||
Änderungen an `services/systemd/*.service`) einmal `./scripts/start_services.sh` ausführen — das
|
||
installiert die Unit-Dateien und aktiviert den Autostart.
|
||
|
||
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. Updates einspielen
|
||
|
||
Open Notebook läuft hier auf einer **festgenagelten Version** (Digest-Pin in
|
||
`docker-compose.yml`). Weder ein Start noch ein Reboot tauscht die Anwendung unbemerkt aus — Sie
|
||
bekommen immer genau den Stand, der zuletzt geprüft wurde.
|
||
|
||
**Beim Sitzungsstart** (ändert nichts, dauert Sekunden):
|
||
|
||
```bash
|
||
./scripts/check_updates.sh
|
||
```
|
||
|
||
Ausgabe entweder „Du laeufst auf dem aktuellen Release. Nichts zu tun." oder ein Hinweis auf ein
|
||
neues Image.
|
||
|
||
**Update einspielen:**
|
||
|
||
```bash
|
||
./scripts/update_stack.sh
|
||
```
|
||
|
||
Das Skript sichert zuerst Datenbank und Notebook-Daten nach `backups/<Zeitstempel>/`, stellt dann
|
||
auf das neue Image um, startet den Stack und fährt einen **Rauchtest** (API, Sprachausgabe,
|
||
Spracherkennung, YouTube-Import, alle lokalen Anpassungen). Schlägt der fehl, wird **automatisch
|
||
zurückgerollt** — Daten und Konfiguration landen wieder auf dem alten Stand.
|
||
|
||
Läuft alles, committen Sie den neuen Stand:
|
||
|
||
```bash
|
||
git add docker-compose.yml && git commit -m "Update auf <Version>"
|
||
```
|
||
|
||
**Warum nicht einfach immer die neueste Version?** Ein Update verändert das Datenbankschema, und
|
||
eine ältere Version kann eine bereits migrierte Datenbank in der Regel nicht mehr lesen — ohne
|
||
Sicherung wäre ein misslungenes Update also eine Einbahnstraße. Außerdem hängen die Anpassungen
|
||
dieser Installation (Podcast-Vorlagen, YouTube-Sprachen, TTS-Einstellungen) an Interna der
|
||
Anwendung und können durch ein Update **still** kaputtgehen, ohne dass etwas abstürzt. Genau
|
||
deshalb der Rauchtest.
|
||
|
||
Ihn können Sie auch jederzeit einzeln laufen lassen, etwa wenn sich etwas seltsam verhält:
|
||
|
||
```bash
|
||
./scripts/smoke_test.sh
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Suche über Chunks (Search/Ask)
|
||
|
||
Jedes Dokument wird beim Hochladen automatisch in feste, überlappende Textabschnitte
|
||
("Chunks", ~400 Tokens) zerlegt und eingebettet — **nicht** kapitelweise, sondern rein
|
||
größenbasiert (siehe Abschnitt 2). Um gezielt in diesen Chunks zu suchen, gibt es zwei
|
||
API-Endpunkte (in der UI vermutlich unter einem eigenen Menüpunkt wie „Search"/„Ask" mit
|
||
Lupen-Symbol, getrennt von „Mit Notebook chatten" — die genaue Beschriftung war ohne
|
||
Browser-Zugriff nicht zu verifizieren):
|
||
|
||
### Einfache Suche — schnell, liefert Roh-Textstellen
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:5055/api/search \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"query": "Suchbegriff", "type": "vector", "limit": 5}'
|
||
```
|
||
|
||
Liefert die ähnlichsten Chunks per Embedding-Vergleich samt Ähnlichkeits-Score (0–1), ohne
|
||
KI-Interpretation — am schnellsten, aber du musst die Antwort selbst zusammensetzen.
|
||
|
||
### „Ask" — durchsuchen und direkt beantworten lassen
|
||
|
||
Die smartere Variante: Die KI zerlegt die Frage selbst in mehrere gezielte Suchanfragen,
|
||
durchsucht die Chunks für jede getrennt und fasst alles zu einer einzigen, mit
|
||
Quellenverweisen (`[source:...]`) belegten Antwort zusammen. Getestet mit „Wie kam Wallenstein
|
||
ums Leben?" — Ergebnis war eine vollständige, korrekt zitierte Antwort in ca. 3 Minuten
|
||
(mehrere LLM-Aufrufe hintereinander: Suchstrategie → Einzelantworten → Zusammenfassung).
|
||
|
||
```bash
|
||
MODEL_ID=$(curl -s http://127.0.0.1:5055/api/models/defaults | python3 -c "import json,sys; print(json.load(sys.stdin)['default_chat_model'])")
|
||
curl -N -X POST http://127.0.0.1:5055/api/search/ask \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"question\": \"Deine Frage\", \"strategy_model\": \"$MODEL_ID\", \"answer_model\": \"$MODEL_ID\", \"final_answer_model\": \"$MODEL_ID\"}"
|
||
```
|
||
|
||
Antwort kommt als Server-Sent-Events-Stream (`data: {...}`-Zeilen, kein einzelnes JSON-Objekt);
|
||
das letzte `"type": "complete"`-Event enthält die finale Antwort im Feld `final_answer`.
|
||
|
||
**Wann was nutzen:** Für gezielte Detailfragen über lange/viele Quellen ist „Ask" meist
|
||
treffsicherer und dabei noch schneller als der „vollständiger Inhalt"-Chat-Modus (Abschnitt 3),
|
||
weil nur die relevanten Chunks statt des ganzen Dokuments verarbeitet werden.
|
||
|
||
---
|
||
|
||
## 10. Die API in eigenen Programmen nutzen
|
||
|
||
Die REST-API ist eine ganz normale JSON-über-HTTP-API **ohne Authentifizierung** — kein API-Key
|
||
nötig, da nur `127.0.0.1` gebunden (jeder lokale Prozess auf diesem Rechner hat Zugriff, aber
|
||
niemand aus dem Netzwerk). Funktioniert mit jeder Sprache, die HTTP kann.
|
||
|
||
**Bester Startpunkt:** `http://127.0.0.1:5055/docs` — interaktive Swagger-UI mit allen
|
||
Endpunkten, Schemas und einer „Try it out"-Funktion zum direkten Ausprobieren im Browser.
|
||
Rohes OpenAPI-Schema: `http://127.0.0.1:5055/openapi.json`.
|
||
|
||
### Mit curl
|
||
|
||
```bash
|
||
# Notebooks auflisten
|
||
curl -s http://127.0.0.1:5055/api/notebooks | python3 -m json.tool
|
||
|
||
# Neues Notebook anlegen
|
||
curl -s -X POST http://127.0.0.1:5055/api/notebooks \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"name": "Mein Notebook", "description": "..."}'
|
||
|
||
# Text-Quelle hinzufügen (multipart, nicht JSON)
|
||
curl -s -X POST http://127.0.0.1:5055/api/sources \
|
||
-F "type=text" -F "notebook_id=notebook:xxx" -F "title=..." \
|
||
-F "content=..." -F "embed=true"
|
||
|
||
# Chat-Session erstellen + Nachricht senden
|
||
curl -s -X POST http://127.0.0.1:5055/api/chat/sessions \
|
||
-H "Content-Type: application/json" -d '{"notebook_id": "notebook:xxx"}'
|
||
curl -s -X POST http://127.0.0.1:5055/api/chat/execute \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"session_id": "chat_session:xxx", "message": "...", "context": {}}'
|
||
```
|
||
|
||
Weitere Beispiele (Quellen-Insights, Podcast-Generierung, Modell-/Provider-Verwaltung,
|
||
Chunk-Suche) stehen bereits in den jeweiligen Abschnitten dieser Anleitung.
|
||
|
||
### Aus Python
|
||
|
||
```python
|
||
import requests
|
||
|
||
BASE = "http://127.0.0.1:5055/api"
|
||
|
||
notebooks = requests.get(f"{BASE}/notebooks").json()
|
||
|
||
session = requests.post(f"{BASE}/chat/sessions",
|
||
json={"notebook_id": notebooks[0]["id"]}).json()
|
||
answer = requests.post(f"{BASE}/chat/execute", json={
|
||
"session_id": session["id"],
|
||
"message": "Wovon handelt das Buch?",
|
||
"context": {},
|
||
}).json()
|
||
print(answer["messages"][-1]["content"])
|
||
```
|
||
|
||
Genauso mit JavaScript (`fetch`), Go, oder jeder anderen Sprache — nichts Open-Notebook-
|
||
Spezifisches nötig, kein SDK erforderlich.
|
||
|
||
---
|
||
|
||
## 11. 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 Dienste laufen nicht
|
||
(`systemctl --user status open-notebook-tts open-notebook-stt`).
|
||
|
||
### 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.
|
||
|
||
### Podcast wird auf Englisch statt Deutsch erzeugt (oder auf Englisch mit deutschem Akzent vorgelesen)
|
||
|
||
Das ist behoben: Die Podcast-Prompt-Vorlagen (`prompts/podcast/outline.jinja` und
|
||
`transcript.jinja` in diesem Projekt) wurden um eine explizite Sprachanweisung ergänzt, die das
|
||
`language`-Feld des Episode-Profils auswertet — sie werden per Bind-Mount aus `docker-compose.yml`
|
||
in den Container eingehängt. Setzt du am Episode-Profil `language: "de"`, kommt der Podcast auf
|
||
Deutsch. (Vorher hatte das `language`-Feld keine Wirkung, weil die mitgelieferten Vorlagen es
|
||
ignorierten — daher englischer Text, der von der deutsch-fixierten TTS mit Akzent vorgelesen wurde.)
|
||
|
||
Falls doch wieder Englisch erscheint, prüfe das `language`-Feld:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:5055/api/episode-profiles | python3 -c \
|
||
"import json,sys; [print(p['name'], p['language']) for p in json.load(sys.stdin)]"
|
||
```
|
||
|
||
Fehlt `de`/`de-DE`, per `PUT /api/episode-profiles/{id}` (vollständiger Body) ergänzen. Zusätzlich
|
||
sind Briefing und Sprecherbeschreibungen der mitgelieferten Profile auf Deutsch übersetzt (hilft
|
||
gegen Sprach-Drift, wenn der Quellinhalt englisch ist). **Wichtig:** Änderst du die
|
||
Vorlagen-Dateien selbst, ist danach ein Container-Neustart nötig (`docker compose up -d
|
||
--force-recreate open_notebook`), damit der Bind-Mount die neue Version übernimmt.
|
||
|
||
### Podcast-Erzeugung schlägt mit „Invalid json output" fehl
|
||
|
||
Das Textmodell hat ungültiges JSON geliefert — passierte bei `qwen3.5:27b` (einem „denkenden"
|
||
Modell), wenn der gesamte Notebook-Inhalt (bei einem ganzen Buch ~70.000 Tokens) in jeden
|
||
Transkript-Abschnitt gepackt wird: Der Denk-Block frisst dann das Antwort-Budget auf und das JSON
|
||
wird abgeschnitten. Behoben durch `/no_think` als erste Zeile beider Podcast-Vorlagen (schaltet
|
||
den Denkmodus ab, ~3× schneller, volles Budget fürs JSON). Falls es dennoch auftritt: kürzeren
|
||
Quellinhalt verwenden (nicht das ganze Buch) oder die Generierung erneut starten.
|
||
|
||
### Video ohne Untertitel hinzufügen (`scripts/add_video_source.py`)
|
||
|
||
Open Notebook liest bei Videos **nur vorhandene Untertitel**. Gibt es keine, bleibt die Quelle
|
||
leer. Für diesen Fall gibt es das Skript:
|
||
|
||
```bash
|
||
./scripts/add_video_source.py https://www.youtube.com/watch?v=...
|
||
./scripts/add_video_source.py <url> --notebook notebook:abc --language de
|
||
./scripts/add_video_source.py <url> --force-audio # Untertitel ignorieren
|
||
```
|
||
|
||
Es wählt den Weg selbst:
|
||
|
||
- **Untertitel vorhanden** → die URL wird normal als Link-Quelle übergeben (schnell, kein
|
||
Download).
|
||
- **keine Untertitel** → `yt-dlp` lädt nur die Tonspur, die wird hochgeladen und von Open
|
||
Notebook mit dem lokalen Whisper-Server transkribiert und eingebettet. Rechnen Sie mit etwa
|
||
1–2 Minuten pro 20 Minuten Video (läuft auf GPU 2). Die hochgeladene Audiodatei wird danach
|
||
automatisch wieder gelöscht.
|
||
|
||
Das funktioniert für alles, was `yt-dlp` kennt (YouTube, Vimeo, Mediatheken …), nicht nur für
|
||
YouTube. `--force-audio` ist auch dann nützlich, wenn die automatischen YouTube-Untertitel zu
|
||
schlecht sind: Whisper liefert meist deutlich saubereren Text (richtige Satzzeichen,
|
||
korrekte Eigennamen).
|
||
|
||
Hinweis: Beim Audio-Weg wird die Audiodatei als Quell-Asset hinterlegt, die ursprüngliche
|
||
Video-URL steht dann nicht mehr am Eintrag (beim Untertitel-Weg schon).
|
||
|
||
### YouTube-Video wird als leere Quelle angelegt
|
||
|
||
Behoben. Symptom war: Quelle hinzufügen scheint zu klappen (der **Titel** erscheint), aber es gibt
|
||
keinen Inhalt — kein Text, nichts zum Chatten, nichts zum Zusammenfassen.
|
||
|
||
Ursache: Open Notebook holt YouTube-Untertitel über die Bibliothek `content-core`, und die suchte
|
||
nur nach Transkripten in **Englisch, Spanisch und Portugiesisch**. Ein Video mit ausschließlich
|
||
deutschem Transkript fiel damit durch; der Fehler stand nur im Container-Log, in der Oberfläche
|
||
sah die Quelle einfach leer aus. Behoben durch `config/content_core.yaml` (per Bind-Mount
|
||
eingehängt), die Deutsch an erste Stelle setzt.
|
||
|
||
Falls doch mal ein Video leer bleibt, ist meist **gar kein Untertitel** vorhanden (weder manuell
|
||
noch automatisch) — das lässt sich so prüfen:
|
||
|
||
```bash
|
||
docker compose exec open_notebook /app/.venv/bin/python -c "
|
||
from youtube_transcript_api import YouTubeTranscriptApi
|
||
for t in YouTubeTranscriptApi().list('VIDEO_ID'): print(t.language_code, t.is_generated)"
|
||
```
|
||
|
||
Kommt hier nichts zurück, hat das Video keine Untertitel — dann hilft nur, das Video separat
|
||
herunterzuladen und über die STT-Funktion (Audio-Upload) zu transkribieren.
|
||
|
||
### Podcast schlägt beim Vertonen fehl („Failed to generate speech")
|
||
|
||
Text und Transkript sind fertig, erst die Sprachausgabe scheitert. Die genaue Fehlermeldung
|
||
(UI-Episodenliste oder `docker compose logs open_notebook`) unterscheidet **zwei verschiedene
|
||
Ursachen** — die Verwechslung kostet sonst viel Zeit:
|
||
|
||
| Meldung | Bedeutung | Abhilfe |
|
||
|---|---|---|
|
||
| `All connection attempts failed` (`ConnectError`) | Der TTS-Server **läuft nicht** — niemand nimmt den Request an. | `systemctl --user status open-notebook-tts`, ggf. `restart`. |
|
||
| `OpenAI-compatible TTS endpoint error: HTTP 500` | Der TTS-Server **lebt**, bricht aber bei der Arbeit ab — meist `CUDA out of memory`. | `journalctl --user -u open-notebook-tts -n 50` prüfen. |
|
||
|
||
Der GPU-Speicherfall entstand so: GPU 2 teilen sich drei Prozesse (TTS, STT und der separate
|
||
`chatterbox-tts`-MCP-Dienst auf Port 9999), es bleiben nur ~12 GB übrig. Der Podcast-Generator
|
||
schickte aber 5 Clips gleichzeitig, die im TTS-Server echt parallel auf derselben GPU liefen —
|
||
der Speicherbedarf vervielfachte sich (bis 16,7 GB) und sprengte die Karte. Behoben durch zwei
|
||
Maßnahmen: `tts_server.py` serialisiert seine Generierung intern (ein Clip zur Zeit — auf einer
|
||
einzelnen GPU kostet das keinen Durchsatz), und `TTS_BATCH_SIZE=1` in `docker-compose.yml` sorgt
|
||
dafür, dass gar nicht erst mehrere Anfragen gleichzeitig eintreffen. Ein fehlgeschlagener Podcast
|
||
lässt sich anschließend ohne Neuanlage wiederholen:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:5055/api/podcasts/episodes/{episode_id}/retry
|
||
```
|
||
|
||
(Der Retry legt einen neuen Episoden-Eintrag an; der alte, fehlgeschlagene verschwindet.)
|
||
|
||
### Weitere technische Details
|
||
|
||
Siehe [`CLAUDE.md`](CLAUDE.md) — dort stehen alle bisher aufgetretenen Probleme mit exakter
|
||
Ursache (Codestellen, Bibliotheks-Bugs) dokumentiert.
|