feat: Audio-Streaming (chunked TTS) über WebSocket (#4 Ausbau)
- SentenceChunker (pipeline/sentence_chunker.py): inkrementelle Satzsegmentierung
- Orchestrator.chat_stream(on_audio): satzweise TTS, Audio-Chunk pro fertigem Satz;
Gesamtaudio zusaetzlich an den Output-Endpunkt
- WS /ws/chat {"audio_stream":true}: audio-Events (json seq + binaerer Frame) live,
kein finales Vollaudio; mit stream kombinierbar
- Tests: 45 gruen (+2: Sentence-Chunker, Audio-Streaming)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
379e002460
commit
b5913b0a44
7 changed files with 157 additions and 19 deletions
36
app/pipeline/sentence_chunker.py
Normal file
36
app/pipeline/sentence_chunker.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import re
|
||||
|
||||
# Satzende: . ! ? … gefolgt von Whitespace (oder Stringende beim flush).
|
||||
_SENTENCE_END = re.compile(r"[.!?…]+(?=\s)")
|
||||
|
||||
|
||||
class SentenceChunker:
|
||||
"""Inkrementelle Satzsegmentierung fuer gestreamte LLM-Token.
|
||||
|
||||
`feed(delta)` liefert die seit dem letzten Aufruf fertig gewordenen Saetze,
|
||||
`flush()` den verbleibenden Rest (z. B. der letzte Satz ohne abschliessendes
|
||||
Leerzeichen). Damit kann pro Satz schon TTS erzeugt werden, waehrend das LLM
|
||||
noch weiterschreibt.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._buffer = ""
|
||||
|
||||
def feed(self, text: str) -> list[str]:
|
||||
self._buffer += text
|
||||
sentences: list[str] = []
|
||||
while True:
|
||||
match = _SENTENCE_END.search(self._buffer)
|
||||
if not match:
|
||||
break
|
||||
end = match.end()
|
||||
sentence = self._buffer[:end].strip()
|
||||
self._buffer = self._buffer[end:]
|
||||
if sentence:
|
||||
sentences.append(sentence)
|
||||
return sentences
|
||||
|
||||
def flush(self) -> str:
|
||||
rest = self._buffer.strip()
|
||||
self._buffer = ""
|
||||
return rest
|
||||
Loading…
Add table
Add a link
Reference in a new issue