个人 Agent 明明能跑通,为什么评测还是判它失败——用 PAST-Bench 拆开失败前的那几步

文章导读
手动能跑出结果,评测仍然判失败,通常不是同一件事被两次判定,而是两次观察的窗口不同:手动跑的时候你在终端看到的是最后一段输出,评测器看到的是整条轨迹、每一步的工具返回和最终环境状态。要拆开失败,做法不是重跑一遍看能不能过,而是从判失败的那一步往前回看几步,确认是环境被改写、工具返回没被正确解析,还是判定口径本身没对齐。
📋 目录
  1. A 先区分跑通和通过是两件事
  2. B 在失败样本里往前定位最后一次正常动作
  3. C 检查环境和工具返回是否改变了任务状态
  4. D 把同类失败归成几组
  5. E 决定补判定口径还是补 Agent 行为
A A

手动能跑出结果,评测仍然判失败,通常不是同一件事被两次判定,而是两次观察的窗口不同:手动跑的时候你在终端看到的是最后一段输出,评测器看到的是整条轨迹、每一步的工具返回和最终环境状态。要拆开失败,做法不是重跑一遍看能不能过,而是从判失败的那一步往前回看几步,确认是环境被改写、工具返回没被正确解析,还是判定口径本身没对齐。

手动跑通只说明这条路径在你的机器上产出了结果;评测判失败说明评测器按自己的断言在某个 step 上没有拿到它要的证据。建议先用轨迹定位失败 step,再向前回看 3 到 5 步的动作与工具返回,优先排除环境状态被改写和返回解析失败,然后才区分是判定口径问题还是 Agent 行为问题。轨迹粒度不够时不要先改 Agent,先补记录字段,再用同一批样本重放。所有结论都应以可复现的日志片段为准,不能只看最终那行输出。

先区分跑通和通过是两件事

跑通的判定主体是你或本地脚本,依据通常是进程有没有报错、屏幕上有没有出现看起来正确的结果。评测通过的判定主体是评测器的断言和 judge,依据是它记录下来的轨迹、工具返回、最终环境状态以及格式约束。两者的观察面不一样,所以「能运行」不能替代「能判定」。

对比项手动跑通评测通过
判定主体你、本地脚本评测器的断言与 judge
判定依据退出码、终端输出、肉眼检查轨迹记录、最终状态、格式校验
失败信号报错、卡住、结果明显不对step 断言不满足、解析失败、超时、状态不符
复现方式同一台机器手动重跑同一份初始状态重放整条轨迹
常见偏差你在中间补了步骤、改了参数评测器看不到这些补步骤

适用场景是评测给出失败但你在本地手测正常。操作动作是把同一条任务的轨迹和本地命令逐行对照。验证方式是看评测器是否真读到了你以为它读到的那个产物。风险边界是本地环境和评测环境的初始状态本来就可能不同,需要结合环境确认,不能默认一致。

在失败样本里往前定位最后一次正常动作

回溯顺序建议固定下来:先拿到失败 step 的序号,再看该 step 的断言输入与 judge 输出,然后看最后一次工具返回,再往前看那次工具调用的参数,接着看模型当步的 action 或 thought,最后看当时的环境快照。每一步只看与这一步有关的那几行,不要一次读完整份日志。

个人 Agent 明明能跑通,为什么评测还是判它失败——用 PAST-Bench 拆开失败前的那几步
# 查看失败样本最近的若干步(字段名按你的记录结构替换)
jq '.steps[] | {i, action, tool, args, result, state_digest}' trace.json | tail -n 12

# 只看失败 step 的判定输入
jq '.steps[12] | {assertion_input, judge_output, terminal_reason}' trace.json

每一步需要看的片段:工具返回里是否有报错字段或空结果;参数里的路径、文件名、ID 是否与上一步一致;环境快照里的工作目录、关键文件是否存在。如果记录缺失,先补字段再重放,最小字段建议包含 step 序号、动作类型、工具名、调用参数、返回摘要、状态摘要、终止原因。粒度不够时可以把前 N-1 步的状态固化,只重放第 N 步,这样单步是否正常会更容易判断。

检查环境和工具返回是否改变了任务状态

不少失败不是模型答错,而是任务状态在中间被别人改写了。下面几类是常见情形,每类给一个检查点和判断依据,逐条对照即可。

情形检查点判断依据
文件被覆盖或删除失败 step 前后的文件列表与内容摘要同名前缀的临时文件出现、目标文件 mtime 变化
工作目录漂移每一步之前与之后的 pwd相对路径解析结果在不同 step 指向不同位置
进程或端口被占用启动命令的返回码与 stderr报错里出现 address already in use 一类信息
外部依赖返回异常工具返回的 status 与 body 形状返回限流、鉴权过期或结构被截断
时间与随机性时间戳、时区、随机种子同一输入两次重放得到不同中间结果
临时状态残留评测开始前的缓存目录、临时目录上一批样本留下的文件影响本次判定
返回被解析错误工具原始返回与 Agent 读到的内容原始返回有内容,Agent 侧解析结果为空或字段丢失

如果某一步工具返回的是成功状态,但语义上是失败,例如返回了一段错误说明却用 200 包装,这类需要结合工具约定确认,不能只看状态码。

把同类失败归成几组

单条样本修好不算处理完,建议按三个维度分组:失败发生的阶段(规划、工具调用、参数构造、返回解析、判定)、责任方(Agent 行为、环境状态、判定口径)、可复现性(稳定复现、偶发、只在特定任务出现)。每组的最小证据要求不同,稳定复现需要有一条完整轨迹加本地重放结果,偶发至少要有两条失败样本的相同 step 特征。

个人 Agent 明明能跑通,为什么评测还是判它失败——用 PAST-Bench 拆开失败前的那几步
  • 只在特定工具上失败,且换任务依然出现:先按工具维度建组。
  • 同一任务不同时间跑,失败 step 不同:先按环境与随机性建组。
  • 行为正确但判定不通过:归入口径组,不要混进行为组。

只出现一次的失败先记为个案,记录任务 ID、失败 step、当时的输入摘要即可,不急着改 Agent,也不急着改断言。等同类特征第二次出现,再升格为分组处理,这样可以避免为偶发噪声引入不必要的改动。

决定补判定口径还是补 Agent 行为

补判定口径

判断条件是:多条轨迹的行为和最终结果都符合任务意图,失败集中在断言、格式或归一化环节,并且手动核对判定输入也能看出是评测器读错了。改动范围应限制在评测适配层与断言,例如放宽等价写法、增加字段归一化、修正超时阈值,不动 Agent 的提示词和工具。验证方式是把改动前后的判定结果逐条对比,人工核对哪些样本发生了翻转,确认翻转的都是口径问题。

# 断言侧最小校验样例(函数名按你的 harness 替换)
def check_final(task, state, out):
    expected = normalize(task.expected)
    actual = normalize(out.final_answer)
    assert actual is not None, 'final answer missing'
    assert actual == expected, f'mismatch: {actual}'

补 Agent 行为

判断条件是:同类任务多次卡在同一步,且用同一套判定口径手动复核也判失败;或者失败集中在参数构造、工具选择、返回解析的处理上。改动范围通常是提示词里的动作约束、工具入参校验、失败重试与状态复位逻辑。验证方式是用同一批样本重放,逐步确认失败 step 是否后移或消失,并保留改动前的轨迹作为对照。两条路径都可能同时需要一小步改动,先做证据最充分的那一条,另一条留到下一轮再评估。