系列:AI Agent 实验方法论(第二篇) 上一篇:如何用双盲实验验证 skill 改动的有效性

TL;DR: 双盲实验跑完第一轮,B 赢了 3/4 但没过"幅度筛选",结论是"证据不足"。排查发现 S1-A 的输出被终端颜色代码污染,scorer 在 ANSI 乱码上认认真真打了 8 个维度的分。修复执行上下文后重跑,B 变成 4/4 全胜。翻车的原因不是实验设计,是子 agent 的上下文构造没约束异常行为。


第一轮:协议完美,结论不可用

上一篇讲了双盲实验的设计:两个版本、四个场景、盲映射、独立 scorer。协议本身挑不出毛病。

第一轮结果出来了:

场景目标代码Variant AVariant B胜者差距
S1Python user service0.6252.875B+2.250
S2React payment form2.6252.750B+0.125
S3Java order processor2.6252.6250.000
S4Node.js cache2.2502.875B+0.625

B 赢了 3/4,S3 平局。但"幅度筛选"没过——A 只有 B 的 73%,低于 90% 门槛。

8 个 evaluator run、一个 scorer、一堆 token——结论是"证据不足,不能采纳"。得重跑。

S1-A 那个 0.625 是个明显的异常值。满分 3 分,8 个维度里有 5 个得 0 或 1。但数据就是数据,你不能因为一个数字看着不对就扔掉它——除非你找到原因。

排查:不是实验设计的错

第一反应是怀疑实验设计有问题:是不是某个场景太难?是不是 rubric 设计偏向 B?

逐维度排查,S1-A 的 8 个维度里有 5 个得 0 或 1,和其他场景差距极大。最终定位到了:S1-A 的输出文件被 ANSI escape sequences 污染——终端颜色代码混进了文本,文件几乎不可读。

这不是实验设计的错。双盲协议、secret mapping、8 维度 rubric——每一步都正确。问题出在执行链路上:evaluator 的输出没有做完整性校验,scorer 的输入没有做可读性检查

evaluator 产出了一个被污染的结果,scorer 接到乱码也没报错——它对着 ANSI 转义序列,认认真真完成了 8 个维度的评分。Prompt 里没写"如果输入不可读,必须拒绝打分",它就默认自己能处理。

这和上一篇里的"幽灵投递"是同一个模式:LLM 不会主动告诉你它出错了。你不在 prompt 里写明拒绝条件,它就会在垃圾输入上"正常"完成工作。

ANSI 污染链:干净数据流经 evaluator 节点时被终端颜色代码污染,scorer 在乱码上输出整齐但错误的 8 张评分卡

逐维度看一下 S1-A 的评分有多离谱:

维度S1-A 得分原因
Prompt Contamination1ANSI 污染被判定为 prompt 问题
Dual-Pass Adherence1输出不可读,无法判断流程
Severity Accuracy0无法识别任何内容
Defect Discovery1勉强发现一些模式
False Positive Control1无法区分真假
Suggestion Quality1建议基于乱码
Critical Opinion1意见基于不可读内容
Format Compliance0完全不可读

scorer 不是在故意打低分——它在尽力完成一个不可能的任务。5 个维度得 0 或 1,不是因为 Variant A 的审查 skill 差,是因为它在评分的对象根本不是正常的审查输出。

第二个错误:一个 scorer 做了不该做的事

S1-A 的 ANSI 污染是第一个执行问题。v1 里还有第二个——汇总逻辑错误。上一篇从结论角度讲了 0.03 差距是怎么来的,这里从执行角度看同一个问题。

v1 的 scorer 是一个 subagent 对所有场景的结果评分。给它四个场景的输入,它看到了 X 和 Y 的标签,不知道背后的映射关系。于是它做了一个"合理"的操作:把四个 X 加起来求平均、四个 Y 加起来求平均。

问题是:四个 X 里混了两个 A 的分和两个 B 的分。四个 Y 也一样。A 和 B 的分数被混在一起平均了,差异被抹平了。

X 均分 2.44,Y 均分 2.41,差 0.03。看起来"没区别"。

汇总错误 vs 独立评分:左侧四条数据带汇入漏斗变灰差异消失,右侧四个独立管道各自保持颜色差异一目了然

这不是 scorer 犯了什么罕见的错误。给它所有数据,它会自然地做汇总——这是 LLM 的默认行为。错误在于我的执行架构设计:应该每个场景用独立的 scorer subagent,只给它一个场景的评分输入,它就没有机会做跨场景汇总。

同一个 subagent,给它 1 个场景的数据 vs 给它 4 个场景的数据,产出完全不同的结论。这不是 prompt 措辞的问题,是上下文构造的问题

修上下文,重跑

修复方式不是改实验设计,是重新构造每个执行环节的上下文

  • 修复 evaluator 的输出捕获(终端颜色代码不再混入)
  • 每个场景起一个独立的 scorer subagent,只给它该场景的评分输入
  • 汇总步骤由知道 secret mapping 的人来做,不让 scorer 接触跨场景数据

两处修复,两处都有数据变化。S1-A 从 0.625 恢复到 2.500,汇总逻辑从"一个 scorer 评所有场景"改成"每个场景独立 scorer"。v2 结果:

场景Variant AVariant B胜者差距
S12.5002.750B+0.250
S22.3752.500B+0.125
S32.2502.375B+0.125
S42.1252.500B+0.375
指标v1(无执行约束)v2(重构造上下文)
B 胜场3/4(S3 平局)4/4
幅度筛选0.73 ❌0.91 ✅
A 平均2.0312.313
B 平均2.7812.531
结论证据不足,不能采纳采纳 B

值得注意的一点:v2 里 B 的平均分从 2.781 降到了 2.531。不是 B 变弱了,是 v1 里 S1-A 的 0.625 把 A 的平均分严重拉低,让 B 看起来赢了很多。修复后差距变小了,但数据反而更可靠。

重构了两个节点的执行上下文,结论从"证据不足"变成了"采纳 B"。

v1 vs v2 结论翻转:相同的实验设计,左侧两节点红圈警告产出模糊结论,右侧隔离屏障修复后产出明确结果

设计对了,为什么还是翻车

这个实验的设计挑不出毛病——双盲协议、secret mapping、8 维度 rubric。但 v1 还是翻车了,因为设计对了只解决了一半问题:

  • Evaluator 没被要求校验输出完整性,所以它没报告 ANSI 污染
  • Scorer 被给了 4 个场景的数据,所以它自然地做了跨场景汇总——差异被抹平

v2 的修复没动实验设计,只做了两件事:evaluator 加了输出完整性校验,scorer 从"一个 agent 评所有场景"改成"每个场景一个独立 agent"。

做 tdd-pipeline 的过程里,类似的教训反复出现。设计 workflow 是一件事——做什么、按什么顺序做。构造上下文是另一件事——每个 subagent 看到什么、不看到什么、什么情况下该拒绝执行。

这次实验的迭代,再次提醒我:驾驭工程的实施,既要有合理的 workflow 设计,对完成各节点任务的 subagent,也要审慎设计它们的上下文构造,这对目标实现至关重要。


参考

  1. 双盲实验 skill 源码(含三层分级协议和五大失效模式)
  2. TDD Pipeline 项目仓库
  3. 上一篇:如何用双盲实验验证 skill 改动的有效性