Phase 6 · 评测安全与生产

使用 Evals 系统地测试智能体技能 | OpenAI 开发者

OpenAI·2026/7/21·9 阅读

使用 Evals 系统地测试智能体技能 | OpenAI 开发者

来源: https://developers.openai.com/blog/eval-skills 抓取时间: 2026-07-21 16:21:55


当您为像 Codex 这样的智能体迭代技能时,很难判断您是真的在改进它,还是只是在改变它的行为。一个版本感觉更快,另一个似乎更可靠,然后一个回归悄悄出现:技能没有触发,它跳过了一个必要的步骤,或者留下了额外的文件。

从本质上讲,技能是 LLM 的有组织的提示和指令集合。随着时间的推移改进技能的最可靠方法是使用与评估LLM 应用程序的任何其他提示相同的方式来评估它。

Evalsevaluations 的缩写)检查模型的输出以及生成输出所采取的步骤是否与您的意图相符。Evals 让您可以提出具体的问题,而不是问"这感觉更好吗?"(或依靠感觉):

  • 智能体是否调用了该技能?
  • 它是否运行了预期的命令?
  • 它产生的输出是否遵循您关心的约定?

具体来说,一个 eval 是:一个提示 → 一个捕获的运行(追踪 + 工件)→ 一小组检查 → 一个您可以随时间比较的分数。

实际上,智能体技能的 evals 看起来很像轻量级的端到端测试:您运行智能体,记录发生了什么,并根据一小组规则对结果进行评分。

本文介绍了使用 Codex 执行此操作的清晰模式,从定义成功开始,然后添加确定性检查和基于标准的评分,以便改进(和回归)变得清晰。

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

在编写技能本身之前,写下您可以实际衡量的"成功"意味着什么。一个有用的思考方式是将您的检查分为几类:

  • 结果目标: 任务是否完成?应用程序是否运行?
  • 流程目标: Codex 是否调用了该技能并遵循了您想要的工具和步骤?
  • 风格目标: 输出是否遵循了您要求的约定?
  • 效率目标: 它是否在没有反复尝试(例如,不必要的命令或过多的令牌使用)的情况下完成?

保持这个列表小而专注于必须通过的检查。目标不是预先编码每个偏好,而是捕获您最关心的行为。

例如,在本文中,该指南评估了一个设置演示应用程序的技能。一些检查是具体的。它是否运行了 npm install?是否创建了 package.json?该指南将这些与结构化的风格评分标准相结合,以评估约定和布局。

这种混合是有意的。您需要快速、有针对性的信号,及早发现特定的回归,而不是在最后给出单一的通过/失败结论。

2. 创建技能

Codex 技能是一个包含 SKILL.md 文件的目录,该文件包括 YAML 前导(namedescription),然后是定义技能行为的 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 设置或收紧触发描述,都是未来 eval 的候选,因此您可以在大规模评估之前锁定预期行为。

4. 使用小的、有针对性的提示集及早发现回归

您不需要大型基准测试就能从 evals 中获得价值。对于单个技能,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 过于急切地选择技能并在用户想要对现有应用程序进行增量更改时搭建新项目。

这种混合是有意的。一些 evals 应该确认技能在显式调用时表现正确;其他 evals 应该检查它是否在用户从未提及技能的真实提示中激活。

当您发现遗漏、未能触发技能的提示或输出偏离您预期的情况时,将它们添加为新行。随着时间的推移,这个小 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 是否遵循预期的配置。仅靠基本的文件存在检查或命令计数很难捕捉这些。

一个务实的解决方案是在您的 eval 管道中添加第二个模型辅助步骤:

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

Codex 通过 --output-schema 直接支持这一点,它将最终响应约束到您定义的 JSON Schema。

一个小的评分标准 schema

首先定义一个小的 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
}

这个 schema 为您提供了稳定的字段(overall_passscore、每项检查结果),您可以在时间上组合、比较和跟踪。

风格检查提示

接下来,运行第二个 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 对象,您的 eval 工具可以在多次运行中进行评分,而不是难以解析或比较的自由格式文本。

如果您稍后将此 eval 套件移到 CI 中,Codex GitHub Action 明确支持通过 codex-args 传递 --output-schema,因此您可以在自动化工作流程中强制执行相同的结构化输出。

7. 随着技能成熟扩展您的 evals

一旦核心循环就位,您可以在对您的技能最重要的方向上扩展您的 evals。从小处开始,然后只在真正增加信心的地方分层进行更深入的检查。

一些示例包括:

  • 命令计数和反复尝试: 计算 JSONL 追踪中的 command_execution 项,以捕捉智能体开始循环或重新运行命令的回归。令牌使用情况也可在 turn.completed 事件中获得。
  • 令牌预算: 跟踪 usage.input_tokensusage.output_tokens,以发现意外的提示膨胀并比较版本间的效率。
  • 构建检查: 技能完成后运行 npm run build。这作为更强的端到端信号,并捕获损坏的导入或配置不正确的工具。
  • 运行时烟雾检查: 启动 npm run dev 并用 curl 访问开发服务器,或者如果您已经有一个轻量级的 Playwright 检查,则运行它。有选择地使用它。它增加信心但花费时间。
  • 仓库清洁度: 确保运行不会生成不需要的文件,并且 git status --porcelain 是空的(或匹配明确的允许列表)。
  • 沙箱和权限回归: 验证技能在不将权限提升到超出您预期的情况下仍然有效。一旦自动化,最小权限默认值最重要。

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

8. 关键要点

这个小的 setup-demo-app 示例展示了从"感觉更好"到"证明"的转变:运行智能体,记录发生了什么,并用一小组检查对其进行评分。一旦该循环存在,每个调整都更容易确认,每个回归都变得清晰。以下是关键要点:

  • 衡量重要的事情。 好的 evals 使回归清晰,失败可解释。
  • 从可检查的完成定义开始。 使用 $skill-creator 进行引导,然后收紧指令直到成功明确无误。
  • 将 evals 建立在行为上。 使用 codex exec --json 捕获 JSONL 并针对 command_execution 事件编写确定性检查。
  • 在规则不足的地方使用 Codex。 添加带有 --output-schema 的结构化、基于评分标准的传递,以可靠地对样式和约定进行评分。
  • 让真正的失败驱动覆盖率。 每个手动修复都是一个信号。把它变成一个测试,这样技能就会继续做对。

评论 (0)

暂无评论,快来抢沙发吧!

91学AI

© 2026 91学AI · 按岗位学 AI 与大数据. All rights reserved.