1
0

[loop-cycle-61] fix: strip think tags and harden fact parsing (#237) (#254)

This commit is contained in:
2026-03-15 14:50:09 -04:00
parent f9911c002c
commit 7bc355eed6
2 changed files with 137 additions and 6 deletions

View File

@@ -885,3 +885,98 @@ async def test_call_agent_does_not_use_session_chat(tmp_path):
await engine._call_agent("prompt")
mock_session_chat.assert_not_awaited()
@pytest.mark.asyncio
async def test_call_agent_strips_think_tags(tmp_path):
"""_call_agent must strip <think> tags from reasoning models like qwen3."""
engine = _make_engine(tmp_path)
mock_agent = AsyncMock()
mock_run = AsyncMock()
mock_run.content = (
"<think>Let me reason about this carefully...</think>"
"The actual thought content."
)
mock_agent.arun.return_value = mock_run
with patch("timmy.agent.create_timmy", return_value=mock_agent):
result = await engine._call_agent("test prompt")
assert "<think>" not in result
assert result == "The actual thought content."
@pytest.mark.asyncio
async def test_call_agent_strips_multiline_think_tags(tmp_path):
"""_call_agent handles multi-line <think> blocks."""
engine = _make_engine(tmp_path)
mock_agent = AsyncMock()
mock_run = AsyncMock()
mock_run.content = (
"<think>\nStep 1: analyze\nStep 2: synthesize\n</think>\n"
"Clean output here."
)
mock_agent.arun.return_value = mock_run
with patch("timmy.agent.create_timmy", return_value=mock_agent):
result = await engine._call_agent("test prompt")
assert "<think>" not in result
assert result == "Clean output here."
# ---------------------------------------------------------------------------
# _parse_facts_response resilience (#237)
# ---------------------------------------------------------------------------
def test_parse_facts_clean_json(tmp_path):
"""Direct JSON array should parse normally."""
engine = _make_engine(tmp_path)
result = engine._parse_facts_response('["fact one", "fact two"]')
assert result == ["fact one", "fact two"]
def test_parse_facts_empty_array(tmp_path):
"""Empty JSON array should return empty list."""
engine = _make_engine(tmp_path)
assert engine._parse_facts_response("[]") == []
def test_parse_facts_with_prose_prefix(tmp_path):
"""JSON array preceded by prose should still parse (#237)."""
engine = _make_engine(tmp_path)
raw = 'Here are the facts:\n["Alexander prefers YAML", "Timmy runs locally"]'
result = engine._parse_facts_response(raw)
assert result == ["Alexander prefers YAML", "Timmy runs locally"]
def test_parse_facts_with_markdown_fences(tmp_path):
"""JSON wrapped in markdown code fences should parse."""
engine = _make_engine(tmp_path)
raw = '```json\n["fact in fences"]\n```'
result = engine._parse_facts_response(raw)
assert result == ["fact in fences"]
def test_parse_facts_filters_non_strings(tmp_path):
"""Non-string entries in the array should be filtered out."""
engine = _make_engine(tmp_path)
result = engine._parse_facts_response('[42, "valid fact", null, true]')
assert result == ["valid fact"]
def test_parse_facts_none_and_empty(tmp_path):
"""None and empty input should return empty list."""
engine = _make_engine(tmp_path)
assert engine._parse_facts_response(None) == []
assert engine._parse_facts_response("") == []
assert engine._parse_facts_response(" ") == []
def test_parse_facts_invalid_json(tmp_path):
"""Totally invalid text with no JSON array should return empty list."""
engine = _make_engine(tmp_path)
assert engine._parse_facts_response("no json here at all") == []