diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md new file mode 100644 index 0000000..44f63e4 --- /dev/null +++ b/BEDIENUNGSANLEITUNG.md @@ -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 **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": ""}' + ``` +- 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/.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 `` in der `voices`-Liste zeigen. +5. In einem Sprecherprofil (Podcast) den betreffenden Sprecher auf `voice_id: ""` 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 .onnx --output_file services/voices/.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": ""}' +``` + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc3eb86 --- /dev/null +++ b/README.md @@ -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 ` 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 < **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). diff --git a/scripts/setup_models.sh b/scripts/setup_models.sh new file mode 100755 index 0000000..391122e --- /dev/null +++ b/scripts/setup_models.sh @@ -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 + 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 [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" diff --git a/scripts/start_services.sh b/scripts/start_services.sh new file mode 100755 index 0000000..f1030c5 --- /dev/null +++ b/scripts/start_services.sh @@ -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 &'