9 minute read

Prompt 的 77%,为什么还不够发布

同一批评测样本上的版本比较,不能只看两行准确率;你需要知道每一次改动究竟修了什么,又伤了什么。

爱迪生为灯丝列出过一千六百种耐热材料,然后逐一测试。有人问他,失败这么多次是否浪费时间;他的回答是:没有浪费,因为他已经知道哪些材料不适合。评测 Prompt 也是同样的工作:77% 不是一个可以孤立庆祝的数字,而是一份尚未读完的失败清单。若不知道剩下的 23% 是哪些样本,也不知道新版本为了多答对几题而弄错了什么,你就无法判断这次改动是在积累可靠性,还是只是在这批题上碰巧得分。

在你的评测集上运行:

./scripts/benchmark.sh

如果它最后只打印两行总准确率,这个基准还不足以判断 Prompt 是否真的变好了。它必须保留逐样本结果:旧版本答对了什么,新版本改对了什么,又把什么改坏了。

本文的承诺很具体:读完后,你可以用逐样本配对结果、Wilson 置信区间和 McNemar 精确检验,判断一次 Prompt 改动究竟值得保留,还是只是在追逐评测噪音;同时把 Prompt 从业务代码里散落的字符串,变成可追溯、可复现的版本化工件。

准确率的差值描述结果;成对分歧才回答“改动是否造成了变化”。

两种比较方式,回答的根本不是同一个问题

假设这是一个纯粹的示例:Prompt A 的准确率为 75%,Prompt B 加上一句“深呼吸”后为 77%。B 一定更好吗?

绝大多数情况下,不能仅凭这两个百分比下结论。差异可能来自真实改进,也可能只是当前评测样本里的波动。尤其在样本有限时,少量 case 的翻转就足以改变总准确率。

更常见的崩溃场景是:为了修复一个 case,加了一句 Prompt;重新运行后,总分看起来变好了,或者恰好没变;但此前通过的一些 case 已经失败。这里不应把“此前通过的 case”写成某个未经观测的固定数量。它可能是零个,也可能是多个;关键在于,总准确率会把这种交换压缩成一个数字。

这种“打地鼠式”的 Prompt 工程没有形成实验。它只是在不断移动失败的位置,然后希望汇总分数替自己解释一切。严肃的部署工作不该依赖“感觉”,而应依赖统计证据和消融式比较:一次只改变有明确假设的因素,再检查那项改变带来的净影响。

下面这张表是两个方案的真正分水岭:

比较方式 保留的信息 能回答的问题 主要盲区
Accuracy Delta 两个汇总准确率 总体得分相差多少 不知道哪些 case 改善或回归
逐样本配对比较 每个 case 在 A、B 下的结果 B 修了什么、伤了什么 需要稳定的 case ID 与判定结果
McNemar 精确检验 两类分歧样本的数量 观察到的净变化是否有足够统计证据 适用于同一输入上的二元配对结果

📌 可截图的判断法:比较 Prompt 时,先数“错误→正确”和“正确→错误”;两边都正确、都错误的样本,不能证明哪一版更好。

惊喜也正在这里:决定版本优劣的信息,往往不在全部样本里,而在两版回答不同的那一小撮样本里。两个版本都答对的 case,当然值得高兴,但它们无法支持“B 比 A 更好”;两个版本都答错的 case,则提示有尚未解决的问题,同样不能区分版本。

为什么生硬的“准确率差值(Accuracy Delta)”是骗人的?

“骗人”不是说准确率没用,而是说脱离上下文的 Accuracy Delta 很容易让人误读。

假设 A 为 75%,B 为 77%。没有样本量、没有逐样本配对结果、没有失败类型,这两个数字几乎不包含足够的决策信息。小样本里,几个输出的变化就可能令百分比跳动;即使样本较大,B 新增的正确和新增的错误也可能同时存在,只是汇总后留下一个看起来不错的净差值。

因此,每个准确率数字背后都应带上 95% Wilson 置信区间。它不是给准确率披上一层统计术语,而是在提醒读者:一个观察到的比例只是对未知真实表现的估计。对于二元结果,尤其是样本量有限或正确率接近边界时,Wilson 区间通常比直接套用正态近似更稳妥。

Wilson 区间回答的是:在当前数据下,这个版本的真实正确率可能落在什么范围。区间较宽时,不该把细小差异写成能力提升。需要注意,两个置信区间重叠本身并不是“差异不显著”的正式判据;它只是一个很有用的刹车,避免人对不稳定的点估计过度解读。

而在同一批 case 上比较两个 Prompt 时,应把目光移到分歧样本上:

  • b:A 错、B 对。B 修复的样本数。
  • c:A 对、B 错。B 引入回归的样本数。
  • n = b + c:真正参与版本比较的分歧样本数。

这正是 McNemar 确切检验(McNemar Exact Test) 的输入。它不关心 A、B 同时正确或同时错误的样本,而是检验 b 和 c 是否呈现出足以支持版本差异的不对称性。对于分歧样本较少的场景,精确检验比依赖大样本近似更合适。

在“两个 Prompt 没有真实差异”的零假设下,分歧样本落到 b 或 c 的机会可视作对称。精确检验据此计算 p-value。若预先约定显著性阈值为 0.05,而结果 p > 0.05,正确结论不是“B 已被证明无效”,而是:当前评测集没有提供足够证据拒绝“两版表现相同”的假设。

这不是文字游戏。即便观察到的准确率提升达到 3%,只要统计证据没有达到预先约定的阈值,就应写“未观察到显著性提升”,而不是宣布改进已经成立。B 仍可能更好;也可能是评测集尚不足以分辨。下一步可能是补充样本、改善判定标准,或者检查改动是否只在某类输入上生效,而不是为那几个百分点补一段自信的解释。

我的取舍:发布决策不该只看 Accuracy Delta

这是一个可以被同行反驳的立场:对于在同一批 case 上运行的 Prompt 版本,单独的 Accuracy Delta 不应作为发布或保留改动的依据。

反对意见很实际。探索阶段往往需要快速排除明显差的想法;评测集可能很小;有些输出也无法诚实地压成“正确/错误”二值。每改一句都跑统计检验,确实增加流程成本。这个反对意见成立。

所以,这不是要求把每个草稿都送上统计法庭。小规模探索可以先看准确率、失败类型和代表性输出,明显退化的版本也不必等待检验结果才淘汰。McNemar 的前提同样不能忽略:两个版本应运行在同一输入上,并且每个结果应能得到相对稳定的二元判定。若你比较的是连续分数、多维 rubric,或两版处理的不是同一组输入,就该选择适合数据形态的方法,而不是机械套用一个检验名称。

但当你准备写下“B 优于 A”,或让 B 进入更广泛的使用范围时,成对证据不应缺席。最危险的时刻不是快速试验,而是把一次局部胜利误当作系统性进步。

把 Prompt 视为不可变的工件(Versioned Artifacts)

不要在主函数和调用链各处散落 Prompt 字符串。这样做的损失不只是代码难读,而是实验边界消失了:这次到底改了什么?为什么改?它替代的是哪个版本?以后面对下一版,谁也说不清。

更可靠的做法,是把每一个 Prompt 封存为不可变的 PromptVersion 实例,集中放在类似 src/prompts.py 的模块中。这里的“不可变”不是指永远不能改,而是指一个已经参与评测的版本不应被原地覆盖。要改,就创建新版本。

这样,v2 相比 v1 增加了什么 hypothesis(假设),v3 相比 v2 又验证了什么,才有可以复查的答案。假设不能只是“试试看会不会更好”,而应尽量可证伪,例如:“补充输出格式约束,预期会减少某类格式错误。”如果评测不支持它,失败的不是评测;被否定的是这项假设。这正是下一轮调优应从哪里开始的信息。

PromptVersion
├── id: v1 / v2 / v3
├── prompt: 不可变的完整文本
├── hypothesis: 此次变更想验证什么
├── parent: 从哪个版本演化而来
└── benchmark result
    ├── accuracy + 95% Wilson CI
    ├── per-case outputs
    └── McNemar exact test against parent

每次改动后运行:

./scripts/benchmark.sh

让它生成一份严谨的对比表格:准确率及其 95% Wilson 区间、逐 case 结果、与父版本相比的分歧计数,以及 McNemar exact test 的 p-value。版本、假设、数据和结论必须能一起阅读;否则你保存的只是 Prompt 文本,不是实验记录。

只有这样,才能有把握地说 Agent 是随着迭代不断进化,而不是在原地无规律地震荡。具体实现可以放在团队可审阅的评测代码库中;重点不是某个仓库地址,而是输出必须能回到每个 case、每个版本和每条假设。

统计学不会替你拯救坏的评测集

把 Prompt 版本化,并不自动使结论可信。一个在固定 benchmark 上显著的改动,仍可能只是在适应那份 benchmark;一个未显著的改动,也可能在评测集没覆盖的真实场景里有价值。评测集需要持续吸收真实失败模式,并警惕把同一种模式的轻微改写误当成独立证据。

同样,0.05 不是自然定律,而是应当在试验前约定的决策阈值。连续尝试多种 Prompt、不断查看结果、最后挑出 p-value 最小的一版,会提高偶然发现的风险。至少应记录尝试过哪些版本、比较了什么,以及何时决定停止。

可迁移的原则只有一个:改动的代价必须写在证据里。 Prompt 改动看似只是一句文本,真正的成本却是它改变了所有输入的处理条件。逐样本记录让这种代价可见;版本假设让它可追溯。它在探索期不必妨碍速度,在发布前却应成为最低限度的约束。

下次看到一个从 75% 变成 77% 的示例式结论,先别急着合并。打开逐 case 结果,数一数 错误→正确 和 正确→错误。然后问自己:你的版本记录里,是否写得出这两个百分点究竟从哪里来?

Updated: