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