有很长一段时间,我不知道该怎么写 OpenHarness 的 Eval。

不是因为没有内容。正好相反,我已经给项目跑过完整的 SWE-bench Lite,给工具选择、错误恢复、Skill 触发、Memory、Context 压缩、权限审查和完成判断写过不同的 Eval,也在日常 dogfood 里不断发现问题、修改实现、补回归用例。

该做的事情做了不少,但当我想把它们写成文章时,却一直不知道自己真正想表达什么。

直到最近我才意识到:Eval 对我来说已经不再神秘了。

一开始,我把 Eval 想成一种属于 AI 领域的特殊技术:它有 dataset、scorer、LLM-as-judge、benchmark、pass rate,还有一整套陌生的工具和术语。真正做过以后,我发现它并没有脱离软件工程。它只是把软件测试继续向前推进了一步——推进到了那些由模型参与、不能再用一次确定性断言覆盖的地方。

我原以为,Eval 是给不确定的模型找一套确定的考试。后来才发现,Eval 真正做的,是把开发者对“什么算好”的判断,切成可观察、可重复、可争论的工程契约。

当模型参与决策,Eval 开始

传统软件当然也有不确定性。网络会超时,线程会竞争,外部系统会失败。但只要代码路径和输入条件确定,我们通常仍然能为它写下明确的断言:这个函数应该返回什么,这个状态机应该进入哪个状态,这次失败应该产生什么错误。

Agent 增加了一类新的不确定性:模型开始参与决策。

同一句用户请求,模型可能选择不同的工具;即使选择了同一个工具,也可能构造不同的参数;一次工具失败后,它可能修正参数、寻找替代路径,也可能固执地重复同一个调用。它还要决定什么值得记住、什么时候应该加载额外能力、哪些上下文必须在压缩后保留下来,以及任务是否真的已经完成。

这些都不是传统意义上的代码分支,却都会改变产品行为。

我后来把 OpenHarness 的验证分成四个部分:

  1. 机制测试:通过 TDD 写下确定性测试,锁住状态机、权限规则、工具执行、持久化和失败路径。
  2. 决策面 Eval:模型在某个局部决策点上是否表现合理,例如是否选对工具、是否从错误中恢复。
  3. Dogfood 与真实使用:把完整产品放回真实工作,观察它是否真的解决问题、是否值得持续使用,并发现已有测试和 Eval 尚未描述的问题。
  4. 公共 Benchmark:检验核心 coding loop 能否在公共任务和外部判定下完成端到端工作,补充一份内部 Dataset 之外的阶段性证据。

这四项并不是频率相同、依次升级的四层测试。TDD、决策面 Eval、Dogfood 与真实使用构成持续开发闭环;公共 Benchmark 为核心工作流补充一份公共、但有边界的端到端证据。共享题目和结果判定并不意味着运行条件完全可比;Benchmark 分数属于模型、Harness、工具、运行预算和执行环境共同组成的系统,不是产品总分。

Eval 和 TDD 不一样的地方是,被测行为开始由模型参与,结果存在概率波动,因此需要另一套 Dataset、Oracle 和稳定性证据。

Dogfood 和真实使用在这个闭环里尤其重要。Eval 只能覆盖我已经知道应该关注什么;真实使用才会暴露我不知道自己不知道什么。

而要把模型带来的概率性变成可以测量的对象,第一步就是把 Agent 拆成一个个决策面。

Agent 不是一个黑盒,而是一连串决策

“怎么测试一个 Agent”是一个大到几乎无法下手的问题。但如果把 Agent 展开,它其实在不断做局部决策:

  • 要不要调用工具?
  • 应该调用哪个工具?
  • 参数应该怎么构造?
  • 工具失败后应该重试、换路,还是停止?
  • 当前请求是否需要加载一个 Skill?
  • 一段信息是否值得写入长期 Memory?
  • Context 太长时,哪些事实必须留下?
  • 一个动作是否越过权限边界?
  • 现在是否有足够证据宣布任务完成?

不是每一个决策都值得单独建立 Eval,但每一个由模型参与、结果存在合理波动、又会显著影响产品行为的决策,都是一个候选 Eval 面。

这个视角让我第一次觉得“测试 Agent”是可以被工程化的。我不再试图给整个 Agent 打一个模糊的总分,而是追问:我现在究竟在测试哪一次决策?给它什么输入?观察什么输出?什么证据足以判断好坏?

OpenHarness 的每一份 Eval dataset card 都必须回答四个问题:

  • Capability claim:这份 Eval 声称测量什么,又明确不测量什么?
  • Input spec:Case 代表哪一类输入,来自什么真实问题?
  • Judgment spec:用什么 oracle 判定结果?
  • Reference policy:固定用哪个模型作为参照,以及需要怎样的稳定性证据,才把这一轮行为接受为当前基线?

它们迫使我说清楚:一个分数到底证明了什么,又不证明什么。

Eval 不只测试 Agent,也测试写 Eval 的人

真正让我理解 Eval 的,并不是那些顺利通过的 Case,而是 Eval 自己出错的时刻。

第一次:模型没有错,是我的标准答案错了

tool_choice Eval 的早期版本里,我设计了一个“运行项目测试”的 Case。模型应该选择 Bash,我还要求它构造的命令必须包含 pytest

参考模型连续四次选择了 make test

按照 scorer,它四次都错了。但合成环境里并没有项目说明告诉模型应该使用 pytest。make test 是完全合理的选择。真正发生的事情是:我把自己熟悉的实现方式偷偷写进了“正确答案”,然后把所有其他合理路径判成失败。

最终,我撤掉了这个参数断言,只保留这个 Case 真正想测试的能力:用户要求运行测试时,模型是否选择了 Bash。

这件事让我第一次清楚地意识到,Eval 的失败至少有三种可能:模型错了,产品契约错了,或者 Eval 错了。

看到红灯时,如果第一反应永远是修改 Prompt,我们很容易为了通过一个坏掉的测量仪器,把产品真的改坏。

第二次:模型读了 README,Eval 却要求它什么都别做

OpenHarness 的 Plan mode 只允许只读探索,不允许修改文件、运行命令或委派任务。我最初为它写的 restraint Case 要求模型零工具调用。

模型却稳定地选择读取 README.md

我一开始把它视为“不够克制”,后来才发现是 Eval 和产品契约发生了冲突:Plan mode 本来就允许 Read。模型没有越界,甚至在做一件对规划有帮助的事情。

于是 Case 被改成:允许并期待只读探索,同时严格禁止 Write、Edit、Bash 和 Agent。

Eval 不应该规定唯一轨迹,除非唯一轨迹本身就是产品要求。很多 Agent 任务存在多条正确路径。测量的关键不是让模型复现我脑中的动作序列,而是找出必须发生的事情和绝不能发生的事情。

第三次:真正的问题根本不在已有 Dataset 里

一次真实 dogfood 中,我要求 OpenHarness 向 workspace 外的 /tmp 写文件。产品的权限设计是:模型可以提出精确的 Write 请求,Harness 再根据当前权限进行审查。

但当时的工具描述却告诉模型,workspace 外的写入永久不允许。于是模型在调用工具前自行拒绝了任务,权限生命周期根本没有机会启动。

权限实现是对的,工具也能接收那个路径,每个局部测试都可能是绿的;真正出错的是模型根据工具描述形成的理解。

这个问题只有在完整链路中才显现。Dogfood 先让我皱了一下眉头,我再把它还原成一个可重复的 Case:给模型一个明确的外部绝对路径,要求它保留路径和内容调用 Write,并禁止它偷偷换成 Bash 或改写到 workspace 内。

这就是我后来形成的 Eval 飞轮:

Dogfood(偶尔也来自 Benchmark)
        ↓
发现具体失败
        ↓
判断是机制缺陷,还是模型决策缺陷
        ↓
写成确定性测试,或 capability eval
        ↓
修复并进行 live 验证
        ↓
沉淀为回归基线

Case 最好的来源不是坐在房间里想象模型可能怎么失败,而是真实使用中已经让我付出过注意力的问题。

Oracle 是被编码的判断力

有了 Case,还需要回答更困难的问题:什么算通过?

模型的自然语言输出通常没有唯一答案。一个摘要可以有无数种写法,一条合理的工具轨迹也不一定只有一种顺序。如果把所有东西都交给另一个 LLM 判断,只是把一层不确定性叠到了另一层之上。

因此我逐渐形成了一条纪律:能用硬 oracle,就不用软 judge。

工具选择可以直接比较工具名;参数构造可以检查必要字段;错误恢复不必判断“下一步聪不聪明”,可以先检查它是否原样重放刚刚失败的调用;Context 压缩不必评价“摘要写得好不好”,可以在输入里种植必须保存的事实,再检查压缩后是否仍然存在。

只有当判断本身确实需要语义理解,例如一条 Memory 是否真正具有长期价值、当前 Goal 是否准确概括了用户意图时,才让 LLM judge 接管那个维度。

不确定性还意味着单次运行不能自动等于结论。同一个 Case 需要重复运行,观察它是稳定失败、稳定通过,还是偶发波动。Pass bar 也不能先凭感觉写一个数字,再要求数据服从它;它应该来自参考模型在完整 Dataset 上的稳定性画像。

OpenHarness 因此把 liverecordreplay 分开。live 用当前模型观察真实行为;稳定性达到约定后,record 保存一次已经认可的响应;replay 不再调用模型,只验证 Dataset、scorer 和已录制行为是否仍然一致。

Replay 很便宜,也很确定,但它不能证明今天的模型仍然表现良好。Prompt、工具描述、Context 组装、Judge、模型或 Provider 发生变化后,仍然需要重新 live ratification。

这也是 Eval 和普通测试最不同的地方之一:测试代码只是工作的一部分,维护“这份证据在什么条件下仍然有效”同样是工作。

Dogfood 验证产品价值,Benchmark 提供有限证据

Dogfood 是我会持续进行的日常工作。每完成一段能力,我都需要把它放回完整产品里使用,观察各个局部契约组合起来以后是否仍然成立。但一个 Coding Agent 最终是否值得使用,不能由 Benchmark 替用户决定。真实任务的成功、长期协作中的信任、重复使用和主动推荐,才是更接近完整产品价值的证据。公共 Benchmark 不是每次开发都要运行的回归环节。它成本更高,也更适合在系统已经基本成形、我自己觉得“差不多了”的时候运行。

公共 Benchmark 的价值,在于提供一组公开任务和共享的结果判定方式,让一个 Agent 系统接受内部 Dataset 之外的端到端检验。它可以减少每个项目只挑选成功案例讲述自己的空间,也让不同系统的结果进入一场有共同上下文的讨论。

但“使用同一组题”并不等于“使用同一把完整的尺子”。Benchmark 测到的从来不是一个裸模型,而是模型、Harness、工具、Prompt、Context、运行预算、重试策略和执行环境共同组成的系统。只要这些条件不同,分数就不能直接拆解成模型或 Harness 的能力,更不能单凭一个总分判断哪个 Coding 产品更好。它提供的是共享任务下的一份系统级结果,而不是完整产品价值的替代品。

OpenHarness 曾通过产品实际使用的运行时跑完 SWE-bench Lite 的全部 300 个任务。在一个固定基线上,qwen3.7-max 最终解决了 170 个,得到 56.7% 的 resolved rate。

我当时很自然地把 56.7% 当成这次运行最重要的结果。后来回头看,它真正证明的事情其实很有限:OpenHarness 能够在标准化环境里完成完整的 coding loop,阅读仓库、使用工具、修改代码,并交付可以由外部测试验证的 Patch。它证明了这个系统“能跑”,却不能单独证明它是一个更好的 Coding Agent 产品。

这次运行也没有覆盖 OpenHarness 的全部能力。Benchmark runner 使用的是受限的默认 coding 模式:Skills、Commands、Memory 和 Web 被关闭,Goal、Plugins、多次会话中的持续协作也不在任务契约之内。因此,56.7% 不是 OpenHarness 的产品总分,只是某一套运行条件下,一个局部能力面的结果。

Benchmark 因此只是一种有边界的证据:它验证公共任务的最终结果,不替代 Capability Eval、Dogfood 和真实用户反馈。

为什么 Coding Agent 是一种特殊的反馈环境

也正是在反复观察任务、轨迹、结果和用户反馈的过程中,我开始意识到,Coding Agent 本身就是一种非常特殊的反馈环境,也开始理解为什么今天几乎每一家模型公司都如此重视 Coding Agent。

我不会简单地说,做 Coding Agent 就是为了收集训练数据。外部并没有足够信息支持这样的因果判断。更准确的说法是:

Coding Agent 是一种天然高密度的模型反馈环境。

用户的目标通常比较明确;模型做过什么,可以从工具调用和文件修改中看到;任务是否完成,可以运行代码和测试验证;如果结果不对,用户还会直接纠正它。

一次 Coding Agent session 里,同时存在意图、决策轨迹、环境变化、可执行结果和人类反馈。相比开放式聊天,它更容易形成“行为—结果—反馈”的闭环。

这种闭环可以帮助模型团队理解和改善模型,也帮助产品团队改善 Agent Harness。很多失败并不是模型不够聪明,而是工具描述不准确、错误信息没有提供正确恢复路径、Context 中缺了关键事实、权限边界无法被模型理解,或者完成条件根本不可验证。

Coding Agent 把模型和软件系统紧密地绑在了一起,也因此成为观察两者如何共同工作的绝佳实验场。

原来,别人也走到了相似的地方

完成这些实践以后,我回头看 Claude Code、OpenAI 和 LangChain 的公开资料,产生了一种很奇妙的感觉:我并不是先学会了一套行业方法论,再把它应用到 OpenHarness;我是追着自己的真实问题一路往下走,最后发现大家逐渐走到了相似的地方。

Anthropic 在《Demystifying evals for AI agents》中提到,Claude Code 最初依赖员工和外部用户反馈快速迭代,后来才逐渐增加 Eval:先测简洁度、文件编辑这样的窄行为,再扩展到过度设计等复杂行为。它也把 Agent Eval 的 grader 分成代码、模型和人工三类,强调多次 trial、阅读 transcript,以及从真实失败中积累任务。

OpenAI 官方文档把 Agent Eval 组织为 traces、graders、datasets 和 eval runs。Trace 保存模型调用、工具调用、guardrail 和 handoff,评测问题则直接包括:是否选对工具、是否在正确时机 handoff、是否违反指令,以及 Prompt 或路由变化是否改善了端到端行为。Trace grading进一步把完整决策过程作为定位失败和验证改进的证据,而不只观察最后一句回答。

LangChain 的文档同样把 Agent 评测拆成 final response、single step 和 trajectory。轨迹既可以用确定性规则匹配,也可以交给 LLM judge;LangSmith 则区分离线 Eval 与线上监控,并建议把生产环境中的失败重新加入离线 Dataset,形成持续反馈循环。Trajectory EvalLangSmith Evaluation描述的,正是这种从真实运行回流测试集的结构。

具体实现各不相同,但背后的判断已经相当接近:

  • 把系统拆成可以说明的能力面;
  • 同时观察单步决策、完整轨迹和最终结果;
  • 能使用硬 oracle,就不使用软 judge;
  • 用重复运行和稳定性画像面对模型波动;
  • 从 Dogfood、生产 Trace 和真实失败中生长 Eval Case;
  • 把自动 Eval、线上反馈和人工体验组合起来,而不是相信其中任何一层能够覆盖全部问题。

这可能就是一个新工程领域的方法论形成的方式。

它不是由某家公司一次性设计完毕,再作为标准答案分发给所有人。不同团队面对相似的现实约束:模型会波动,轨迹有多条合理路径,Judge 也会犯错,合成 Dataset 会脱离真实使用,而人类注意力又极其有限。问题不断深入以后,大家的判断力自然开始收敛,方法论才从实践中慢慢浮现出来。

对 Eval 去魅以后

现在回头看,AI 已经可以帮助我完成 Eval 的大量具体工作:读取 Trace、整理失败、生成 Case 草稿、实现 scorer、运行实验、比较结果,甚至指出断言可能存在的漏洞。

但真正的瓶颈仍然在人。

AI 无法替我决定一个 Agent 最重要的产品行为是什么,无法替我区分“我偏好的路径”和“唯一正确的路径”,也无法保证我选择的 oracle 没有偷偷奖励错误的东西。如果我不知道自己不知道什么,它只会更快地帮助我把那个盲区工程化。

所以,对 Eval 去魅并不是因为我终于记住了一套标准做法,而是因为我逐渐能够面对一个具体的 Agent 行为,判断应该观察什么、相信什么证据,以及承认还有什么不知道。

一开始,判断力只表现为 Dogfood 时的一次皱眉:“这个 Agent 用起来不太对。”

后来,它变成一条可以复现的 Trace、一个明确的能力声明、一组 Case、一个 oracle 和一条 Pass bar。即使 Prompt、模型和 Harness 继续变化,这份判断仍然作为工程资产留在项目里。

这可能才是 Eval 最重要的意义:

Eval 是把人的品味变成工程资产。

在一个 AI 能够越来越快地完成“已经被定义清楚的事情”的时代,真正稀缺的能力,是看见值得解决的问题,为“好”建立标准,并知道什么证据足以让自己相信。