主导航
2026年1月22日 Codex

系统化测试 Agent 技能与评估(Evals)

一份实用指南,教你如何将 Agent 技能转化为可测试、可评分并能持续改进的对象。

作者: Dominik Kundel, Gabriel Chua

Testing Agent Skills Systematically with Evals

当你为一个像 Codex 这样的 Agent 迭代某项技能时,很难判断自己是在真正改进它,还是仅仅在改变其行为。一个版本感觉更快,另一个似乎更可靠,然后回归问题悄然而至:技能无法触发、跳过了必要的步骤,或者留下了多余的文件。

从本质上讲,一项技能是 LLM 的一组有组织的提示词和指令。随着时间的推移,改进技能最可靠的方法是像评估任何其他 LLM 应用的提示词一样去评估它。

评估(Evals)旨在检查模型的输出及其产生过程中的步骤是否符合你的意图。与其问“这感觉好点了吗?”(或者仅仅依赖主观感觉),评估让你能够提出具体的问题,例如:

  • Agent 是否调用了该技能?
  • 它是否执行了预期的命令?
  • 它产生的输出是否符合你所关心的约定?

具体来说,评估的过程是:一个提示词 → 一次捕获的运行记录(追踪记录 + 工件) → 一组简单的检查 → 一个可随时间对比的评分。

在实践中,Agent 技能的评估看起来非常像轻量级的端到端测试:你运行 Agent,记录发生的事情,并根据一套简单的规则对结果进行评分。

本文介绍了一种清晰的 Codex 评估模式,从定义成功标准开始,然后添加确定性检查和基于规则的评分,从而使改进(和回归)一目了然。

1. 在编写技能前定义成功标准

在编写技能本身之前,先写下“成功”意味着什么,并将其转化为可以实际衡量的术语。一个有用的思考方式是将你的检查分为几类:

  • 结果目标:任务完成了吗?应用能否运行?
  • 过程目标:Codex 是否调用了该技能并遵循了你预期的工具和步骤?
  • 风格目标:输出是否遵循了你要求的约定?
  • 效率目标:它是否在没有乱操作的情况下达成目标(例如,是否有不必要的命令或过多的 Token 使用)?

保持这份列表简短且专注于必须通过的检查。目标不是预先编码所有的偏好,而是捕捉你最关心的那些行为。

例如,本文的指南评估了一个设置演示应用的技能。有些检查是具体的:它运行了 npm install 吗?它创建了 package.json 吗?该指南将这些检查与结构化的风格规则结合起来,以评估约定和布局。

这种组合是有意为之。你想要的是快速、有针对性的信号,以便尽早发现特定的回归,而不是在最后给出一个简单的通过/失败结论。

2. 创建技能

Codex 技能是一个包含 SKILL.md 文件的目录,其中包含 YAML 元数据(name, description),随后是定义技能行为的 Markdown 指令以及可选的资源和脚本。名称和描述比看起来更重要。它们是 Codex 用来决定是否调用该技能,以及何时SKILL.md 的其余部分注入 Agent 上下文的主要信号。如果这些信息模糊或定义过多,技能将无法可靠触发。

最快的入门方式是使用 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 且优先于其他包管理器。

  • 执行假设:Agent 跳过了 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 的案例有助于捕捉假阳性(False Positives),即 Codex 在用户想要对现有项目进行增量更改时,却过于积极地选择了该技能并搭建了新项目。

这种组合是有意为之。一些评估旨在确认技能在显式调用时表现正确;另一些则旨在检查它在用户根本不提及技能的实际提示词中是否能激活。

当你发现遗漏、提示词未能触发技能或输出偏离预期的情况时,将它们作为新行添加进去。随着时间的推移,这个小的 CSV 文件将成为 setup-demo-app 技能必须持续做对的场景的实时记录。

随着时间的推移,这个小型数据集将成为技能必须持续做对内容的实时记录。

5. 使用轻量级确定性分级器入门

这是评估步骤的核心:使用 codex exec --json,以便你的评估工具能够根据实际发生的情况进行评分,而不仅仅是查看最终输出是否看起来正确。

当你启用 --json 时,stdout 将成为结构化事件的 JSONL 流。这使得编写直接绑定到你关心的行为的确定性检查变得简单,例如:

  • 它运行了 npm install 吗?
  • 它创建了 package.json 吗?
  • 它是否以预期的顺序调用了预期的命令?

这些检查是特意设计的轻量级。它们在你添加任何基于模型的评分之前,为你提供快速、可解释的信号。

最小化的 Node.js 运行器

一种“足够好”的方法如下:

  1. 对于每个提示词,运行 codex exec --json --full-auto "<prompt>"
  2. 将 JSONL 追踪记录保存到磁盘
  3. 解析追踪记录并在事件上运行确定性检查
// 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 是否遵循了预期的配置。这些很难仅通过基本的文件存在检查或命令计数来捕捉。

一个务实的解决方案是在你的评估流水线中添加第二个模型辅助步骤:

  1. 运行设置技能(这会将代码写入磁盘)
  2. 对生成的仓库运行只读样式检查
  3. 要求一个你的工具可以一致评分的结构化响应

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 项,以捕获 Agent 开始循环或重复运行命令的回归。Token 使用情况也可在 turn.completed 事件中获取。

  • Token 预算:跟踪 usage.input_tokensusage.output_tokens,以发现意外的提示词臃肿并比较不同版本间的效率。

  • 构建检查:在技能完成后运行 npm run build。这充当了更强有力的端到端信号,可以捕捉损坏的导入或错误配置的工具。

  • 运行时冒烟检查:启动 npm run dev 并使用 curl 访问开发服务器,或者如果你已有 Playwright 检查,则运行轻量级的 Playwright 测试。有选择地使用它,它增加了信心但消耗时间。

  • 仓库整洁度:确保运行后没有生成不需要的文件,并且 git status --porcelain 为空(或匹配明确的允许列表)。

  • 沙箱和权限回归:验证技能在不超出预定权限的情况下仍能工作。一旦自动化,最低权限默认值最为重要。

模式是一致的:从解释行为的快速检查开始,只有在降低风险时才添加较慢、较重的检查。

8. 关键点总结

这个小的 setup-demo-app 示例展示了从“感觉好多了”到“证明”的转变:运行 Agent,记录发生的事情,并用一组简单的检查进行分级。一旦该循环建立,每次调整都更容易确认,每次回归都变得清晰。以下是关键点总结:

  • 衡量重要的事情。良好的评估使回归变得清晰,使失败变得可解释。
  • 从可检查的完成定义开始。使用 $skill-creator 进行引导,然后加强指令,直到成功变得明确无误。
  • 将评估建立在行为基础上。使用 codex exec --json 捕获 JSONL,并针对 command_execution 事件编写确定性检查。
  • 在规则不足的地方使用 Codex。添加使用 --output-schema 的结构化、基于规则的步骤,以可靠地对风格和约定进行评分。
  • 让实际失败推动测试覆盖。每一个手动修复都是一个信号。将其转化为测试,以便技能不断获得提升。
© . This website operates independently and is not affiliated with or endorsed by OpenAI, Inc. All brand names, logos, and trademarks are the property of their respective owners.