- /ws/voice: binaere Audio-Frames puffern, {"type":"end"} -> STT-Transkription
-> transcript-Event -> bestehende Antwort-Pipeline (ack/token/audio/semantic/done)
- ws.py refaktoriert: gemeinsame _resolve + _run_turn fuer /ws/chat und /ws/voice
- stream/audio_stream auch fuer Sprach-Turns nutzbar; mehrere Utterances pro Verbindung
- Tests: 47 gruen (+2: Transkript->Antwort, leerer Puffer)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)
Hinweis: STT pro Aeusserung (gepuffert); partielle Live-Transkripte (Streaming-STT
mit VAD) bleiben naechster Increment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
318 lines
9.9 KiB
Markdown
318 lines
9.9 KiB
Markdown
# Bedienungsanleitung — Voice Assistant Gateway
|
|
|
|
Diese Anleitung führt Schritt für Schritt durch Installation, Start, Konfiguration
|
|
und Fehlerbehebung. Technische Hintergründe stehen im
|
|
[Architektur-Dokument](Docs/voice-assistant-architecture.md), eine kompakte
|
|
Übersicht im [README](README.md).
|
|
|
|
---
|
|
|
|
## 1. Voraussetzungen
|
|
|
|
- **Python 3.11 oder neuer** (`python3 --version`)
|
|
- Ein **OpenRouter-API-Key** — nur nötig, wenn ein Profil entfernte KI nutzt
|
|
(`hybrid`, `cloud`). Für rein lokalen Betrieb (`local-dev`) nicht erforderlich.
|
|
- Optional: Docker, falls im Container betrieben.
|
|
|
|
---
|
|
|
|
## 2. Installation
|
|
|
|
```bash
|
|
cd voice-assistant-scaffold
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install -U pip
|
|
pip install -e .[test]
|
|
```
|
|
|
|
Danach die zentrale Konfigurationsdatei anlegen:
|
|
|
|
```bash
|
|
cp config/voice-assistant.example.toml config/voice-assistant.toml
|
|
```
|
|
|
|
---
|
|
|
|
## 3. API-Key hinterlegen (für Cloud/Hybrid)
|
|
|
|
Der Schlüssel wird **aus der Umgebung** gelesen und gehört **nicht** in eine Datei.
|
|
Dauerhaft am besten in `~/.bashrc`:
|
|
|
|
```bash
|
|
echo 'export OPENROUTER_API_KEY=sk-or-v1-DEIN_KEY' >> ~/.bashrc
|
|
chmod 600 ~/.bashrc
|
|
source ~/.bashrc
|
|
```
|
|
|
|
Prüfen, ob er ankommt:
|
|
|
|
```bash
|
|
echo ${OPENROUTER_API_KEY:0:8} # zeigt nur den Anfang
|
|
```
|
|
|
|
> **Sicherheit:** Den Key niemals in `.env` oder `config/*.toml` schreiben. Wird ein
|
|
> Key versehentlich öffentlich, im OpenRouter-Dashboard löschen (= widerrufen) und
|
|
> neu erzeugen.
|
|
|
|
---
|
|
|
|
## 4. Betriebsart (Profil) wählen
|
|
|
|
Profile bestimmen, welche KI-Module genutzt werden:
|
|
|
|
| Profil | Bedeutung | Key nötig? |
|
|
|-------------|--------------------------------------------|------------|
|
|
| `local-dev` | alles lokal (eigene KI/Hardware) | nein |
|
|
| `hybrid` | STT/TTS über Cloud, Haupt-LLM lokal | ja |
|
|
| `cloud` | alles über OpenRouter (Standardbetrieb) | ja |
|
|
|
|
Profil **einmalig** für einen Start:
|
|
|
|
```bash
|
|
VA_PROFILE=cloud make run
|
|
```
|
|
|
|
Profil **dauerhaft** — in `.env` eintragen:
|
|
|
|
```
|
|
VA_PROFILE=cloud
|
|
```
|
|
|
|
> Hinweis: Stehen in `.env` noch `DEFAULT_STT_PROVIDER` / `DEFAULT_LLM_PROVIDER` /
|
|
> `DEFAULT_TTS_PROVIDER`, überschreiben diese das Profil. Für profilbasiertes
|
|
> Umschalten sollten sie auskommentiert sein.
|
|
|
|
---
|
|
|
|
## 5. Starten und Stoppen
|
|
|
|
```bash
|
|
make run
|
|
```
|
|
|
|
Standard-Adresse: `http://localhost:8080` (Port änderbar, siehe Abschnitt 8).
|
|
Beenden mit **Strg + C**.
|
|
|
|
Schnelltest in einem zweiten Terminal:
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
# {"status":"ok"}
|
|
|
|
curl http://localhost:8080/api/config
|
|
# zeigt aktives Profil und die aufgelöste Standard-Route
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Tägliche Bedienung — typische Aufgaben
|
|
|
|
### a) Text sprechen lassen (`/api/speak`)
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/speak \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"text":"Guten Morgen, wie geht es Ihnen?"}' \
|
|
--output antwort.pcm
|
|
```
|
|
|
|
### b) Chatten (Text rein, gesprochene Antwort raus) (`/api/chat`)
|
|
|
|
Nur den Trace als JSON ansehen (ohne Audio):
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8080/api/chat?debug=true" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"text":"Wie wird das Wetter morgen?"}'
|
|
```
|
|
|
|
Komfortabler mit dem mitgelieferten Client (spielt die Antwort ab):
|
|
|
|
```bash
|
|
python chat_client.py "Erzähl mir einen guten Morgen-Spruch"
|
|
```
|
|
|
|
> `chat_client.py` erwartet den Dienst auf Port **8003** — bei Bedarf im Skript
|
|
> `GATEWAY_URL` anpassen oder den Dienst mit `PORT=8003 make run` starten.
|
|
|
|
**Fortlaufendes Gespräch (Gedächtnis):** Wird eine `session_id` mitgegeben, merkt
|
|
sich der Assistent den Verlauf und bezieht ihn in die nächste Antwort ein:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8080/api/chat?session_id=oma-anna&debug=true" \
|
|
-H 'Content-Type: application/json' -d '{"text":"Ich heiße Anna."}'
|
|
curl -X POST "http://localhost:8080/api/chat?session_id=oma-anna&debug=true" \
|
|
-H 'Content-Type: application/json' -d '{"text":"Wie war noch mein Name?"}'
|
|
```
|
|
|
|
Ohne `session_id` ist jeder Aufruf eigenständig (kein Gedächtnis). Wie viele
|
|
zurückliegende Nachrichten einfließen, steuert `HISTORY_MAX_MESSAGES` (Standard 10).
|
|
|
|
### c) Audio transkribieren (`/api/transcribe`)
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/transcribe \
|
|
-F "file=@aufnahme.wav" -F "language=de"
|
|
```
|
|
|
|
### d) Gerät oder Provider einmalig umstellen (pro Aufruf)
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/speak \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"text":"Test","tts_provider":"piper","output_endpoint":"loopback"}'
|
|
```
|
|
|
|
### e) Präferenzen für eine Session festlegen
|
|
|
|
```bash
|
|
# einmal setzen
|
|
curl -X POST http://localhost:8080/api/sessions/oma-anna/route \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"llm_provider":"openrouter","language":"de"}'
|
|
|
|
# danach mit dieser Session nutzen
|
|
curl -X POST "http://localhost:8080/api/chat?session_id=oma-anna&debug=true" \
|
|
-H 'Content-Type: application/json' -d '{"text":"Hallo!"}'
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Verfügbare Geräte und Bausteine ansehen
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/devices # Audio-Endpunkte mit Fähigkeiten
|
|
curl http://localhost:8080/api/config # Profil, Route, Provider, Endpunkte
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Port ändern
|
|
|
|
```bash
|
|
PORT=8003 make run # einmalig
|
|
sed -i 's/^PORT=.*/PORT=8003/' .env # dauerhaft
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Mit Docker betreiben
|
|
|
|
```bash
|
|
export OPENROUTER_API_KEY=sk-or-v1-...
|
|
docker compose up --build
|
|
```
|
|
|
|
Der Key wird aus der Shell in den Container durchgereicht; fehlt er, bricht der
|
|
Start mit klarer Meldung ab.
|
|
|
|
---
|
|
|
|
## 10. Authentifizierung & Mehrbenutzer
|
|
|
|
Im Produktivbetrieb ist `AUTH_ENABLED=true` (Standard). Dann brauchen
|
|
`chat`/`speak`/`transcribe`/`sessions`/`me` ein **Bearer-Token pro Nutzer**.
|
|
Nutzer und Sessions werden in einer SQLite-Datei gespeichert (`DB_PATH`, Standard
|
|
`data/voice-assistant.db`).
|
|
|
|
**Schritt 1 — Admin-Schlüssel setzen** (nur über die Umgebung):
|
|
|
|
```bash
|
|
export ADMIN_API_KEY=ein-langes-geheimnis
|
|
```
|
|
|
|
**Schritt 2 — Nutzer anlegen** (Token erscheint **nur einmal**, sicher notieren):
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/admin/users \
|
|
-H "X-Admin-Key: $ADMIN_API_KEY" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"display_name":"Oma Anna"}'
|
|
```
|
|
|
|
**Schritt 3 — mit Token nutzen:**
|
|
|
|
```bash
|
|
TOKEN=<das-token-von-oben>
|
|
curl http://localhost:8080/api/me -H "Authorization: Bearer $TOKEN"
|
|
|
|
curl -X POST http://localhost:8080/api/speak \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"text":"Guten Morgen!"}'
|
|
```
|
|
|
|
**Dauerhafte Vorlieben** eines Nutzers (Gerät/Provider/Sprache) setzen:
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/me/prefs \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"language":"de","llm_provider":"openrouter"}'
|
|
```
|
|
|
|
Diese Vorlieben gelten automatisch für alle Aufrufe dieses Nutzers (Ebene zwischen
|
|
Profil und Session). Eine fremde Session zu nutzen, wird mit `403` abgelehnt.
|
|
|
|
**Langzeit-Erinnerungen** (dauerhafte Fakten/Vorlieben, gelten über alle Gespräche):
|
|
|
|
```bash
|
|
# anlegen
|
|
curl -X POST http://localhost:8080/api/me/memories \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H 'Content-Type: application/json' -d '{"content":"Mag morgens Kamillentee."}'
|
|
# auflisten / löschen
|
|
curl http://localhost:8080/api/me/memories -H "Authorization: Bearer $TOKEN"
|
|
curl -X DELETE http://localhost:8080/api/me/memories/1 -H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
Diese Erinnerungen gibt der Assistent bei jedem Chat als Kontext mit — auch ohne
|
|
`session_id`.
|
|
|
|
**Echtzeit-Chat über WebSocket** (`/ws/chat`): dauerhafter Kanal, pro Nachricht
|
|
`{"text": "..."}`; Antwort kommt als Event-Folge (`ack`, `semantic`, Audio, `done`).
|
|
Token per Query (`?token=…`), Gedächtnis per `?session_id=…`. Mit
|
|
`{"text": "...", "stream": true}` kommt die Antwort schon während der Generierung
|
|
als `token`-Events (geringere wahrgenommene Latenz). Mit `"audio_stream": true`
|
|
kommt zusätzlich das Audio satzweise (`audio`-Event + binärer Frame), sobald ein
|
|
Satz fertig ist.
|
|
|
|
**Sprach-Eingang** (`/ws/voice`): Mikrofon-Audio als binäre Frames senden, dann
|
|
`{"type":"end"}`. Der Server schickt ein `transcript`-Event und danach die Antwort
|
|
wie bei `/ws/chat` (`stream`/`audio_stream` im `end`-Frame möglich).
|
|
|
|
> **Für lokale Entwicklung** ist in der mitgelieferten `.env` `AUTH_ENABLED=false`
|
|
> gesetzt — dann ist kein Token nötig (anonymer Nutzer).
|
|
|
|
---
|
|
|
|
## 11. Fehlerbehebung
|
|
|
|
| Symptom | Ursache | Lösung |
|
|
|---|---|---|
|
|
| `OPENROUTER_API_KEY is empty` | Key nicht in der Umgebung | `export OPENROUTER_API_KEY=…`, neues Terminal / `source ~/.bashrc` |
|
|
| HTTP **401** „Bearer token required/Invalid token" | Auth an, Token fehlt/falsch | gültiges Token im Header `Authorization: Bearer …`, oder `AUTH_ENABLED=false` für dev |
|
|
| HTTP **401** bei `/api/admin/users` | falscher/fehlender Admin-Key | `X-Admin-Key` mit `ADMIN_API_KEY` abgleichen |
|
|
| HTTP **403** bei `?session_id=…` | Session gehört anderem Nutzer | eigene `session_id` verwenden |
|
|
| HTTP **503** bei `/api/admin/users` | `ADMIN_API_KEY` nicht gesetzt | Admin-Key in der Umgebung setzen |
|
|
| HTTP **422** „Unbekannter …-Provider/Endpunkt" | Tippfehler in `*_provider` / `*_endpoint` | gültige Werte via `GET /api/config` prüfen |
|
|
| `VA_PROFILE` wirkt nicht | `DEFAULT_*_PROVIDER` in `.env` überschreibt es | diese Zeilen in `.env` auskommentieren |
|
|
| LLM-Timeout / Connection refused (lokal) | lokaler LLM-Server (Port 11434) läuft nicht | LLM-Server starten oder Profil `cloud` wählen |
|
|
| `Address already in use` | Port belegt | anderen `PORT` setzen (Abschnitt 8) |
|
|
| `chat_client.py` bekommt keine Antwort | Client nutzt Port 8003 | Dienst mit `PORT=8003` starten oder `GATEWAY_URL` anpassen |
|
|
| Profil greift nicht / Standardwerte | `config/voice-assistant.toml` fehlt | Datei aus `*.example.toml` kopieren (Abschnitt 2) |
|
|
|
|
Logs erscheinen im Terminal, in dem `make run` läuft. Für mehr Details
|
|
`LOG_LEVEL=debug` in `.env` setzen.
|
|
|
|
---
|
|
|
|
## 12. Tests ausführen
|
|
|
|
```bash
|
|
make test
|
|
```
|
|
|
|
Alle Tests sollten grün sein. Schlägt etwas fehl, gibt die Ausgabe den genauen
|
|
Testnamen und die Ursache an.
|