fix(agent): normalize fenced and flat-args LLM outputs in parser
核心算法 #10(Agent Loop):仅加固 _parse_response 解析路径—— 剥除 ```json 围栏 + 收拢 action 平铺参数(deepseek 稳定输出变体, 案例 637-3/615-3 三连拒 0 步阵亡)。Thinking+JSON 协议、json_repair 兜底链、hook 时序与步数语义均未改动。
This commit is contained in:
@@ -239,3 +239,60 @@ class TestAgentLoop:
|
||||
assert "after_tool:0" in tracker.events
|
||||
assert "after_step:0" in tracker.events
|
||||
assert "on_finish:finished" in tracker.events
|
||||
|
||||
|
||||
# ── A1 解析容错测试(Spec-1)──────────────────────────────────
|
||||
|
||||
# 生产真实样本结构:尾部围栏残留 + action.args 平铺(开头围栏场景由
|
||||
# test_leading_json_fence 单独覆盖)
|
||||
_REAL_FLAT_FENCED = """{
|
||||
"plan": {
|
||||
"goal": "从三个L1根节点开始建立全局认知",
|
||||
"tool": "view_node",
|
||||
"reason": "三个L1节点覆盖整个视频"
|
||||
},
|
||||
"action": {
|
||||
"tool": "view_node",
|
||||
"node_id": "J5Npf2xJpag_L1_000",
|
||||
"question": "What is the overall topic of this video?"
|
||||
}
|
||||
}
|
||||
```"""
|
||||
|
||||
|
||||
class TestParseNormalization:
|
||||
"""deepseek 输出变体(args 平铺 + ```json 围栏)归一化。"""
|
||||
|
||||
def _parse(self, content: str):
|
||||
loop = AgentLoop(llm=AsyncMock(), max_steps=10)
|
||||
return loop._parse_response(_make_response(content))
|
||||
|
||||
def test_flat_args_with_trailing_fence(self) -> None:
|
||||
"""生产样本:action 平铺 node_id/question + 尾部围栏。"""
|
||||
parsed = self._parse(_REAL_FLAT_FENCED)
|
||||
assert parsed is not None
|
||||
action = parsed[4]
|
||||
assert action["tool"] == "view_node"
|
||||
assert action["args"] == {
|
||||
"node_id": "J5Npf2xJpag_L1_000",
|
||||
"question": "What is the overall topic of this video?",
|
||||
}
|
||||
|
||||
def test_leading_json_fence(self) -> None:
|
||||
content = '```json\n{"reflect": {}, "plan": {}, "action": {"tool": "submit_answer", "args": {"answer": "A"}}}\n```'
|
||||
parsed = self._parse(content)
|
||||
assert parsed is not None
|
||||
assert parsed[4]["args"] == {"answer": "A"}
|
||||
|
||||
def test_nested_args_unchanged(self) -> None:
|
||||
"""标准嵌套结构不受归一化影响。"""
|
||||
parsed = self._parse(_submit_json("B"))
|
||||
assert parsed is not None
|
||||
assert parsed[4] == {"tool": "submit_answer", "args": {"answer": "B"}}
|
||||
|
||||
def test_action_missing_tool_still_rejected(self) -> None:
|
||||
content = json.dumps({"reflect": {}, "plan": {}, "action": {"node_id": "x"}})
|
||||
assert self._parse(content) is None
|
||||
|
||||
def test_empty_content_still_rejected(self) -> None:
|
||||
assert self._parse("") is None
|
||||
|
||||
Reference in New Issue
Block a user