2026-06-17 04:37:37 +02:00
|
|
|
import json
|
2026-06-17 01:48:56 +02:00
|
|
|
from abc import ABC, abstractmethod
|
2026-06-17 04:37:37 +02:00
|
|
|
from collections.abc import AsyncIterator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-06-17 01:48:56 +02:00
|
|
|
|
|
|
|
|
class LLMProvider(ABC):
|
|
|
|
|
@abstractmethod
|
2026-06-17 04:16:35 +02:00
|
|
|
async def complete(
|
|
|
|
|
self,
|
|
|
|
|
text: str,
|
|
|
|
|
history: list[dict] | None = None,
|
|
|
|
|
session_id: str | None = None,
|
|
|
|
|
) -> str: ...
|
2026-06-17 04:37:37 +02:00
|
|
|
|
|
|
|
|
async def stream(
|
|
|
|
|
self,
|
|
|
|
|
text: str,
|
|
|
|
|
history: list[dict] | None = None,
|
|
|
|
|
session_id: 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)
|