OpenAI 让 AI Agent 的交付有了一张验收单:坏在哪一步查得到,改好了拿得出证据

OpenAI 教你给 AI Agent 写「单元测试」:从凭感觉迭代到可量化改进2026年9月22日 约 13 分钟

改完提示词觉得变快了,几天后 Skill 却在某个场景里不再触发,说不清是哪一步坏的。OpenAI 这套评测把「感觉」换成可验收的证据——跑一遍、存下轨迹、按规则打分。

OpenAICodexAgent SkillAI 评测开发流程

一分钟速览

  • AI 交付最怕的不是做不出来,而是改坏了没人知道坏在哪一步,验收时也拿不出依据。
  • 值得读的地方在于能直接落地、不用买平台:命令、用例表、检查脚本和评分 Schema 都是现成的。
  • 给单个 Skill 写 10–20 条提示,带一条不该触发的阴性对照,就能跨版本比分数。

改了几行提示词,Skill 怎么就悄悄坏了

Skill 坏掉的时候很安静。你改了几行提示词,跑一遍,感觉快了一点;再改一版,感觉稳了一点。几天后才发现:它在某个场景里不再触发,跳过了必要的一步,或者在目录里留下多余文件。回头看那次改动,你把触发条件的描述写模糊了——一个场景确实跑得更快,另一个场景的触发却被一并改坏。

这不是水平问题。Agent Skill就是一组写给大模型的提示词和指令,改动的效果本来就不容易预测:你既拿不出客观证据证明这一版更好,也拦不住上一版已经修好的问题在下一版复发。放到团队里更麻烦:没有客观证据,验收时就只能听改的人自己讲。

2026 年 1 月 22 日,OpenAI 开发者博客发表了《Testing Agent Skills Systematically with Evals》(用评测系统地测试 Agent Skill),作者是 Dominik Kundel 和 Gabriel Chua。文章把这个困境写得很直白:给 Codex 这类 AI Agent(智能体)迭代 Skill 时,很难分清自己是真正在改进它,还是只是在改变它的行为。他们给出的解法很轻:不需要重型评估平台,用 Codex CLI(命令行工具)加几十行脚本就能搭起来。

要把「感觉」换成证据,先得说清楚 Eval 到底在测什么。

Eval 就是把「感觉变好了」换成三个能回答的问题

Eval(评估)是 evaluations 的缩写,检查的是模型的输出以及产生输出的那些步骤是否符合你的预期。它不让开发者回答「这版是不是感觉更好」,只让回答三个具体问题:Agent 有没有调用这个 Skill,有没有运行预期的命令,输出有没有遵循你关心的约定。

一个完整的 Eval 由四样东西组成:一个提示词、一次被捕获的运行(包括轨迹和产物)、一小套检查,以及一个可以随时间对比的分数。这套结构和软件里的端到端测试很像,区别在于被测对象不是传统代码,而是大模型的行为。

换来的是可比较的证据。有了检查项,回归能被明确指出来,比如「npm install 没有执行」;有了分数,两个版本可以直接比高低,而不是靠印象。

原稿素材OpenAI 官方博客为这篇指南配的示意图:左边是一次 Skill 运行,中间是评测动作,右边是这次运行的产出。
类比Eval 的四段结构像一场考试:提示词是考题,被捕获的运行是考生答题的过程,检查是阅卷标准,分数是成绩单。有了成绩单,两版 Skill 谁更好才比得出来。

一条 Skill 评测回路,六个阶段

第 1 / 6 步 · 定义成功

先把成功写成可测量的检查

动手写 Skill 之前,列出结果、过程、风格、效率四类必须通过的检查,清单小而精。

第 2 / 6 步 · 建 Skill

用 $skill-creator 搭出第一版

回答「做什么、什么时候触发、是否带脚本」三个问题,默认纯指令型,名称和描述要写准。

第 3 / 6 步 · 手动触发

在真实环境里跑,找出隐藏假设

用 /skills 或 $ 显式激活,观察触发、环境、执行三类假设里的偏差,把每个手动修复记下来。

第 4 / 6 步 · 捕获轨迹

用 codex exec --json 跑测试用例

对每条提示跑一次,stdout 变成 JSONL 事件流,把轨迹存到磁盘;单个 Skill 从 10–20 条提示起步。

第 5 / 6 步 · 分层评分

先做确定性检查,再做模型辅助评分

脚本查有没有 command_execution 事件、文件有没有生成,再用 --output-schema 让模型按标准给出结构化分数。

第 6 / 6 步 · 对比迭代

跨版本比分数,把失败变成用例

同一套检查在新旧版本上重复运行,回归能被指出来,每一次真实失败都补成永久测试。

六个阶段按先后推进,前一步的产出是后一步的输入。流程中的先后顺序不代表实测耗时,也不代表任何质量差距。

既然要出成绩单,题目该照着什么标准来出?

写 Skill 之前,先把「什么算成功」拆成四类检查

OpenAI 的建议是把顺序倒过来:在动手写 Skill 之前,先用可以测量的语言写清楚「成功」是什么。检查分成四类。结果目标看的是任务有没有完成、应用能不能跑起来。过程目标看的是 Agent 有没有调用 Skill、有没有用预期的工具、有没有按预期的步骤去做。风格目标看的是输出有没有遵循约定的写法;效率目标看的是它有没有在不折腾的情况下把事办完,比如没有多余命令、没有过度消耗 Token(词元)。

这份清单要小而精,只收录必须通过的检查,而不是把每个偏好都编码进去。以贯穿全篇的示例 Skill setup-demo-app 为例:它负责用 Vite的 React + TypeScript 模板搭一个演示应用并配上 Tailwind。检查项里既有确定性检查(有没有运行 npm install、有没有生成 package.json),也有用来评估代码约定和版式的结构化风格评分。这种混合是刻意的:早期就要拿到快而具体的信号,而不是到最后只得到一个笼统的通过或失败。

先定义成功,还会反过来约束 Skill 的写法。如果「成功」本身是模糊的,Skill 也会跟着模糊,到最后你就没有具体的东西可供评估。示例 Skill 因此在文件结构、技术栈和完成定义上都给出了明确规定,这正是它能被评价的前提。

标准定下来了,Skill 本身怎么写、又该从哪里开始试?

先用 $skill-creator 把 Skill 建起来,再手动跑几次挖出隐藏假设

创建 Skill 最快的方式是用 Codex 内置的创建器,它本身也是一个 Skill。输入 $skill-creator,它会问你三个问题——这个 Skill 做什么、什么时候该触发、以及它属于纯指令型还是带脚本。默认推荐纯指令型。

这里有一个容易被忽略的细节:Skill 的名称和描述比看上去重要得多。它们是 Codex 判断要不要触发这个 Skill、什么时候把 SKILL.md 其余内容注入上下文的信号。名称和描述一旦模糊或过载,会导致触发不稳定。

建好之后先别急着写自动化测试。用 /skills 斜杠命令或 $ 前缀提示显式激活,在真实仓库或临时目录里跑几遍,目的不是追求速度和完成度,而是找出 Skill 里藏着的假设。这些假设分三类。第一类是触发假设:「快速搭一个 React demo」这种本该触发的提示没有触发,或者「加个 Tailwind 样式」这种通用说法意外触发了它。第二类是环境假设:它默认在空目录里运行、默认 npm 比别的包管理器优先。第三类是执行假设:Agent 因为认定依赖已装好而跳过 npm install,或者在 Vite 项目还不存在时就去配置 Tailwind。

手动阶段每修掉一个问题,就把这次修复记下来,它很可能成为未来的一条 Eval 用例。等到需要让这些运行变得可重复时,就换成 codex exec:它把进度输出到 stderr(标准错误)、只把最终结果输出到 stdout(标准输出),方便脚本化、捕获和检查。默认它在受限沙箱里运行,任务需要写文件时加 --full-auto。越是要自动化,越应该只给完成任务所需的最小权限。

运行方式定下来了,接下去是决定用多少条提示来跑这套检查。

10–20 条提示就够,但其中必须有一条「不该触发」的用例

单个 Skill 不需要大型基准测试。OpenAI 给出的量级是 10–20 条提示,足以在早期暴露回归、确认改进。先把这些提示写成一个小 CSV(逗号分隔的表格文件),之后遇到真实失败再往里加行。每一行是一个场景:setup-demo-app 该不该激活,以及激活之后「成功」长什么样。

提示分四类,各测一件事。显式调用的用例直接点名 Skill,确认被要求时它能被调用。隐式调用的用例只描述场景、不提 Skill 名字,测的是 SKILL.md 里的名称和描述够不够强。上下文调用的用例要加上领域上下文,比如「创建一个小演示应用来展示 Responses API」,测的是在真实的、略带噪音的提示里还能不能触发这个 Skill,产出是否仍然符合预期的结构和约定。阴性对照用例则是一条明确不该触发 Skill 的相邻请求,例如「给我现有的 React 应用添加 Tailwind 样式」。

阴性对照最容易被省掉,作用却恰恰体现在容易出错的那一侧:它抓的是误报,防止 Codex 在用户只想对现有项目做增量修改时,过于急切地选中 Skill、另起一个项目。

四种测试用例,各自在测什么

第 5 节数据表格
用例类型测的是什么示例提示词
显式调用直接点名 Skill 时能不能被正确调用,名称、描述、指令的改动会不会破坏直接调用「使用 $setup-demo-app Skill 创建一个叫 devday-demo 的演示应用」
隐式调用不提 Skill 名字时,SKILL.md 里的名称和描述够不够强,Codex 会不会自己选中它「搭一个最小化的 React + Tailwind 演示应用,用于快速 UI 实验」
上下文调用在真实的、略带噪音的提示里还能不能触发这个 Skill,产出是否仍符合预期结构和约定「创建一个小演示应用来展示 Responses API」
阴性对照明确不该触发的相邻请求会不会被误触发,也就是 Codex 会不会过于急切地选中 Skill「给我现有的 React 应用添加 Tailwind 样式」

用例有了,结果要怎么看才算「查过」?

确定性检查:把 Agent 每一步操作变成能解析的数据

确定性的检查靠 codex exec 的 --json。开启之后,stdout 不再只是给人看的文本,而是一行一个事件的 JSONL事件流,评测脚本因此可以对「实际发生了什么」打分,而不只是判断最终输出对不对。

有了这串事件流,就可以检查 command_execution 事件、命令顺序和文件是否存在:有没有运行 npm install,有没有创建 package.json,命令有没有按预期顺序执行。一个最小可用的 Node.js 脚本做三件事:对每条提示运行一次 codex exec --json --full-auto,把 JSONL 轨迹存到磁盘,再解析事件做检查。判断有没有执行 npm install,就是遍历所有事件,找 type 为 item.started 或 item.completed、item.type 为 command_execution、且 item.command 里包含 npm install 的那一条;判断 package.json 有没有创建,直接用文件系统接口检查路径。

这些检查刻意做得轻,作用是在引入任何模型评分之前给出快速、可解释的信号。一旦失败,打开 JSONL 文件就能看到每个命令按顺序记录在 item.* 事件里,回归因此可解释、可修复。差别就在这里:不带 --json 跑一遍,你只看到最终文本,不知道中间执行了什么命令、跳过了哪些步骤。

命令和文件查得出来,可「写得合不合你的约定」怎么查?

规则管不到的风格和约定,交给模型按 Schema 打分

确定性检查回答的是「有没有做对基础的事」,回答不了「是不是按你想要的方式做的」。组件结构合不合理、样式约定有没有遵守、Tailwind 有没有按预期的方式配置,这些很难靠文件存在性检查和命令计数捕捉。

务实的做法是在流程里再加一层模型辅助的评分。先跑一遍搭建 Skill,让它把代码写到磁盘;再对生成的仓库做一次只读的风格检查。最后要求模型返回一个结构化响应,让评测工具能一致地打分。Codex 通过 --output-schema 直接支持这一点,它要求最终响应符合你定义的 JSON Schema。

OpenAI 给的评分 Schema 只有三个稳定字段,字段为 overall_pass、score 和 checks。overall_pass 表示整体是否通过,score 是 0 到 100 的整数,checks 是检查项数组,每一项带 id、pass 和 notes。第二个 codex exec 负责读仓库并按 Schema 输出,提示词里列明四条评估标准:Vite + React + TypeScript 项目是否存在;Tailwind 是否通过 @tailwindcss/vite 配置,CSS 里是否 import tailwindcss;src/components 下是否有 Header.tsx 和 Card.tsx;组件是不是函数式、是不是用 Tailwind 工具类而不是 CSS modules。返回的评分 JSON 里包含 vite、tailwind、structure、style 四个检查项。

稳定字段的价值在于可以合并、对比,也能跨多次运行追踪。如果之后把这套评测搬进 CI(持续集成),Codex GitHub Action 支持通过 codex-args 把 --output-schema 传进去,在自动化流程里强制要求同一套结构化输出。

原稿素材关联文章《Lieflat Charts》里一个数据可视化 Skill 的产出:统一的字体、留白和线条,这些就是「风格与约定」在成品里的样子。

两层评分各回答什么问题

第 7 节数据表格
层面回答的问题用什么代价
确定性检查有没有做对基础的事:命令有没有执行、文件有没有生成、顺序对不对解析 JSONL 轨迹的几十行脚本快,失败了能直接打开轨迹定位
模型辅助评分有没有按你要求的方式做:结构、风格、约定第二次 codex exec 加 --output-schema慢,要花一次模型调用,换来可跨运行对比的结构化分数

核心回路搭好之后,还该往哪些方向加检查?

先做快的检查,再按风险加慢的检查:六类扩展项怎么排

核心回路跑通之后,扩展方向有六类,但不必一次全上。命令计数与反复执行统计 JSONL 里的 command_execution 事件数量,抓的是 Agent 开始循环或反复重跑命令这类回归。Token 预算盯 usage.input_tokens 和 usage.output_tokens,用来发现提示词悄悄变胖,并跨版本比较效率。构建检查在 Skill 跑完后执行 npm run build,作为更强的端到端信号,抓坏掉的导入和配置错误的工具。运行时冒烟检查启动 npm run dev 再用 curl 访问,或者跑一个轻量的 Playwright 检查;它增加信心但花时间,要选择性使用。仓库清洁度要求是跑完之后不留下多余文件,git status --porcelain 的输出为空,或者只匹配明确的允许列表。沙箱与权限回归确认 Skill 仍然不需要超出预期的权限升级,这项在自动化之后最要紧。

这六项的顺序是一致的:先上能解释行为的快检查,只在确实能降低风险的时候才加更慢、更重的检查。每一次手动修复都是一个信号,只有把它变成测试,Skill 才会持续做对。

这份指南把整条链路收成五条要点。衡量重要的事,让回归清楚、失败可解释;从可检查的完成定义开始,用 $skill-creator 起手,再把指令收紧到成功没有歧义;把评测建立在行为上,用 codex exec --json 捕获轨迹,对 command_execution 事件写确定性检查。规则够不着的地方交给 Codex,让它用 --output-schema 给出结构化评分;最后让真实失败驱动用例覆盖,每一次手动修复都补成一个用例。

起步动作可以很具体。先用 $skill-creator 建一个 Skill,然后用 codex exec --json 跑一次,看轨迹长什么样。再从一个小 CSV 开始写用例,至少包含一个显式调用、一个隐式调用和一个阴性对照。写完用例,用几十行脚本盯住你最关心的两三个命令执行事件。最后定义一个简单的 JSON Schema,让模型输出结构化评分,先跑通再迭代。

六类扩展检查分别在抓什么,什么时候加

条件与选择
  1. 条件命令计数与反复执行
    结果Agent 开始循环或反复重跑命令
    可以怎么做早期就加,解析 JSONL 计数
  2. 条件Token 预算
    结果提示词意外变胖、效率出现波动
    可以怎么做早期就加,读 turn.completed 里的用量字段
  3. 条件构建检查
    结果坏掉的导入或配置错误的工具
    可以怎么做核心功能稳定后再加,跑 npm run build
  4. 条件运行时冒烟检查
    结果应用能不能正常启动并响应请求
    可以怎么做选择性使用,启动 dev server 后用 curl 访问,或跑一个轻量 Playwright 检查
  5. 条件仓库清洁度
    结果运行之后留下不需要的文件
    可以怎么做有 CI 之后再加,检查 git status --porcelain
  6. 条件沙箱与权限回归
    结果Skill 要求超出预期的权限升级
    可以怎么做自动化之后必须加,核对运行模式

适用范围:条件与顺序来自 OpenAI 指南;成本高低是定性描述,实际取决于 Skill 复杂度和 CI 资源。六类可以叠加选用,不必一次全部启用。

零件都齐了,这套回路放进一个团队里,究竟能变成什么?

这套做法放进项目里,管理者拿到三样东西

把这套评测回路当成管理工具看,它交付的是三样能进流程的东西:一张验收清单、一份可回查的运行记录、一条权限边界。

验收清单就是那 10–20 条提示加检查项。团队改完 Skill 按同一张清单跑一遍,哪一项没过、分数掉了多少都写在结果里,签字时不必再依赖改的人自己描述手感。运行记录是 codex exec --json 留下的 JSONL 轨迹:出问题可以逐条命令回看,是跳过了 npm install,还是把 Tailwind 配在了 Vite 之前,都能直接指出来——这就是「失败可解释」在项目里的样子,也是复盘的起点。

权限边界来自沙箱与权限回归检查。自动化跑起来之后最容易失控的是权限,用最小权限再加一项回归检查,能把「它到底动了什么」限制在预期范围内。还有一层对团队的用处:每一次真实失败都变成永久用例、写进小 CSV,下一轮迭代自动带着它跑,经验不再只留在某个人脑子里。

边界同样要说清:这套东西管的是「有没有按标准做到」,不替你判断业务该不该上线;OpenAI 也没有公布过它能提升多少质量、省多少时间。要落地,成本不高——不需要采购评估平台,Codex CLI 加几十行脚本就能起步。