你的 Skill 真的好用吗?来自OpenAI的 Eval 系统化验证 Agent 技能方法论

0 阅读9分钟

你最后一次验证 Skill 是怎么做的?

写完一个 Skill,手动触发了几次,输出看起来还不错——然后就上线了。

这大概是大多数人验证 Skill 的完整流程。说出来有点惭愧,但确实如此。我们在写普通代码时会写单元测试、跑 CI,但到了 Skill 这里,突然回到了"凭感觉"的时代。

问题不是懒。问题是我们不知道 Skill 会以哪些方式悄悄失效,也没有一套语言来描述"什么叫做好"。

这篇文章就是要解决这两件事:先把 Skill 的失效路径说清楚,再用这套失效地图反推出验证体系。


Skill 会怎么失效?

在聊怎么测之前,先想清楚有什么可能出错。Skill 的失效通常沿着四条路径发生,而且往往是静悄悄的——没有报错,只有"结果不对劲"。

路径一:根本没触发

这是最隐蔽的失效。用户明明说了"帮我格式化一下代码",但 Agent 没有调用你的代码格式化 Skill,而是直接用模型自己的知识瞎改了一通。

原因通常在 SKILL.mddescription 字段写得太模糊——太宽泛会和其他 Skill 冲突,太窄又会漏掉很多合理的触发场景。更棘手的是,这种失效在手动测试时很难发现,因为你测试的时候往往用的是最标准的触发语句,而用户在真实场景里的表达千变万化。

路径二:触发了,但任务没完成

Skill 被调用了,工具也跑了,但最终该做的事没做到。比如:应该创建三个文件,结果只创建了两个;应该执行一段迁移脚本,结果中途退出了。

这类失效叫做 Outcome 失效,是最直接、影响最大的一类。用户最终看到的就是结果——结果不对,Skill 就等于没用。

路径三:结果对了,但过程走歪了

这一类更隐蔽。最终输出看起来没问题,但 Agent 的执行路径是错的:调用了不该调的工具、步骤顺序乱了、绕了一大圈弯路才完成任务。

举个例子:一个数据库迁移 Skill,正确的顺序应该是"备份 → 迁移 → 验证"。如果 Agent 先迁移再备份,输出文件可能是对的,但下次迁移失败时你就没有可用的备份了。这是 Process 失效,在纯结果导向的验证里完全看不出来。

路径四:完成了,但质量不达标

任务完成了,过程也对了,但:生成的代码风格和项目规范不一致;Commit message 格式不符合团队约定;用了 500 个 token 完成了本可以用 100 个搞定的任务。

这是 Style 和 Efficiency 失效,不会直接报错,但会在团队协作和成本控制上慢慢积累成大问题。


定义"成功":四个维度的验证目标

这四条失效路径,正好对应四类成功标准。确认过这四个维度,才算真正定义清楚了"这个 Skill 应该做到什么"。

维度对应失效核心问题
Outcome(结果)任务没完成该做的事做了吗?
Process(过程)执行路径错误用对工具了吗?顺序对吗?
Style(风格)质量不达标输出符合规范吗?
Efficiency(效率)资源浪费有没有绕弯路?token 用量合理吗?

接下来,针对每个维度,介绍对应的验证方法。


验证 Outcome:确定性检查

Outcome 是最容易量化的维度,适合用确定性检查(Deterministic Grader)——即通过解析运行日志或检查文件系统状态,来判断任务是否完成。

先建一个小测试集

不需要几百条用例,10~20 条就够了。但要覆盖三种场景:

显式触发:"/use code-formatter 帮我格式化这个文件"
隐式触发:"这段代码格式有点乱,能帮我整理一下吗?"
负样本:  "帮我写一个排序算法"(不应该触发格式化 Skill)

负样本尤其重要,它检验的是 Skill 有没有被错误触发——过度触发和没触发一样是问题。

用 JSON 输出做确定性断言

codex exec --json 会输出结构化的运行日志(JSONL 格式),包含每次工具调用的详情。针对 Outcome 验证,可以这样检查:

import json, sys

# 加载运行日志
with open("run_output.jsonl") as f:
    events = [json.loads(line) for line in f]

# 检查目标文件是否被创建
created_files = [
    e["path"] for e in events
    if e.get("type") == "file_write"
]

expected = ["src/index.ts", "src/types.ts", "README.md"]
missing = [f for f in expected if f not in created_files]

if missing:
    print(f"❌ FAIL: 以下文件未被创建: {missing}")
    sys.exit(1)
else:
    print("✅ PASS: 所有预期文件均已创建")

这类检查有一个好处:它是确定性的,不依赖模型判断,运行结果稳定可重复。Outcome 层的 Eval 应该尽量用这种方式,把主观判断留给后面的 Rubric 评分。


验证 Process:工具调用序列检查

Outcome 检查告诉你"事做了没有",Process 检查告诉你"事是怎么做的"。

对于有明确执行顺序要求的 Skill,需要验证工具调用的序列是否符合预期。还是用 JSONL 日志:

# 提取所有工具调用,按顺序
tool_calls = [
    e["tool"] for e in events
    if e.get("type") == "tool_use"
]

# 定义期望的调用序列
expected_sequence = ["db_backup", "db_migrate", "db_verify"]

# 检查是否是子序列(允许中间有其他工具调用)
def is_subsequence(expected, actual):
    it = iter(actual)
    return all(step in it for step in expected)

if not is_subsequence(expected_sequence, tool_calls):
    print(f"❌ FAIL: 工具调用顺序不符合预期")
    print(f"   期望包含: {expected_sequence}")
    print(f"   实际调用: {tool_calls}")
    sys.exit(1)
else:
    print("✅ PASS: 工具调用序列正确")

Process 验证还有一个更进阶的用途:检测"命令抖动"(command thrashing)——即 Agent 在同一个操作上反复尝试、来回横跳。这通常意味着 Skill 的指令不够清晰,Agent 在不确定的情况下乱试。可以通过统计同一工具的连续调用次数来检测:

# 检测连续重复调用(超过 3 次视为抖动)
from itertools import groupby

for tool, group in groupby(tool_calls):
    count = sum(1 for _ in group)
    if count > 3:
        print(f"⚠️  WARNING: '{tool}' 连续调用 {count} 次,疑似命令抖动")

验证 Style 和 Efficiency:Rubric 模型评分

Outcome 和 Process 都是"事实性"检查,对错非黑即白。但 Style 和 Efficiency 是定性判断——代码风格对不对、Commit message 规不规范、有没有绕弯路——这些没有唯一标准答案。

这时候需要换一个工具:让另一个模型来打分,但要给它一张清晰的评分表(Rubric),并用 --output-schema 强制返回结构化 JSON,保证分数可比较。

定义 Rubric

# rubric.yaml
criteria:
  - name: code_style
    description: "生成的代码是否符合项目的 ESLint 规范?"
    scale: [1, 5]
    anchor_1: "完全不符合,大量违规"
    anchor_5: "完全符合,零违规"

  - name: commit_format
    description: "Commit message 是否遵循 Conventional Commits 规范?"
    scale: [1, 5]
    anchor_1: "格式完全错误"
    anchor_5: "格式完全正确,类型、scope、描述均规范"

  - name: efficiency
    description: "Agent 是否存在明显的冗余步骤或不必要的工具调用?"
    scale: [1, 5]
    anchor_1: "大量冗余,步骤混乱"
    anchor_5: "执行路径简洁高效"

--output-schema 强制结构化输出

import subprocess, json

# 定义输出 schema
output_schema = {
    "type": "object",
    "properties": {
        "code_style":     {"type": "integer", "minimum": 1, "maximum": 5},
        "commit_format":  {"type": "integer", "minimum": 1, "maximum": 5},
        "efficiency":     {"type": "integer", "minimum": 1, "maximum": 5},
        "reasoning":      {"type": "string"}
    },
    "required": ["code_style", "commit_format", "efficiency", "reasoning"]
}

result = subprocess.run(
    ["codex", "exec", "--output-schema", json.dumps(output_schema),
     "--", "评估以下运行结果是否符合代码规范..."],
    capture_output=True, text=True
)

scores = json.loads(result.stdout)
print(f"代码风格: {scores['code_style']}/5")
print(f"提交规范: {scores['commit_format']}/5")
print(f"执行效率: {scores['efficiency']}/5")
print(f"评分理由: {scores['reasoning']}")

使用结构化输出的核心价值:跨版本可比较。你改了 Skill 的某个指令,重新跑一遍 Eval,Style 分从 3.2 变成 4.1——这才是可以信赖的改进信号,而不是"感觉更好了"。


渐进式叠加:让 Eval 随 Skill 一起成长

上面四个维度的检查,不需要一次全部建好。Skill 验证应该和 Skill 本身一起迭代。

第一阶段(Skill 刚写完):先跑手动测试,确认基本 Outcome 没问题。
第二阶段(准备推广):加入 Outcome 的确定性检查,建 10 条测试用例。
第三阶段(团队在用):补 Process 的序列验证,加 Style 的 Rubric 评分。
第四阶段(生产关键路径):加入命令抖动检测、token 用量监控、构建验证、运行时冒烟测试。

这套渐进式思路有一个好处:在 Skill 最简单的时候就养成写 Eval 的习惯,不会因为"要建完整体系"而拖延。一个有 2 条 Outcome 检查的 Skill,也比一个零检查的 Skill 安全得多。

随着 Eval 套件成熟,把它集成进 CI 管道,每次修改 Skill 都自动跑一遍——这时候你改 Skill 才是真的有底气,而不是每次都在赌"这次应该没问题吧"。


总结

回到最开始的问题:你的 Skill 真的好用吗?

现在我们有了一套可以回答这个问题的框架:

你想知道…用这个方法
任务完成了吗?确定性检查(解析 JSONL,验证文件/状态)
步骤走对了吗?工具调用序列验证 + 命令抖动检测
质量达标了吗?Rubric 模型评分(结构化 JSON 输出)
有没有在浪费?token 用量追踪 + 冗余步骤检测

好的 Eval 有两个作用:让回归清晰——你知道改了什么导致分数下降;让失败可解释——不是"感觉不对",而是"第 3 步工具调用顺序错了"。

这才是让你敢持续迭代 Skill 的底气。


参考来源:本文核心方法论来自 OpenAI 官方技术博客 Testing Agent Skills Systematically with Evals


🎉 感谢关注,让我们一起享受技术带来的精彩!

我做了一个个人主页,能找到所有我提供给你的资源个人主页