核心问题:当 AI 智能体技能每次改动后,我如何确定它真的变好了,而不是换了种“姿势”继续出错?
一句话答案:先把“好”写成可测量的检查清单,再把每一次运行录成数据,用轻量脚本打分,让改进与回滚都有数字作证。


本文欲回答的核心问题

  1. 什么叫“技能 Eval”?为什么它能让迭代从玄学变工程?
  2. 怎样在 Codex 上把一次“感觉更快”变成可复现的实验?
  3. 如何只用 20 行 Node.js 脚本,就能在本地跑通“提示 → 运行 → 打分”闭环?
  4. 当规则检查不够用,怎样让模型自己当“评委”,还不把结果写成散文?
  5. 这套玩法除了“搭 Demo”,还能迁移到哪些真实场景?

一、把“感觉更好”翻译成可量化的检查项

维度 示例检查项 典型失败案例
结果 outcome npm run dev 能启动 端口占用没提示,进程直接退出
过程 process 必须执行 npm install 代理缓存导致跳过了安装
风格 style 组件用 Tailwind 而非 CSS 模块 手滑写了 import './App.css'
效率 efficiency 命令条数 ≤ 8 陷入循环 npm install 三次

作者反思:早期我写过一条“技能说明”——“让项目漂亮一点”。结果一周后回来看,AI 把首页涂成了荧光绿。教训是:不把“漂亮”拆成“用 Tailwind、统一圆角、主色 indigo-600”,就永远不知道谁在瞎猜。


二、三步定义“完成”再动手写技能

  1. 先写 SKILL.md 里的 name + description——它们决定技能会不会被触发。
  2. 把“完成”写成 Definition of Done,直接放进 SKILL.md 底部。
  3. 把可能踩坑的假设列成表格,留作后续 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 给自己打分

  1. 定义 rubric 模式——只含 overall_passscorechecks[] 三个字段。
  2. 第二次调用 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
  1. 得到稳定 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 个现成的“复制粘贴”用法

  1. SQL 助手技能
    检查:EXPLAIN 结果是否出现 “Full Table Scan”、查询耗时是否 <200 ms。

  2. 文档翻译技能
    检查:输出 JSON 是否保持与原文字段一一对应、术语表命中率 ≥90%。

  3. 运维巡检技能
    检查:脚本是否只读 kubectl get、未执行 delete;输出是否包含必填标签 team=xxx

共同点:把“人类最怕机器乱来的地方”写成断言,让 Eval 成为安全网,而不是枷锁。


实用摘要 / 操作清单

  1. 先写“完成定义”再写技能,防止边做边改。
  2. codex exec --json 记录每一次运行,保留 JSONL。
  3. 从 JSONL 里捞 command_execution 事件,做确定性断言。
  4. 负样本必须占 1/4,避免假阳性。
  5. 风格评审用 --output-schema 强制 JSON,方便 CI 自动门禁。
  6. 每出现一次真实故障,就把现象转成新检查项,分数自然上涨。

一页速览(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)

  1. Q:我只有 5 条提示,会不会太少?
    A:只要覆盖“显式调用、语义召回、负样本”三类,5 条也能拦 80% 回归;后续随踩坑追加即可。

  2. Q:JSONL 文件很大,怎样快速定位失败点?
    A:用 jq 'select(.type=="item.failed")' 直接过滤异常事件,再按 .timestamp 排序。

  3. Q:必须会 Node.js 吗?
    A:任何能读文本、跑脚本的语言都行;核心逻辑只是“找命令 + 看文件”。

  4. Q:评分算法会影响 Skill 触发吗?
    A:Eval 是事后评分,不改变运行时行为;想调触发率请改 descriptionname

  5. Q:能否把 Eval 放进 GitHub Marketplace?
    A:已有官方 Action 支持 --output-schema,直接调用即可。

  6. Q:如果模型升级后风格变了怎么办?
    A:把“新风格”更新为 rubric 检查项,重新跑基准,确保分数仍 100% 再上线。

  7. Q:负样本老是误触发,怎么破?
    A:把负样本提示贴进 SKILL.md 的“何时不要用”列表,帮助模型对比边界。