mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
fix(llm): normalise Mistral structured content in llm_call_async
llm_call_async returned raw list content for Mistral thinking models, breaking callers that expect a str (e.g. auto-title). Match the sync and streaming parsers by running list content through _normalize_mistral_content. Fixes #5435
This commit is contained in:
parent
25c9e735ef
commit
a7a27053f1
2 changed files with 82 additions and 1 deletions
|
|
@ -2109,7 +2109,17 @@ async def llm_call_async(
|
|||
response = _parse_ollama_response(data)
|
||||
else:
|
||||
msg = data["choices"][0]["message"]
|
||||
response = msg.get("content") or msg.get("reasoning_content") or ""
|
||||
content = msg.get("content")
|
||||
if isinstance(content, list):
|
||||
# Mistral structured content — extract thinking + text
|
||||
# (same contract as llm_call / stream_llm; see #5435).
|
||||
text_part, thinking_part = _normalize_mistral_content(content)
|
||||
if thinking_part:
|
||||
response = thinking_part + "\n\n" + (text_part or "")
|
||||
else:
|
||||
response = text_part or msg.get("reasoning_content") or ""
|
||||
else:
|
||||
response = content or msg.get("reasoning_content") or ""
|
||||
_set_cached_response(cache_key, response)
|
||||
return response
|
||||
except Exception:
|
||||
|
|
|
|||
71
tests/test_llm_core_async_mistral_content.py
Normal file
71
tests/test_llm_core_async_mistral_content.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""Integration regression test for #5435.
|
||||
|
||||
llm_call_async must normalise Mistral structured content to a plain string,
|
||||
matching llm_call (sync) and stream_llm. Before the fix, the async
|
||||
non-streaming parser returned the raw list when Mistral reasoning was enabled,
|
||||
violating its -> str contract, leaking a non-string into callers such as
|
||||
auto-title generation and memory extraction, and poisoning _response_cache
|
||||
with a non-string value.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
import src.llm_core as llm_core
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
is_success = True
|
||||
status_code = 200
|
||||
text = ""
|
||||
|
||||
def __init__(self, payload):
|
||||
self._payload = payload
|
||||
|
||||
def json(self):
|
||||
return self._payload
|
||||
|
||||
|
||||
def _payload(content):
|
||||
return {"choices": [{"message": {"role": "assistant", "content": content}}]}
|
||||
|
||||
|
||||
def _call(monkeypatch, content):
|
||||
async def fake_post(client, url, headers, **kwargs):
|
||||
return _FakeResponse(_payload(content))
|
||||
|
||||
monkeypatch.setattr(llm_core, "httpx_post_kimi_aware_async", fake_post)
|
||||
llm_core._response_cache.clear()
|
||||
return asyncio.run(llm_core.llm_call_async(
|
||||
"http://mistral.test/v1/chat/completions",
|
||||
"mistral-medium",
|
||||
[{"role": "user", "content": "q"}],
|
||||
))
|
||||
|
||||
|
||||
def test_llm_call_async_normalizes_mistral_structured_content(monkeypatch):
|
||||
out = _call(monkeypatch, [
|
||||
{"type": "thinking",
|
||||
"thinking": [{"type": "text", "text": "Let me work through this..."}],
|
||||
"closed": True},
|
||||
{"type": "text", "text": "The answer is 42."},
|
||||
])
|
||||
assert isinstance(out, str), f"expected str, got {type(out).__name__}"
|
||||
assert "The answer is 42." in out
|
||||
assert "Let me work through this..." in out
|
||||
# The cache must hold the normalised string, not the raw list,
|
||||
# otherwise repeat calls serve the poisoned value.
|
||||
assert all(isinstance(v, str) for v in llm_core._response_cache.values())
|
||||
|
||||
|
||||
def test_llm_call_async_thinking_only_still_returns_str(monkeypatch):
|
||||
out = _call(monkeypatch, [
|
||||
{"type": "thinking",
|
||||
"thinking": [{"type": "text", "text": "still thinking"}],
|
||||
"closed": True},
|
||||
])
|
||||
assert isinstance(out, str)
|
||||
assert "still thinking" in out
|
||||
|
||||
|
||||
def test_llm_call_async_plain_string_passthrough(monkeypatch):
|
||||
out = _call(monkeypatch, "plain answer")
|
||||
assert out == "plain answer"
|
||||
Loading…
Add table
Reference in a new issue