跳转至

Trace、Eval 与结果核对:运行结束不等于任务正确

上一篇:Session、Context、Memory 与恢复 · 返回学习入口

运行轨迹、环境产物与独立评测的关系

Harness(位于模型外部,负责输入装配、工具、权限、状态和产品表面的程序)可以顺利跑完一项 Task。结果仍可能出错。循环停下,只能说明这次 Run(运行)结束了。Trace(执行轨迹)让你看见中间发生过什么,Eval 再拿明确的任务定义和判定方法检查结果。讲 Tool 或 Session 时,不必每篇都把这两部分塞进来。

运费案例有三个不同结论

模型完成编辑并运行命令后,不能立刻用一个「成功」概括全部结果,至少要区分:

  1. 运行是否正常结束:模型请求、工具执行和消息循环有没有协议错误;
  2. 目标测试是否通过:测试进程是否运行了正确文件并返回预期断言;
  3. 用户目标是否满足:金额 100 的行为是否正确,同时没有破坏其他运费规则。

第一项看 Harness 有没有正常运转,第二项核对 Environment(运行环境)里究竟发生了什么,第三项才按用户的任务口径判断有没有做对。只读最终回复不够,因为它无法可靠回答后两项。

Trace 应该记录什么

先看一份教学数据,它展示了最小 Trace 需要串起哪些 Event。

{
  "task_id": "shipping-boundary",
  "events": [
    {"type": "user_message", "content": "修复金额 100 时仍收费的问题"},
    {"type": "tool_call", "id": "read-1", "tool": "read_file"},
    {"type": "tool_result", "id": "read-1", "status": "success"},
    {"type": "tool_call", "id": "edit-1", "tool": "edit_file"},
    {"type": "tool_result", "id": "edit-1", "status": "success"},
    {"type": "tool_call", "id": "test-1", "tool": "run_command"},
    {"type": "tool_result", "id": "test-1", "exit_code": 0},
    {"type": "final_message"}
  ]
}

真实项目未必照搬上面的格式,也可能用 JSONL(逐行 JSON)、数据库事件、Protocol(协议)通知或内存对象来记。载体可以换。事件顺序和关联标识却必须留下,否则你就查不出哪个请求带来了哪个结果。

Trace 不必收下所有原始内容。遇到敏感字段、超长日志和二进制 Artifact(产物),可以只存引用或哈希,但读取这些记录的人必须知道数据全不全,也要知道哪些地方截断或脱敏了。

最终文本为什么不够

模型可能说「测试已经全部通过」,但存在多种反例:

  • 测试命令没有真正执行;
  • 执行的是错误目录中的测试;
  • 输出属于旧进程;
  • 只跑了不覆盖金额 100 的测试;
  • 测试通过,但修改了无关文件;
  • 最终回复在测试失败后仍声称完成。

最后那段文字不可靠。它可能和实际执行毫无关系。任务只要能验证,Scorer(评分器)就该先读环境留下的产物,不能只凭结论听起来像不像真的。

Eval 的最小组成

一个可解释的 Eval 至少要包含下面这些部分。

组成 在案例中的内容
Task 修复金额 100 的免运费边界,并运行测试
Initial Environment 固定版本的仓库和依赖
Runner 使用目标 Harness 执行任务
Trace 与 Artifact 消息、工具事件、补丁、测试输出
Evaluator 检查目标断言、回归测试和文件差异
Result 通过、失败或无法判断,并附原因

这里可以让 Evaluator 运行确定性测试、套用规则、交给人工审查,也可以用模型评分。只要环境里有能直接核对的真值,就优先用确定性方法。

过程正确和结果正确

目标测试通过了,Agent 做事的过程仍可能不合格。它可能删掉失败测试,把结果硬编码进输出,或者顺手改了很多无关文件。绿色结果也会骗人。Eval 因此要同时查下面几类条件:

  • 结果条件:金额 100 返回 0,其他边界仍正确;
  • 过程条件:没有越界写文件,没有修改测试来迎合实现;
  • 资源条件:运行时间、模型调用和工具次数处于合理范围;
  • 证据条件:补丁和测试输出确实属于本次任务。

别只把过程条件写进 Prompt,然后相信模型会照办。模型到底守没守规矩,还得看得见证据。能验证的规则,尽量用文件 Diff、Permission 事件和测试来查。

失败、无法运行和无法判断

评测结果至少要区分:

  • 任务失败:运行完成,但用户目标没有满足;
  • 环境阻塞:依赖、网络或执行环境无法启动任务;
  • 结果不完整:缺少必要产物,无法可靠判定;
  • Harness 错误:协议、工具或持久化发生故障;
  • 用户取消:任务主动终止。

如果把这些情况全记成失败,产品质量统计会混进别的问题。直接丢掉环境错误也不行,那会遮住运行本身的故障。后面的高级 Eval 专题还会讲统计单位和恢复 Attempt,这里先把每种结果分开。

重试为什么会改变解释

测试服务若只是偶发超时,可以沿用同一项任务定义,再启动一次运行尝试。重试也会改变数据。基础设施出了故障,可以靠它恢复;Agent 如果确实改错了,就不能一直重试到碰巧成功,否则测出来的产品质量已经变了。

读源码时,你可以跟着一次重试往下查:

  • 谁决定重试;
  • 重试的是模型请求、工具调用还是整项任务;
  • 旧的失败是否保留;
  • 最终结果引用哪一次产物;
  • 有副作用动作是否会重复。

这些问题能直接说明重试改动了哪些事实,比背住某个 Eval Harness 的类名管用。

从 Trace 回到源码

Trace 里的字段能追到哪段代码写出来,才算有可靠来源。读项目时还要继续找下面这些位置:

  1. 事件类型或消息类型的定义;
  2. 模型请求开始和结束的发射点;
  3. 工具调用、权限决定和执行结果的关联方式;
  4. Session 持久化使用的序号或时间;
  5. 最终输出和错误怎样暴露给 CLI、SDK 或服务端;
  6. 是否存在 OpenTelemetry、日志、回放或测试适配器。

项目提供 Telemetry,不等于它已经做了完整的任务 Eval。仓库里有测试,也不等于测试覆盖真实的运行轨迹。写文章时要按各项目确实提供的能力说明这些接缝,不能一概声称所有 Harness 都内建了独立评测。

一个可复核的最小判定

对运费案例来说,跑完下面这组确定性检查,就能得到最小判定。

1. 从固定仓库版本创建工作区
2. 让 Harness 执行用户任务
3. 保存补丁和工具事件
4. 在干净测试进程中运行目标测试与回归测试
5. 确认测试文件没有被修改
6. 把结果关联到本次任务和补丁

即使模型最后没回文字,只要 Patch 和测试事实齐全,照样能判断任务结果。回复再自信也没用。目标断言失败就算没通过。

如何使用后续内容

六条源码课程只有在项目确实提供事件、日志、Telemetry 或 Eval 接缝时,才会讲到相应实现。DeepSeek Harness 的评测代码会放回它自己的 Call 链里解释。Codex、Gemini CLI、Claude、pi 和 OpenCode 则只讲锁定来源能够证实的能力。

跨项目评测、Trial、Attempt、RewardAdapter、Feedback 和独立发布评估都留到进阶专题。现在读基础 Agent Loop,还不用掌握这些内容。

练习:最终回复与任务结果冲突时相信谁

一次运行中,模型回复「所有测试已通过」,Trace 里却找不到测试命令,而独立验证又发现金额 100 的断言仍然失败。请分别写出 Harness 运行状态和 Eval 结果。

查看核对要点 Harness 可以记录「模型正常给出最终文本并结束」,但不能把它改写成「任务成功」。Eval 应当判定结果失败,并且说明系统缺少执行证据,独立测试也没有通过。这两种结论并不矛盾,因为一个描述协议生命周期,另一个描述用户目标是否实现。

现在应该能解释

Harness 结束,只说明运行停在了什么状态。测试输出告诉你环境里真正发生了什么,Eval 再按任务口径判断结果。Trace 要把请求、决定和产物串起来。解释重试时也得分清它是在修复基础设施,还是在掩盖真实的产品失败。最终判定才不会走样。

你可以任选一条主要课程,从真实源码开始。