my_voice_assistant_v3/CLAUDE.md
2026-06-21 17:22:36 +02:00

5.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Sprache: Code-Kommentare, Doku und Commits sind auf Deutsch. Antworte und kommentiere auf Deutsch.

Befehle

make install          # venv anlegen + pip install -e .[test]
make run              # Gateway (uvicorn --reload, Port aus .env, Standard 8080)
make test             # gesamte Pytest-Suite (offline, ohne Netz/Kosten)
make smoke            # echter End-to-End-Test gegen OpenRouter (kostet wenig)
make stop / restart   # Gateway + llama.cpp stoppen / neu starten

# Einzelner Test / einzelner Fall:
. .venv/bin/activate && pytest tests/test_routing.py
. .venv/bin/activate && pytest tests/test_routing.py::test_name -q

Profil beim Start wählen (überschreibt .env): VA_PROFILE=cloud|hybrid|local-dev make run. Lokales LLM (llama.cpp-Docker) für hybrid/local-dev: make llm-upmake llm-statusmake llm-down. Backend wechseln (llama.cpp ↔ Ollama, teilen sich die GPU, nie gleichzeitig): make llm-llamacpp / make llm-ollama.

Vor make run: wenn .env fehlt, wird sie aus .env.example erzeugt. Für Cloud/Hybrid OPENROUTER_API_KEY setzen.

Architektur (großes Bild)

Modulares FastAPI-Gateway für einen seniorengerechten Sprachassistenten. Leitprinzip: jede Achse (STT/LLM/TTS, Audio-I/O, lokal vs. remote) austauschbar, ohne Kern-Code zu ändern. Ausführliche Referenz: Docs/voice-assistant-architecture.md und BEDIENUNGSANLEITUNG.md.

Zwei zentrale Mechanismen, die man verstehen muss

  1. Geschichtete Konfiguration mit Profilen (app/config.py). Präzedenz, höhere Ebene gewinnt: Defaults < config/voice-assistant.toml ([defaults]+[profiles.<aktiv>]) < ENV/.env < Nutzer-Prefs < Session-Route < Request. VA_PROFILE aktiviert ein Profil; TomlProfileSource hängt die TOML-Werte als pydantic-settings-Quelle unter ENV ein. Secrets nie in die TOML — nur in die Umgebung. Profile: cloud (alles OpenRouter), hybrid (LLM lokal), local-dev (alles lokal).

  2. Route-Auflösung + Registries (app/dependencies.py). resolve_route(user, session_id, overrides) liefert pro Aufruf eine ResolvedRoute (Endpunkte, STT/LLM/TTS-Provider, Sprache) nach obiger Präzedenz. Provider kommen aus STT_REGISTRY/LLM_REGISTRY/TTS_REGISTRY (name -> factory(settings)). Neuen Provider = ein Registry-Eintrag + ABC-Implementierung in app/providers/{stt,llm,tts}/. Unbekannter Name → UnknownComponentError → HTTP 422.

Speech-Pipeline (app/pipeline/, orchestriert in app/core/orchestrator.py)

Kernidee: semantische Antwort ≠ gesprochene Antwort. Stufen: raw_transcript → cleaned_transcript (input_cleaner) → semantic_response (LLM) → spoken_response (spoken_response_adapter) → tts_ready_text (tts_normalizer) → TTS.

  • spoken_response_adapter: macht LLM-Text sprechbar (Markdown raus, Listen → Ordinalwörter).
  • tts_normalizer: füllt nur Phonemizer-Lücken (Ordinalia, Einheiten, Abkürzungen, YAML-Lexikon config/pronunciation.<lang>.yaml). Stufe provider-abhängig (TTS_NORMALIZE_LEVEL): piper→full, Cloud-TTS→light. Deutsche Ordinalzahlen in german_numbers.py.
  • sentence_chunker: inkrementelle Satzsegmentierung fürs satzweise Streaming-TTS; trennt bewusst nicht bei „1. Mai", „z. B." usw.

Der Orchestrator schreibt synthetisiertes Audio zusätzlich in den Output-Endpunkt (open→write_chunk→flush→close) und gibt es als HTTP-Stream zurück. Bei /api/chat mit session_id lädt die API-Schicht den Verlauf aus dem Store, gibt ihn als history ans LLM und speichert beide Turns; ohne session_id zustandslos.

Querschnitt

  • Persistenz (app/store.py): Store-Interface + SQLiteStore (Nutzer, Sessions, Verlauf, Erinnerungen) — DB in data/ (gitignored).
  • Auth (app/auth.py): Bearer-Token (require_user) + Admin-Schutz (require_admin). main.py hat zwei Middlewares: Metriken + Web-UI-Gate (statische Seiten hinter Auth/SSO; /api, /ws, /health ausgenommen). AUTH_ENABLED=false → anonymer Nutzer (LAN-Dev).
  • Resilienz (app/providers/fallback.py): Fallback-Ketten je Modul (*_FALLBACK). Metriken in app/metrics.py (/api/metrics, JSON + Prometheus). Tageskontingent app/quota.py (429).
  • Sicherheit (app/safety/): zweistufige Notfall-Eskalation — schnelle Stichwort-Heuristik im Hot-Path (emergency.py) + LLM-Klassifikation als nicht-blockierender Hintergrund-Task (llm_classifier.py).
  • Auto-Erinnerungen (app/core/memory_extractor.py): nach N Turns destilliert ein LLM dauerhafte Fakten als Hintergrund-Task.
  • Echtzeit (app/api/ws.py): /ws/chat (Token-/Audio-Streaming) und /ws/voice (Audio→STT→Pipeline, VAD, Barge-in via interrupt).
  • Admin (app/api/admin.py, app/admin_llm.py, app/audit.py, app/runtime_config.py): Live-Config, Backend-Wechsel, Gateway-Neustart, Lexika, Live-Log — schreibende Aktionen werden ins Audit-Log geschrieben.
  • Frontend (app/web/): Tailwind ohne Build-Schritt, mobiltauglich. Geräte-TTS (Browser-SpeechSynthesis) mit Fallback auf Server-Audio.

Bewusste Platzhalter (nicht „kaputt")

Audio-Endpunkte (app/audio/endpoints/) liefern leere Chunks — nur Auswahl/Lifecycle sind verdrahtet, kein echtes Hardware-I/O. app/audio/transport_router.py (Ebene 4 lokal/remote) existiert, ist aber noch nicht aktiv; lokal/remote trägt vorerst der Provider-Name.

Provider & lokale Module

  • Cloud: OpenRouter (STT/LLM/TTS). Lokales LLM: OpenAI-kompatibel (local_openai_compatible.py) gegen llama.cpp oder Ollama.
  • Lokale KI ist optional: pip install -e .[local] (faster-whisper, piper). Beim Serverstart werden lokale Modelle vorgeladen (app/core/warmup.py).
  • chatterbox-TTS (eigener GPU-HTTP-Dienst, Voice-Cloning) ist als wählbarer Provider angebunden (job-basiert /speak/status/audio).