给 AI codegen 流水线装一个体温计:Harness 自评分设计完整复盘

6 阅读6分钟

§1 / 问题:AI 跑完了,你其实不知道它好不好

用 AI 写代码最诡异的地方是:它跑得越快,你越不知道它跑对了没有。

Cursor 给你一个 diff,你 accept 或 reject。Claude Code 给你修改过的文件,你 review。这个流程解决了「看到结果」的问题,但没有解决「这次比上次好还是差」的问题。

跑了 30 次之后,你对这套 AI coding 工作流的认知其实是:感觉还不错。

感觉这东西不准。

更具体地说,有两个问题靠人眼解决不了:

跨 run 无法比较。 同一个需求让 AI 跑两次,出来的文件大概率不一样。你没有办法判断第二次是不是比第一次更好。可能是模型随机性,可能是 prompt 写法有影响,可能是项目命名约定这次记住了上次忘了。没有任何客观信号。

不知道它在哪摔过。 AI 在项目里犯过的错,下次还会再犯。你手动修了一个 phantom import,下一次跑类似功能,它大概还会再犯。没有持久化的记忆告诉它「这条路走不通」。

这两件事本质上是同一个问题:流水线缺少质量记忆。

§2 / 解决思路:每次 run 跑完算一个分

我的方案是每次 ai-spec run 结束输出一个 0-10 的分数,叫 Harness Score。

名字是从汽车行业借来的:harness 是「安全带」,它的作用不是让车跑得更快,而是在车失控时给你一个信号「现在状态不对」。Harness Score 的作用类似——不是替代你判断代码好不好,而是在人审之前给你一个客观信号。

4 个维度加权:

维度权重计算方式
Spec Compliance30%Spec(md) 里的验收条件 vs 实际输出的 diff
DSL Coverage25%DSL(json) 里的 models/endpoints 有多少被实现了
Compile/Lint20%tsc --noEmit 的退出码
3-pass Review25%LLM 评审(通过 / 需修改 / 不通过)

除了 Review 那 25%(本来就要调 LLM 做评审),其它三个维度全是机械计算的。没有任何额外的 LLM 调用,不存在「再调一个更强的模型打分」这种循环论证。

§3 / 为什么不是「用 LLM 当裁判」

这个方向我认真试过,有两个绕不过去的问题。

第一个:循环论证。

让模型 A 生成,让模型 B 评分,然后根据 B 的反馈改 A——你在训练一个模型裁判,但这个裁判本身没有 ground truth。评分高不代表代码真的好,只代表模型 B 和模型 A 看法一致。

更准确的说法是:你在用模型的「自信度」代替真实的「质量」。模型对自己输出的评分永远偏高,因为它不知道自己不知道什么。

第二个:成本。

每次 run 额外调一次 LLM 做评分,对于高频使用的场景(我每天跑 5-10 次)是可观的额外开销。更重要的是,LLM 评分每次的输出不稳定——同样的代码两次调用可能给出 8.2 和 7.6,两个分数的差异到底反映了代码质量的变化还是模型的随机性?无法区分。

所以 Harness 的定位是温度计,不是裁判

温度计不判断对错,它只需要读数准确。它告诉你「这次是 8.4,上次是 7.6」,你根据这个信号决定要不要多 review 一次,而不是让它告诉你「这段代码好不好」。

§4 / 四个维度各自怎么算

Spec Compliance(30%):diff 说话

Spec(md) 是 ai-spec pipeline 的第一个输出——它把用户的一句话需求扩展成一份结构化的需求文档,包含背景、模型定义、端点列表、验收条件。

跑完之后,拿验收条件和实际输出的文件做 diff。验收条件里写了「登录接口要返回 refreshToken」,实际代码里有这个字段,分数就高;没写,分数就低。

这个维度的意义:防止 Spec 写得清楚但实现跑偏。AI 有时候会把 spec 里明确写的内容漏掉,compliance 维度能把这个信号放大。

DSL Coverage(25%):路径匹配说话

DSL(json) 是 ai-spec 的第二个输出——它把需求翻译成机器可读的结构化契约,包含 models、endpoints、behaviors。

跑完之后,扫实际生成的文件路径,看哪些 models 有对应的 model 文件,哪些 endpoints 有对应的 api 文件。

这个维度的意义:防止「接口文档写了但代码没实现」。Coverage 低了说明 DSL 里定义了但实际没写,通常是 AI 在某个文件上卡住之后跳过了某些端点。

Compile/Lint(20%):编译器说话

tsc --noEmit 直接跑,退出码 0 = 10 分,非 0 = 0 分。

这个维度没有模糊空间:代码要么能编译,要么不能。能编译不代表逻辑对,但编译不过一定有问题。

20% 的权重不算高,但它是一个硬门槛。如果 compile 是 0,即使其它三个维度都是满分,总分也只有 8.0——这个门槛足够让人不能忽视。

3-pass Review(25%):LLM 评审说话

这是四个维度里唯一一个依赖 LLM 的环节。

Review 分成 3 轮:第一轮检查代码逻辑是否和 spec 一致,第二轮检查边界情况,第三轮检查是否引入了明显的性能或安全问题。

三轮评审的结果映射成「通过 / 需修改 / 不通过」,再映射成分数:10 / 5 / 0,加权均分。

§5 / Prompt hash 绑定:跨 run 比较的关键

分数本身没有意义,比较才有意义。

我给每次 run 的分数绑定了 prompt hash——相同 prompt 多次跑,分数可以横比。这才能回答「这次比上次好还是差」的问题。

Harness Score  8.4 / 10
  compliance 8.6  ·  coverage 9.2  ·  compile 10  ·  review 7.1
  prompt_hash: a7f2d3e1
  run_id: 20260408-143022

同一个 prompt 跑了 3 次:

run 1: 7.2  (compliance 7.0 / coverage 6.8 / compile 10 / review 5.8)
run 2: 7.8  (compliance 8.2 / coverage 7.5 / compile 10 / review 6.4)
run 3: 8.4  (compliance 8.6 / coverage 9.2 / compile 10 / review 7.1)

趋势在变好。这比「感觉还不错」有说服力多了。

Prompt hash 绑定的另一个好处:你能定位「是什么让分数变了」。如果从 Claude 换到 DeepSeek 之后分数跌了,你就知道这个 provider 对这类任务的适配度不够,可以切回去或者调整 prompt。

§6 / 这个分数能做什么、不能做什么

它能量化的:

  • 这次 run 和 spec 的吻合度
  • 跨 run 的质量趋势
  • 不同 provider 对同一类任务的适配度差异
  • Compile 是否能过

它不能量化的:

  • Runtime 正确性(业务逻辑 bug Harness 看不出来的)
  • 代码可读性和可维护性(这部分还是靠人审)
  • 架构是否合理(Harness 只看你写没写,不看你写得好不好)

Harness 的价值是把「我这次跑完觉得还行」变成一个可记录的量化数字。你仍然要 review,但 review 的切入点从「全看一遍」变成「看分数低的地方」。

§7 / 现在的状态

Harness 已经集成在 ai-spec 的 pipeline 里,不需要额外配置。跑完任何 ai-spec create 命令都会自动输出分数。

$ ai-spec create "给订单模块加退款功能"
  ✔ Spec generated     .ai-spec/specs/order-refund.md
  ✔ DSL validated     .ai-spec/dsl/order-refund.json
  ✔ data             src/models/Refund.ts  src/models/Order.ts
  ✔ service          src/services/RefundService.ts
  ✔ api              src/api/refund.ts
  ✔ test             src/tests/refund.spec.ts
  3-pass review  ✔
  Harness Score  8.1 / 10
    compliance 8.4  ·  coverage 9.0  ·  compile 10  ·  review 5.8
  9 files written  ·  restore: ai-spec restore 20260410-094521-c3d8

GitHub: github.com/hzhongzhong… npm: npm install -g ai-spec-dev 站点: ai-spec.dev