← 返回信息流

dev.to #ai短讯

ButterflyBench:我改了一个指令,AI 还变了什么?

dev.to作者:Emmimal P Alexander评测AI评分:50/100

作者提交 Kaggle 基准测试挑战时发现,修改提示词读取 JSON 而非 CSV 时,多个模型自动调整了分隔符和表头设置。作者意识到自己的评分代码错误地假设其他参数必须保持不变,这一发现比排行榜分数更有价值。

这是 Kaggle 基准测试挑战的参赛作品

我进行了哪些基准测试

我的一个测试提示词写道:“读取 JSON 文件而不是 CSV。”

在我的多次重跑中,有三个模型的回答还同时设置了分隔符和表头行为未指定。它们并没有错。JSON 文件确实没有分隔符也没有表头行。我的评分代码错了,因为它假设其他 19 个设置必须保持原样。这个错误教会我的比任何排行榜数字都多,而这正是这篇文章真正要讨论的内容:检查模型是否只改变了你要求它改变的部分,对模型而言和对编写测试的人来说,都比听起来更难。

在我自己处理提示词和代码的工作中,我不断遇到同样的问题:当我改变一件事时,还有什么会出问题?助手可以执行你要求的编辑,但仍然移动了你从未提及的东西。因此,我构建了 ButterflyBench 来直接测量这一点。

这个名字来源于蝴蝶效应概念:微小的变化会在其他地方产生效应,我想衡量在 AI 规范中这种情况发生了多少。

工作原理。每个场景都从命令行数据工具的相同 20 个设置的规范开始。它涵盖 Python 版本、输入和输出格式、排序、编码、超时、分隔符、表头行、日志级别等,并以纯英语描述给模型。在一到十个回合中,模拟用户对其进行编辑。用户可能:

  • 设置值(“给每个文件 60 秒超时。”)
  • 撤销上一次更改,或间接描述的更改(“撤销关于日期打印方式的更改。”)
  • 撤销“仍在生效的最早更改”
  • 重做刚刚撤销的更改
  • 删除要求(“排序不再重要。”)
  • 说一些必须不改变任何东西的话,比如问题或对队友设置的评论

在每个回合之后,模型必须以 JSON 形式重新陈述完整规范。我还提前声明了三个耦合规则:TSV 输入强制使用制表符分隔符,并行处理关闭进度条,HTML 输出强制使用 UTF-8。这样,必需的副作用就不会被误认为是附带损害。

没有 LLM 裁判。一个小 Python 预言机在每个回合后重播活动更改并计算出完全正确的规范。Kaggle 分数是最终规范完全匹配的 40 个场景的比例。离线我还计算变更半径(未被请求而移动的设置的数目)、撤销后的恢复能力以及模型出错的第一个回合。

共有 40 个场景:6 个是我手动编写的,34 个是使用固定随机种子从模板生成的。它们涵盖了通过间接描述撤销(6 个)、撤销仍在生效的最早更改(6 个)、耦合规则(6 个)、干扰项(5 个)、撤销最后一次(4 个)、取消(4 个)、长链(4 个)、重做(3 个),以及各一个的取消后撤销和间接引用。

我并不声称发明了其中任何内容。例如,在多轮指令遵循方面已有深入研究,如 Multi-IF、MultiChallenge 和 EvolIF。我想要的是小而可复现的东西,带有确切的答案键,其中核心问题是附带变化。

我的第一次试点太简单了。它有 10 个设置和一到三次编辑的场景,Gemini 3.7 Flash 得了 5 分中的 5 分。我用 20 个设置、撤销、重做、间接引用、干扰项和耦合规则重建了它,那个版本开始区分模型。

测试的模型

我运行了来自四个提供商的七个模型:Gemini 3.7 Flash、Grok 4.20(推理和非推理版本)、Claude Sonnet 5、Claude Haiku 4.5、GPT-5.4 mini 和 gpt-oss-20b。

我是根据一条在查看任何结果之前就定好的规则来选择它们的:涵盖不同的提供商和尺寸,并在同一模型存在推理和非推理变体时各选一个(Grok 4.20 提供了这种变体)。实际限制也塑造了这份名单,主要是 Kaggle 的每日 AI 配额以及其“添加模型”列表中提供的模型。每个模型都接受了相同的 40 个场景、提示词和设置(使用库默认值,温度设为 0,种子设为 0)。Sonnet 5 的输出上限为 8,000 个 token,这远远超过了一次回复所需的几百个 token。

发现

首先说明一下数据。下面的排行榜是 Kaggle 上的公开运行结果,每个模型仅运行一次。在开发过程中,我还在本地运行了相同的场景,其中一些是针对较早版本的场景集进行的。这些运行的结果差异高达六个场景(针对同一模型),而下方详细的表格来自另一次本地运行,因此其总数与排行榜并不完全一致。请将排行榜视为快照,而非排名。

最有趣的结果并非排行榜本身,而是得分相似的模型以截然不同的方式失败。

模型得分(40 个场景)
Grok 4.20 Reasoning1.00
Claude Sonnet 51.00
Gemini 3.7 Flash1.00
gpt-oss-20b0.90
Claude Haiku 4.50.78
GPT-5.4 mini0.75
Grok 4.20 Non-Reasoning0.75

在我所有的运行中,gpt-oss-20b 的得分在 40 分中的 30 到 36 分之间,Haiku 为 29 到 31 分,GPT-5.4 mini 为 27 到 30 分,Grok 非推理版为 27 到 30 分。因此,这三个最低分模型之间的差距处于噪声范围内,而顶级组别则各运行一次。

难点在于历史,而非编辑

为了最后一次本地运行,我详细考察了四个模型。其中三个——Haiku、GPT-5.4 mini 和 Grok 非推理版——表现几乎完全相同:

类别(场景数)Haiku 4.5GPT-5.4 miniGrok non-reasoninggpt-oss-20b
撤销仍在生效的最早更改(6)0006
撤销最后一次更改(4)4443
通过间接描述撤销(6)6665
重做(3)3332
耦合规则(6)6535
取消(4)3334
干扰项(5)4554
长链条(4)1210
总计(40)29302730

这三个模型处理“撤销上次更改”、“撤销对 X 的更改”以及重做操作时毫无困难。它们无法做到的是“撤销仍在生效的最早更改”,因为目标必须从哪些更改仍然活跃中推导出来。在我的第一次完整运行中,GPT-5.4 mini 在所有包含该句子的十个场景中全部失败。通常它什么都不改,或者反而撤销了较晚的更改。Haiku 在早期的一次运行中完成了 6 个中的 2 个,而在最后一次运行中完成了 6 个中的 0 个。大多数长链条失败源于同一原因,因为四条长链条中有三条包含这一转折。

gpt-oss-20b 是异常值。它通过了所有六个此类场景,却丢掉了几个简单的场景。在这次运行中,它与 GPT-5.4 mini 均得 30/40 分,但表现模式几乎相反。

无论是否推理,同一模型

Grok 4.20 推理版得分为 1.00,非推理版为 0.75。在我的本地运行中,非推理版每次都在最早更改场景中得 0/6 分,而推理版的唯一遗漏是我后来因措辞模糊而修正的一个场景。这是一对模型和各自的一次运行,我是在看到早期结果后才选择这对模型的。因此,我将此视为一个有趣的线索,并据此不对推理模型得出普遍结论。

通过率掩盖了模型的失败方式

Haiku 在 40 个场景中失败了 11 个,但移动的不相关设置最少(平均变化半径为 0.075)。GPT-5.4 mini 失败了 10 个,但移动最多(0.325)。Grok 非推理版为 0.125,gpt-oss-20b 为 0.200。单一得分无法告诉你模型的错误是局部的还是散布在整个规范中的。

我的许多“模型失败”其实是我自己的措辞问题

在责怪任何模型之前,我阅读了转录记录,发现了四个措辞问题。它们都没有改变预言机、评分标准或提示词,我为每次修复都做了记录。

  • “收回我之前关于每个文件可能需要多长时间的说法。”Gemini 3.7 Flash 将超时时间设置为未指定(unspecified),而不是恢复为 30 秒。我的初始规范也提到了超时时间,因此“关于它们的说法”这一解读是合理的。我将措辞改写为“撤销我对……所做的更改”。
  • “删除标题行。”我的指示告诉模型,对于用户已放弃的要求应使用未指定值。相同的场景在完全相同的文本上通过了一次,失败了一次。事实证明,这是每次都会出现的模式:一个在通过和失败之间切换的场景通常具有歧义性。
  • “我不再关心每个文件的时间限制。”Claude Haiku 和 Grok non-reasoning 在所有四次全新运行中都回答 timeout='none'。none 是一个合法值,表示无限制,因此两种解读都是合理的。
  • “读取 JSON 文件而不是 CSV。”这是从头开始的故事。在 9 次重跑中的 5 次里,Sonnet 5、Grok reasoning 和 gpt-oss-20b 都将分隔符和标题行设置为未指定,而我的预言机只知道三个耦合关系,导致其将合理行为误判为附带损害。我从场景中移除了未声明的耦合关系,而不是添加第四条规则。

如果你构建一个带有确切答案基准的测试集,我的建议是:当一个模型似乎破坏了某些内容时,检查你的提示词的合理读者是否也会做出同样的反应。

最终状态评分掩盖了波动

某次 gpt-oss-20b 的运行在第 1 至 3 轮中,有 20 个设置中的 18 个返回了未指定值,然后在第 4 轮恢复了。最终状态是正确的,因此最终状态评分永远不会显示这一点。只有首次分歧轮次显示了这一点。

头部饱和

三个模型的得分为 1.00,因此 ButterflyBench 对中等强度模型的区分能力优于对强模型的区分能力。

接下来我想测量的内容

  • 最早变更指令的释义。所有包含该指令的十个场景都使用相同的句子,因此我还无法区分是“无法跟踪历史”还是“这种措辞很难理解”。
  • 对每个模型进行重复运行,以便在排行榜上添加误差棒。
  • 更长的链式交互以及每轮重置状态的版本,以区分早期失败与晚期失败。
  • 代码作为工件。ButterflyBench 跟踪结构化规范。它不测试生成的代码是否能经受住编辑。

局限性

它测试的是对结构化规范的跟踪,而非代码内部的附带损害。这是一个小型公开基准,因此模型可能已在其中训练过。此外,预言机仅编码了我陈述的三个耦合关系。

如果你正在基于 Kaggle Benchmarks 构建应用

有三件事耗费了我的时间。嵌套评估强制要求 max_attempts=1,因此你需要在自己的任务中进行重试,否则某个提供商的一次小故障会表现为整个模型的“错误”。一个昂贵的模型曾出现 403 错误(“预估成本超过您的可用配额”),直到我通过 extra_api_params 限制了其输出令牌数。此外,主任务的 docstring 会成为其描述,且必须少于 255 个字符。

我的基准

  • Kaggle 上的基准:https://www.kaggle.com/benchmarks/emmimalalexander/butterflybench
  • 任务和排行榜:https://www.kaggle.com/benchmarks/tasks/emmimalalexander/butterflybench
  • Notebook(包含预言机、评分及全部 40 个场景):https://www.kaggle.com/code/emmimalalexander/butterflybench

译文已达到本站中文翻译的字数上限,剩余内容请查看原文。

阅读原文