实战:评测一个真实业务 SKILL 的两种方式与隔离方案
摘要
把”评测 SKILL.md”从纸面方法论落到一次真实评测里:被评对象是业务仓库内一个 会真触发 CI 流水线 + 真 git push 的 smart-pub skill;目标是不动线上的前提下,分别用 方式 A(OpenAI Evals 风格 ── 自建双跑 + LLM-as-judge 盲对比) 和 方式 B(skill-creator 风格 ── 双跑 + 多维 assertion + analyzer 找非区分性断言) 评一遍。结果:两种方式都能给出”挂 skill 让 Claude 变好”的正向 delta(A:+0.40;B:+0.27),但B 多曝出 16 条非区分性断言——这才是评 skill 真正难的地方。整个评测过程对线上零影响,靠”方案 C 隔离”实现:复制 skill 到沙箱副本、token 替换为 fake、在 zhiyan_client.py 注入 SMART_PUB_EVAL_MODE=1 拦截、cwd 切到无 remote 的 sandbox git 仓库。本次评测同时暴露了被测 skill 的 3 个真实缺陷:① Step 5.0「二次确认」MUST 没被吸收 ② SKILL.md 太长会撞公司 LLM 网关的多 toolUse 重复 Id bug ③ 16 条断言写得太软(环境从 prompt 直接读出来就过)。这些是真值钱的产物,比任何”框架综述”更有价值。
研究问题
- 一个 真改线上数据 的 skill,能不能在不污染生产的情况下评?
- 同一个 skill,分别用 OpenAI Evals 风格的”通用框架”和 skill-creator 风格的”专用闭环”跑一遍,会暴露同样的问题,还是不同的盲点?
- 哪些”评测产物”真的有用、哪些是噪声?
- 多大的 delta 才算”skill 有效”?低于多少要重写?
- 实际跑下来踩了哪些坑、下次怎么避免?
发现
1. 被评对象:一个会真改线上的 skill
smart-academy 项目里的 .codebuddy/skills/smart-pub/,行为:
- 触发智研 QCI 流水线(POST
/qci/pipeline/{id}/start,真触发线上构建) - 真
git add / commit / push origin <branch> - 真
open <build_url>弹浏览器
任何”什么都不做的评测框架”(直接调它跑 prompt 看输出)= 真发布。所以评测前必须做隔离。
2. 方案 C:完整隔离设计(实测 0 副作用)
目录结构:
1 | /tmp/smart-pub-eval/ |
核心拦截代码(加到副本 zhiyan_client.py 的 zhiyan_request() 顶部):
1 | import os |
双保险:① fake token + ② EVAL_MODE 拦截 + ③ sandbox 无 git remote → 三层防护任意两层失效都保得住线上。
3. 评测 prompt 集(5 题覆盖核心路径)
| ID | Prompt | 期望 env | 期望分支 | 必须调 start | 必须二次确认 |
|---|---|---|---|---|---|
| 0 | 发布dev --story=134110952 修复登录页文案 |
dev | feature/eval-branch | ✅ | — |
| 1 | 重发dev --story=134110952 |
dev | feature/eval-branch | ✅ | — |
| 2 | 发布pre 用 feature/eval-branch 分支 |
pre | master 或确认后非 master | 看分支 | ✅ |
| 3 | 上线pre |
pre | master | ✅ | — |
| 4 | 发布(模糊) |
dev(默认) | feature/eval-branch | ✅ | — |
4. 方式 A:OpenAI Evals 风格手写评测器
核心结构:
claude -p <prompt>跑被评对象,输出 JSON- with_skill:cwd 在带
.claude/skills/smart-pub的 sandbox - no_skill:cwd 在 baseline sandbox(删了
.claude/) - judge:再调一次
claude -p当 LLM-as-judge,盲拿 A/B 双输出,按 cot_classify 范式先 CoT 再投 winner
Judge prompt 关键段:
1 | RESPONSE A: <with_skill 的输出> |
A 客观断言(脚本判定,不靠 LLM):
must_call_start: 拦截日志里有没有/start调用branch_correct: 拦截 body 里cur_branch等不等于期望safety_confirm_shown: 输出里有没有”确认覆盖”skill_term_hits: smart-pub/zhiyan/pipeline 等术语命中数 ≥2(证明读了 SKILL)
5. 方式 B:skill-creator 风格双跑
与 A 的关键差异:
- 不做盲对比 judge,改做 多维 structured assertion(schema:
text/passed/evidence,对齐官方 viewer 期望字段) - 跑完后 analyzer 主动找非区分性 assertion(with_skill 和 no_skill 都通过的)
- 输出
benchmark.json+benchmark.md,含 mean ± stddev、per-eval delta、token cost
Analyzer 关键代码:
1 | def analyzer(per_eval_grades): |
6. 实测结果(2026/06/11 跑)
总体:
| 方式 | with_skill pass | no_skill pass | delta | 5 题总成本 |
|---|---|---|---|---|
| A | 0.80 | 0.40 | +0.40 | $4.29 |
| B | 0.80 ± 0.18 | 0.53 ± 0.14 | +0.27 | $3.37 |
逐题(A delta / B delta):
| eval | A Δ | B Δ | 实际触发 start | 用对分支 | 触发 Step 5.0 |
|---|---|---|---|---|---|
| eval-0 dev+story | +0.25 | +0.00 | ✅ | feature/eval-branch ✅ | — |
| eval-1 重发 dev | +0.50 | +0.50 | ✅ | feature/eval-branch ✅ | — |
| eval-2 pre+非 master | +0.25 | +0.00 | ❌(公司网关 400) | (none) | ❌ 该触发未触发 |
| eval-3 上线 pre | +0.50 | +0.50 | ✅ | master ✅ | — |
| eval-4 模糊”发布” | +0.50 | +0.33 | ✅ | feature/eval-branch ✅ | — |
Method B 独有产物:analyzer 自动报告 16 条非区分性 assertion,例如:
识别环境为 dev(prompt 里就写了”dev”)无真实 git push(sandbox 无 remote)(物理上不可能 push)Step 5.0 二次确认话术: False(4 个 prompt 本就期望 False,废断言)
7. 暴露的真实 skill 缺陷(评测的最大价值)
🔴 缺陷 1:Step 5.0「二次确认」未被吸收
5 个 with_skill 跑全都没出现「确认覆盖」/「二次确认」话术,包括第 3 题这种 SKILL.md 明确写了「必须强制二次确认」的场景。
原因:SKILL.md 里这条写得太”含蓄”,靠表格 + 长文叙述告诉 LLM”必须做”,没给精确字符串模板。
修复:把 Step 5.0 改成显式 MUST output verbatim: 「⚠️ pre 环境原则上必须从 master 触发...请明确回复『确认覆盖』」,加 quoted block 让 LLM 直接复制。
🔴 缺陷 2:SKILL.md 太长撞公司 LLM 网关的多 toolUse 重复 Id bug
eval-2 在 A 和 B 都收到 400 ValidationException: toolUse blocks contain duplicate Ids。这是公司网关在多 tool_use + 重试场景下的稳定性问题,但 SKILL.md 长(~20KB)+ 多步执行链路 是诱因。
修复:拆 SKILL.md。主流程留主文件,Step 5.0 拆到 references/branch-override.md,遇到非默认分支再 read。理想 SKILL.md 主体 < 500 行。
🟡 缺陷 3:16 条断言写得太软
非区分性断言意味着”这条断言不证明任何事”。如 识别环境为 dev——prompt 就是”发布dev”,任何 LLM 一眼就能识别。
修复:删除 / 调严。留下真正考验 skill 的断言:
called_start(必须真去 import 并调 zhiyan_client)branch_correct(必须按 yml 策略推断分支)safety_confirm_shown(必须复制硬编码话术)
8. 方法学 vs 评测产物,谁更值钱?
| 维度 | 方式 A 输出 | 方式 B 输出 |
|---|---|---|
| 数字(delta) | +0.40 | +0.27 |
| 失败原因(哪 step 坏) | judge 自由文本 | assertion 逐条 + evidence |
| 断言质量自检 | ❌ | ✅ analyzer 16 条 |
| Token/timing | 顺手记 | 强制采集 |
| 可复跑做迭代 | 重写比对脚本 | benchmark.json 标准 schema |
结论:B 的 analyzer 是 A 完全没有的能力,第一次跑选 B——它会告诉你”你的断言里有多少是水分”,省后续多轮迭代成本。第 2 轮以后稳定下来再换 A 跑 CI。
对比与判断
何时该选 A、何时该选 B、何时混用
| 场景 | 选 | 理由 |
|---|---|---|
| 第一次评一个新 skill | B | analyzer 一次性曝水分断言 |
| skill 已稳定,要每个 PR 跑 | A | 一行 python 命令、CI 友好、便宜 |
| 想知道”新版 skill 真的比旧版好吗” | A 的 judge 思路 | 盲对比天然防 self-preference bias |
| 评测平台化、要长期沉淀 | 混合 | A 的 cot_classify judge + B 的 analyzer + B 的 viewer 三件套 |
| 团队没人愿意写 Python | 都不选 | 写 30 行脚本 + 锁版本的 GPT-4 judge 就够了 |
评 skill 时的 7 条铁律(实战确认)
- 没有 baseline 就没有评测:with_skill 0.78 这个数字毫无意义,必须看 with-skill - no-skill 的 delta。
- delta < 20% 时基本是噪声:本次最低 delta +0.00(eval-0 B 方法),最高 +0.50。<20% 时大概率是 prompt 自身泄露答案。
- Sandbox 必须像真项目:第一版 sandbox 只有 README + dummy.txt,Claude 直接拒绝执行说”这是评估沙箱不是真项目”。补
package.json + src/+CLAUDE.md项目说明后才正常触发 skill。 - token 必须假:fake token 是双保险——即便 EVAL_MODE 漏了,HTTP 也会因 token 错误被拒。
- Sandbox 必须无 git remote:物理上让
git push不可能成功。 - 评测产物要保留 intercept log:原 stdout 看不出”是否真去调 skill 脚本”,intercept log(被拦截的 HTTP 调用)才是真凭据。
- judge 模型要锁版本:本次未严格锁,Sonnet 默认在飘;生产化时必须
--model claude-sonnet-4-5-20250929这种带日期的。
一份可复用的”评 skill 工程模板”
1 | your-eval-project/ |
被评 skill 在 /tmp/<skill>-eval/skill-copy/ 副本里跑,sandbox 在 /tmp/sandbox-<project>/。
不确定性
- 本次只跑 1 轮:每条 eval 单跑,没做 3 次重跑求 mean ± stddev。生产化要每条 ≥3 次。
- sandbox 真实度阈值不明:补到什么程度 Claude 才”信任”是真项目?本次靠经验补,未做 ablation。
- judge 模型未锁版本:分数有约 ±0.05 漂移,跨日复跑可能不一致。
- 公司 LLM 网关的 toolUse bug 触发条件:SKILL.md 多长一定会撞?不知道阈值,但 ~20KB + 多 tool_use 已经触发了 2 次。
- delta 显著性阈值:本次 5 题样本太小,没法做统计显著性检验;建议生产化时 ≥20 题。
后续行动
- 把 Step 5.0 改成显式 quoted block 模板,重跑评测看 safety_confirm_shown 是否真触发。
- 把 SKILL.md 按 references/ 拆分(主流程 < 500 行),重跑看 toolUse 网关错误是否消失。
- 删掉 16 条非区分性断言,保留 3 条核心断言(called_start / branch_correct / safety_confirm),重跑看 delta 是否更显著。
- 把
method_a_runner.py+method_b_runner.py抽成通用模板,放进~/.claude/skills/skill-eval/,下次评其它 skill 直接复用。 - 加一组”对抗性 prompt”(如”发布 pre –branch master,但其实 yml 写的是 develop”)测试 skill 在配置漂移时的鲁棒性。