- prueft LLM, TTS und STT live (inkl. TTS->STT-Round-Trip); klare [OK]/[FAIL]-Ausgabe - bewusst ausserhalb von tests/ (pytest sammelt es nicht ein); macht echte Netz-Aufrufe - Makefile-Target `make smoke`; Doku in README + BEDIENUNGSANLEITUNG - live ausgefuehrt: LLM/TTS/STT alle bestanden Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
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, eine kompakte Übersicht im README.
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
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:
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:
echo 'export OPENROUTER_API_KEY=sk-or-v1-DEIN_KEY' >> ~/.bashrc
chmod 600 ~/.bashrc
source ~/.bashrc
Prüfen, ob er ankommt:
echo ${OPENROUTER_API_KEY:0:8} # zeigt nur den Anfang
Sicherheit: Den Key niemals in
.envoderconfig/*.tomlschreiben. 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:
VA_PROFILE=cloud make run
Profil dauerhaft — in .env eintragen:
VA_PROFILE=cloud
Hinweis: Stehen in
.envnochDEFAULT_STT_PROVIDER/DEFAULT_LLM_PROVIDER/DEFAULT_TTS_PROVIDER, überschreiben diese das Profil. Für profilbasiertes Umschalten sollten sie auskommentiert sein.
5. Starten und Stoppen
make run
Standard-Adresse: http://localhost:8080 (Port änderbar, siehe Abschnitt 8).
Beenden mit Strg + C.
Schnelltest in einem zweiten Terminal:
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)
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):
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):
python chat_client.py "Erzähl mir einen guten Morgen-Spruch"
chat_client.pyerwartet den Dienst auf Port 8003 — bei Bedarf im SkriptGATEWAY_URLanpassen oder den Dienst mitPORT=8003 make runstarten.
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:
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)
curl -X POST http://localhost:8080/api/transcribe \
-F "file=@aufnahme.wav" -F "language=de"
d) Gerät oder Provider einmalig umstellen (pro Aufruf)
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
# 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
curl http://localhost:8080/api/devices # Audio-Endpunkte mit Fähigkeiten
curl http://localhost:8080/api/config # Profil, Route, Provider, Endpunkte
8. Port ändern
PORT=8003 make run # einmalig
sed -i 's/^PORT=.*/PORT=8003/' .env # dauerhaft
9. Mit Docker betreiben
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):
export ADMIN_API_KEY=ein-langes-geheimnis
Schritt 2 — Nutzer anlegen (Token erscheint nur einmal, sicher notieren):
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:
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:
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):
# 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
.envAUTH_ENABLED=falsegesetzt — 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:
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
make test
Alle Tests sollten grün sein. Schlägt etwas fehl, gibt die Ausgabe den genauen Testnamen und die Ursache an. Diese Tests laufen offline (mit Platzhaltern).
Echte Funktion gegen OpenRouter prüfen (LLM, TTS und STT live):
make smoke
Macht echte Cloud-Aufrufe (geringe Kosten) und meldet pro Modul [OK]/[FAIL].
Braucht OPENROUTER_API_KEY in der Umgebung.