fix(#4146): read reasoning_content gateway chunks
This commit is contained in:
parent
42e85813a3
commit
769fd11f22
2 changed files with 83 additions and 0 deletions
|
|
@ -131,6 +131,24 @@ def _gateway_sse_delta(payload: dict) -> str:
|
|||
return ""
|
||||
|
||||
|
||||
def _gateway_sse_reasoning_delta(payload: dict) -> str:
|
||||
"""Extract reasoning text from OpenAI-compatible streaming chunks."""
|
||||
try:
|
||||
choices = payload.get("choices") or []
|
||||
if not choices:
|
||||
return ""
|
||||
choice = choices[0] or {}
|
||||
delta = choice.get("delta") or {}
|
||||
reasoning = delta.get("reasoning_content")
|
||||
if isinstance(reasoning, str) and reasoning.strip():
|
||||
return reasoning
|
||||
message = choice.get("message") or {}
|
||||
reasoning = message.get("reasoning_content")
|
||||
return reasoning if isinstance(reasoning, str) and reasoning.strip() else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _gateway_stream_usage(payload: dict) -> dict:
|
||||
usage = payload.get("usage") if isinstance(payload, dict) else None
|
||||
if not isinstance(usage, dict):
|
||||
|
|
@ -406,6 +424,11 @@ def _run_gateway_chat_streaming(
|
|||
sse_event = "message"
|
||||
continue
|
||||
last_payload = payload
|
||||
reasoning_delta = _gateway_sse_reasoning_delta(payload)
|
||||
if reasoning_delta:
|
||||
if stream_id in STREAM_REASONING_TEXT:
|
||||
STREAM_REASONING_TEXT[stream_id] += reasoning_delta
|
||||
put_gateway_event("reasoning", {"text": reasoning_delta})
|
||||
delta = _gateway_sse_delta(payload)
|
||||
if delta:
|
||||
final_text += delta
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from api.gateway_chat import (
|
|||
_gateway_http_error_event,
|
||||
_gateway_reasoning_delta,
|
||||
_gateway_sse_delta,
|
||||
_gateway_sse_reasoning_delta,
|
||||
_gateway_stream_usage,
|
||||
_gateway_tool_progress_event,
|
||||
gateway_chat_config_status,
|
||||
|
|
@ -157,6 +158,12 @@ def test_gateway_reasoning_delta_keeps_string_deltas_and_ignores_structured_payl
|
|||
assert _gateway_reasoning_delta({"text": ["safe"], "preview": " more"}) == " more"
|
||||
|
||||
|
||||
def test_gateway_sse_reasoning_delta_extracts_reasoning_content_chunks():
|
||||
assert _gateway_sse_reasoning_delta({"choices": [{"delta": {"reasoning_content": "Let me"}}]}) == "Let me"
|
||||
assert _gateway_sse_reasoning_delta({"choices": [{"message": {"reasoning_content": "Done thinking"}}]}) == "Done thinking"
|
||||
assert _gateway_sse_reasoning_delta({"choices": [{"delta": {"reasoning_content": " "}}]}) == ""
|
||||
|
||||
|
||||
def test_gateway_http_401_reports_gateway_auth_not_provider_key():
|
||||
exc = urllib.error.HTTPError(
|
||||
"http://gateway.local/v1/chat/completions",
|
||||
|
|
@ -427,6 +434,59 @@ def test_gateway_chat_worker_preserves_reasoning_delta_whitespace_and_persists_r
|
|||
assert not any("debug" in text for text in reasoning_events)
|
||||
|
||||
|
||||
def test_gateway_chat_worker_reads_reasoning_content_deltas_from_chat_completions(tmp_path, monkeypatch):
|
||||
session_dir = tmp_path / "sessions"
|
||||
session_dir.mkdir()
|
||||
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
|
||||
monkeypatch.setattr(models, "SESSION_INDEX_FILE", session_dir / "_index.json")
|
||||
monkeypatch.setattr(models, "SESSIONS", OrderedDict())
|
||||
|
||||
class FakeResponse:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
yield b'data: {"choices":[{"delta":{"reasoning_content":"Let me ","content":"hel"}}]}\n\n'
|
||||
yield b'data: {"choices":[{"delta":{"reasoning_content":"think","content":"lo"}}]}\n\n'
|
||||
yield b'data: [DONE]\n\n'
|
||||
|
||||
monkeypatch.setenv("HERMES_WEBUI_GATEWAY_BASE_URL", "http://gateway.local")
|
||||
monkeypatch.setattr(gateway_chat.urllib.request, "urlopen", lambda req, timeout=0: FakeResponse())
|
||||
|
||||
s = new_session()
|
||||
stream_id = "stream-gateway-reasoning-content-test"
|
||||
s.active_stream_id = stream_id
|
||||
s.pending_user_message = "Say hello"
|
||||
s.pending_attachments = []
|
||||
s.pending_started_at = 123
|
||||
s.save()
|
||||
channel = create_stream_channel()
|
||||
subscriber = channel.subscribe()
|
||||
STREAMS[stream_id] = channel
|
||||
|
||||
gateway_chat._run_gateway_chat_streaming(
|
||||
s.session_id,
|
||||
"Say hello",
|
||||
"test-model",
|
||||
str(tmp_path),
|
||||
stream_id,
|
||||
[],
|
||||
)
|
||||
|
||||
saved = models.get_session(s.session_id)
|
||||
assert saved.messages[-1]["content"] == "hello"
|
||||
assert saved.messages[-1]["reasoning"] == "Let me think"
|
||||
reasoning_events = []
|
||||
while not subscriber.empty():
|
||||
item = subscriber.get_nowait()
|
||||
if item[0] == "reasoning":
|
||||
reasoning_events.append(item[1]["text"])
|
||||
assert reasoning_events == ["Let me ", "think"]
|
||||
|
||||
|
||||
def test_gateway_chat_worker_normalizes_prefill_slice_before_system_prefix(tmp_path, monkeypatch):
|
||||
session_dir = tmp_path / "sessions"
|
||||
session_dir.mkdir()
|
||||
|
|
|
|||
Loading…
Reference in a new issue