通用与垂类 harness:三格不适用,两格清单上没有
拿六类职责去量一个测试用例生成 harness,会发现工具那格是空的、验证那格装不下。垂类的难点不在少做,在清单上根本没有的那两格。
上一篇那份六职责清单是通用的。拿它去量一个具体的垂类 harness,才看得出通用清单的边界在哪。
用的例子是「从产品文档生成可运行的测试用例」—— 输入 spec、代码或者一个跑起来的界面,输出能交给执行引擎跑的用例。
结论先说:六格里有三格对不上,而这个垂类最难的两格,清单上压根没有。
先对着六格量一遍
| 职责 | 通用 coding agent | 测试用例生成 |
|---|---|---|
| Observation 观察 | 读文件、跑命令看输出 | 读 spec / 代码;或者漫游界面去猜 |
| Context 上下文 | 代码检索 + 会话历史 | spec 片段 + 代码约束 + 跨故事记忆 |
| Control 控制 | 模型每步自己决定 | 流水线写死,模型不决定 |
| Action 动作 | 工具注册表,模型挑着调 | 没有工具,模型只产出数据 |
| State 状态 | 会话树、检查点 | 任务树 + 用例库,要能续跑 |
| Verification 验证 | 测试跑没跑过 | 凭什么说这条断言是对的 |
加粗那三格就是差别所在。
差别一:Action 那格是空的
通用 harness 的定义几乎都建立在工具上。那篇讲 harness 与后训练的论文给 harness 下的定义是:决定哪些工具被暴露、它们怎么被描述、每一步观察附带什么辅助信息的脚手架。整句话的主语是工具。
而一个 spec → 用例 的 harness 里,模型不调工具。它读材料,吐一份结构化数据(用户故事、状态机、用例),动手的全是确定性代码。
于是一连串通用能力集体失效:工具注册表没有对象,权限模型没有拦截点,那套常见的三段执行管线(pre-execute / execute / post-execute)没有东西可以挂。
这不是「简化版」,是形态不同。 按学术定义,这类系统甚至不该叫 agent —— 它是 workflow。
差别二:Control 那格是刻意让出的
通用 agent 的核心特征是模型在循环里选动作。垂类里经常反过来:步骤是确定的,把控制权交出去只会引入不确定性。
理由不是保守,是可测量性。From Agent Loops to Deterministic Graphs 那篇的论证可以直接搬过来:交出控制权就交出了可复现性 —— 同一份输入两次运行走不同路径,产出就没法比较。
而在测试这个垂类,可比较性不是锦上添花,是产品本身的价值:这一版和上一版覆盖了什么、少了什么,是使用者最关心的问题。一个每次跑出不同路径的测试生成器,产出物没法做基线。
但也别一刀切。同一个垂类里,探索模式必须是 agent —— 让模型漫游一个陌生界面去猜用户故事,你没法预先写死步骤。所以现实形态是混合的:
spec / code 输入 ── workflow(步骤确定,可复现)explore 输入 ── agent loop(界面未知,必须模型决定)执行与判定 ── 又是一层 agent(执行引擎自带的循环)三种控制形态共存,各有各的理由 —— 这比纯 workflow 或纯 agent 都难写,但把「哪一段为什么这样」说清楚,本身就是设计。
差别三:Verification 那格装不下
这是最大的差别。通用 harness 的 Verification 通常是二元的:测试过了没有、编译过了没有。这个垂类里,验证本身要分层。
一条用例的判定可以有三种硬度:
| 档 | 判据 | 谁能判 |
|---|---|---|
| assert | 页面上有没有这句文案、这个数等不等于那个数 | 程序 |
| invariant | 删一条之后列表少一条、总额等于各项之和 | 程序,但要对照物 |
| judge | 让模型看着截图说像不像话 | 模型 |
分层的意义在于它让「生成了 66 条用例」这个数变得可读 —— 66 条全是 judge,和 66 条全是 assert,是完全不同的两批产物,而数量一样。
更麻烦的是通用清单里根本没提的一条:在这个垂类里,误报比漏报致命。
一条判据模糊的用例会周期性地红一次绿一次。每红一次就有人去翻产品代码,翻半天发现是判据的问题。翻三次之后,没人再信这份报告 —— 然后漏报也跟着来了,因为一份没人信的报告,红了也不会有人去看。所以一条软判据同时制造误报和漏报,它不是「差一点的判据」,是负资产。
通用 coding agent 里,误报的代价是浪费一次 review;这里的代价是整套东西失去信任。代价结构不同,设计就不同。
我在自己那套东西里加过一道「判据够不够具体」的形式检查,用正则查有没有引号、数字、状态动词。跑了两轮之后战绩是:开火十几次,真阳性零次。 拦下来的全是「登录按钮恢复可点击」「进入已锁定状态」这类好判据,而它想抓的模糊判据,模型那两轮压根没写出来过。后来我把它从拒收门降级成了警告 —— 一道门能不能拒收,看的是它的战绩,不是它的道理。
清单上没有的两格
前面三格是「有但不一样」。下面两格是通用清单里压根没提,而垂类里决定成败的。
一、Grounding:每条产出能不能指回出处
这个词在通用 harness 语境里指视觉定位,在这里指另一件事:一条断言凭什么这么写。
具体的失败长这样:spec 里写着「密码至少 8 位,必须含一个大写字母」,模型生成的断言是「页面出现文案『密码至少 8 位』」。看起来合理,实际上是编的 —— spec 承诺了规则,没承诺产品会显示这六个字。 拿没承诺的措辞去断言,对着一个完全正确的产品也会红。
规则和文案是两回事,而模型很自然地把前者写成后者。
这不是从论文里读来的,是我自己撞的。 我的经验里这是最容易被漏掉的一类缺陷 —— 我拿一份四段的产品文档跑了一轮,66 条用例里断言引用了 12 条界面文案,回头去 spec 里查,只有 4 条查得到。剩下的「邮箱格式无效」「账户已锁定,请 10 分钟后再试」全是编的。更糟的是我后来试着写一个更狠的检测器——把断言里所有中文词块拿去材料里查——66 条全被标可疑。那个 100% 的误报率反而说明问题比我原先量到的大得多:多数断言压根不带引号,编造的措辞混在自然语句里,连数都数不出来。
这个垂类的综述(覆盖 2000–2025 年 21 项工作)把这件事列为六个质量维度中的两个 —— hallucination control 和 traceability —— 并且结论是没有任何现有方法同时满足六项。另外四项是 automation、ambiguity handling、domain applicability、evaluation thoroughness。
更值钱的是那篇顺带给出的解法方向:幻觉控制做得好的系统(UMTG、NAT2TESTSCR、CiRA、RAGTAG)无一例外都引入了 external anchoring mechanism —— 每条产出必须能指回一个外部锚点。还有一类(TECO)用代码执行轨迹给断言重排序。
翻译成 harness 的语言:产出物的 schema 里必须有一个 source 字段,而且它要被校验,不是被信任。
二、产物的可复用与参数化
通用 agent 的产出通常是一次性的:一个 PR、一段回答。测试用例不是 —— 它要天天跑,要在几十条之间共享登录步骤,要用同一个骨架跑二十组参数。
这意味着产物 schema 从一开始就得能表达「片段」和「变量」,而不是把一条用例写成一段扁平的自然语言。这一格如果一开始没设计,后面补代价极大 —— 它会污染已经生成的全部存量。
通用 harness 清单里没有这一格,因为通用 agent 的产出没有复用需求。
所以垂类 harness 的清单该怎么列
不是六格全填,也不是照搬。我认为的做法是三步:
- 对着通用六格盘点,明确划掉不适用的。划掉要写理由 —— 「模型不调工具,所以 Action 这格不适用」,比留一个空着的 TODO 有用得多。
- 找出这个垂类特有的格,并且承认它们通常最难。grounding 和可复用这两格,没有现成方案可抄。
- 按代价结构重排优先级。通用 agent 优化的是成功率,这个垂类优化的是「不制造假信号」。
第三条常被忽略。同样是「让判定更严」,在通用 agent 里是锦上添花,在这里是核心功能。
边界与代价
这一篇是拿一个垂类去量通用清单,不是普适结论。 换一个垂类 —— 比如运维排障、合同审阅 —— 空的格和多出来的格都会不一样。方法可以搬,结论不能搬。
「不做」也有代价,而且经常被低估。 不做工具注册表,就没法在执行前后拦截;不给模型控制权,探索能力就是零。这些不是白拿的,是买来的。
垂类清单容易变成自我辩护。 「这一格我不做,因为我的场景不需要」和「这一格我做不了」在文档里长得一模一样。区分方式只有一个:不做的那一格,要写清楚什么情况下你会回来做。
下一篇
上面全在讲 harness 怎么设计。还有一个绕不开的问题:这些能力,有多少该靠 harness 解,有多少该靠微调模型解?
直觉是「垂类当然要微调」。而实测数据说的是另一回事:harness 的杠杆在很多配置下大过模型容量,而且微调用错顺序会让模型比不微调还差 —— 有一组数是从 55.6% 掉到 2.7%。
第三篇先讲为什么微调之前要先修 harness。