feat: Token-Level-LLM-Streaming über WebSocket (#4 Ausbau)
- LLMProvider.stream: Basis-Default (Fallback über complete) + SSE-Streaming
fuer OpenRouter und lokalen OpenAI-kompatiblen Provider; gemeinsamer Parser sse_delta
- Orchestrator.chat_stream: LLM-Token live via on_token-Callback, danach
Spoken-Adapter/Normalizer/TTS/Output; Fallback fuer Provider ohne stream
- WS /ws/chat: opt-in {"stream":true} -> ack -> token* -> semantic -> audio -> done
- Tests: 43 gruen (+5: SSE-Parsing, Default-Fallback, WS-Token-Flow)
- Doku aktualisiert; .gitignore: *.wav (generierte Audio-Ausgaben)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
531b57e08d
commit
379e002460
10 changed files with 302 additions and 24 deletions
|
|
@ -1,4 +1,21 @@
|
|||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
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
|
||||
|
||||
|
||||
class LLMProvider(ABC):
|
||||
@abstractmethod
|
||||
|
|
@ -8,3 +25,15 @@ class LLMProvider(ABC):
|
|||
history: list[dict] | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> str: ...
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue