import json from abc import ABC, abstractmethod from collections.abc import AsyncIterator _LANG_NAMES: dict[str, str] = { "de": "Deutsch", "en": "English", "fr": "Français", "es": "Español", "it": "Italiano", "nl": "Nederlands", "ru": "Русский", "zh": "中文", "cmn": "中文", } def lang_instruction(language: str | None) -> str | None: """'Respond in X.' instruction for the given ISO language code, or None.""" if not language: return None name = _LANG_NAMES.get(language, language) return f"Respond in {name}." def sse_delta(line: str) -> str | None: """Extrahiert das Token-Delta aus einer OpenAI-kompatiblen SSE-Zeile (oder None).""" if not line.startswith("data:"): return None data = line[len("data:"):].strip() if not data or data == "[DONE]": return None try: obj = json.loads(data) return obj["choices"][0]["delta"].get("content") except (ValueError, KeyError, IndexError, TypeError): return None class LLMProvider(ABC): @abstractmethod async def complete( self, text: str, history: list[dict] | None = None, session_id: str | None = None, language: str | None = None, ) -> str: ... async def stream( self, text: str, history: list[dict] | None = None, session_id: str | None = None, language: str | None = None, ) -> AsyncIterator[str]: """Token-Stream. Default: kein echtes Streaming -> komplette Antwort als ein Chunk. Provider mit SSE-Unterstuetzung ueberschreiben diese Methode. """ yield await self.complete(text, history=history, session_id=session_id, language=language)