my_voice_assistant_v2/app/providers/llm/base.py

61 lines
1.7 KiB
Python
Raw Normal View History

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)