82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""Reasoning-model summaries must not trip the empty-content failure path.
|
|
|
|
Local / thinking backends (DeepSeek, Qwen, Kimi) often return content=""
|
|
with the usable text in reasoning / reasoning_content. _generate_summary
|
|
should accept that via extract_content_or_reasoning, bound the fallback
|
|
so a CoT dump cannot grow the transcript, and still fail closed when
|
|
both fields are empty (#11978).
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from agent.context_compressor import ContextCompressor, SUMMARY_PREFIX
|
|
|
|
|
|
def _compressor(**overrides):
|
|
kwargs = dict(model="test/model", quiet_mode=True, tail_mode="legacy")
|
|
kwargs.update(overrides)
|
|
with patch("agent.context_compressor.get_model_context_length", return_value=100000):
|
|
return ContextCompressor(**kwargs)
|
|
|
|
|
|
def _turns():
|
|
return [
|
|
{"role": "user", "content": "do something"},
|
|
{"role": "assistant", "content": "ok"},
|
|
]
|
|
|
|
|
|
def test_empty_content_uses_reasoning_content():
|
|
response = SimpleNamespace(
|
|
choices=[SimpleNamespace(message=SimpleNamespace(
|
|
content="",
|
|
reasoning_content="kept reasoning summary",
|
|
))]
|
|
)
|
|
with patch("agent.context_compressor.call_llm", return_value=response):
|
|
out = _compressor()._generate_summary(_turns())
|
|
assert out is not None
|
|
assert "kept reasoning summary" in out
|
|
assert out.startswith(SUMMARY_PREFIX)
|
|
|
|
|
|
def test_whitespace_content_falls_back_for_dict_and_object():
|
|
class _Msg:
|
|
content = " "
|
|
reasoning_content = "object reasoning"
|
|
|
|
for message, needle in (
|
|
({"content": " ", "reasoning_content": "dict reasoning"}, "dict reasoning"),
|
|
(_Msg(), "object reasoning"),
|
|
):
|
|
response = {"choices": [{"message": message}]}
|
|
with patch("agent.context_compressor.call_llm", return_value=response):
|
|
out = _compressor()._generate_summary(_turns())
|
|
assert out is not None
|
|
assert needle in out
|
|
|
|
|
|
def test_oversized_reasoning_fallback_is_truncated():
|
|
reasoning = "t" * 20_000
|
|
response = SimpleNamespace(
|
|
choices=[SimpleNamespace(message=SimpleNamespace(
|
|
content="",
|
|
reasoning_content=reasoning,
|
|
))]
|
|
)
|
|
with patch("agent.context_compressor.call_llm", return_value=response):
|
|
out = _compressor()._generate_summary(_turns())
|
|
assert out is not None
|
|
assert reasoning not in out
|
|
assert len(out) < 15_000
|
|
|
|
|
|
def test_empty_content_without_reasoning_still_fails():
|
|
mock_response = MagicMock()
|
|
mock_response.choices = [MagicMock()]
|
|
mock_response.choices[0].message.content = None
|
|
with patch("agent.context_compressor.call_llm", return_value=mock_response):
|
|
out = _compressor()._generate_summary(_turns())
|
|
assert out is None
|