Initial commit: Voice Assistant Gateway mit Konfig-/Routing-Fundament
- FastAPI-Gateway mit REST-Endpunkten (chat/speak/transcribe/devices/sessions/config) - Geschichtete Konfiguration mit Profilen (local-dev/hybrid/cloud) via TOML + ENV - Registry-Pattern + einheitliche Route-Aufloesung (Default->Profil->ENV->Session->Request) - Device Router (strikt, Singleton) und Output-Lifecycle im Orchestrator - OpenRouter-Adapter (STT multipart/LLM/TTS) + lokale Provider-Stubs - Regelbasierte Pipeline (Cleaner/Spoken-Adapter/TTS-Normalizer) - 22 automatisierte Tests; Doku: README, BEDIENUNGSANLEITUNG, Architektur - Secrets ausschliesslich ueber Umgebung; .env und lokale config gitignored Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
293ed257db
72 changed files with 2612 additions and 0 deletions
220
Docs/voice-assistant-architecture.md
Normal file
220
Docs/voice-assistant-architecture.md
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
# Architektur: Modularer Voice-Assistent
|
||||
|
||||
> Stand: aktueller Implementierungsstand des Gateways. Dieses Dokument beschreibt
|
||||
> das Konzept, den umgesetzten Stand und die geplanten nächsten Schritte.
|
||||
|
||||
## 1. Ziel & Leitidee
|
||||
|
||||
Ein modularer, **cloud-first, aber hybrid betreibbarer** Sprachassistent für
|
||||
Senioren — ein „digitaler Vertrauter", erreichbar von zuhause und unterwegs.
|
||||
|
||||
Der Hauptbetrieb läuft in der Cloud / auf einem vHost (Senioren sollen keinen
|
||||
teuren KI-Rechner zuhause brauchen). Gleichzeitig muss jede Achse frei wählbar
|
||||
bleiben, je nach Installation und Entwicklungs-/Testbedarf:
|
||||
|
||||
- **Hardware** — Audio-Eingabe und -Ausgabe (lokal, Bluetooth, Handy, Netzwerk)
|
||||
- **Betrieb** — lokal, Cloud oder hybrid
|
||||
- **Software** — lokale oder entfernte KI (STT / LLM / TTS), ganz oder teilweise
|
||||
|
||||
Designziele: geringe Latenz, robuste Fallbacks, gute Debugbarkeit, klare Trennung
|
||||
von **semantischer** und **gesprochener** Antwort, und vor allem **Austauschbarkeit**:
|
||||
einzelne Module gegen andere tauschen, ohne das Programm umzuschreiben.
|
||||
|
||||
## 2. Architekturprinzip — fünf Ebenen
|
||||
|
||||
1. **Audio Endpoints** – konkrete Quellen/Ziele (lokal, Bluetooth, Handy, WebRTC)
|
||||
2. **Device Router** – wählt passende Input-/Output-Endpunkte
|
||||
3. **Speech Pipeline** – STT, Input-Cleaner, Dialog-LLM, Spoken-Response-Adapter, TTS-Normalizer, TTS
|
||||
4. **Transport Router** – lokale vs. entfernte Ausführung einzelner Module
|
||||
5. **Orchestrator** – Session, Turn-Taking, Fallbacks, Metrik
|
||||
|
||||
Jede Ebene kommuniziert über wohldefinierte Interfaces (ABCs + Pydantic-Schemas),
|
||||
nicht über konkrete Bibliotheken.
|
||||
|
||||
## 3. Konfigurations- & Routing-Modell (umgesetzt)
|
||||
|
||||
Das Herzstück der Austauschbarkeit. Jede Achse ist auf mehreren Ebenen
|
||||
einstellbar; höhere Ebene gewinnt:
|
||||
|
||||
```
|
||||
eingebaute Defaults < config/voice-assistant.toml (inkl. aktivem Profil)
|
||||
< ENV / .env < Session-Route < Request
|
||||
```
|
||||
|
||||
### 3.1 Zentrale Config + Profile
|
||||
|
||||
Eine geschichtete zentrale Datei (`config/voice-assistant.toml`, Vorlage
|
||||
`config/voice-assistant.example.toml`), gelesen über die stdlib (`tomllib`).
|
||||
**Profile** bündeln Betriebsarten und werden per ENV `VA_PROFILE` aktiviert:
|
||||
|
||||
| Profil | STT | LLM | TTS |
|
||||
|-------------|----------------|--------------------------|------------|
|
||||
| `local-dev` | faster-whisper | local-openai-compatible | piper |
|
||||
| `hybrid` | openrouter | local-openai-compatible | openrouter |
|
||||
| `cloud` | openrouter | openrouter | openrouter |
|
||||
|
||||
Umgesetzt in `app/config.py`: `load_profile_config()` merged `[defaults]` +
|
||||
`[profiles.<aktiv>]`; `TomlProfileSource` hängt diese Werte als Settings-Quelle
|
||||
**unter** ENV ein (`settings_customise_sources`). `VA_PROFILE`/`VA_CONFIG_FILE`
|
||||
werden aus echter Umgebung **oder** `.env` gelesen.
|
||||
|
||||
> **Secrets gehören nie in die Config-Datei** — nur in die Umgebung
|
||||
> (z. B. `OPENROUTER_API_KEY`). Die TOML-Datei darf versioniert/geteilt werden.
|
||||
|
||||
### 3.2 Einheitliche Route-Auflösung
|
||||
|
||||
`app/dependencies.py` löst pro Aufruf eine `ResolvedRoute` auf
|
||||
(`resolve_route(session_id, overrides)`): Defaults < Session-Route < Request.
|
||||
Die Route umfasst `input_endpoint`, `output_endpoint`, `stt_provider`,
|
||||
`llm_provider`, `tts_provider`, `language`.
|
||||
|
||||
### 3.3 Registry-Pattern (Provider austauschbar)
|
||||
|
||||
`STT_REGISTRY` / `LLM_REGISTRY` / `TTS_REGISTRY` bilden `name -> factory(settings)`.
|
||||
Ein neuer Provider = ein Eintrag, ohne Kern-Code zu ändern. Unbekannter Name →
|
||||
`UnknownComponentError` → HTTP 422.
|
||||
|
||||
### 3.4 Device Router
|
||||
|
||||
`app/audio/router.py` wählt Endpunkte per `id`- oder `kind`-Match. Ein angefragter,
|
||||
aber unbekannter Endpunkt wirft `UnknownEndpointError` (→ 422) — **kein** stiller
|
||||
Default-Fallback. Ohne Wunsch greift der als `default` markierte Endpunkt. Der
|
||||
Router ist ein Singleton (stabiler Zustand, z. B. `LoopbackOutput`).
|
||||
|
||||
## 4. Datenmodelle & Interfaces
|
||||
|
||||
Schemas in `app/schemas.py`, Interfaces als ABCs in den jeweiligen `base.py`.
|
||||
|
||||
```python
|
||||
class EndpointCapabilities(BaseModel):
|
||||
id: str; kind: str
|
||||
direction: Literal["input", "output"]
|
||||
sample_rate: int = 16000; channels: int = 1
|
||||
latency_class: Literal["low", "medium", "high"] = "medium"
|
||||
supports_aec: bool = False; supports_barge_in: bool = False
|
||||
networked: bool = False; bluetooth: bool = False
|
||||
mobile: bool = False; default: bool = False
|
||||
|
||||
class AudioChunk(BaseModel):
|
||||
data: bytes; sample_rate: int = 16000; channels: int = 1
|
||||
format: str = "wav"; timestamp_ms: int = 0
|
||||
|
||||
class PipelineTrace(BaseModel):
|
||||
raw_transcript: str | None = None
|
||||
cleaned_transcript: str | None = None
|
||||
semantic_response: str | None = None
|
||||
spoken_response: str | None = None
|
||||
tts_ready_text: str | None = None
|
||||
```
|
||||
|
||||
Provider-Interfaces:
|
||||
|
||||
```python
|
||||
class STTProvider(ABC):
|
||||
async def transcribe(self, audio_bytes: bytes, fmt: str, language: str | None = None) -> str: ...
|
||||
|
||||
class LLMProvider(ABC):
|
||||
async def complete(self, text: str, session_id: str | None = None) -> str: ...
|
||||
|
||||
class TTSProvider(ABC):
|
||||
async def synthesize(self, text: str, voice: str | None = None, audio_format: str = "pcm") -> bytes: ...
|
||||
```
|
||||
|
||||
Audio-Endpunkt-Interfaces: `AudioInputEndpoint` (`capabilities/open/read_chunk/close`),
|
||||
`AudioOutputEndpoint` (`capabilities/open/write_chunk/flush/close`) — alle async.
|
||||
|
||||
## 5. Speech Pipeline
|
||||
|
||||
Trennung von **semantischer** und **gesprochener** Antwort ist zentral: eine
|
||||
inhaltlich gute Antwort ist nicht automatisch gut hörbar.
|
||||
|
||||
Stufen: `raw_transcript → cleaned_transcript → semantic_response → spoken_response → tts_ready_text`.
|
||||
|
||||
- **Input Cleaner** (`pipeline/input_cleaner.py`) – konservative Bereinigung des STT-Texts (Füllwörter, Whitespace). Verändert die Nutzerintention nicht.
|
||||
- **Dialog-LLM** – semantische Antwort; Persona/Sicherheitsregeln im System-Prompt (`providers/llm/openrouter.py`).
|
||||
- **Spoken Response Adapter** (`pipeline/spoken_response_adapter.py`) – macht die Antwort sprechbar/seniorengerecht (Markdown raus, Listen → Sätze, Uhrzeiten erhalten).
|
||||
- **TTS Normalizer** (`pipeline/tts_normalizer.py`) – Zahlen/Abkürzungen/Einheiten verbal ausformulieren, sprachabhängig (de/en).
|
||||
|
||||
Empfehlung: nicht jede Zwischenstufe braucht ein großes LLM — Cleaner und
|
||||
Normalizer überwiegend regelbasiert (so heute umgesetzt), Adapter promptbasiert.
|
||||
|
||||
## 6. Orchestrator
|
||||
|
||||
`app/core/orchestrator.py` verbindet Provider, Pipeline und Output-Endpunkt.
|
||||
|
||||
- `chat_text(text, language, voice, output)` → `(trace, audio_bytes)`
|
||||
- `speak_only(text, voice, language, output)` → `audio_bytes`
|
||||
- `transcribe_only(audio_bytes, fmt, language, input)` → `trace`
|
||||
|
||||
Das synthetisierte Audio wird **zusätzlich** durch den gewählten Output-Endpunkt
|
||||
geschrieben (`open → write_chunk → flush → close`) und **gleichzeitig** als
|
||||
HTTP-Stream zurückgegeben (additiv). Bei lokalen Geräten ist `write_chunk` heute
|
||||
ein No-op; `LoopbackOutput` sammelt die Chunks (testbar ohne Hardware).
|
||||
|
||||
## 7. FastAPI-Endpunkte (umgesetzt)
|
||||
|
||||
| Methode & Pfad | Zweck |
|
||||
|--------------------------------------|-------|
|
||||
| `GET /health` | Liveness |
|
||||
| `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` | bevorzugte Geräte/Provider/Sprache je Session |
|
||||
| `GET /api/config` | aktives Profil + aufgelöste Route (ohne Secrets) |
|
||||
|
||||
Endpunkt-/Provider-Auswahl ist über **Request-Body** (pro Aufruf), **Session**
|
||||
(`?session_id=…`) und **Defaults/Profil** steuerbar. Verwendete Route erscheint als
|
||||
`X-*`-Header bzw. im `?debug`-JSON.
|
||||
|
||||
## 8. Stand der Implementierung
|
||||
|
||||
**Umgesetzt:** FastAPI-Gateway, alle o. g. REST-Endpunkte; OpenRouter-Adapter für
|
||||
STT (multipart), LLM und TTS; lokaler OpenAI-kompatibler LLM-Adapter; regelbasierte
|
||||
Pipeline; geschichtete Config + Profile; Registry + einheitliche Route-Auflösung;
|
||||
Device Router (strikt, Singleton); Output-Lifecycle; 22 automatisierte Tests.
|
||||
|
||||
**Platzhalter (Gerüst):** Audio-Endpunkte (`local-default`, `bluetooth`,
|
||||
`mobile-ws`, `mobile-webrtc`) liefern leere Chunks — nur Auswahl/Lifecycle sind
|
||||
verdrahtet, kein echtes Hardware-I/O. Lokale Provider `faster-whisper`, `piper`,
|
||||
`chatterbox` sind Stubs. `transport_router.py` (Ebene 4) existiert, ist aber noch
|
||||
nicht aktiv (lokal/remote trägt vorerst der Provider-Name).
|
||||
|
||||
## 9. Roadmap / bewusste nächste Schritte
|
||||
|
||||
Reihenfolge der Weiterentwicklung:
|
||||
|
||||
1. **(erledigt)** Konfig- & Routing-Fundament: Profile, Device Router, Registry, Pro-Request-Override.
|
||||
2. **Cloud-Fundament:** Authentifizierung + Mehrbenutzer + persistenter Session-/Profil-Store (heute ist `SessionManager` in-memory → nicht skalierend, ohne Auth).
|
||||
3. **Konversationsgedächtnis:** Verlauf + Langzeit-Präferenzen (heute ist `llm.complete` zustandslos).
|
||||
4. **Echtzeit:** Streaming-STT/TTS, WebSocket/WebRTC-Endpunkte real, Barge-in, Turn-Manager.
|
||||
5. **Resilienz:** Fallback-Policy (remote KI fällt aus → lokaler/alternativer Provider), Metriken/Tracing.
|
||||
6. **Betrieb:** Kosten-/Quota-Kontrolle pro Nutzer; Notfall-/Eskalationskonzept (Senioren-Kontext).
|
||||
7. **TransportRouter** als eigene lokal/remote-Achse aktivieren.
|
||||
|
||||
**Datenschutz (querschnittlich, ab sofort mitdenken):** Senioren-Sprachdaten sind
|
||||
hochsensibel (oft gesundheitsbezogen → DSGVO Art. 9). EU-Datenresidenz,
|
||||
Verschlüsselung at-rest/in-transit, Löschkonzept, Einwilligung — „privacy by design".
|
||||
|
||||
## 10. Verzeichnisstruktur (Ist)
|
||||
|
||||
```text
|
||||
voice-assistant-scaffold/
|
||||
├── app/
|
||||
│ ├── main.py # FastAPI-App + Router-Registrierung
|
||||
│ ├── config.py # Settings, TOML-Profile, Präzedenz
|
||||
│ ├── dependencies.py # Registries, ResolvedRoute, resolve_route, Singleton-Router
|
||||
│ ├── errors.py # RoutingError -> HTTP 422
|
||||
│ ├── schemas.py # Pydantic-Modelle
|
||||
│ ├── api/ # health, chat, speak, transcribe, devices, sessions, config
|
||||
│ ├── core/ # orchestrator, session_manager
|
||||
│ ├── audio/ # router, transport_router, endpoints/input|output/*
|
||||
│ ├── pipeline/ # input_cleaner, spoken_response_adapter, tts_normalizer
|
||||
│ └── providers/ # stt/ llm/ tts/ (openrouter + lokale Stubs)
|
||||
├── config/ # voice-assistant.example.toml (+ lokale .toml, gitignored)
|
||||
├── deploy/ # systemd unit + env-Beispiel
|
||||
├── tests/ # config-profile, routing, audio-router, e2e
|
||||
├── Docs/ # dieses Dokument
|
||||
├── Dockerfile, docker-compose.yml, Makefile, pyproject.toml
|
||||
└── README.md, BEDIENUNGSANLEITUNG.md
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue