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:
Dieter Schlüter 2026-07-10 23:50:58 +02:00
commit 7838863dbf
4 changed files with 652 additions and 0 deletions

278
BEDIENUNGSANLEITUNG.md Normal file
View 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 **180350
Buchseiten**, abhängig von Textdichte. Wichtig: Die **Datei­größ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: **1030 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
View 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, 1030s, 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
View 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
View 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 &'