feat: Resilienz (Fallback-Ketten) und Metriken (#5)
- 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>
This commit is contained in:
parent
093da817d8
commit
6422444017
13 changed files with 452 additions and 23 deletions
|
|
@ -19,6 +19,11 @@ from app.providers.llm.openrouter import OpenRouterLLMProvider
|
|||
from app.providers.tts.openrouter import OpenRouterTTSProvider
|
||||
from app.providers.tts.chatterbox import ChatterboxTTSProvider
|
||||
from app.providers.tts.piper import PiperTTSProvider
|
||||
from app.providers.fallback import (
|
||||
FallbackSTTProvider,
|
||||
FallbackLLMProvider,
|
||||
FallbackTTSProvider,
|
||||
)
|
||||
from app.pipeline.input_cleaner import InputCleaner
|
||||
from app.pipeline.spoken_response_adapter import SpokenResponseAdapter
|
||||
from app.pipeline.tts_normalizer import TTSNormalizer
|
||||
|
|
@ -198,11 +203,32 @@ def resolve_route(
|
|||
return ResolvedRoute(**resolved)
|
||||
|
||||
|
||||
_FALLBACK_CLASS = {
|
||||
"stt": FallbackSTTProvider,
|
||||
"llm": FallbackLLMProvider,
|
||||
"tts": FallbackTTSProvider,
|
||||
}
|
||||
|
||||
|
||||
def _provider_chain(registry, primary: str, fallback_csv: str, module: str, cfg: Settings):
|
||||
"""Baut primaeren Provider + optionale Fallback-Kette (dedupliziert, Reihenfolge erhalten)."""
|
||||
names = [primary] + [n.strip() for n in (fallback_csv or "").split(",") if n.strip()]
|
||||
seen, ordered = set(), []
|
||||
for name in names:
|
||||
if name not in seen:
|
||||
seen.add(name)
|
||||
ordered.append(name)
|
||||
entries = [(name, _from_registry(registry, name, module.upper(), cfg)) for name in ordered]
|
||||
if len(entries) == 1:
|
||||
return entries[0][1]
|
||||
return _FALLBACK_CLASS[module](module, entries)
|
||||
|
||||
|
||||
def build_orchestrator(route: ResolvedRoute, cfg: Settings = settings) -> Orchestrator:
|
||||
return Orchestrator(
|
||||
stt=get_stt_provider(route.stt_provider, cfg),
|
||||
llm=get_llm_provider(route.llm_provider, cfg),
|
||||
tts=get_tts_provider(route.tts_provider, cfg),
|
||||
stt=_provider_chain(STT_REGISTRY, route.stt_provider, cfg.stt_fallback, "stt", cfg),
|
||||
llm=_provider_chain(LLM_REGISTRY, route.llm_provider, cfg.llm_fallback, "llm", cfg),
|
||||
tts=_provider_chain(TTS_REGISTRY, route.tts_provider, cfg.tts_fallback, "tts", cfg),
|
||||
input_cleaner=InputCleaner(),
|
||||
spoken_adapter=SpokenResponseAdapter(),
|
||||
tts_normalizer=TTSNormalizer(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue