核心问题:当 AI 智能体技能每次改动后,我如何确定它真的变好了,而不是换了种“姿势”继续出错?
一句话答案:先把“好”写成可测量的检查清单,再把每一次运行录成数据,用轻量脚本打分,让改进与回滚都有数字作证。
本文欲回答的核心问题
-
什么叫“技能 Eval”?为什么它能让迭代从玄学变工程? -
怎样在 Codex 上把一次“感觉更快”变成可复现的实验? -
如何只用 20 行 Node.js 脚本,就能在本地跑通“提示 → 运行 → 打分”闭环? -
当规则检查不够用,怎样让模型自己当“评委”,还不把结果写成散文? -
这套玩法除了“搭 Demo”,还能迁移到哪些真实场景?
一、把“感觉更好”翻译成可量化的检查项
| 维度 | 示例检查项 | 典型失败案例 |
|---|---|---|
| 结果 outcome | npm run dev 能启动 |
端口占用没提示,进程直接退出 |
| 过程 process | 必须执行 npm install |
代理缓存导致跳过了安装 |
| 风格 style | 组件用 Tailwind 而非 CSS 模块 | 手滑写了 import './App.css' |
| 效率 efficiency | 命令条数 ≤ 8 | 陷入循环 npm install 三次 |
作者反思:早期我写过一条“技能说明”——“让项目漂亮一点”。结果一周后回来看,AI 把首页涂成了荧光绿。教训是:不把“漂亮”拆成“用 Tailwind、统一圆角、主色 indigo-600”,就永远不知道谁在瞎猜。
二、三步定义“完成”再动手写技能
-
先写 SKILL.md里的name+description——它们决定技能会不会被触发。 -
把“完成”写成 Definition of Done,直接放进 SKILL.md底部。 -
把可能踩坑的假设列成表格,留作后续 Eval 的素材。
示例:
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx exist
三、手动触发一次,把“隐藏假设”逼到桌面
常见隐藏假设与快速验证办法:
| 假设 | 快速验证命令 | 失败现象 |
|---|---|---|
| 目录为空 | ls -la |
旧文件残留导致 Vite 冲突 |
| npm 可用 | which npm |
容器里只有 yarn |
| 技能触发词足够 | codex exec --full-auto '搭个 React 小 demo' |
技能未被调用 |
作者反思:我第一次写技能时,描述里用了“scaffold”这个词。结果中文提示“搭个脚手架”时,Codex 没认出。把描述改成“Scaffold / 搭建”双语后,触发率从 60% 升到 95%。别嫌描述长——召回率比字数更贵。
四、用 10~20 条提示就把回归拦截在本地
文件 evals/setup-demo-app.prompts.csv 示例:
id,should_trigger,prompt
test-01,true,Create a demo app named `devday-demo` using the $setup-demo-app skill
test-02,true,Set up a minimal React demo app with Tailwind for quick UI experiments
test-03,true,Create a small demo app to showcase the Responses API
test-04,false,Add Tailwind styling to my existing React app
每条只测一件事:
-
test-01测“显式调用” -
test-02测“语义召回” -
test-03测“带噪声语境” -
test-04测“负样本防误触”
小技巧:负样本必须占 ≥25%,否则假阳性会像漏水的船,越晚发现越难修。
五、Node.js 15 行核心代码:从 JSONL 里捞出“到底跑没跑 npm install”
// evals/run.mjs (片段)
function checkRanNpmInstall(events) {
return events.some(
e => (e.type === "item.completed") &&
e.item?.type === "command_execution" &&
e.item.command.includes("npm install")
);
}
function checkPackageJsonExists(dir) {
return existsSync(join(dir, "package.json"));
}
运行:
codex exec --json --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.' \
> trace.jsonl
node evals/run.mjs
终端直接给出:
{ ranNpmInstall: true, hasPackageJson: true }
数字 1 就是“过了”,0 就是“回滚”。再也不用打开浏览器盯控制台。
六、当规则检查不够用:让模型按 JSON Schema 给自己打分
-
定义 rubric 模式——只含 overall_pass、score、checks[]三个字段。 -
第二次调用 Codex,加 --output-schema约束返回:
codex exec \
"Evaluate the demo-app repo against the rubric: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o style.json
-
得到稳定 JSON 后,CI 就能 jq '.overall_pass'做门控。
作者反思:我第一次用自由文本让模型写评审,结果它洋洋洒洒 300 字,CI 根本抓不到重点。强制 JSON 后,评审时间从 5 分钟读散文变成 0.2 秒解析字段,人类也轻松。
七、把 Eval 做成生长型文档:每次踩坑就加一条检查
真实案例迭代记录:
| 日期 | 失败现象 | 新增检查项 | 合并后得分 |
|---|---|---|---|
| 01-12 | 生成多余 utils/ 目录 |
git status --porcelain 必须为空白 |
100 → 100 |
| 01-15 | Token 用量暴涨 40% | 统计 turn.completed.usage |
100 → 95 |
| 01-18 | build 失败 | 新增 npm run build 必须 0 退出码 |
95 → 90(修复后回到 100) |
规则:
-
只有“导致用户肉眼可见的坏体验”才加检查。 -
每个 PR 同步更新 Eval,主分支红线“overall_pass == true”才能合并。 -
检查项一旦加入,除非产品需求变更,否则永不降标。
八、迁移场景:Eval 模板 3 个现成的“复制粘贴”用法
-
SQL 助手技能
检查:EXPLAIN 结果是否出现 “Full Table Scan”、查询耗时是否 <200 ms。 -
文档翻译技能
检查:输出 JSON 是否保持与原文字段一一对应、术语表命中率 ≥90%。 -
运维巡检技能
检查:脚本是否只读kubectl get、未执行delete;输出是否包含必填标签team=xxx。
共同点:把“人类最怕机器乱来的地方”写成断言,让 Eval 成为安全网,而不是枷锁。
实用摘要 / 操作清单
-
先写“完成定义”再写技能,防止边做边改。 -
用 codex exec --json记录每一次运行,保留 JSONL。 -
从 JSONL 里捞 command_execution事件,做确定性断言。 -
负样本必须占 1/4,避免假阳性。 -
风格评审用 --output-schema强制 JSON,方便 CI 自动门禁。 -
每出现一次真实故障,就把现象转成新检查项,分数自然上涨。
一页速览(One-page Summary)
| 阶段 | 关键命令 | 输出物 | 通过标准 |
|---|---|---|---|
| 定义 | 手写 SKILL.md + Definition of Done |
文本 | 人工评审 |
| 手动触发 | codex exec --full-auto |
终端日志 | 技能被召回 |
| 小规模回归 | node run.mjs + CSV |
{pass:true} |
100% |
| 风格评审 | codex exec --output-schema |
style.json |
overall_pass == true |
| CI 门禁 | GitHub Action | 报告 | 主分支红线 |
常见问答(FAQ)
-
Q:我只有 5 条提示,会不会太少?
A:只要覆盖“显式调用、语义召回、负样本”三类,5 条也能拦 80% 回归;后续随踩坑追加即可。 -
Q:JSONL 文件很大,怎样快速定位失败点?
A:用jq 'select(.type=="item.failed")'直接过滤异常事件,再按.timestamp排序。 -
Q:必须会 Node.js 吗?
A:任何能读文本、跑脚本的语言都行;核心逻辑只是“找命令 + 看文件”。 -
Q:评分算法会影响 Skill 触发吗?
A:Eval 是事后评分,不改变运行时行为;想调触发率请改description和name。 -
Q:能否把 Eval 放进 GitHub Marketplace?
A:已有官方 Action 支持--output-schema,直接调用即可。 -
Q:如果模型升级后风格变了怎么办?
A:把“新风格”更新为 rubric 检查项,重新跑基准,确保分数仍 100% 再上线。 -
Q:负样本老是误触发,怎么破?
A:把负样本提示贴进SKILL.md的“何时不要用”列表,帮助模型对比边界。
