在受控回归测试中,工具两次返回完全一样的内容,旧检索脚本却可能选出不同文件。这个问题出在候选文件的评分上:脚本看到了工具没有返回的部分。

我在 agent-retrieval-bench 的 PR #4 修复了这个问题,随后又在 PR #6 统一了读取内容的计数与截断规则。两项修改都在 2026 年 10 月 22 日合并。

工具只给了开头,评分却看到了后面

这个项目的 closed-tool 评测会限制工具调用次数和读取预算。脚本基线搜索、读取文件后,根据内容与问题的重合程度给候选文件加分,再选出结果。

读取时,系统按 included_tokens 扣减预算,并截出相应的文件前缀。但旧评分代码直接使用原文件的前 20,000 个字符。这两个范围不一定相同:预算可能只够读文件开头,相关关键词却藏在后面。

用一个简化示意表示:

text
工具返回:文件开头的一段普通代码
未读部分:与问题高度相关的关键词

只改未读部分 → 工具返回不变 → 候选排名却可能改变

这样得到的结果,混入了预算外的信息。问题发生在脚本策略的候选评分中,不能据此断言所有模型评测都受到了影响。

修复后,评分也先调用 token_prefix(text, included_tokens),再保留原来的 20,000 字符上限。排序权重没有改,只收紧评分可用的内容。

先证明“没看到”,再检查结果

回归测试固定搜索结果、读取元数据和实际返回的文本,只替换未读尾部的关键词。如果这些可见信息都相同,最终选择就不该被那个关键词改变。

测试还保留了另一组对照:放宽预算,让关键词真正进入可见范围。这时它仍然可以影响选择,避免把评分功能一起关掉。

处理前缀时也没必要先扫描完整文件。修复把正则匹配限制在预算加一次前瞻:多看一个匹配,就能判断是否需要标记截断,不必先把全文件的匹配对象存进列表。

截得短,还不一定算得对

接着检查时又发现,扣预算和截断用的不是同一套规则。计数器会计算标点和非 ASCII 字符,旧截断函数的正则却主要匹配标识符与数字。

例如,给 call(x, y) 两个单位的预算,旧函数会返回 call(x。按项目的记账规则,call、(、x 已经是三个单位。对 ;;; 设置一个单位的预算,旧函数甚至会返回三个分号。

PR #6 让计数和截断共用同一个 TOKEN_RE。两单位预算现在得到 call(,一单位预算得到 ;,随后附上截断标记。模型收到的文件内容和初始上下文也按这条规则截断。

这里的单位是项目定义的正则代码 token,不是模型供应商的计费 token。截断标记和工具包装文本也不能算作文件正文的读取量。

修复以后,分数变了吗?

在 PR #6 的前后对照中,公开的 101 条 v2_trace2code 样本的最终文件选择、轨迹和非延迟指标没有变化。按项目函数重建这批读取前缀时,545 次读取中超出正文预算的次数从 389 次降到 0 次。修复了预算约束,不等于提高了检索准确率。

PR #4 的目标模块当时有 17 项测试通过,PR #6 的四个相关模块有 33 项通过,均在 Windows 和离线 Linux 验证。完整 Windows 测试未全部通过;这里不宣称全项目 CI 通过,也没有付费模型调用的效果对照。本次文章依据这些公开记录整理,没有重跑评测。

之后的 PR #13 在执行前拒绝重复样本 ID,防止一个样本重复进入汇总;PR #16 则保留断点续跑的累计记录与失败尝试成本。这些修改都在检查同一件事:结果表里的选择、样本数和成本,能否对应到实际发生过的过程。

本文使用 AI 工具辅助整理。实验设置和测试结果见正文。

相关原始记录 ↗更多 PR 修复记录 →讨论这个问题 ↗