评测体系的完整方法论
对比式评测
第 4 章讲过一个核心公式:
Skill 价值 = 有 Skill 时的表现 - 没有 Skill 时的表现
方法论的第一条:每个 eval 必须同时跑 with_skill 和 without_skill,不能只跑一个。
跑完之后的产出放在固定的目录结构里:
eval-results/
iteration-1/
eval-detect-missing-header/
with_skill/
outputs/
transcript.json # AI 的完整对话记录
result.md # AI 的最终输出
grading.json # 断言评判结果
without_skill/
outputs/
transcript.json
result.md
grading.json
eval-fix-wrong-year/
with_skill/
...
without_skill/
...
summary.json # 本轮汇总:pass_rate、delta、失败断言列表
iteration-2/
...为什么要这个结构?因为你会反复迭代。第一轮 eval 发现 pass_rate 只有 0.60,改了 Skill 再跑第二轮变成 0.75,再改再跑第三轮到 0.90。每一轮的结果都需要留存,这样你才能看到趋势,而不是每次都在猜”上次好像是 0.7 来着”。
summary.json 的结构很简单:
{
"iteration": 1,
"date": "2025-04-01",
"skill_version": "abc1234",
"results": {
"with_skill": { "total": 10, "passed": 8, "pass_rate": 0.80 },
"without_skill": { "total": 10, "passed": 4, "pass_rate": 0.40 },
"delta": 0.40
},
"failed_assertions": [
{ "eval": "complex-pr", "assertion": "指出未处理的 Promise rejection", "reason": "AI 提到了错误处理但未定位到具体行号" }
]
}双角色评测
跑完 eval 得到 AI 的输出,你需要评判它。这里有两个容易忽略的层面。
角色一:评输出质量(Grader)
这是直觉上的评测——AI 的输出对不对。
给 grader 的输入是:eval 的 prompt + AI 的输出 + 断言列表。grader 逐条判断每个断言是否通过。
断言:"输出中包含 src/utils/format.ts"
AI 输出:"检查发现以下文件缺少 header:src/utils/format.ts, src/components/Button.tsx"
判定:PASS这个角色可以由 LLM 自动完成。大部分断言是客观的——输出里有没有某个文件名、有没有提到某个安全风险、格式对不对。
Grader 的 7 步流程
skill-creator 里的 grader 子代理把评测拆成了严格的 7 步,每一步都有明确的产出:
- 读完整 transcript——不是只看最终输出,而是看 AI 的整个推理过程。如果 AI 走了弯路但碰巧得到了正确答案,grader 要能发现这一点。只看结果不看过程,等于放过了一个定时炸弹。
- 检查实际输出——对照用户的期望,逐项核对。输出里该有的东西有没有,不该有的东西有没有混进来。
- 评判每条断言——Pass 还是 Fail,必须给出具体 evidence。“看起来不错”不算 evidence,“输出第 3 行包含了预期的函数签名
async function deploy(config: DeployConfig)”才算。没有 evidence 的 Pass 和没评一样。 - 提取额外发现——断言没覆盖到但 grader 注意到的问题。比如 AI 输出了一段不相关的警告信息,或者漏掉了一个边界情况。这些发现会反馈到下一轮断言设计中。
- 检查执行者备注——如果 AI 在执行过程中留下了备注(“这个 API 似乎已经废弃了”),grader 要纳入考量。这些备注有时候比最终输出更有信息量。
- 批判断言质量——这条断言是不是太弱了?是不是永远都会通过?是不是在测试实现细节而非行为?这一步是整个设计的点睛之笔。没有它,你可能写了一堆永远绿灯的断言,自我感觉良好,实际上什么都没测出来。
- 写评测报告——结构化输出,供 aggregate_benchmark.py 后续聚合。报告格式固定,包含每条断言的判定、evidence、额外发现、断言质量评价。
第 6 步值得展开说。常见的弱断言有三种模式:
- 永真断言:“输出包含文本”——只要 AI 说了任何话就通过
- 格式依赖断言:“第三行是 XXX”——输出多加一行空行就挂了,测的是格式而非语义
- 模糊断言:“输出的建议是有用的”——grader 自己也说不清什么叫”有用”
grader 标记出这些问题之后,你在下一轮迭代时修正断言,再重跑 eval。断言质量和 Skill 质量是同步提升的。
角色二:评断言质量(Meta-Grader)
这个角色更重要也更容易被忽略。
你写的断言本身靠谱吗?
三种常见问题:
断言太弱:“输出中包含代码审查结果”——AI 只要说了任何跟 review 相关的话就通过了。这种断言通过了也不说明什么。
断言太强:“输出的第三行必须是 src/utils/format.ts:1 - Missing license header”——格式稍有变化就失败。你在测 Skill 还是在测 AI 的格式精确度?
断言不可验证:“输出的建议是有用的”——什么叫”有用”?grader 也说不清。
Meta-grader 的工作是标记这些有问题的断言,然后你人工修正。这个步骤在第一轮 eval 时尤其重要——你对断言质量的判断会随着经验积累而提升,但一开始写的断言大概率有问题。
为什么需要双角色?防止”假阳性自信”。如果你的断言太弱,pass_rate 会虚高。你看到 0.90 以为 Skill 很好,实际上是断言没有区分度。
盲比(Comparator)
你改了 Skill,跑了一轮 eval,pass_rate 从 0.80 升到了 0.85。很好。但 pass_rate 只测客观指标——断言通过了几条。有些质量维度不好用断言表达:输出的组织是否清晰?建议是否具体到可以直接操作?语气是否符合团队风格?
盲比解决这个问题。
做法:把 with_skill 和 without_skill 的两个输出匿名呈现给评审者(可以是人,也可以是 LLM),标记为”输出 A”和”输出 B”。评审者不知道哪个是加了 Skill 的。
评分维度:
| 维度 | 子项 | 说明 |
|---|---|---|
| 内容 | 正确性 | 指出的问题是否真实存在 |
| 内容 | 完整性 | 是否覆盖了所有应检查的点 |
| 内容 | 准确性 | 文件路径、行号是否准确 |
| 结构 | 组织 | 问题是否分类清晰 |
| 结构 | 格式 | 是否易于阅读和后续处理 |
| 结构 | 可操作性 | 看完能不能直接改代码 |
评完分之后再揭示来源。如果匿名状态下评审者一致选了”输出 A 更好”,揭示后发现 A 是 with_skill 的——Skill 有效。如果选的是 without_skill 的——你的 Skill 可能在帮倒忙。
盲比消除的偏差很重要。人有一个倾向:自己改过的东西总觉得更好。你花了两小时调 Skill 的指令措辞,下意识会觉得改完的输出”明显好多了”。盲比让你诚实面对结果。
Comparator 的评分标准
分维度评分不是随便分的。内容维度和结构维度各自回答一个核心问题:
内容维度——“说的对不对?“
| 子项 | 评判标准 | 典型失分场景 |
|---|---|---|
| 正确性 | 指出的问题是否真实存在,有没有无中生有 | AI 把合理的代码标记为 bug |
| 完整性 | 是否覆盖了所有应检查的点,有没有遗漏关键问题 | 安全漏洞没被发现 |
| 准确性 | 文件路径、行号、变量名是否准确 | 路径写错、行号偏移 |
结构维度——“好不好用?“
| 子项 | 评判标准 | 典型失分场景 |
|---|---|---|
| 组织 | 问题是否分类清晰,有没有逻辑分组 | 所有问题堆在一起没有分类 |
| 格式 | 是否易于阅读和后续处理(复制、搜索) | 纯文本墙,没有代码块标记 |
| 可操作性 | 看完能不能直接改代码,而不是还要去查文档 | ”建议优化性能”但不说怎么优化 |
为什么要分两个维度?因为改进 Skill 时经常出现此消彼长的情况。你改了 prompt 措辞让 AI 更精确地定位问题(内容维度提升),但输出变成了密密麻麻的技术术语堆砌(结构维度下降)。单一分数会掩盖这种退步,分维度评分能把它暴露出来。
comparator 对每个维度分别给出 A/B 的优劣判定和理由。最终汇总时,如果两个维度方向一致(A 在内容和结构上都更好),结论很清晰;如果方向相反,就需要人工权衡——这个 Skill 的使用场景更看重正确性还是可读性?
模式分析(Analyzer)
grader 和 comparator 回答的是”这一轮 eval 结果怎么样”。但跑了三五轮之后,你需要回答一个更高层的问题:跨轮次看,有没有反复出现的模式?
这就是 analyzer 的工作。它是 skill-creator 的 agents/ 目录下第三个子代理,做的是事后的模式识别。
analyzer 的输入是多轮 eval 的汇总数据(summary.json)和 grader 报告。它要找的东西包括:
- 顽固断言:连续三轮都失败的同一条断言。这说明当前的 SKILL.md 指令在某个方向上有结构性缺陷,小修小补解决不了,需要重新审视 Skill 的设计思路。
- 退步模式:第 N 轮通过了但第 N+1 轮又失败的断言。这说明你的修改引入了回归,得检查两轮之间改了什么。
- 弱断言集中区:grader 标记为”太弱”的断言如果集中在某一类测试用例上,说明你对这类场景的预期定义不够清晰。
- 维度偏科:如果内容维度持续提升但结构维度持续下降,说明你在迭代中只关注了”说对”而忽略了”说好”。
analyzer 的输出是一份诊断报告,指出最需要关注的 2-3 个模式,并给出改进方向的建议。注意它只给方向,不给具体的 SKILL.md 修改——具体怎么改是你的决策,analyzer 只负责把问题摆到台面上。
这个角色在前两轮 eval 时用处不大(数据太少),但从第三轮开始价值会明显体现。如果你发现自己在第五轮还在跟同一个问题纠缠,大概率是因为没有跳出来看模式。
人工审查补位
自动 eval 抓客观问题:断言通过没有、格式对不对、有没有遗漏的检查项。
但有一类问题自动 eval 抓不到:技术正确但没用。
AI 审查一段代码,指出”这个函数的圈复杂度是 15”。从技术上说完全正确。但作为 review 意见,这句话毫无意义——开发者需要知道的是”这个函数需要拆分,建议把验证逻辑提取到 validateInput() 中”。
人工审查就是补这个缺口。review 最近 5 次 eval 的输出,找那些”断言通过了但其实不够好”的地方。
人工反馈的格式要具体、可操作:
eval: complex-pr
问题:AI 指出了 3 个正确的问题,但建议太笼统。
具体:"建议添加错误处理" → 应该说 "在 fetchUser() 的 catch 块中,
建议将 error 上报到 Sentry 而不是只 console.log,参考 known-pitfalls.md 中的监控规范"。
操作:在 SKILL.md 中加一条指令 —— "给出修复建议时,指出具体的修改方式和引用的团队规范"。迭代改进循环
评测不是一次性的。改 Skill → 跑 eval → 看结果 → 再改 → 再跑。这个循环什么时候停?
信号源有三个:
- 失败的断言:最直接。哪条断言没通过,去看 AI 的输出到底差在哪
- 人工反馈:断言通过了但人觉得不够好的地方
- transcript:AI 的推理过程。有时候输出正确但推理路径错误——这次碰巧对了,下次不一定
把这三个信号喂给 LLM,让它生成改进建议:
以下是 code-review Skill 的当前 eval 结果:
- 失败断言:[列表]
- 人工反馈:[列表]
- transcript 中的异常推理:[列表]
请分析这些信号,给出具体的 SKILL.md 修改建议。LLM 给出的建议不一定全对,但它能帮你快速定位方向。改完之后在 iteration-N+1 重跑 eval。
停止条件:
- 连续两轮人工反馈为空(没有新的改进点)
- pass_rate 连续两轮无显著提升(波动在 0.02 以内)
- delta 已经足够大(比如 with_skill 比 without_skill 高 0.30 以上)
到了这个阶段,Skill 就进入维护期了。不需要持续迭代,只需要在业务变化时更新规则,然后跑一轮 eval 确认没退化。
实战:为 code-review 编写完整 evals.json
[
{
"name": "simple-function",
"description": "审查一个简单的工具函数",
"prompt": "review src/utils/formatCurrency.ts",
"workspace": "test-simple-function",
"assertions": [
"指出函数缺少对 NaN 输入的处理",
"指出函数没有处理负数的显示格式",
"不误报:函数命名符合 camelCase 规范"
]
},
{
"name": "complex-pr",
"description": "审查一个涉及多文件的 PR",
"prompt": "review 当前 PR 的所有变更",
"workspace": "test-complex-pr",
"assertions": [
"检查了所有变更文件,不遗漏",
"指出 src/api/userService.ts 中的未处理 Promise rejection",
"指出 src/hooks/useData.ts 中 useEffect 缺少依赖数组",
"按文件分组输出,不是一整坨"
]
},
{
"name": "security-vuln",
"description": "审查包含安全漏洞的代码",
"prompt": "review src/components/CommentBox.tsx",
"workspace": "test-security-vuln",
"assertions": [
"指出 dangerouslySetInnerHTML 的 XSS 风险",
"给出具体的修复方式(使用 DOMPurify)",
"说明风险场景(用户可以注入恶意脚本)"
]
},
{
"name": "react-component",
"description": "审查一个 React 组件",
"prompt": "review src/components/UserProfile.tsx",
"workspace": "test-react-component",
"assertions": [
"指出组件中使用了 any 类型",
"指出 useEffect 的清理函数缺失(存在内存泄漏风险)",
"给出的类型建议是具体的接口定义,不是泛泛的'请加类型'"
]
}
]四个测试用例,覆盖四种典型场景:简单函数、复杂 PR、安全漏洞、React 组件。每个用例 3-4 条断言,总共 14 条。
注意第三条断言的写法——“不误报”也是一种断言。你不仅要测 AI 能找到问题,还要测它不会无中生有。一个把所有代码都标红的 linter 没有价值。