- Python 74.7%
- JavaScript 18.6%
- HTML 4%
- Shell 2.4%
- Makefile 0.3%
- SentenceChunker (pipeline/sentence_chunker.py): inkrementelle Satzsegmentierung
- Orchestrator.chat_stream(on_audio): satzweise TTS, Audio-Chunk pro fertigem Satz;
Gesamtaudio zusaetzlich an den Output-Endpunkt
- WS /ws/chat {"audio_stream":true}: audio-Events (json seq + binaerer Frame) live,
kein finales Vollaudio; mit stream kombinierbar
- Tests: 45 gruen (+2: Sentence-Chunker, Audio-Streaming)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| app | ||
| config | ||
| deploy | ||
| Docs | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| BEDIENUNGSANLEITUNG.md | ||
| chat_client.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
Voice Assistant Gateway
Modulares FastAPI-Gateway für einen seniorengerechten Sprachassistenten — cloud-first, aber hybrid/lokal betreibbar, mit austauschbaren Audio-Endpunkten und STT-/LLM-/TTS-Providern.
Jede Achse — Hardware (Audio In/Out), Betrieb (lokal/cloud) und Software
(lokale/remote KI) — ist frei konfigurierbar, ohne Code zu ändern. Konzept und
Details: Docs/voice-assistant-architecture.md.
Praktische Bedienung: BEDIENUNGSANLEITUNG.md.
Features
- Pipeline mit getrennter Semantik/Sprache: STT → Input-Cleaner → LLM → Spoken-Adapter → TTS-Normalizer → TTS
- Provider austauschbar über Registry (OpenRouter remote; faster-whisper/piper/chatterbox als lokale Stubs)
- Geschichtete Konfiguration mit Profilen (
local-dev/hybrid/cloud) - Routing auf jeder Ebene: Default → Profil → Nutzer → Session → Request
- Authentifizierung (Bearer-Token) + persistente Nutzer/Sessions (SQLite)
- Gesprächsgedächtnis pro Session: Verlauf wird gespeichert und fließt ins LLM
- Langzeit-Erinnerungen pro Nutzer: dauerhafte Fakten/Vorlieben als LLM-Kontext
- WebSocket-Streaming-Chat (
/ws/chat) als Echtzeit-Transport - REST-API für Chat, Transkription, Sprachausgabe, Geräte, Sessions, Config
- Ohne Secrets im Code — API-Keys nur über die Umgebung
Schnellstart
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e .[test]
cp config/voice-assistant.example.toml config/voice-assistant.toml
export OPENROUTER_API_KEY=sk-or-v1-... # nur für Cloud-/Hybrid-Profile nötig
make run
Fehlt .env, wird sie beim ersten make run aus .env.example erzeugt.
Die App läuft dann auf http://localhost:8080 (bzw. dem in .env gesetzten PORT).
Kurztest:
curl http://localhost:8080/health
curl http://localhost:8080/api/config
Konfiguration & Profile
Höhere Ebene gewinnt:
eingebaute Defaults < config/voice-assistant.toml (inkl. aktivem Profil)
< ENV / .env < Session-Route < Request
Profile umschalten per Umgebungsvariable (oder dauerhaft in .env):
VA_PROFILE=local-dev make run # alles lokal (faster-whisper / lokales LLM / piper)
VA_PROFILE=hybrid make run # STT/TTS remote, LLM lokal
VA_PROFILE=cloud make run # alles über OpenRouter
Secrets gehören nicht in
config/*.toml— nur in die Umgebung (export OPENROUTER_API_KEY=…). GesetzteDEFAULT_*_PROVIDER-Werte in.envüberschreiben einVA_PROFILE.
Pro Session: POST /api/sessions/{id}/route (input_endpoint, output_endpoint,
stt_provider, llm_provider, tts_provider, language), dann Aufrufe mit ?session_id=….
Pro Request: dieselben Felder im Body von /api/chat bzw. /api/speak.
Aktive Konfiguration prüfen: curl http://localhost:8080/api/config.
API-Überblick
| Methode & Pfad | Zweck |
|---|---|
GET /health |
Liveness-Check |
POST /api/chat |
Text rein → Audio raus (?debug=true → JSON-Trace) |
POST /api/speak |
Text rein → TTS-Audio raus |
POST /api/transcribe |
Audio-Upload → Transkript |
GET /api/devices |
verfügbare Audio-Endpunkte + Capabilities |
POST /api/sessions/{id}/route |
Geräte/Provider/Sprache je Session setzen |
GET /api/config |
aktives Profil + aufgelöste Route (ohne Secrets) |
POST /api/admin/users |
Nutzer anlegen (Admin-Key) → Token einmalig |
GET /api/me |
aktueller Nutzer + Präferenzen |
PUT /api/me/prefs |
dauerhafte Routing-Präferenzen des Nutzers setzen |
GET/POST/DELETE /api/me/memories |
Langzeit-Erinnerungen des Nutzers verwalten |
WS /ws/chat |
Echtzeit-Chat über WebSocket (Streaming-Events) |
Beispiel (Sprachausgabe an den Test-Loopback, lokaler TTS-Stub):
curl -X POST http://localhost:8080/api/speak \
-H 'Content-Type: application/json' \
-d '{"text":"Guten Morgen!","tts_provider":"piper","output_endpoint":"loopback"}'
Unbekannter Endpunkt/Provider → HTTP 422 mit Klartext-Hinweis.
Gesprächsgedächtnis
Wird bei /api/chat eine session_id mitgegeben, merkt sich der Assistent den
Verlauf: vergangene Turns werden gespeichert und beim nächsten Aufruf ans LLM
gegeben (begrenzt auf die letzten HISTORY_MAX_MESSAGES Nachrichten, Default 10).
Ohne session_id bleibt der Aufruf zustandslos.
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?"}'
Langzeit-Erinnerungen (über Sessions hinweg, pro Nutzer) werden über
/api/me/memories gepflegt und bei jedem Chat als Kontext ans LLM gegeben — auch
ohne session_id:
curl -X POST http://localhost:8080/api/me/memories \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"content":"Mag morgens Kamillentee."}'
Echtzeit-Chat (WebSocket)
/ws/chat bietet einen dauerhaften, bidirektionalen Kanal. Der Client sendet pro
Turn eine JSON-Nachricht ({"text": "..."}, optional Provider/Endpunkt-Overrides),
der Server streamt strukturierte Events zurück: ack → semantic → Audio (binär)
→ done. Auth (Token-Query ?token=…), Session-Gedächtnis (?session_id=…) und
Erinnerungen gelten wie bei POST /api/chat.
Token-Streaming: Mit {"text": "...", "stream": true} schickt der Server die
LLM-Antwort schon während der Generierung als token-Events — spürbar geringere
wahrgenommene Latenz. OpenRouter und der lokale OpenAI-kompatible Provider streamen
via SSE; Provider ohne Streaming liefern die komplette Antwort als ein token-Event.
Audio-Streaming: Mit {"text": "...", "audio_stream": true} wird das Audio
satzweise erzeugt (chunked TTS) und pro fertigem Satz als audio-Event (JSON
mit seq + binärer Frame) gesendet — die Ausgabe beginnt, bevor die Antwort fertig
ist. stream und audio_stream lassen sich kombinieren.
Audio-Eingang/Streaming-STT, Barge-in/Turn-Manager und WebRTC sind als nächste Increments vorgesehen (siehe Architektur-Dokument).
Authentifizierung
Standardmäßig (AUTH_ENABLED=true) sind chat/speak/transcribe/sessions/me
durch ein Bearer-Token pro Nutzer geschützt. Nutzer/Sessions werden in SQLite
persistiert (DB_PATH, Default data/voice-assistant.db).
# 1) Nutzer anlegen (Admin-Key aus der Umgebung) — Token erscheint EINMALIG
export ADMIN_API_KEY=ein-langes-geheimnis
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"}'
# -> {"user_id":"…","display_name":"Oma Anna","token":"…"}
# 2) Mit dem Token aufrufen
curl http://localhost:8080/api/me -H "Authorization: Bearer <TOKEN>"
Dauerhafte Präferenzen pro Nutzer (PUT /api/me/prefs) fließen in die Route-Auflösung
ein (Ebene zwischen Profil und Session). Fremde Sessions → HTTP 403.
Lokale Entwicklung:
AUTH_ENABLED=falsesetzen — dann gilt ein anonymer Standardnutzer und es ist kein Token nötig.
Tests
make test # oder: pytest -q
Abgedeckt: Config-Profile & Präzedenz, Route-Auflösung, Device Router,
End-to-End (Loopback, 422-Fälle, Session-/Request-Override, /api/config).
Port ändern
PORT=8003 make run # einmalig
sed -i 's/^PORT=.*/PORT=8003/' .env # dauerhaft
PORT=8003 docker compose up # mit Docker
Deployment
- Docker:
docker compose up --build(reichtOPENROUTER_API_KEYaus der Shell durch) - systemd: Vorlagen unter
deploy/(voice-assistant.service,voice-assistant.env.example)
Projektstruktur (Kurzform)
app/ Gateway: config, dependencies, api/, core/, audio/, pipeline/, providers/
config/ voice-assistant.example.toml (lokale .toml ist gitignored)
deploy/ systemd-Unit + env-Beispiel
tests/ Pytest-Suite
Docs/ Architektur-Dokument
Lizenz / Status
Frühes, aktiv entwickeltes Projektgerüst. Audio-Hardware-/Streaming-Anbindung, Authentifizierung, Persistenz und Gedächtnis sind als nächste Schritte vorgesehen (siehe Roadmap im Architektur-Dokument).