Skip to content

评估 Skill 输出质量

本页为评估指南的非官方中文翻译

你写了一个 Skill,在某个提示上尝试过,它似乎有效。但它在各种提示、边界情况下、并且在没有 Skill 的情况下都能稳定地做得更好吗?运行结构化评估(evals)可以回答这些问题,并为你提供一个系统改进 Skill 的反馈循环。

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

  • Prompt / 提示:一个真实的用户消息 — 用户实际会输入的那类内容。
  • Expected output / 预期输出:对成功是什么样子的可读描述。
  • Input files / 输入文件(可选):Skill 需要处理的输入文件。

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

{
"skill_name": "csv-analyzer",
"evals": [
{
"id": 1,
"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?",
"expected_output": "A bar chart image showing the top 3 months by revenue, with labeled axes and values.",
"files": ["evals/files/sales_2025.csv"]
},
{
"id": 2,
"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?",
"expected_output": "A cleaned CSV with missing emails handled, plus a count of how many were missing.",
"files": ["evals/files/customers.csv"]
}
]
}

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

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

暂时不必定义具体的通过/失败检查 — 只要有提示和预期输出即可。在看到第一轮运行的输出后,你将添加详细的检查(称为断言)。

核心模式是针对每个测试用例运行两次:一次 使用 Skill,一次 不使用 Skill(或使用之前的版本)。这为你提供了一个可供比较的基线。

在你的 Skill 目录旁边的工作区目录中组织评估结果。每次完整地通过评估循环都有自己的 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 # tokens 和耗时
│ │ └── 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)在评估过程中生成 — 由智能体、脚本或你生成。

每次评估运行都应从一个干净的上下文开始 — 没有先前运行或 Skill 开发过程的遗留状态。这确保智能体仅遵循 SKILL.md 所告诉它的内容。在支持子智能体的环境中(Claude Code 就是这种情况),这种隔离是自然实现的:每个子任务都从零开始。如果没有子智能体,请为每次运行使用单独的会话。

对于每次运行,请提供:

  • Skill 路径(或基线则不提供 Skill)
  • 测试提示
  • 任何输入文件
  • 输出目录

以下是你会为单个带 Skill 的运行给智能体的指令示例:

Execute this task:
- Skill path: /path/to/csv-analyzer
- Task: 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?
- Input files: evals/files/sales_2025.csv
- Save outputs to: 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 相对于基线所花费的时间和 tokens — 一个大幅提升输出质量但使 tokens 用量增加三倍的 Skill,与一个既更好又更便宜的 Skill,是不同的权衡。每次运行完成时,请记录 token 数量和耗时:

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

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

断言是关于输出应包含或实现什么的可验证陈述。在看到第一轮输出后再添加它们 — 在 Skill 运行之前,你通常不知道”良好”是什么样的。

良好的断言:

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

弱断言:

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

并非所有内容都需要断言。一些质量(写作风格、视觉设计、输出是否”感觉正确”)难以分解为通过/失败检查。这些最好在 人工审核 时发现。将断言留给那些可以客观检查的事项。

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

{
"skill_name": "csv-analyzer",
"evals": [
{
"id": 1,
"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?",
"expected_output": "A bar chart image showing the top 3 months by revenue, with labeled axes and values.",
"files": ["evals/files/sales_2025.csv"],
"assertions": [
"The output includes a bar chart image file",
"The chart shows exactly 3 months",
"Both axes are labeled",
"The chart title or caption mentions revenue"
]
}
]
}

评分意味着根据实际输出评估每个断言,并以具体证据记录 PASSFAIL。证据应引用或参考输出,而不仅仅是陈述观点。

最简单的方法是将输出和断言交给 LLM,并要求它评估每个断言。对于可以通过代码检查的断言(有效的 JSON、正确的行数、具有预期维度的文件存在),请使用验证脚本 — 对于机械检查,脚本比 LLM 判断更可靠,并且可以在迭代之间复用。

{
"assertion_results": [
{
"text": "The output includes a bar chart image file",
"passed": true,
"evidence": "Found chart.png (45KB) in outputs directory"
},
{
"text": "The chart shows exactly 3 months",
"passed": true,
"evidence": "Chart displays bars for March, July, and November"
},
{
"text": "Both axes are labeled",
"passed": false,
"evidence": "Y-axis is labeled 'Revenue ($)' but X-axis has no label"
},
{
"text": "The chart title or caption mentions revenue",
"passed": true,
"evidence": "Chart title reads 'Top 3 Months by Revenue'"
}
],
"summary": {
"passed": 3,
"failed": 1,
"total": 4,
"pass_rate": 0.75
}
}
  • PASS 需要具体证据。 不要凭空臆断。如果某个断言说”包含摘要”而输出有一个标题为”Summary”的部分,其中只有一句含糊的句子,那么这是 FAIL — 标签在那里但实质内容缺失。
  • 检查断言本身,而不仅仅是结果。 在评分过程中,注意断言是否过于容易(无论 Skill 质量如何都始终通过)、过于困难(即使输出良好也始终失败),或无法验证(无法仅从输出进行检查)。针对下一次迭代修复这些问题。

译者注:要比较两个 Skill 版本,请尝试 盲测比较:将两个输出交给 LLM 评判,而不透露哪个来自哪个版本。评判者根据其自己的评分标准,对组织、格式、可用性、润色等整体质量进行评分,避免关于哪个版本”应该”更好的偏见。这补充了断言评分:两个输出可能都通过所有断言,但在整体质量上有显著差异。

一旦迭代中的每次运行都已评分,就为每个配置计算汇总统计信息,并将其保存到评估目录旁边的 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 告诉你 Skill 的成本(更多时间、更多 tokens)以及它带来的收益(更高的通过率)。一个增加 13 秒但将通过率提高 50 个百分点的 Skill 可能是值得的。一个为提高 2 个百分点而使 tokens 用量翻倍的 Skill 则可能不值得。

译者注:标准差(stddev)仅在每个评估有多个运行的情况下才有意义。在只有 2-3 个测试用例和单次运行的早期迭代中,请关注原始通过计数和 delta — 随着你扩展测试集并多次运行每个评估,统计指标将变得有用。

汇总统计可能掩盖重要的模式。在计算基准之后:

  • 删除或替换在两种配置中始终通过的断言。 这些不会告诉你任何有用的信息 — 模型在没有 Skill 的情况下也能很好地处理它们。它们会抬高带 Skill 的通过率,却无法反映实际的 Skill 价值。
  • 调查在两种配置中始终失败的断言。 要么是断言有问题(要求模型无法完成的事情)、测试用例太难,要么是断言检查的东西不正确。在下一次迭代之前修复这些问题。
  • 研究那些在使用 Skill 时通过、而不使用时失败的断言。 这就是 Skill 明显增加价值的地方。理解 为什么 — 是哪些指令或脚本产生了差异?
  • 当结果在不同运行中不一致时,加强指令。 如果同一个评估有时通过有时失败(在基准中表现为高 stddev),则该评估可能是 flaky(对模型随机性敏感),或者 Skill 的指令可能足够模糊,以致模型每次都会对其进行不同的解释。添加示例或更具体的指导以减少歧义。
  • 检查时间和 token 离群值。 如果一个评估的耗时是其他评估的 3 倍,请阅读其执行轨迹(模型在运行期间所做操作的完整日志)以找出瓶颈。

断言评分和模式分析能发现很多问题,但它们只检查了你事先想到要编写断言的内容。人工审核者带来全新的视角 — 找出你没有预料到的问题、注意那些技术上正确但偏离要点的情况,或发现难以用通过/失败检查表达的问题。对于每个测试用例,请同时查看实际输出和评分。

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

{
"eval-top-months-chart": "The chart is missing axis labels and the months are in alphabetical order instead of chronological.",
"eval-clean-missing-emails": ""
}

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

在评分和审核之后,你拥有三类信号:

  • 失败的断言 指向具体的差距 — 一个缺失的步骤、一个不清晰的指令,或 Skill 未处理的情况。
  • 人工反馈 指向更广泛的质量问题 — 方法不对、输出结构不良,或 Skill 产生了技术正确但无用的结果。
  • 执行轨迹 揭示了 为什么 事情出错。如果智能体忽略了某条指令,则该指令可能存在歧义。如果智能体在不具生产力的步骤上花费了时间,则这些指令可能需要简化或删除。

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

  • 从反馈中归纳总结。 Skill 将被用于许多不同的提示,而不仅仅是测试用例。修复方案 SHOULD 广泛地解决潜在问题,而不是为特定示例添加狭窄的补丁。
  • 保持 Skill 精简。 少而精的指令往往胜过详尽的规则。如果轨迹显示存在浪费的工作(不必要的验证、不需要的中间输出),请删除这些指令。如果在添加更多规则后通过率停滞不前,则 Skill 可能过于受约束 — 尝试删除指令并查看结果是保持还是改善。
  • 解释原因。 基于推理的指令(“执行 X,因为 Y 往往会导致 Z”)比僵化的指令(“始终执行 X,绝不执行 Y”)更有效。当模型理解目的时,会更可靠地遵循指令。
  • 打包重复的工作。 如果每次测试运行都独立编写了类似的辅助脚本(图表构建器、数据解析器),那么这就是一个信号:应将该脚本打包到 Skill 的 scripts/ 目录中。有关如何执行此操作,请参阅 使用脚本
  1. 将评估信号和当前的 SKILL.md 交给 LLM,并要求它提出改进建议。
  2. 审查并应用这些更改。
  3. 在新的 iteration-<N+1>/ 目录中重新运行所有测试用例。
  4. 对新结果进行评分并汇总。
  5. 由人工审核。重复此过程。

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

译者注:skill-creator Skill 可以自动化此工作流的许多部分 — 运行评估、断言评分、汇总基准,并提供结果供人工审核。