skill 评估

3899 字
19 分钟
skill 评估

如何使用基于评估的迭代来测试你的技能是否能产生良好的输出。

你编写了一个技能,在一个提示上尝试了一下,似乎有效。但它在各种不同的提示下、在边缘情况下,是否都能可靠地工作?是否比没有技能时更好?运行结构化评估(evals)可以回答这些问题,并为你提供一个系统性地改进技能的反馈循环。

1. 设计测试用例#

一个测试用例包含三个部分:

  • 提示:一条真实的用户消息——类似用户实际会输入的内容。
  • 预期输出:对成功结果的人工可读描述。
  • 输入文件(可选):技能需要处理的文件。

将测试用例存储在技能目录内的 evals/evals.json 中:

{
"skill_name": "csv-analyzer",
"evals": [
{
"id": 1,
"prompt": "我有一份月度销售数据的 CSV 文件,在 data/sales_2025.csv。你能找出收入最高的 3 个月并生成一个柱状图吗?",
"expected_output": "一张柱状图图片,显示收入最高的 3 个月,带有标注的坐标轴和数值。",
"files": ["evals/files/sales_2025.csv"]
},
{
"id": 2,
"prompt": "我的下载文件夹里有个叫 customers.csv 的 CSV 文件,有些行缺少邮箱——你能清理一下并告诉我有多少行缺失吗?",
"expected_output": "一份处理了缺失邮箱的清理后 CSV,以及缺失数量的计数。",
"files": ["evals/files/customers.csv"]
}
]
}

编写良好测试提示的技巧:

  • 从 2-3 个测试用例开始。 在看到第一轮结果之前不要过度投入。后续可以扩充测试集。
  • 变化提示风格。 使用不同的措辞、详细程度和正式程度。一些提示应该随意(“嘿,能清理一下这个 csv 吗”),另一些则应精确(“解析 data/input.csv 中的 CSV,删除 B 列为空的行,并将结果写入 data/output.csv”)。
  • 覆盖边缘情况。 至少包含一个测试边界条件的提示——格式错误的输入、不寻常的请求,或技能指令可能存在歧义的情况。
  • 使用真实的上下文。 真实用户会提及文件路径、列名和个人背景。像 “处理这些数据” 这样的提示过于模糊,无法测试任何有用的东西。

暂时不要担心定义具体的通过/失败检查——只需提供提示和预期输出。你将在看到第一轮运行产生的结果后,再添加详细的检查(称为 assertions)。

2. 运行评估#

核心模式是每个测试用例运行两次:一次携带技能,一次不带技能(或使用先前版本)。这样你就有了一个可对比的基准。

2.1 工作区结构#

在技能目录旁边的工作区目录中组织评估结果。每完成一轮完整的评估循环,都获得自己的 iteration-N/ 目录。在该目录内,每个测试用例都有一个评估目录,内含 with_skill/without_skill/ 子目录:

csv-analyzer/
├── SKILL.md
└── evals/
└── evals.json
csv-analyzer-workspace/
└── iteration-1/
├── eval-top-months-chart/
│ ├── with_skill/
│ │ ├── outputs/ # 运行产生的文件
│ │ ├── timing.json # Token 数和耗时
│ │ └── grading.json # 断言结果
│ └── without_skill/
│ ├── outputs/
│ ├── timing.json
│ └── grading.json
├── eval-clean-missing-emails/
│ ├── with_skill/
│ │ ├── outputs/
│ │ ├── timing.json
│ │ └── grading.json
│ └── without_skill/
│ ├── outputs/
│ ├── timing.json
│ └── grading.json
└── benchmark.json # 聚合统计

你手工编写的主要文件是 evals/evals.json。其他 JSON 文件(grading.jsontiming.jsonbenchmark.json)是在评估过程中生成的——由代理、脚本或你本人生成。

2.2 启动运行#

每次评估运行都应从干净上下文开始——不保留之前运行或技能开发过程中的任何残留状态。这能确保代理只遵循 SKILL.md 告诉它的内容。在支持子代理的环境(例如 Claude Code)中,这种隔离是天然的:每个子任务都从全新状态开始。如果没有子代理,可以为每次运行使用单独的会话。

对于每次运行,提供:

  • 技能路径(基线运行则不提供)
  • 测试提示
  • 任何输入文件
  • 输出目录

以下是针对单次带技能运行,你需要提供给代理的指令示例:

执行此任务:
- 技能路径:/path/to/csv-analyzer
- 任务:我有一份月度销售数据的 CSV 文件,在 data/sales_2025.csv。
你能找出收入最高的 3 个月并生成一个柱状图吗?
- 输入文件:evals/files/sales_2025.csv
- 将输出保存到:csv-analyzer-workspace/iteration-1/eval-top-months-chart/with_skill/outputs/

对于基线,使用相同的提示但不提供技能路径,并保存到 without_skill/outputs/

当改进现有技能时,使用先前版本作为基线。在编辑之前对其进行快照(cp -r <skill-path> <workspace>/skill-snapshot/),将基线运行指向快照,并保存到 old_skill/outputs/ 而不是 without_skill/

2.3 捕获计时数据#

计时数据让你能对比技能相对于基线消耗的时间和 token 数——一个能显著提升输出质量但使 token 使用量增加两倍的技能,与一个既更好又更便宜的技能,是不同性质的取舍。每次运行完成后,记录 token 数和耗时:

{
"total_tokens": 84852,
"duration_ms": 23332
}
Tip

在 Claude Code 中,当子代理任务完成时,任务完成通知会包含 total_tokensduration_ms。请立即保存这些值——它们不会在其他地方持久化。

3. 编写断言#

断言是关于输出应包含或达成哪些内容的可验证陈述。在你看完第一轮输出后再添加它们——你通常需要在技能运行之后才知道“好”是什么样子。

好的断言:

  • "输出文件是有效的 JSON" —— 可通过编程验证。
  • "柱状图有标注的坐标轴" —— 具体且可观察。
  • "报告包含至少 3 条建议" —— 可计数。

弱的断言:

  • "输出很好" —— 过于模糊,无法评分。
  • "输出精确使用短语 'Total Revenue: $X'" —— 过于脆弱;措辞不同但正确的输出会失败。

并非所有东西都需要断言。某些质量属性——写作风格、视觉设计、输出是否“感觉对了”——难以分解为通过/失败检查。这些更适合在人工评审中发现。请将断言保留给可以客观检查的内容。

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

{
"skill_name": "csv-analyzer",
"evals": [
{
"id": 1,
"prompt": "我有一份月度销售数据的 CSV 文件,在 data/sales_2025.csv。你能找出收入最高的 3 个月并生成一个柱状图吗?",
"expected_output": "一张柱状图图片,显示收入最高的 3 个月,带有标注的坐标轴和数值。",
"files": ["evals/files/sales_2025.csv"],
"assertions": [
"输出包含一张柱状图图片文件",
"图表正好显示 3 个月",
"两个坐标轴都有标签",
"图表标题或说明提到了收入"
]
}
]
}

4. 对输出进行评分#

评分是指根据实际输出评估每个断言,并记录 通过(PASS)失败(FAIL),附上具体证据。证据应引用或参考输出,而不仅仅是陈述观点。

最简单的方法是将输出和断言交给 LLM,让其逐条评估。对于可通过代码检查的断言(有效 JSON、正确的行数、存在符合预期尺寸的文件),应使用验证脚本——对于机械性检查,脚本比 LLM 判断更可靠,并且可跨迭代复用。

{
"assertion_results": [
{
"text": "输出包含一张柱状图图片文件",
"passed": true,
"evidence": "在 outputs 目录中找到 chart.png(45KB)"
},
{
"text": "图表正好显示 3 个月",
"passed": true,
"evidence": "图表显示三月、七月和十一月的柱状条"
},
{
"text": "两个坐标轴都有标签",
"passed": false,
"evidence": "Y 轴标注了 'Revenue ($)',但 X 轴没有标签"
},
{
"text": "图表标题或说明提到了收入",
"passed": true,
"evidence": "图表标题为 'Top 3 Months by Revenue'"
}
],
"summary": {
"passed": 3,
"failed": 1,
"total": 4,
"pass_rate": 0.75
}
}

4.1 评分原则#

  • 通过必须有具体证据。 不要给予怀疑的好处。如果一个断言说“包含摘要”,而输出中有一个标题为“摘要”的部分但只有一句含糊的话,那就是 失败——标签存在,但实质内容不足。
  • 不仅检查结果,也要检查断言本身。 在评分过程中,注意断言是否过于容易(无论技能质量如何总是通过)、过于困难(即使输出很好也总是失败)或无法验证(无法仅从输出中检查)。在下一轮迭代中修正这些问题。
Tip

对于比较两个技能版本,可以尝试盲测对比:将两份输出交给 LLM 评判者,但不透露哪个来自哪个版本。评判者根据自己设定的标准对整体质量——组织、格式、可用性、润色程度——打分,从而避免对哪个版本“应该”更好的偏见。这可以作为断言评分的补充:两份输出可能都通过了所有断言,但整体质量却存在显著差异。

5. 聚合结果#

在迭代中的每次运行都完成评分后,计算每个配置的汇总统计,并将结果保存到 benchmark.json 中,放在评估目录旁边(例如 csv-analyzer-workspace/iteration-1/benchmark.json):

{
"run_summary": {
"with_skill": {
"pass_rate": { "mean": 0.83, "stddev": 0.06 },
"time_seconds": { "mean": 45.0, "stddev": 12.0 },
"tokens": { "mean": 3800, "stddev": 400 }
},
"without_skill": {
"pass_rate": { "mean": 0.33, "stddev": 0.10 },
"time_seconds": { "mean": 32.0, "stddev": 8.0 },
"tokens": { "mean": 2100, "stddev": 300 }
},
"delta": {
"pass_rate": 0.50,
"time_seconds": 13.0,
"tokens": 1700
}
}
}

delta 告诉你技能的成本(更多时间、更多 token)和收益(更高的通过率)。一个增加 13 秒但将通过率提升 50 个百分点的技能,很可能是值得的。一个 token 使用量翻倍但仅带来 2 个百分点提升的技能,可能就不值得了。

Tip

标准差(stddev)只有在每个评估有多次运行时才有意义。在早期迭代中,如果只有 2-3 个测试用例且各运行一次,应关注原始通过计数和 delta——当你扩展测试集并对每个评估进行多次运行后,统计指标才变得有用。

6. 分析模式#

聚合统计可能会掩盖重要的模式。在计算基准后:

  • 删除或替换在两个配置中总是通过的断言。 这些断言不会告诉你任何有用信息——模型在没有技能的情况下也能很好地处理它们。它们抬高了带技能的通过率,却没有反映实际技能价值。
  • 调查在两个配置中总是失败的断言。 要么断言本身有问题(要求模型无法做到的事情),要么测试用例太难,要么断言检查了错误的内容。在下一轮迭代前修复这些问题。
  • 研究在带技能时通过、不带技能时失败的断言。 这些是技能明显增加价值的地方。理解为什么——哪些指令或脚本起到了作用?
  • 当结果在不同运行之间不一致时,收紧指令。 如果同一个评估有时通过有时失败(在基准中体现为高 stddev),可能评估本身不稳定(对模型的随机性敏感),或者技能的指令可能过于模糊,导致模型每次理解不同。通过增加示例或更具体的指导来减少歧义。
  • 检查时间和 token 异常值。 如果某个评估耗时是其他评估的 3 倍,阅读其执行记录(运行期间模型所做操作的完整日志)以找到瓶颈。

7. 人工评审结果#

断言评分和模式分析能发现很多问题,但它们只能检查你想到要写断言的那些方面。人工评审者带来新的视角——发现你未预料到的问题,注意到输出在技术上正确但未切中要点,或发现难以用通过/失败检查表达的问题。对于每个测试用例,请评审实际输出及其评分。

为每个测试用例记录具体反馈,并保存到工作区中(例如,作为与评估目录并列的 feedback.json):

{
"eval-top-months-chart": "图表缺少坐标轴标签,且月份按字母顺序排列而非时间顺序。",
"eval-clean-missing-emails": ""
}

“图表缺少坐标轴标签”是可操作的;“看起来不好”则不是。空反馈意味着输出看起来没问题——该测试用例通过了你的评审。在迭代步骤中,将改进重点放在你有具体意见的测试用例上。

8. 迭代技能#

在完成评分和评审后,你拥有三个信号来源:

  • 失败的断言指向具体的缺口——遗漏的步骤、不清晰的指令,或技能未处理的情况。
  • 人工反馈指向更广泛的质量问题——方法错误、输出结构不佳,或技能产生了技术上正确但无用的结果。
  • 执行记录揭示了为什么出错。如果代理忽略了一条指令,该指令可能模棱两可。如果代理在无效步骤上浪费时间,这些指令可能需要简化或删除。

将这些信号转化为技能改进的最有效方式,是同时将这三个来源以及当前的 SKILL.md 交给 LLM,并要求它提出修改建议。LLM 可以综合失败断言、评审意见和记录行为中的模式,而这些模式手动连接会很繁琐。在提示 LLM 时,请包含以下指导原则:

  • 从反馈中泛化。 技能将在许多不同的提示下使用,而不仅仅是测试用例。修复应广泛解决根本问题,而不是为特定示例添加窄补丁。
  • 保持技能精简。 更少、更好的指令往往胜过详尽的规则。如果记录显示有浪费的工作(不必要的验证、不需要的中间输出),请删除这些指令。如果尽管添加了更多规则,通过率仍停滞不前,可能技能约束过多了——尝试删除一些指令,看看结果是否保持不变或有所改善。
  • 解释原因。 基于推理的指令(“做 X,因为 Y 往往导致 Z”)比刚性指令(“总是做 X,绝不做 Y”)效果更好。当模型理解目的时,它们会更可靠地遵循指令。
  • 捆绑重复工作。 如果每次测试运行都独立编写了类似的辅助脚本(图表构建器、数据解析器),这说明应将脚本打包到技能的 scripts/ 目录中。参见 skill 脚本 了解具体做法。

8.1 循环#

  1. 将评估信号和当前 SKILL.md 交给 LLM,要求其提出改进建议。
  2. 评审并应用修改。
  3. 在新的 iteration-<N+1>/ 目录中重新运行所有测试用例。
  4. 对结果进行评分和聚合。
  5. 进行人工评审。重复。

当结果令你满意、反馈始终为空,或你不再看到迭代之间有意义的改进时,即可停止。

Tip

skill-creator 技能可以自动化该工作流的大部分——运行评估、评分断言、聚合基准,并呈现结果供人工评审。具体流程参见 skill-creator.

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

skill 评估
https://agentskills.io/
作者
HAC
发布于
2026-07-14
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录