SkillPkg Logo
如何评估Skill的输出质量

如何评估Skill的输出质量

Ironben/

你写好了一个 Skill,用一个提示词试了试,似乎能用。但它是否在各种不同提示下、在边界情况里、以及相对于「不用 Skill」时都同样可靠?运行结构化评测(evals)可以回答这些问题,并为你提供一个系统改进 Skill 的反馈闭环。

设计测试用例

一个测试用例由三部分组成:

  • Prompt(提示):一个真实感的用户消息——类似真实用户会输入的话。
  • 期望输出:用自然语言描述成功应该是什么样子。
  • 输入文件(可选):Skill 运行时需要用到的文件。

将测试用例保存在 Skill 目录下的 evals/evals.json 中:

1{
2  "skill_name": "csv-analyzer",
3  "evals": [
4   {
5      "id": 1,
6      "prompt": "I have a CSV of monthly sales data in data/sales_2025.csv. Can you find the top 3 months by revenue and make a bar chart?",
7      "expected_output": "A bar chart image showing the top 3 months by revenue, with labeled axes and values.",
8      "files": ["evals/files/sales_2025.csv"]
9   },
10   {
11      "id": 2,
12      "prompt": "there's a csv in my downloads called customers.csv, some rows have missing emails — can you clean it up and tell me how many were missing?",
13      "expected_output": "A cleaned CSV with missing emails handled, plus a count of how many were missing.",
14      "files": ["evals/files/customers.csv"]
15   }
16 ]
17}

编写高质量测试提示的建议:

  • 先从 2–3 个测试用例开始。 在你看到第一轮结果之前,不要过度投入。之后可以再扩展集合。
  • 让提示多样化。 使用不同的措辞、细节程度和正式程度。有些提示可以很随意(“嘿,你能清理一下这个 CSV 文件吗?”),有些则要非常精确(“解析 data/input.csv 中的 CSV 文件,删除 B 列为空的行,并将结果写入 data/output.csv。”)。
  • 覆盖边界情况。 至少包含一个测试边界条件的提示——如格式错误的输入、不寻常的请求,或 Skill 指令存在歧义的情形。
  • 使用真实语境。 真实用户会提到文件路径、列名和个人背景。像 “process this data” 这样太模糊的提示无法有效测试任何东西。

先不用担心定义具体的通过 / 失败检查——只需要先写提示和期望输出。等你看到第一轮运行结果后,再补充更详细的检查(称为 assertions,断言)。

运行 evals

核心模式是对每个测试用例运行两次:一次启用 Skill,一次不启用 Skill(或者使用之前的版本)。这样你就有了可以对比的基线。

工作区结构

在 Skill 目录旁边创建一个 workspace(工作区)目录,用来组织评测结果。每完整跑一轮 eval,就建一个 iteration-N/ 目录。在其中,每个测试用例都有一个 eval 目录,下面再分 with_skill/without_skill/ 子目录:

1csv-analyzer/
2├── SKILL.md
3└── evals/
4   └── evals.json
5csv-analyzer-workspace/
6└── iteration-1/
7   ├── eval-top-months-chart/
8   │   ├── with_skill/
9   │   │   ├── outputs/       # 本次运行产出的文件
10   │   │   ├── timing.json   # Token 数与耗时
11   │   │   └── grading.json   # 断言评测结果
12   │   └── without_skill/
13   │       ├── outputs/
14   │       ├── timing.json
15   │       └── grading.json
16   ├── eval-clean-missing-emails/
17   │   ├── with_skill/
18   │   │   ├── outputs/
19   │   │   ├── timing.json
20   │   │   └── grading.json
21   │   └── without_skill/
22   │       ├── outputs/
23   │       ├── timing.json
24   │       └── grading.json
25   └── benchmark.json         # 汇总统计数据

你手动维护的主要文件是 evals/evals.json。其他 JSON 文件(grading.jsontiming.jsonbenchmark.json)会在评测过程中由 Agent、脚本或你自己生成。

运行评测

每次 eval 运行都应从一个「干净」的上下文开始——没有之前运行或 Skill 开发过程留下的状态。这能保证 Agent 只遵循 SKILL.md 中的内容。

在支持子 Agent 的环境(例如 Claude Code)中,这种隔离是天然存在的:每个子任务都会全新启动。如果没有子 Agent,就为每次运行开一个独立会话。

对每次运行,需要提供:

  • Skill 路径(或者没有 Skill,用于基线)
  • 测试提示(test prompt)
  • 任意输入文件
  • 输出目录

下面是一个启用 Skill 的单次运行的示例指令:

1执行此任务:
2- 技能路径:/path/to/csv-analyzer
3- 任务:我有一个包含月度销售数据的 CSV 文件 data/sales_2025.csv。请找出销售额排名前 3 的月份,并制作一个柱状图。
4- 输入文件:evals/files/sales_2025.csv
5- 将输出保存到:csv-analyzer-workspace/iteration-1/eval-top-months-chart/with_skill/outputs/

对于基线运行,使用同样的提示,只是不提供 Skill 路径,输出保存到 without_skill/outputs/

如果是在改进一个已有 Skill,就用旧版本作为基线。在修改前做一份快照(cp -r <skill-path> <workspace>/skill-snapshot/),基线运行指向这个快照,并将输出保存到 old_skill/outputs/ 而不是 without_skill/

记录时间数据

时间数据让你比较启用 Skill 相对基线的耗时和 Token 成本——一个显著提升输出质量、但 Token 使用量增加 3 倍的 Skill,与一个既更好又更省的 Skill,是完全不同的权衡。

每次运行完成后,记录 Token 数和时长:timing.json

1{
2  "total_tokens": 84852,
3  "duration_ms": 23332
4}

有些 Agent 工具,如Claude Code ,当一个子 Agent 任务结束时,任务完成通知会包含 total_tokensduration_ms。要立即保存这些值——系统不会在其他地方持久化它们。

编写断言

断言是关于输出应该包含或实现什么的、可验证的陈述。通常在你看到第一轮输出之后再加断言——在 Skill 实际跑之前,你往往并不知道「好结果」具体长什么样。

好的断言示例:

  • "输出是一个合法的 JSON"——可以用程序验证。
  • "条形图的坐标轴已标注"——具体且可观察。
  • "该报告至少包含三项建议"——可计数。

较弱的断言示例:

  • "输出结果良好"——过于模糊,无法打分。
  • "输出结果正是“总收入:$X”"——太脆弱;即使输出是正确的,只要措辞稍有不同就会失败。

无需对所有东西都写断言。有些质量指标——比如写作风格、视觉设计、或者输出是否「感觉对」——很难拆成通过 / 失败的检查。这类问题更适合在人工审阅中发现。把断言保留给那些可以客观检查的点。

evals/evals.json 中为每个测试用例添加断言:

1{
2  "skill_name": "csv-analyzer",
3  "evals": [
4   {
5      "id": 1,
6      "prompt": "我有一个包含每月销售数据的 CSV 文件,文件名为 data/sales_2025.csv。请问您能否找出销售额排名前 3 的月份,并制作一个柱状图?",
7      "expected_output": "一张柱状图,显示了收入排名前 3 个月的月份,并标注了坐标轴和数值。",
8      "files": ["evals/files/sales_2025.csv"],
9      "assertions": [
10        "输出结果包含一个柱状图图像文件",
11        "图表显示正好三个月",
12        "两个坐标轴都已标注",
13        "图表标题或说明文字提到了收入"
14     ]
15   }
16 ]
17}

为输出打分

打分是指根据实际输出来评估每条断言,并记录 PASS(通过)或 FAIL(失败),同时提供具体证据。证据应引用或指向实际输出,而不仅仅是主观判断。

最简单的方法是把输出和断言一起交给一个 LLM,让它评估每一条。对于可以通过代码检查的断言(例如 JSON 是否有效、行数是否正确、某文件是否以预期尺寸存在),使用验证脚本——脚本在机械检查上比 LLM 更可靠,也更容易在多轮迭代间复用。

1{
2  "assertion_results": [
3   {
4      "text": "输出结果包含一个柱状图图像文件",
5      "passed": true,
6      "evidence": "在 outputs 目录中找到 chart.png (45KB)"
7   },
8   {
9      "text": "图表显示正好三个月",
10      "passed": true,
11      "evidence": "图表显示了三月、七月和十一月的柱状图。"
12   },
13   {
14      "text": "两个坐标轴都已标注",
15      "passed": false,
16      "evidence": "Y轴标签为“收入($)”,但X轴没有标签。"
17   },
18   {
19      "text": "图表标题或说明文字提到了收入",
20      "passed": true,
21      "evidence": "图表标题为“收入排名前 3 个月”"
22   }
23 ],
24  "summary": {
25    "passed": 3,
26    "failed": 1,
27    "total": 4,
28    "pass_rate": 0.75
29 }
30}

打分原则

  • 要求明确证据才能判定通过。 不要「宁可错杀也不放过」。如果断言写的是 “includes a summary(包含摘要)”,而输出只有一个叫 “Summary” 的小节、且只有一句非常含糊的话,那就应该判为失败——标签有了,但内容不达标。
  • 不仅要看打分结果,还要回头审视断言本身。 在打分过程中,留意那些总是通过(无论是否启用 Skill 都能通过)的断言,这类断言太容易了,对 Skill 价值几乎没有区分度;也留意那些总是失败的断言,可能是断言本身写错了(要求模型做不到的事情)、测试用例太难,或断言检查的点不对。把这些问题留到下一轮迭代中修正。

在比较两个 Skill 版本时,可以尝试盲评(blind comparison):把两份输出都给一个 LLM 评委,但不告诉它哪份来自哪个版本。评委根据自己的评分标准,从整体上打分(结构是否清晰、格式是否良好、可用性、打磨程度等),不受对「哪个版本应该更好」的先入为主影响。这样可以补充断言打分:两份输出可能都通过了所有断言,但整体质量依然有明显差异。

汇总结果

当本轮迭代中所有运行都打分完成后,按配置(with_skill / without_skill 等)汇总统计,并将结果保存到本轮的 benchmark.json 中(如 csv-analyzer-workspace/iteration-1/benchmark.json):

1{
2  "run_summary": {
3    "with_skill": {
4      "pass_rate": { "mean": 0.83, "stddev": 0.06 },
5      "time_seconds": { "mean": 45.0, "stddev": 12.0 },
6      "tokens": { "mean": 3800, "stddev": 400 }
7   },
8    "without_skill": {
9      "pass_rate": { "mean": 0.33, "stddev": 0.10 },
10      "time_seconds": { "mean": 32.0, "stddev": 8.0 },
11      "tokens": { "mean": 2100, "stddev": 300 }
12   },
13    "delta": {
14      "pass_rate": 0.50,
15      "time_seconds": 13.0,
16      "tokens": 1700
17   }
18 }
19}

delta 告诉你这个 Skill 的代价(更多的时间、更多的 Token)以及它带来的收益(更高的通过率)。如果一个 Skill 让运行多花了 13 秒,但使通过率提升了 50 个百分点,那通常是很值得的;而如果一个 Skill 让 Token 使用量翻倍,却只提升了 2 个百分点的通过率,那可能就不太划算。

标准差(stddev)只有在每个 eval 跑多次时才有意义。在早期阶段,你也许只有 2–3 个测试用例、且每个只跑一次,这时可以先关注原始通过数量和 delta——随着测试集扩展、每个 eval 运行多次之后,统计指标才会真正有用。

分析模式

汇总统计有时会掩盖重要模式。在计算完 benchmark 后:

  • 移除或替换那些在所有配置下都能通过的断言。 这些断言说明模型在不依赖 Skill 的情况下就能轻松完成,对 Skill 价值判断帮助不大。它们只会虚高启用 Skill 时的通过率,却不能反映真实增益。
  • 排查那些在所有配置下都失败的断言。 要么是断言本身有问题(要求的内容模型无法做到),要么是测试用例太难,或者断言检查的点根本不对。在下一轮迭代前先把这些修好。
  • 重点关注那些启用 Skill 通过、未启用 Skill 失败的断言。 这里就是 Skill 明确创造价值的地方。要弄清楚为什么——是哪些指令或脚本带来了差异?
  • 当同一个 eval 在多次运行中结果不稳定时,收紧指令。 如果同一测试有时通过、有时失败(在 benchmark 中表现为较高的 stddev),可能是评测本身不稳定(对模型随机性的敏感性高),也可能是 Skill 指令存在歧义,导致模型每次理解不一。通过增加示例或更具体的指导来减少歧义。
  • 排查时间和 Token 的异常值。 如果某个 eval 比其他用例慢了 3 倍,查看它的执行日志(完整的模型执行记录),找出瓶颈所在。

人工复审结果

断言打分和模式分析能发现很多问题,但它们只能检查「你事先想到要写断言的那些点」。人工审阅则能带来新的视角——发现你没预料到的问题,识别那些技术上正确但实际上不太有用的输出,或者发现难以用通过 / 失败表达的问题。

对每个测试用例,结合输出和打分结果进行人工审阅。把对每个测试用例的具体反馈记录下来,保存在 workspace 中(例如在各 eval 目录旁放一个 feedback.json):

1{
2  "eval-top-months-chart": "图表缺少坐标轴标签,月份是按字母顺序排列的,而不是按时间顺序排列的。",
3  "eval-clean-missing-emails": ""
4}

“图表缺少坐标轴标签,月份按字母顺序而不是时间顺序排列” 这样的反馈是可执行的;“看起来不太好” 则不是。空字符串表示人工审阅时没有问题——该测试用例通过了你的主观检查。在迭代改进步骤中,将精力集中在那些你有具体抱怨的测试用例上。

迭代完善 Skill

在打分和审阅之后,你会获得三类信号:

  • 失败的断言 指出具体缺口——缺了一步操作、某条指令不清晰,或者 Skill 没有覆盖某些情形。
  • 人工反馈 反映更宏观的质量问题——思路不对、输出结构不好,或者虽然技术上没错,但生成结果并不实用。
  • 执行日志(transcripts) 揭示问题产生的原因。如果 Agent 忽略了某条指令,可能是指令有歧义;如果 Agent 花了很多时间在无效步骤上,这些指令可能需要被简化或删除。

将这些信号转化为 Skill 改进的最高效方式,是把三类信息连同当前的 SKILL.md 一起交给一个 LLM,让它提出修改建议。LLM 可以综合失败断言、审阅意见和运行行为之间的模式,而这些关联如果手动梳理会非常费劲。

在给 LLM 写提示时,可包含以下指导原则:

  • 从反馈中抽象出通用规律。 Skill 会在许多不同提示下被使用,而不仅仅是测试用例。修改应当针对底层问题,而不是为特定例子打补丁。
  • 保持 Skill 精简。 更少但更高质量的指令,通常比一大堆规则更有效。如果运行日志中出现很多无用工作(不必要的校验、多余的中间结果),就删掉对应指令。如果在不停加规则后通过率不再提升,说明 Skill 可能过度约束了——可以尝试移除一些指令,看看结果是否依然良好甚至更好。
  • 解释「为什么」。 基于原因的指令(“做 X,因为 Y 往往会导致 Z”)通常比简单的强制指令(“永远做 X,绝不要做 Y”)效果更好。当模型理解指令的目的时,会更可靠地遵循它们。
  • 把重复性工作打包。 如果每次测试运行中,模型都会重复写相似的辅助脚本(比如画图工具、数据解析器),那就是应该把这些脚本打包进 Skill 的 scripts/ 目录的信号。

迭代循环

  1. 将评测信号和当前的 SKILL.md 提供给 LLM,请它给出改进方案。
  2. 审阅并应用这些修改。
  3. 在新的 iteration-<N+1>/ 目录中对所有测试用例重新跑一轮。
  4. 对新结果进行打分和汇总。
  5. 进行人工复审。重复以上流程。

当你对结果满意、反馈持续为空,或者每轮迭代都没有明显改进时,就可以停止这个循环。

skill-creator 这个 Skill 可以自动化上述工作流中的大部分步骤——包括运行 evals、打断言分数、汇总 benchmark,以及整理结果供人工审阅。

如何系统评估 AI Agent Skill 输出质量:Evals 流程与实战指南 | 技能包 SkillPkg