- Quota (app/quota.py): Anfragen pro Nutzer/Tag (usage-Tabelle), DAILY_REQUEST_LIMIT, pro Nutzer via prefs uebersteuerbar; 429 (REST) bzw. error-Event (WS); Metrik - Notfall (app/safety/emergency.py): heuristische Erkennung (de/en); Log im Store (emergency_events) + optionaler Webhook (best-effort) + X-Emergency/emergency-Event; Metrik emergency_total; Notfaelle umgehen das Kontingent - verdrahtet in chat/speak/transcribe + WS-Turns - Tests: 64 gruen (+6); Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, .env.example) Hinweis: Notfall-Erkennung ist eine Heuristik (kein Lebensretter); erkannte Texte sind sensibel -> DSGVO beachten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
367 lines
12 KiB
Markdown
367 lines
12 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). Mit
|
|
`{"type":"start","vad":true,"format":"pcm","sample_rate":16000}` erkennt der Server
|
|
das Äußerungsende automatisch an einer Sprechpause (kein `end` nötig).
|
|
|
|
**Unterbrechen (Barge-in):** Während der Assistent antwortet, `{"type":"interrupt"}`
|
|
senden — die laufende Antwort wird abgebrochen (`interrupted`-Event).
|
|
|
|
> **Für lokale Entwicklung** ist in der mitgelieferten `.env` `AUTH_ENABLED=false`
|
|
> gesetzt — dann ist kein Token nötig (anonymer Nutzer).
|
|
|
|
---
|
|
|
|
## 11. Resilienz & Metriken (Betrieb)
|
|
|
|
**Fallback bei Provider-Ausfall:** Pro Modul eine Ersatzliste setzen (in `.env`).
|
|
Fällt der primäre Provider aus, übernimmt der nächste automatisch:
|
|
|
|
```
|
|
LLM_FALLBACK=local-openai-compatible
|
|
STT_FALLBACK=faster-whisper
|
|
TTS_FALLBACK=piper
|
|
```
|
|
|
|
**Metriken ansehen:**
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/metrics # JSON
|
|
curl http://localhost:8080/api/metrics?format=prometheus
|
|
```
|
|
|
|
Enthält Request-Zahlen/-Laufzeiten, Pipeline-Stufen (`stt`/`llm`/`tts`) und
|
|
Fallback-/Fehlerzähler. Die Werte gelten pro laufendem Prozess.
|
|
|
|
**Tageskontingent** (Kostenbremse) in `.env`:
|
|
|
|
```
|
|
DAILY_REQUEST_LIMIT=200 # Anfragen pro Nutzer/Tag; 0 = unbegrenzt
|
|
```
|
|
|
|
Bei Überschreitung antwortet der Dienst mit `429`. Notfall-Eingaben werden nie
|
|
blockiert.
|
|
|
|
**Notfall-Eskalation:** Erkennt der Dienst in einer Chat-/Sprach-Eingabe ein
|
|
Notlagen-Signal (z. B. „Schmerzen in der Brust", „gestürzt", „kann nicht atmen"),
|
|
protokolliert er das, macht es sichtbar (`X-Emergency` / `emergency`-Event) und ruft
|
|
optional einen Webhook auf:
|
|
|
|
```
|
|
# EMERGENCY_WEBHOOK_URL=https://example.org/alert
|
|
```
|
|
|
|
> ⚠️ Nur eine **Heuristik** — kein Ersatz für einen echten Notruf. Erkannte Texte
|
|
> sind sensibel; auf Einwilligung und Datenschutz achten.
|
|
|
|
---
|
|
|
|
## 12. 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.
|
|
|
|
---
|
|
|
|
## 13. 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.
|