开源PlannerCritic引擎通过170个目标测试验证Agent系统稳定性,总结10类测试陷阱与回归门禁设计经验。
Unit test 只测试你能想到要测试的东西。它们使用手工制作的输入来匹配你的预设假设。即使系统以你未曾预料到的方式出了问题,它们依然会通过。
我知道这一点——因为 65 个断言文件中有 57 个格式错误,而测试 harness 根本没有察觉。它只是静静地返回了 0/0 的结果。没有崩溃,没有报错,只有沉默。
我需要一个 field test,能够用真实的 LLM 运行真实的目标,并告诉我实际上哪里出了问题。
第一次 field test 是一次诊断。在 35 个领域、157 个目标的测试中,花费 $0.30,耗时 60 分钟,发现了 10 个问题。其中只有 1 个是传统意义上的失败。其余都是设计问题、prompt 缺陷,以及那些本会悄然上线的 harness bug。
1 个真实失败:planner prompt 没有解释 branches schema。LLM 返回了 kind: "rollback" 以及任务对象数组,而实际上应该返回字符串。
前置条件门控过于严格——established_by 期望 task ID 或 env: 前缀;LLM 却写入了裸露的 fact 名称。Unit test 全绿。
65 个断言文件中有 57 个格式错误。Harness 静默地生成了 0/0 结果。
Critic severity 契约错误——critic 出于完整性考虑拦截了计划,而非针对具体缺陷。
维度分发存在签名不匹配——run_budget() 接受 4 个参数但 dispatch 传了 5 个。Budget 和 replan 维度显示 0/0。
跨维度状态丢失——内存中的 SQLite 存储在运行之间被重置。
结果解析器读取了错误的 JSON 路径——trace.get("status") 而非 trace["result"]["status"]。5 个 PASS 场景被误报为 FAIL。
1 个模型限制:本地模型(Qwen3.5-4B、Qwen3.5-9B)无法生成结构化 JSON。
2 个根本性属性:
LLM critic 是非确定性的——严格目标用 revision_cap=4 重新运行时全部升级;critic 在每次修订中发现了不同的阻塞点。
更强的模型并没有缩小 planner 差距——GPT-4o 产生了与 gpt-4o-mini 相同的缺陷模式。
$0.30 的 LLM API 调用费用。60 分钟的墙上时间。比因一个错误计划导致的线上故障调试成本还低。
v0.2.0 的 field test 本质上不同。v0.1.0 是一个诊断工具,在全新引擎中发现了 10 个问题。v0.2.0 是一个验证工具,确认了 31 个 bug 修复 + 5 个新的企业领域包 + 6 个新的安全机制都能正常工作。
Field test 本身没有发现任何新问题——code review 在 field test 运行之前就发现了全部 31 个 bug。
v0.2.0 的 field-test 程序比 v0.1.0 的要强得多,这直接带来了更高质量的发布:
覆盖范围增长约 8%:40 个领域 170 个目标(v0.1.0:35 个领域 157 个目标)——5 个新企业领域(身份管理、多智能体操作、SRE、供应链策略、FinOps/全新)新增 14 个目标 + 3 个新的对抗性策略目标。
Field test 运行前的 code review 发现了 31 个 bug,而 v0.1.0 的 field test 只发现了 10 个问题。通过在 LLM field test 运行之前用 code review 抓 bug($0,2 小时),意味着 field test 验证的是修复而非发现它们——一个更高杠杆、更便宜、更快的循环。
预修正消除了首次运行时的发布门控失败。v0.1.0 需要对 81 个严格目标进行事后修正;v0.2.0 在执行前对 89 个严格目标进行了预修正,发布门控首次运行 100% 通过。
新的确定性安全机制得到了验证,而不仅仅是构建。确定性前置条件闭合器(#131)、拓扑自动修复(#130)和振荡检测(#152)都经过了 170 个目标的扫描验证。
添加了安全 oracle。v0.1.0 没有;v0.2.0 添加了 SWE-bench 衍生的评估:7/7 正确计划通过,35/35 有缺陷变体被拦截,21 个注入陷阱生成。
新增 3 个基准(自动修复、回滚可信度、family-histogram 静态性)。
新增 90 个确定性子系统测试(0.7 秒,$0),作为 LLM field test 之上的快速回归门控——v0.1.0 单独依赖 LLM 扫描。
v0.2.1 是一个补丁发布。没有新目标,没有新领域,没有 schema 变更。Field test 是一个回归扫描——重新运行相同的 170 个目标,并将每个判决与 v0.2.0 发布的结果进行 diff。
Code review(#222)在 M11 硬化 diff 中发现了 10 个 bug。Field test 验证了这些修复。30 个判决 delta 与 v0.2.0 相比——全部可归因,没有无法解释的。
每个修复都附带了一个 RED-first 的回归测试(先在修复前代码上验证失败,然后由修复使其变绿)。
v0.2.1 有一个 v0.1.0 和 v0.2.0 都没有的新测试:将 #171 boundary-case 语料通过真实的 critic 模型 × 5 次试验,并测量当你用相同问题问 5 次时会发生什么。
Critic 是 100% 非确定性的——而且这没问题。
gpt-4o-mini 对相同输入的每次试验产生不同的判决和不同的解释(label_flip_rate=1.0,evidence_drift_rate=1.0)。然而它从未低报过一个带缺陷的种子(family_migration_rate=0.0,underclaim_approvals=0)。确定性门控是安全权威——LLM critic 的非确定性是安全的,因为它只能增加发现,不能压制门控阻塞。
这是我没有预料到的发现:critic 是最大程度非确定性的,但这不重要。安全契约不依赖于 critic 保持一致——而是依赖于 critic 总是在有缺陷的计划上发现问题。它确实做到了。
经验:确定性门控拥有低报方向(防止坏计划漏过),而代码强制执行的 severity 白名单拥有高报方向。LLM critic 可以是 100% 非确定性的,但仍然完全安全。
v0.2.1 添加了社区要求的前后数据:
解决所需修订次数的中位数是 1.0——大多数目标在一次修订中解决。确定性前置条件闭合器和拓扑自动修复在发挥作用:它们在完全不调用 LLM 的情况下修复了排序和依赖缺陷。
30 个目标在 v0.2.0 和 v0.2.1 之间的判决或原因码发生了变化。每一个都可以归因:
LLM 非确定性:gpt-4o-mini 在不同运行中产生不同的发现。引擎的安全契约是基于容忍度的(balanced→approve,strict→escalate),而非基于发现数量。判决在 converged_stalled 和 revision_cap_reached 之间翻转,取决于 critic 本次找到了哪些具体的阻塞点。
#152 结构振荡现在触发了:5 个目标在 v0.2.1 中以 plan_oscillation_detected 升级(v0.2.0 中为 0)。#232 修复使检测器在默认配置下可达。它更早地检测到循环并更早地终止——节省 LLM 调用。
1 个瞬时 provider 错误:mch-04-blast-radius 遇到 planning_unavailable——OpenRouter 返回了错误。引擎正确地失败了(作为错误升级,而非批准)。
零个无法解释的 delta。没有任何 delta 可归因于 code review 修复改变了引擎行为——修复改善了内部一致性(gate id、消息质量、故障隔离),而没有改变 approve/escalate 决策逻辑。
三次发布的演变讲述了一个关于 field testing 如何演进的故事:
Field test 从诊断工具(在新引擎中发现 10 个问题)变成验证工具(确认 v0.2.0 中的 31 个修复),再变成回归门控(确认 v0.2.1 中的 10 个额外修复,与发布基线进行 diff,零个无法解释的 delta)。
成本从 $0.30 涨到 $0.49。价值从发现问题变成证明问题的缺失。
在 v0.1.0 中,10 个问题中只有 1 个是传统意义上的失败。其余都是会悄然上线的设计问题。Harness 悄无声息地谎报断言结果,比崩溃更糟糕。
经验:一个对模块执行零个断言的 field test harness 必须硬失败——0/0 是错误状态,不是通过。
每个涉及 LLM 行为异常的问题在使用手工输入的 unit test 中都不可见。前置条件门控 bug 隐藏在绿测试背后。
经验:Unit test 无法替代 field test。它们测试的是不同的东西。
v0.1.0 用 field test 发现了 10 个问题(~$0.30 + 60 分钟)。v0.2.0 用 code review 发现了 31 个 bug($0 + 2 小时)。v0.2.1 发现了 10 个更多($0 + 1 小时)。Field test 在两种情况下都发现了 0 个。
经验:对于一个拥有成熟语料库的引擎,field test 前的 code review 是更高杠杆的活动。Field test 验证;code review 诊断。
v0.2.0 和 v0.2.1 之间的 30 个判决 delta 全部可归因于 gpt-4o-mini 在不同运行中产生不同的发现。#218 live-critic boundary run 直接测量了这一点:label_flip_rate=1.0,evidence_drift_rate=1.0——critic 在相同输入的每次试验中都会改变判决和解释。然而 family_migration_rate=0 和 underclaim_approvals=0——它从未低报过一个种子缺陷。
经验:确定性门控拥有低报方向(防止坏计划漏过),而代码强制执行的 severity 白名单拥有高报方向。LLM critic 可以是 100% 非确定性的,但仍然完全安全。
3 个目标现在以 plan_oscillation_detected 而非 revision_cap_reached 升级。信号更早地检测到循环并更早地终止。
经验:振荡信号在实际中触发(3/97 个严格目标)并节省 LLM 调用。
1 个目标遇到 planning_unavailable——OpenRouter 返回了错误。引擎正确地失败了(作为错误升级,而非批准)。
经验:瞬时 LLM provider 错误不是引擎缺陷;引擎会fail closed。
延迟(p50 approved=13.86s)、审核者负担(2.58 blockers/goal)、操作员工作负载(58 decisions/100 goals)。解决所需修订次数的中位数 = 1.0。
经验:运营基线支持前后对比——下游错误率指标需要合作伙伴 runner 集成(推迟到 v0.3.0)。
170 个目标 + 60 个边界审计 = $0.49。比单个开发者工时还便宜。没有理由不对你的 agent 系统做 field test。
经验:Field test 应该在每次发布时运行。成本可以忽略不计。
确定性门控在每次提交时免费运行。1295 个确定性测试在 4.7 秒内运行完。LLM field test 在发布时运行,费用 $0.49。三层,每层捕捉不同的东西。
经验:Unit test 无法替代确定性门控套件。确定性套件无法替代 field test。Field test 无法替代 code review。三者都需要。
approving_authority(F-14)PlannerCritic 系列第 4 篇,共 5 篇。
系列文章: