feat: stream provider responses
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Callable, Sequence
|
||||
from pathlib import Path
|
||||
@@ -27,6 +28,7 @@ from javis.security.secrets import SecretProvider, SecretStoreError
|
||||
|
||||
InputFunction = Callable[[str], str]
|
||||
OutputFunction = Callable[[str], None]
|
||||
StreamOutputFunction = Callable[[str], None]
|
||||
StatusFunction = Callable[[], list[str]]
|
||||
|
||||
HELP_TEXT = """Befehle:
|
||||
@@ -144,12 +146,48 @@ def _show_sessions(sessions: list[ChatSession], output: OutputFunction) -> None:
|
||||
)
|
||||
|
||||
|
||||
class TerminalMarkdownRenderer:
|
||||
"""Turn common Markdown markers into readable incremental terminal text."""
|
||||
|
||||
_tail_size = 3
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._pending = ""
|
||||
|
||||
@staticmethod
|
||||
def _clean(text: str) -> str:
|
||||
text = re.sub(r"(?m)^[ \t]{0,3}#{1,6}[ \t]+", "", text)
|
||||
for marker in ("```", "**", "__", "~~", "`"):
|
||||
text = text.replace(marker, "")
|
||||
return text
|
||||
|
||||
def feed(self, chunk: str) -> str:
|
||||
combined = self._pending + chunk
|
||||
if len(combined) <= self._tail_size:
|
||||
self._pending = combined
|
||||
return ""
|
||||
visible = combined[: -self._tail_size]
|
||||
self._pending = combined[-self._tail_size :]
|
||||
return self._clean(visible)
|
||||
|
||||
def finish(self) -> str:
|
||||
visible = self._clean(self._pending)
|
||||
self._pending = ""
|
||||
return visible
|
||||
|
||||
|
||||
def _terminal_write(text: str) -> None:
|
||||
sys.stdout.write(text)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def run_chat(
|
||||
service: ChatService,
|
||||
*,
|
||||
session_id: str | None = None,
|
||||
input_fn: InputFunction = input,
|
||||
output: OutputFunction = print,
|
||||
stream_output: StreamOutputFunction | None = None,
|
||||
status_fn: StatusFunction | None = None,
|
||||
) -> int:
|
||||
try:
|
||||
@@ -262,12 +300,41 @@ def run_chat(
|
||||
output("Unbekannter Befehl. /help zeigt die verfügbaren Befehle.")
|
||||
continue
|
||||
|
||||
stream = service.stream_send(active_id, entered)
|
||||
writer = stream_output or (_terminal_write if output is print else output)
|
||||
renderer = TerminalMarkdownRenderer()
|
||||
started = False
|
||||
try:
|
||||
response = service.send(active_id, entered)
|
||||
output(f"Javis: {response}")
|
||||
for chunk in stream:
|
||||
visible = renderer.feed(chunk)
|
||||
if not visible:
|
||||
continue
|
||||
if not started:
|
||||
writer("Javis: ")
|
||||
started = True
|
||||
writer(visible)
|
||||
remaining = renderer.finish()
|
||||
if remaining:
|
||||
if not started:
|
||||
writer("Javis: ")
|
||||
started = True
|
||||
writer(remaining)
|
||||
if started:
|
||||
writer("\n")
|
||||
except KeyboardInterrupt:
|
||||
close = getattr(stream, "close", None)
|
||||
if callable(close):
|
||||
close()
|
||||
if started:
|
||||
writer("\n")
|
||||
output("Generierung abgebrochen. Die unvollständige Antwort wurde nicht gespeichert.")
|
||||
except (ProviderError, SessionStoreError, SessionProviderMismatchError) as exc:
|
||||
if started:
|
||||
writer("\n")
|
||||
output(f"Fehler: {exc}")
|
||||
except ValueError as exc:
|
||||
if started:
|
||||
writer("\n")
|
||||
output(f"Fehler: {exc}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user