Web-Such-/Tool-Calling-Funktion in der Doku nachgezogen: CHANGELOG (0.3.0), README, CLAUDE.md (Querschnitt), Architektur-Doc (§6.1), Bedienungsanleitung (§5.5 Nutzer/Admin) und DEPLOYMENT (Tool-Faehigkeit des Modells). Version 0.2.0 -> 0.3.0 (pyproject.toml). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.7 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-up → make llm-status → make 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
-
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_PROFILEaktiviert ein Profil;TomlProfileSourcehä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). -
Route-Auflösung + Registries (
app/dependencies.py).resolve_route(user, session_id, overrides)liefert pro Aufruf eineResolvedRoute(Endpunkte, STT/LLM/TTS-Provider, Sprache) nach obiger Präzedenz. Provider kommen ausSTT_REGISTRY/LLM_REGISTRY/TTS_REGISTRY(name -> factory(settings)). Neuen Provider = ein Registry-Eintrag + ABC-Implementierung inapp/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-Lexikonconfig/pronunciation.<lang>.yaml). Stufe provider-abhängig (TTS_NORMALIZE_LEVEL): piper→full, Cloud-TTS→light. Deutsche Ordinalzahlen ingerman_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 indata/(gitignored). - Auth (
app/auth.py): Bearer-Token (require_user) + Admin-Schutz (require_admin).main.pyhat zwei Middlewares: Metriken + Web-UI-Gate (statische Seiten hinter Auth/SSO;/api,/ws,/healthausgenommen).AUTH_ENABLED=false→ anonymer Nutzer (LAN-Dev). - Resilienz (
app/providers/fallback.py): Fallback-Ketten je Modul (*_FALLBACK). Metriken inapp/metrics.py(/api/metrics, JSON + Prometheus). Tageskontingentapp/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. - Web-Suche / Tool-Calling („Weg 2") (
app/providers/llm/tool_calling.py,app/tools/web_search.py):ToolCallingLLMwickelt als LLMProvider eine Tool-Schleife ab — das Modell entscheidet selbst, ob esweb_search(perplexity/sonar) ruft, und formuliert die Antwort in Persona (Vorrang fürs Tool-Ergebnis). Registry-Eintragopenrouter-tools;web_search_enabled(global an, pro Nutzer abschaltbar) wählt inbuild_orchestratortool-fähig vs. plain. Deterministischer Backstop (app/pipeline/search_backstop.py) erzwingt die Suche für heikle Kategorien (Amtsträger/Wohnort/„lebt X noch"/Öffnungszeiten). Beruhigungssätze (app/pipeline/fillers.py, ephemer) + Koreferenz-Vorstufe (decontextualizer.py) + Citations. Zentrale Datum/Uhrzeit:app/core/clock.py(now_context()in alle LLM-Prompts). Referenz:Docs/weg2-tool-calling.md. - Echtzeit (
app/api/ws.py):/ws/chat(Token-/Audio-Streaming) und/ws/voice(Audio→STT→Pipeline, VAD, Barge-in viainterrupt). - 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).