一个 Coding Agent,到底应该怎么验证?
我在 OpenHarness 中如何用 TDD、Eval 和 Dogfood 构成开发闭环,再用公共 Benchmark 补充一份有限的端到端证据。
有很长一段时间,我不知道该怎么写 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 的验证分成四个部分:
- 机制测试:通过 TDD 写下确定性测试,锁住状态机、权限规则、工具执行、持久化和失败路径。
- 决策面 Eval:模型在某个局部决策点上是否表现合理,例如是否选对工具、是否从错误中恢复。
- Dogfood 与真实使用:把完整产品放回真实工作,观察它是否真的解决问题、是否值得持续使用,并发现已有测试和 Eval 尚未描述的问题。
- 公共 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 因此把 live、record 和 replay 分开。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 Eval和LangSmith 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 能够越来越快地完成“已经被定义清楚的事情”的时代,真正稀缺的能力,是看见值得解决的问题,为“好”建立标准,并知道什么证据足以让自己相信。