Fix sheet-extraction page loss: bare-list wrap, compact retry, reasoning cap
- SheetExtractorAgent accepts top-level array responses as the objects array instead of discarding them (recovered the failure mode behind 16/38 failed sheets on job 475a6f184dd1) - Second-chance compact retry per page before declaring extraction failed - call_json: reasoning_effort param (cloud-only extra_body), finish_reason capture + explicit max_tokens log line, finish_reason in raw dumps, cache key covers reasoning_effort - EXTRACT_MAX_TOKENS default 16384 -> 32768 (Gemini thinking tokens count against the cap); new EXTRACT_REASONING_EFFORT=low default for extract - tests: 5 new fallback-ladder tests
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""SheetExtractorAgent fallback ladder tests (bare-list wrap + compact retry)."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from backend.agents.base import AgentScope, AgentUsage
|
||||
from backend.agents.extractors import SheetExtractorAgent, _wrap_bare_list
|
||||
|
||||
|
||||
def _scope():
|
||||
return AgentScope(
|
||||
scope_id="sheet:4",
|
||||
payload={"page": {"page_number": 4, "base64": "QUJD"}},
|
||||
)
|
||||
|
||||
|
||||
def _objects(n=2):
|
||||
return [
|
||||
{
|
||||
"object_id": f"obj-{i}",
|
||||
"object_type": "equipment",
|
||||
"category": "mechanical",
|
||||
"name": f"RTU-{i}",
|
||||
"source_text": f"RTU-{i}",
|
||||
"confidence": "high",
|
||||
}
|
||||
for i in range(n)
|
||||
]
|
||||
|
||||
|
||||
def test_wrap_bare_list_builds_sheet_envelope():
|
||||
wrapped = _wrap_bare_list(_objects(3), page_number=4)
|
||||
assert wrapped["sheet"] == {}
|
||||
assert len(wrapped["objects"]) == 3
|
||||
|
||||
|
||||
def test_wrap_bare_list_passes_dicts_and_none_through():
|
||||
assert _wrap_bare_list({"sheet": {}, "objects": []}, 1) == {"sheet": {}, "objects": []}
|
||||
assert _wrap_bare_list(None, 1) is None
|
||||
|
||||
|
||||
def test_run_accepts_bare_list_response():
|
||||
agent = SheetExtractorAgent(usage=AgentUsage())
|
||||
with patch("backend.agents.extractors.call_json",
|
||||
return_value=_objects(5)) as mock_call:
|
||||
result = agent.run(_scope())
|
||||
assert not result.error
|
||||
assert len(result.artifacts) == 1
|
||||
sheet = result.artifacts[0]
|
||||
assert sheet["page_number"] == 4
|
||||
assert len(sheet["assertions"]) == 5
|
||||
# No compact retry needed when the first call yields data.
|
||||
assert mock_call.call_count == 1
|
||||
# Reasoning effort knob is forwarded (None when config is blank in tests).
|
||||
assert "reasoning_effort" in mock_call.call_args.kwargs
|
||||
|
||||
|
||||
def test_run_compact_retry_after_hard_failure():
|
||||
agent = SheetExtractorAgent(usage=AgentUsage())
|
||||
with patch("backend.agents.extractors.call_json",
|
||||
side_effect=[None, {"sheet": {"sheet_number": "A102"},
|
||||
"objects": _objects(2)}]) as mock_call:
|
||||
result = agent.run(_scope())
|
||||
assert not result.error
|
||||
assert result.artifacts[0]["sheet_number"] == "A102"
|
||||
assert mock_call.call_count == 2
|
||||
# Second call carried the compact suffix.
|
||||
assert "COMPACT RETRY" in mock_call.call_args_list[1].kwargs["user_text"]
|
||||
|
||||
|
||||
def test_run_fails_only_after_both_attempts_miss():
|
||||
agent = SheetExtractorAgent(usage=AgentUsage())
|
||||
with patch("backend.agents.extractors.call_json", return_value=None) as mock_call:
|
||||
result = agent.run(_scope())
|
||||
assert result.error == "no structured extraction returned"
|
||||
assert mock_call.call_count == 2
|
||||
Reference in New Issue
Block a user