跳到主要内容

评估 Skill 输出质量

编写 Skill 只是第一步,持续评估和改进是确保 Skill 有效性的关键。本章介绍如何系统地评估 Skill 的输出质量,并通过迭代优化提升效果。

为什么需要评估?

Skill 的初稿通常需要优化。你可能面临以下问题:

  • 触发问题:Skill 在不该触发时触发,或应该触发时不触发
  • 输出不稳定:同样的输入产生不同质量的输出
  • 指令模糊:Agent 理解指令的方式与预期不同
  • 遗漏场景:某些边界情况未被覆盖

系统化的评估可以帮助你发现这些问题并持续改进。

评估框架概述

评估 Skill 的核心模式是:对每个测试用例运行两次——一次使用 Skill,一次不使用(或使用旧版本)。这提供了一个可比较的基准线。

评估流程

设计测试用例

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

组成部分说明
提示词真实的用户消息——人们实际会输入的内容
预期输出用人类可读的语言描述成功是什么样的
输入文件(可选)Skill 需要处理的文件

测试用例结构

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

[
{
"id": "basic-review",
"prompt": "请审查这段 Python 代码:def add(a,b): return a+b",
"expected_output": "识别缺少类型注解、文档字符串、空格问题",
"input_files": []
},
{
"id": "complex-review",
"prompt": "审查 src/services/user_service.py,重点关注错误处理",
"expected_output": "检查异常处理、None 检查、资源管理",
"input_files": ["src/services/user_service.py"]
}
]

编写好的测试提示词

从 2-3 个测试用例开始。在看到第一轮结果之前不要过度投入。稍后可以扩展测试集。

变化提示词的表达方式。使用不同的措辞、详细程度和正式程度:

随意风格:hey can you clean up this csv
精确风格:Parse the CSV at data/input.csv, drop rows where column B is null, and write the result to data/output.csv

覆盖边界情况。至少包含一个测试边界条件的提示——格式错误的输入、不寻常的请求,或 Skill 指令可能模糊的情况。

使用真实的上下文。真实用户会提及文件路径、列名和个人上下文。像"处理这个数据"这样的提示太模糊,无法测试任何有用的内容。

工作区结构

组织评估结果在工作区目录中,与 Skill 目录并列。每次完整的评估循环有自己的 iteration-N/ 目录:

csv-analyzer/
├── SKILL.md
├── evals/
│ └── evals.json # 测试用例定义
└── ...

csv-analyzer-workspace/
├── iteration-1/
│ ├── eval-basic-clean/ # 测试用例 1
│ │ ├── with_skill/
│ │ │ └── outputs/
│ │ └── without_skill/
│ │ └── outputs/
│ ├── eval-complex-parse/ # 测试用例 2
│ │ ├── with_skill/
│ │ └── without_skill/
│ ├── benchmark.json # 汇总统计
│ └── timing.json # 时间和 token 数据
└── iteration-2/
└── ...

运行评估

生成运行

每次评估运行应该从干净的上下文开始——没有前一次运行或 Skill 开发过程中的残留状态。这确保 Agent 只遵循 SKILL.md 告诉它的内容。

使用子代理:在支持子代理的环境中(如 Claude Code),这种隔离是自然的:每个子任务从干净状态开始。

使用独立会话:如果没有子代理,每次运行使用单独的会话。

对于每次运行,提供:

  • Skill 路径(或不使用 Skill 的基准线)
  • 测试提示词
  • 任何输入文件
  • 输出目录

捕获时间和 Token 数据

时间数据让你比较 Skill 相对于基准线花费了多少时间和 token:

{
"eval_id": "basic-clean",
"with_skill": {
"tokens": 4500,
"duration_seconds": 12
},
"without_skill": {
"tokens": 2800,
"duration_seconds": 8
}
}

一个显著提高输出质量但使 token 使用量增加三倍的 Skill,与一个既更好又更便宜的 Skill,是不同的权衡。

编写断言

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

好的断言

断言说明
"输出文件是有效的 JSON"可编程验证
"条形图有标签化的坐标轴"具体且可观察
"报告包含至少 3 条建议"可计数

不好的断言

断言问题
"输出是好的"太模糊,无法评分
"输出使用确切短语 'Total Revenue: $X'"太脆弱,正确但不同措辞的输出会失败

添加断言到测试用例

{
"id": "basic-review",
"prompt": "请审查这段 Python 代码",
"expected_output": "识别代码质量问题",
"assertions": [
"输出包含问题列表",
"每个问题都有行号和修改建议",
"输出以总体评价结束",
"问题按严重程度排序"
]
}

评分输出

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

评分原则

要求 PASS 的具体证据。不要给予好处。如果断言说"包含摘要"而输出有一个标题为"摘要"的部分但只有一句模糊的句子,那是 FAIL——标签存在但实质不存在。

审查断言本身。在评分时,注意断言是否:

  • 太容易(无论 Skill 质量如何总是通过)
  • 太难(即使输出很好也总是失败)
  • 不可验证(无法从输出中检查)

评分结果示例

{
"eval_id": "basic-review",
"configuration": "with_skill",
"results": [
{
"assertion": "输出包含问题列表",
"verdict": "PASS",
"evidence": "输出有'### 问题列表'部分,包含 5 个问题"
},
{
"assertion": "每个问题都有行号和修改建议",
"verdict": "FAIL",
"evidence": "问题 3 和 5 缺少行号,只有问题描述"
}
]
}

使用脚本验证

对于可以通过代码检查的断言(有效 JSON、正确的行数、文件存在且尺寸符合预期),使用验证脚本——脚本对于机械检查比 LLM 判断更可靠,并且可跨迭代重用。

汇总结果

一旦迭代中的每次运行都被评分,计算每个配置的汇总统计:

{
"iteration": 1,
"configuration": "with_skill",
"summary": {
"total_assertions": 12,
"passed": 10,
"failed": 2,
"pass_rate": 0.83
},
"delta": {
"pass_rate": 0.25,
"tokens": 1700,
"duration_seconds": 4
}
}

delta 告诉你 Skill 的成本(更多时间、更多 token)和收益(更高的通过率)。

一个增加 13 秒但将通过率提高 50 个百分点的 Skill 可能是值得的。一个将 token 使用量翻倍但只提高 2 个百分点的 Skill 可能不值得。

分析模式

汇总统计可能隐藏重要的模式。在计算基准后:

清理无信息的断言

移除或替换在两种配置中总是通过的断言。这些没有告诉你任何有用的东西——模型在没有 Skill 的情况下也能很好地处理它们。它们夸大了使用 Skill 的通过率,而没有反映实际的 Skill 价值。

调查总是失败的断言

调查在两种配置中总是失败的断言。可能是:

  • 断言有问题(要求模型做不到的事情)
  • 测试用例太难
  • 断言在检查错误的东西

在下次迭代前修复这些。

研究有价值的断言

研究使用 Skill 通过但不使用 Skill 失败的断言。这是 Skill 明显增加价值的地方。理解为什么——哪些指令或脚本产生了差异?

解决不一致性

当结果在运行间不一致时收紧指令。如果同一个评估有时通过有时失败(在基准中反映为高 stddev),可能是:

  • 评估不稳定(对模型随机性敏感)
  • Skill 的指令足够模糊,模型每次解释不同

添加示例或更具体的指导以减少歧义。

检查异常值

检查时间和 token 异常值。如果一个评估花费的时间是其他的三倍,阅读其执行转录(模型在运行期间所做的一切的完整日志)以找到瓶颈。

人工审查

断言评分和模式分析捕捉了很多,但它们只检查你想到写断言的内容。人工审查者带来全新的视角——捕捉你没有预料到的问题,注意到输出在技术上正确但偏离了要点,或发现难以表达为通过/失败检查的问题。

对于每个测试用例,审查实际输出以及评分。记录每个测试用例的具体反馈:

{
"eval_id": "basic-review",
"feedback": "输出正确识别了问题,但建议太笼统。应该提供具体的代码示例来修复每个问题。",
"rating": 6,
"improvement_suggestions": [
"为每个问题添加代码示例",
"按严重程度分组问题"
]
}

迭代改进

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

信号来源指示的内容
失败的断言特定的差距——缺失的步骤、不清晰的指令、Skill 未处理的情况
人工反馈更广泛的质量问题——方法错误、输出结构差、技术上正确但无帮助
执行转录为什么出错了——Agent 忽略了指令(指令可能模糊),Agent 在无用的步骤上浪费时间(指令可能需要简化或移除)

使用 LLM 提出改进建议

将评估信号——评分结果、反馈和当前的 SKILL.md——提供给 LLM,要求它提出修改建议。LLM 可以综合跨失败断言、审查者投诉和转录行为的模式,这些手动连接会很繁琐。

迭代指南

从反馈中泛化。Skill 将用于许多不同的提示词,不仅仅是测试用例。修复应该广泛解决潜在问题,而不是为特定示例添加狭窄的补丁。

保持 Skill 精简。更少、更好的指令通常优于详尽的规则。如果转录显示浪费的工作(不必要的验证、不需要的中间输出),移除这些指令。如果通过率在添加更多规则后趋于平稳,Skill 可能被过度约束——尝试移除指令,看看结果是否保持或改善。

解释原因。基于推理的指令("做 X 因为 Y 往往会导致 Z")比僵化的指令("总是做 X,永远不做 Y")效果更好。当模型理解目的时,更可靠地遵循指令。

捆绑重复工作。如果每次测试运行都独立编写类似的辅助脚本(图表构建器、数据解析器),那就是将脚本捆绑到 Skill 的 scripts/ 目录的信号。

评估循环

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

最佳实践总结

实践说明
从小开始2-3 个测试用例,在看到结果前不要过度投入
变化提示词使用不同措辞、详细程度、正式程度
覆盖边界测试格式错误的输入、不寻常的请求
使用真实上下文包含文件路径、列名等真实信息
要求具体证据PASS 需要具体证据,不要给予好处
审查断言质量移除总是通过的,修复总是失败的
保持精简更少更好的指令 > 详尽的规则
解释原因基于推理的指令效果更好

下一步