4.4 KiB
4.4 KiB
Spec-1:Agent 执行环境修复(解析容错 + 步级重试 + 摘要附实体)
- 日期: 2026-07-11
- 状态: 已批准(用户确认,步级重试退避改为 20s/40s)
- 依据:
research-wiki/findings/2026-07-11-benchmark-failure-taxonomy.md§四(T8,5 题)与 §二 M1(view_node 摘要吞 entities) - 系列: Spec-1/2/3 三件套之一,见 [2026-07-11-batch-tree-build-design.md]、[2026-07-11-question-gen-v2-design.md]
1. 问题
| # | 缺陷 | 证据 | 影响 |
|---|---|---|---|
| A1 | _parse_response(core/agent/loop.py:265-300)只接受 action.args 嵌套结构;deepseek 稳定输出变体(args 平铺 + ```json 围栏)解析三连拒 → 0 步阵亡 |
637-3、615-3 | 整题报废,且 json_repair 修不了结构错位 |
| A2 | LLM 调用异常(loop.py:118-129)直接整题终止,无步级重试 |
796-3(SSL BAD_RECORD_MAC 废掉 13 步上下文) | 一次网络抖动损失全部已积累推理 |
| B | summarize_node(app/search/summarizer.py)两轮按题摘要后 entities/visible_text 不保证幸存 |
786-2、872-3、750-1(Agent 站在证据节点上漏读实体) | M1 负证据幻觉的恶化因素 |
2. 设计
A1 解析容错(结构归一化层)
在现有 repair_json → json.loads → 校验 之后、返回 None 之前,增加确定性归一化:
- 围栏剥除:repair_json 前先剥
json /围栏(正则,幂等)。 - args 收拢:若
action为 dict 且含tool但缺args,把 action 下除tool外的所有平铺键收拢为args嵌套。 - 归一化成功 → 照常执行;失败 → 走现有 retry 追问路径(行为不变)。
纯函数实现,用 637-3/615-3 的真实坏输出作单测样本。
A2 步级重试
_call_llm 异常处理改为步级重试循环:
| 参数 | 值 | 说明 |
|---|---|---|
| 重试次数 | 2 | 第 3 次失败才整题终止(stop_reason=error 不变) |
| 退避 | 20s / 40s | 用户指定 |
| 可重试异常 | 显式类型元组,默认 (TimeoutError, OSError)(ssl.SSLError/ConnectionError 均为 OSError 子类,覆盖实测穿透案例);作为构造参数可注入扩展 |
遵循 CLAUDE.md P5(不做全 Exception 兜底);core/ 不依赖 openai SDK——API 类异常由 GovernedLLMClient 内部重试栈负责;asyncio.CancelledError 绝不吞;未知异常 fail-fast 整题终止(现状行为) |
| 上下文 | 原样保留 | messages 不回滚,重试即重发 |
| 遥测 | 失败尝试的 error 记录由 GovernedLLMClient 内部负责(已有);AgentLoop 侧只以 loguru 记录步级重试事件(不注入 TelemetryRecorder) |
AgentLoop 无遥测端口,不越层补写 |
B 摘要附带实体原文
在 dispatcher 侧(SearchToolDispatcher._handle_view_node)由确定性代码(非 LLM)在摘要结果末尾追加节点 card 的字段原文:
[实体] <entities + visible_entities 原文>
[画面文字] <visible_text 原文>
- 调用链改动(Codex 审查修正):
summarize_node只收raw_text: str、无结构化 card 访问,且_node_full_text递归收值不保留字段名——因此在TreeEnvironment新增结构化字段提取方法(如node_entity_fields(node_id) -> dict[str, str]),dispatcher 调用它并把区块拼接到 summarize_node 返回值之后;summarize_node 本体不改 - 摘要后追加 → LLM 无法吞掉;字段为空则不加对应区块
- 对 anchor / 非 anchor 两种模式一致生效
3. 不做什么(YAGNI)
- 不改 GovernedLLMClient 的内部重试栈(已有四层治理)
- 不改判分协议、不动 prompt 版本化内容
- 不做异常分型重试策略(统一兜底已覆盖已知案例)
4. 验证
- 单测:坏输出样本(围栏/平铺/两者叠加)归一化正确;空 content、缺 tool 仍拒
- 单测:步级重试计数与退避(mock LLM 抛错)
- 单测:summarize_node 追加区块(有/无实体字段两种节点)
- 集成:抽 10 道 T2/T8 错题重跑(637-3、615-3、786-2、872-3、750-1 必含),对比修复前后
make test全绿 + 覆盖率不降
5. 被否方案
- prompt 层要求 LLM 修正输出格式:治标,deepseek 变体是稳定行为,代码归一化是确定性修复
- 重试时区分异常类型(仅网络类重试):已知案例全是穿透型异常,分型收益低且易漏