"""Fallback-Ketten: versuchen mehrere Provider der Reihe nach. Faellt der primaere Provider aus (Timeout/Fehler), wird transparent der naechste versucht. Erfolgreicher Fallback und Provider-Fehler werden als Metrik erfasst. """ from collections.abc import AsyncIterator from app.metrics import metrics class _Chain: def __init__(self, module: str, entries: list[tuple[str, object]]): self.module = module self.entries = entries # [(provider_name, provider), ...] def _on_error(self, name: str) -> None: metrics.inc("provider_error_total", {"module": self.module, "provider": name}) def _on_fallback(self) -> None: metrics.inc("provider_fallback_total", {"module": self.module}) class FallbackSTTProvider(_Chain): async def transcribe(self, audio_bytes, fmt, language=None) -> str: last_exc = None for index, (name, provider) in enumerate(self.entries): try: result = await provider.transcribe(audio_bytes, fmt, language=language) if index > 0: self._on_fallback() return result except Exception as exc: # noqa: BLE001 - bewusst breit fuer Resilienz last_exc = exc self._on_error(name) raise last_exc class FallbackLLMProvider(_Chain): async def complete(self, text, history=None, session_id=None, language=None) -> str: last_exc = None for index, (name, provider) in enumerate(self.entries): try: result = await provider.complete( text, history=history, session_id=session_id, language=language ) if index > 0: self._on_fallback() return result except Exception as exc: # noqa: BLE001 last_exc = exc self._on_error(name) raise last_exc async def stream(self, text, history=None, session_id=None, language=None) -> AsyncIterator[str]: last_exc = None for index, (name, provider) in enumerate(self.entries): produced = False try: async for delta in provider.stream( text, history=history, session_id=session_id, language=language ): produced = True yield delta if index > 0: self._on_fallback() return except Exception as exc: # noqa: BLE001 last_exc = exc self._on_error(name) if produced: # Schon Token gesendet -> kein Fallback mehr moeglich. raise raise last_exc class FallbackTTSProvider(_Chain): async def synthesize(self, text, voice=None, audio_format="pcm") -> bytes: last_exc = None for index, (name, provider) in enumerate(self.entries): try: result = await provider.synthesize(text, voice=voice, audio_format=audio_format) if index > 0: self._on_fallback() return result except Exception as exc: # noqa: BLE001 last_exc = exc self._on_error(name) raise last_exc