- Fallback-Provider (app/providers/fallback.py) fuer STT/LLM/TTS: Provider-Kette der Reihe nach; Config *_FALLBACK; build_orchestrator baut Ketten (dedupliziert) - LLM-Stream-Fallback nur solange kein Token gesendet wurde - Metriken (app/metrics.py): In-Memory Counter/Timer, keine externe Dependency - HTTP-Middleware (Requests/Latenz/Status je Pfad); Pipeline-Stufen-Timing stt/llm/tts; Fallback-/Fehlerzaehler; GET /api/metrics (JSON + Prometheus) - Tests: 58 gruen (+6); Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, .env.example) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 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.
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.