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: """Sprach-Anweisung für den ISO-Code, oder None. Bewusst nachdrücklich formuliert: Eine lange anderssprachige History (z. B. der Nutzer hat zwischendurch englisch gesprochen) soll die gewünschte Sprache NICHT überstimmen. Wird zusätzlich nah an die Generierung gesetzt (siehe with_lang_reminder). """ if not language: return None name = _LANG_NAMES.get(language, language) return f"Respond ONLY in {name}, regardless of the language used earlier in this conversation." def with_lang_reminder(text: str, language: str | None) -> str: """Hängt eine knappe Sprach-Erinnerung an die letzte Nutzer-Nachricht. Direkt vor der Generierung platziert wirkt sie stärker als der (weit zurückliegende) System-Prompt, wenn die bisherige Unterhaltung in einer anderen Sprache lief. """ if not language: return text name = _LANG_NAMES.get(language, language) return f"{text}\n\n[Bitte ausschließlich auf {name} antworten.]" 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)