当您为 Codex 这样的智能体迭代技能时,很难判断自己究竟是在改进技能,还是仅仅改变了它的行为。一个版本感觉更快,另一个版本似乎更可靠,但随后却出现了回归问题:技能没有触发、跳过了必要步骤,或留下了多余的文件。
技能本质上是一组面向 LLM 的有组织的提示和指令。要持续改进技能,最可靠的方法就是像评估用于 LLM 应用的其他提示一样评估它。
评测 (Evals,是 evaluations 的缩写)用于检查模型的输出及其生成输出的步骤是否符合您的预期。评测让您不必再问“这是不是感觉更好了?”或只凭直觉判断,而是可以提出具体的问题,例如:
- 智能体是否调用了技能?
- 它是否运行了预期的命令?
- 它生成的输出是否符合您关注的规范?
具体来说,一次评测的流程是:提示 → 记录一次运行(执行轨迹 + 产物)→ 一小组检查 → 可持续跟踪比较的分数。
在实践中,智能体技能的评测很像轻量级端到端测试:运行智能体,记录发生的情况,再根据一小组规则对结果评分。
本文介绍如何使用 Codex 按清晰的流程完成这些工作:先定义成功标准,再加入确定性检查和基于评分量表的评分,让改进和回归问题都清晰可见。
1. 编写技能前,先定义成功标准
在编写技能之前,先用可实际衡量的标准写清楚什么叫“成功”。一个实用的思路是将检查分为以下几类:
- 结果目标: 任务是否完成?应用能否运行?
- 过程目标: Codex 是否调用了技能,并使用了您预期的工具、遵循了您预期的步骤?
- 风格目标: 输出是否符合您要求的规范?
- 效率目标: 是否在没有无谓折腾的情况下完成任务,例如没有运行不必要的命令或消耗过多 Token?
检查清单应保持精简,专注于必须通过的检查。目标不是一开始就把所有偏好都写成规则,而是明确您最关注的行为。
例如,本文评估的是一项搭建演示应用的技能。有些检查很具体:它是否运行了 npm install?是否创建了 package.json?本文还会结合结构化的风格评分量表,评估规范遵循情况和布局。
这样的组合是有意设计的。您需要的是快速、有针对性的信号,以便及早发现具体的回归问题,而不是到最后只得到一个通过或失败的结论。
2. 创建技能
Codex 技能是一个包含 SKILL.md 文件的目录。该文件以 YAML 前置元数据(name、description)开头,随后是定义技能行为的 Markdown 指令;目录中还可以包含可选的资源和脚本。名称和描述比表面上看起来更重要。Codex 主要依据它们来决定 是否 调用技能,以及 何时 将 SKILL.md 的其余内容注入智能体的上下文。如果名称和描述含糊不清或涵盖过多内容,技能就无法可靠地触发。
最快的入门方式是使用 Codex 内置的技能创建器(它本身也是一项技能)。它会逐步引导您完成创建过程:
$skill-creator
创建器会询问技能的用途、应该何时触发,以及是仅包含指令还是需要脚本支持(默认建议仅包含指令)。要进一步了解如何创建技能,请查看文档。
技能示例
本文特意采用一个极简示例:一项以可预测、可重复的方式搭建小型 React 演示应用的技能。
这项技能将:
- 使用 Vite 的 React + TypeScript 模板搭建项目骨架
- 通过官方 Vite 插件方案配置 Tailwind CSS
- 强制采用精简、一致的文件结构
- 定义明确的“完成标准”,以便直接判断是否成功
下面是一份简短的草稿,您可以将其粘贴到以下任一位置:
.codex/skills/setup-demo-app/SKILL.md(代码仓库级),或~/.codex/skills/setup-demo-app/SKILL.md(用户级)。
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## When to use this
Use when you need a fresh demo app for quick UI experiments or reproductions.
## What to build
Create a Vite React TypeScript app and configure Tailwind. Keep it minimal.
Project structure after setup:
- src/
- main.tsx (entry)
- App.tsx (root UI)
- components/
- Header.tsx
- Card.tsx
- index.css (Tailwind import)
- index.html
- package.json
Style requirements:
- TypeScript components
- Functional components only
- Tailwind classes for styling (no CSS modules)
- No extra UI libraries
## Steps
1. Scaffold with Vite using the React TS template:
npm create vite@latest demo-app -- --template react-ts
2. Install dependencies:
cd demo-app
npm install
3. Install and configure Tailwind using the Vite plugin.
- npm install tailwindcss @tailwindcss/vite
- Add the tailwind plugin to vite.config.ts
- In src/index.css, replace contents with:
@import "tailwindcss";
4. Implement the minimal UI:
- Header: app title and short subtitle
- Card: reusable card container
- App: render Header + 2 Cards with placeholder text
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx exist
这项示例技能特意规定了具体做法。没有明确的约束,就没有可以具体评估的内容。
3. 手动触发技能,发现隐含假设
技能调用在很大程度上取决于 SKILL.md 中的 名称 和 描述 ,因此首先要检查的是:setup-demo-app 技能能否在预期情况下触发。
在早期阶段,请在真实的代码仓库或临时目录中,通过 /skills 斜杠命令或使用 $ 前缀引用技能,显式启用它,并观察哪里会出问题。这样可以发现各种偏差:技能完全没有触发、过于轻易地触发,或虽已运行却偏离了预期步骤。
在这个阶段,重点不是提升速度或完善细节,而是找出技能隐含的假设,例如:
-
触发条件假设:“快速搭建一个 React 演示应用”这样的提示 应该 调用
setup-demo-app,却没有触发;或者“添加 Tailwind 样式”这类更宽泛的提示意外触发了它。 -
环境假设:技能假定自己在空目录中运行,或假定
npm可用且应优先于其他包管理器使用。 -
执行假设:智能体因为假定依赖项已安装而跳过
npm install,或在 Vite 项目尚未创建时就配置 Tailwind。
准备好让这些运行可重复执行后,请改用 codex exec。它专为自动化和 CI 设计:将进度以流式方式输出到 stderr,仅将最终结果写入 stdout,便于通过脚本执行、记录和检查运行情况。
默认情况下,codex exec 在受限沙盒中运行。如果您的任务需要写入文件,请使用 --full-auto 运行。一般原则是只使用完成任务所需的最小权限,尤其是在自动化场景中。
一次基本的手动运行可能如下所示:
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'
第一轮动手尝试的重点更多在于发现边界情况,而非验证正确性。您在此阶段做的每一项手动修复,例如补上缺失的 npm install、修正 Tailwind 配置,或让触发条件的描述更精确,都可以成为后续的评测用例,帮助您在大规模评估之前固定预期行为。
4. 使用小而有针对性的提示集,及早发现回归问题
您不需要大型基准测试,也能从评测中获益。对于单项技能,一组包含 10–20 条提示的小型提示集就足以及早发现回归问题并确认改进效果。
先从一个小型 CSV 文件开始,随着开发或使用过程中出现实际失败案例,再逐步扩充。每一行都应代表一个您关注的场景,说明 setup-demo-app 技能在该场景中 应触发 还是 不应触发 ,以及触发后怎样才算成功。
例如,最初的 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)
这条提示直接指明技能名称。它用于确保 Codex 能在收到要求时调用setup-demo-app,并且技能名称、描述或指令的变更不会影响直接使用。 -
隐式调用(
test-02)
这条提示描述的 正是 技能所针对的场景:搭建一个极简的 React + Tailwind 演示应用,但没有提及技能名称。它用于测试SKILL.md中的名称和描述是否足够明确,让 Codex 能够自行选中该技能。 -
结合上下文的调用(
test-03)
这条提示添加了领域背景(Responses API),但仍需要相同的基础搭建步骤。它用于检查技能能否在贴近实际、带有少量干扰信息的提示中触发,以及生成的应用是否仍符合预期的结构和规范。 -
负对照(
test-04)
这条提示 不应 调用setup-demo-app。它是一种常见的相近请求(“为现有应用添加 Tailwind”),可能意外匹配技能描述(“React + Tailwind 演示应用”)。至少加入一个should_trigger=false用例,有助于发现 误报:用户本想对现有项目做增量修改,Codex 却过于轻易地选中了该技能,搭建了一个新项目。
这样的组合是有意设计的。有些评测应确认技能在显式调用时行为正确;另一些则应检查在用户完全没有提及技能的实际提示中,它是否仍能触发。
当您发现遗漏、未能触发技能的提示,或输出偏离预期的情况时,请将其作为新行添加。久而久之,这个小型 CSV 文件就会成为一份持续更新的记录,记下 setup-demo-app 技能必须始终正确处理的场景。
久而久之,这个小型数据集就会成为一份持续更新的记录,记下技能必须始终做对的事情。
5. 从轻量级确定性评分器入手
这是评估步骤的核心:使用 codex exec --json,让您的评测执行框架能够根据 实际发生的行为评分,而不只是判断最终输出看起来是否正确。
启用 --json 后,stdout 会输出由结构化事件组成的 JSONL 流。这样就可以轻松编写直接针对您所关注行为的确定性检查,例如:
- 它是否运行了
npm install? - 它是否创建了
package.json? - 它是否按预期顺序调用了预期的命令?
这些检查刻意保持轻量。在加入基于模型的评分之前,它们能快速提供易于解释的反馈。
一个极简的 Node.js 运行器
一个“够用”的做法如下:
- 针对每条提示,运行
codex exec --json --full-auto "<prompt>" - 将 JSONL 执行轨迹保存到磁盘
- 解析执行轨迹,并对其中的事件运行确定性检查
// evals/run-setup-demo-app-evals.mjs
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
function runCodex(prompt, outJsonlPath) {
const res = spawnSync(
"codex",
[
"exec",
"--json", // REQUIRED: emit structured events
"--full-auto", // Allow file system changes
prompt,
],
{ encoding: "utf8" }
);
mkdirSync(path.dirname(outJsonlPath), { recursive: true });
// stdout is JSONL when --json is enabled
writeFileSync(outJsonlPath, res.stdout, "utf8");
return { exitCode: res.status ?? 1, stderr: res.stderr };
}
function parseJsonl(jsonlText) {
return jsonlText
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
// deterministic check: did the agent run `npm install`?
function checkRanNpmInstall(events) {
return events.some(
(e) =>
(e.type === "item.started" || e.type === "item.completed") &&
e.item?.type === "command_execution" &&
typeof e.item?.command === "string" &&
e.item.command.includes("npm install")
);
}
// deterministic check: did `package.json` get created?
function checkPackageJsonExists(projectDir) {
return existsSync(path.join(projectDir, "package.json"));
}
// Example single-case run
const projectDir = process.cwd();
const tracePath = path.join(projectDir, "evals", "artifacts", "test-01.jsonl");
const prompt =
"Create a demo app named demo-app using the $setup-demo-app skill";
runCodex(prompt, tracePath);
const events = parseJsonl(readFileSync(tracePath, "utf8"));
console.log({
ranNpmInstall: checkRanNpmInstall(events),
hasPackageJson: checkPackageJsonExists(path.join(projectDir, "demo-app")),
});
这种做法的价值在于,整个过程 具有确定性,而且可以调试。
如果某项检查失败,您可以打开 JSONL 文件,查看具体发生了什么。每次命令执行都会按顺序记录为一个 item.* 事件。这样就很容易解释和修复回归问题,而这正是您在这个阶段需要的。
6. 使用 Codex 和基于评分标准的评估进行定性检查
确定性检查能回答 “它是否完成了基本操作?” ,但无法回答 “它是否按您期望的方式完成了操作?”
对于 setup-demo-app 这样的技能,许多要求都属于定性要求,例如组件结构、样式规范,或 Tailwind 是否采用了预期的配置。仅靠检查文件是否存在或统计命令数量,很难判断是否满足了这些要求。
一个务实的解决方案是在评测流水线中加入第二个由模型辅助的步骤:
- 运行用于搭建项目的技能(这会将代码写入磁盘)
- 对生成的代码仓库运行 只读风格检查
- 要求返回 结构化响应 ,以便执行框架能按一致的方式评分
Codex 通过 --output-schema 直接支持这一功能,将最终响应限制为符合您定义的 JSON Schema 的格式。
一个简单的评分标准模式
首先定义一个简单的模式,涵盖您关心的检查项。例如,创建 evals/style-rubric.schema.json:
{
"type": "object",
"properties": {
"overall_pass": { "type": "boolean" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"checks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"pass": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "pass", "notes"],
"additionalProperties": false
}
}
},
"required": ["overall_pass", "score", "checks"],
"additionalProperties": false
}
这个模式提供了稳定的字段(overall_pass、score 以及各项检查的结果),方便您进行组合、比较差异并持续跟踪。
风格检查提示
接下来,再运行一次 codex exec,让它 仅检查代码仓库 ,并输出符合评分标准的 JSON 响应:
codex exec \
"Evaluate the demo-app repository against these requirements:
- Vite + React + TypeScript project exists
- Tailwind is configured via @tailwindcss/vite and CSS imports tailwindcss
- src/components contains Header.tsx and Card.tsx
- Components are functional and styled with Tailwind utility classes (no CSS modules)
Return a rubric result as JSON with check ids: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.json
这时,--output-schema 就派上用场了。您得到的是结构可预期的 JSON 对象,而不是难以解析或比较的自由格式文本,评测执行框架可以据此对多次运行进行评分。
如果您之后将这套评测迁入 CI,Codex GitHub Action 明确支持通过 codex-args 传入 --output-schema,因此您可以在自动化工作流中强制使用相同的结构化输出。
7. 随着技能逐渐成熟,扩展评测
建立起核心循环后,您就可以围绕对技能最重要的方面扩展评测。先从小规模开始,再逐步加入更深入的检查,但只在这些检查确实能增强信心时添加。
例如:
-
命令数量和无效的反复操作: 统计 JSONL 执行轨迹中的
command_execution条目,以发现智能体开始陷入循环或重复运行命令的回归问题。您还可以从turn.completed事件中获取 Token 用量。 -
Token 预算: 跟踪
usage.input_tokens和usage.output_tokens,以发现提示意外变得冗长的情况,并比较不同版本的效率。 -
构建检查: 技能执行完成后,运行
npm run build。这能提供更有力的端到端验证,并发现导入错误或工具配置不正确的问题。 -
运行时冒烟检查: 运行
npm run dev启动开发服务器,然后使用curl访问它;如果您已有轻量级 Playwright 检查,也可以运行该检查。请按需使用这类检查:它能增强信心,但也需要时间。 -
代码仓库整洁性: 确保运行过程中没有生成不需要的文件,并且
git status --porcelain的输出为空(或符合明确列出的允许清单)。 -
沙盒和权限回归问题: 验证技能在不将权限提升到超出您预期范围的情况下,仍能正常工作。一旦实现自动化,默认采用最小权限就尤为重要。
做法始终一致:先使用能解释行为的快速检查,再仅在有助于降低风险时加入更慢、开销更大的检查。
8. 要点总结
这个简单的 setup-demo-app 示例展示了如何从“感觉更好了”走向“有证据证明”:运行智能体,记录发生的事情,再用少量检查进行评分。建立起这个循环后,每次调整的效果都更容易验证,每个回归问题也都更容易看清。以下是关键要点:
- 衡量真正重要的方面。 好的评测能清楚地揭示回归问题,并让失败原因易于解释。
- 从可验证的完成标准开始。 使用
$skill-creator创建初稿,然后逐步细化指令,直到成功标准清晰无歧义。 - 以实际行为为评测依据。 使用
codex exec --json采集 JSONL,并针对command_execution事件编写确定性检查。 - 在规则不足以判断时使用 Codex。 通过
--output-schema加入一轮基于评分标准的结构化评测,可靠地评估风格和规范。 - 根据实际失败情况扩展测试覆盖。 每次手动修复都是一个信号。将其转化为测试,让技能今后持续正确处理这类情况。