评估技能输出质量
如何使用 Eval 驱动的迭代来测试你的技能是否产生良好的输出。
Documentation Index
Fetch the complete documentation index at: https://agentskills.io/llms.txt
Use this file to discover all available pages before exploring further.
你写了一个技能,在提示符上尝试了一下,似乎有效。但它是否可靠地发挥作用——在各种提示符下,在边缘情况下,是否比完全没有技能要好?运行结构化评估 (evals) 可以回答这些问题,并为你提供一个反馈循环,以系统地提高技能。
(1)
设计测试用例
测试用例包含三个部分:
提示:一条真实的用户信息——那种真的会有人输入的内容。
预期结果:一个人类可读的成功描述。
输入文件 (可选):需要使用该技能的文件。
将测试用例存储在技能目录的 evals/evals.json 中:
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”)。
覆盖边缘情况。包括至少一个测试边界条件的提示——一个畸形的输入、一个不寻常的请求,或者一个技能指令可能模糊的情况。
使用真实的上下文。真实用户会提到文件路径、列名和个人上下文。像 “处理这个数据” 这样的提示太模糊了,无法测试任何有用的内容。
先不要担心定义具体的通过/失败检查——只需要提示符和预期输出。在看到第一次运行的结果后,你将添加详细的检查 (称为断言)。
(1)
运行 evals
核心模式是对每个测试用例运行两次:一次使用技能,一次不使用 (或使用之前的版本)。这为你提供了一个可以进行比较的基线。
(1)
工作空间结构
将 eval 结果组织成一个工作空间目录,与你的技能目录并列。每次完整的 eval 循环都会获得自己的迭代 N/目录。在这个目录中,每个测试用例都会获得一个 eval 目录,其中包含 with_skill/和 without_skill/子目录:
csv-analyzer/
├── SKILL.md
└── evals/
└── evals.json
csv-analyzer-workspace/
└── iteration-1/
├── eval-top-months-chart/
│ ├── with_skill/
│ │ ├── outputs/ # Files produced by the run
│ │ ├── timing.json # Tokens and duration
│ │ └── grading.json # Assertion results
│ └── 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 # Aggregated statistics
你手动创建的主文件是 evals/evals.json。其他 JSON 文件 (grade.json、 timing.json、benchmark.json) 是在 eval 过程中生成的——由代理、脚本或你自己生成。
(1)
生成运行
每次 eval 运行都应该从一个干净的上下文开始——没有前几次运行或 skill.md 开发过程中留下的状态。这确保代理只遵循 SKILL.md 告诉它的内容。在支持子代理的环境中 (例如,Claude Code),这种隔离是自然而然的:每个子任务都是全新开始的。如果没有子代理,请在每次运行中使用单独的会话。
对于每次跑步,请提供:
技能路径 (或无基线技能)
测试提示
任何输入文件
输出目录
以下是你可以给代理的一个示例指令,用于一次带技能的运行:
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/
对于基线,使用相同的提示符,但不使用技能路径,保存为 without_skill/outputs/。
当提升现有技能时,使用之前的版本作为基准。在编辑之前先将其快照 (cp -r/skill-snapshot/),将基准运行指向快照,并将其保存为 old_skill/outputs/而不是 without_skill/。
(1)
捕获时序数据
计时数据可以让你比较该技能相对于基线的时间和令牌消耗量——一个显著提高输出质量但令牌使用量增加三倍的技能,与一个既更好又更便宜的技能是不同的权衡。当每次运行完成时,记录令牌数量和持续时间:
timing.json
{
“total_tokens”: 84852,
“duration_ms”: 23332
}
在 Claude Code 中,当子 Agent 任务完成时,任务完成通知包括 total_tokens 和 duration_ms。立即保存这些值——它们不会在其他地方持久化。
(1)
写断言
断言是关于输出应该包含什么或实现什么的可验证陈述。在看到第一轮输出后再添加断言——在技能运行之前,你通常不知道 “好” 看起来是什么样子。
好的断言:
“输出文件是有效的 JSON”——可以通过编程方式验证。
“条形图标记了坐标轴”——具体且可观察。
“报告至少包含 3 项建议”——可计数。
弱断言:
“输出良好”——太模糊了,无法评分。
“输出正确使用短语 ‘总收入:$X’”——太脆弱了;用不同措辞的正确输出会失败。
并非所有事情都需要断言。一些特质——写作风格、视觉设计、输出是否 “感觉正确”——很难分解为通过/失败检查。这些在人工审核时更容易捕捉。将断言留给可以客观检查的事物。
在 evals/evals.json 中为每个测试用例添加断言:
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" ]
]
}
(1)
输出评分
评分是指根据实际输出评估每个断言,并用特定证据记录 PASS 或 FAIL。证据应该引用或参考输出,而不仅仅是陈述一个观点。
最简单的方法是将输出和断言提供给一个 LLM,并让它对每个断言进行评估。对于可以通过代码检查的断言 (有效的 JSON、正确的行计数、文件存在且维度符合预期),可以使用验证脚本——对于机械检查,脚本比 LLM 判断更可靠,并且可以在迭代中重复使用。
grading.json
{
“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
}
}
(1)
分级原则
需要具体证据才能获得通行证。不要给予怀疑的优势。如果一个断言说 “包含一个总结”,而输出中有一个标题为 “总结” 的部分,其中只有一个模糊的句子,那就是失败——标签在那里,但实质内容不在。
检查断言本身,而不仅仅是结果。在评分时,注意断言是否过于简单 (无论技能质量如何,总是通过)、过于困难 (即使输出良好也总是失败) 或无法验证 (无法仅从输出中检查)。在下次迭代时修正这些问题。
要比较两个技能版本,可以尝试盲比:将两个输出都呈现给 LLM 评委,但不透露哪个版本来自哪个版本。评委根据自己的标准对整体质量 (组织、格式、可用性、润色) 进行评分,不存在哪个版本 “应该” 更好的偏见。这补充了断言评分:两个输出可能都通过了所有断言,但在整体质量上存在显著差异。
(1)
汇总结果
一旦迭代中的每次运行都完成了评级,就计算每个配置的汇总统计数据,并将其保存到 benchmark.json 以及 eval 目录 (例如 csv-analyzer-workspace/iteration-1/benchmark.json):
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
}
}
增量告诉你该技能的成本 (更多时间、更多代币) 和收益 (更高的通过率)。一个增加 13 秒但将通过率提高 50 个百分点的技能可能是值得的。而一个将代币使用量翻倍以获得 2 个百分点提升的技能可能就不值得了。
标准差 (stddev) 只有在每个 eval 多次运行时才有意义。在早期迭代中,只有 2-3 个测试用例和单次运行时,请专注于原始通过次数和增量——随着测试集的扩展和每个 eval 多次运行,统计指标将变得有用。
(1)
模式分析
聚合统计可以隐藏重要的模式。在计算基准后:
移除或替换两种配置中始终通过的断言。这些并不能告诉你任何有用的信息——模型在没有技能的情况下也能很好地处理它们。它们夸大了使用技能的通过率,却没有反映实际的技能价值。
调查在两种配置中总是失败的断言。要么断言已经崩溃 (请求模型无法完成的操作),要么测试用例太难,要么断言检查错误。在下次迭代之前修复这些问题。
研究那些有技能就能通过但没有技能就会失败的断言。这正是技能显然在增加价值的地方。理解为什么——是哪些指令或脚本造成了差异?
当结果在运行中不一致时,应收紧指令。如果相同的 eval 有时通过,有时失败 (在基准中反映为高 stddev),eval 可能是不稳定的 (对模型随机性敏感),或者技能的指令可能足够模糊,以至于模型每次都以不同的方式解释它们。添加示例或更具体的指导来减少模糊性。
检查时间和标记异常值。如果一个 eval 比其他 eval 花费的时间长 3 倍,请阅读其执行记录 (模型在运行过程中所做事情的完整日志),以找出瓶颈。
(1)
与人类一起审查结果
断言评分和模式分析捕捉到很多信息,但它们只检查你认为应该写断言的内容。人工审核员能够提供全新的视角——发现你没有预料到的问题,注意到输出在技术上是正确的,但没有抓住重点,或者发现难以用通过/失败检查来表达的问题。对于每个测试案例,除了评分外,还要检查实际输出。
记录每个测试用例的特定反馈,并将其保存在工作空间中 (例如,作为 eval 目录旁边的 refeedback.json):
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”: “”
}
“图表缺少轴标签” 是可以采取行动的;“看起来不好” 则不行。空白反馈意味着输出看起来不错——那个测试用例通过了你的审核。在迭代步骤中,将改进集中在你有特定抱怨的测试用例上。
(1)
对技能进行迭代
经过评分和审查,您有三个信号源:
失败的断言指向特定的空白——一个缺失的步骤、一个不明确的指示,或者一个技能无法处理的情况。
人类反馈指向更广泛的质量问题——方法错误、输出结构不佳,或者技能产生了技术上正确但无益的结果。
执行记录揭示了事情出错的原因。如果代理人忽视了一个指令,这个指令可能是模棱两可的。如果代理人在无效步骤上花费了时间,这些指令可能需要简化或删除。
将这些信号转化为 skill.md 改进的最有效方法是将所有三个信号 (以及当前的 SKILL.md) 都交给一个 LLM,并要求它提出修改建议。LLM 可以综合各种模式,包括失败的断言、审稿人的投诉以及手动连接会很繁琐的转录行为。在提示 LLM 时,请包括以下指导原则:
根据反馈进行概括。这项技能将在许多不同的提示中使用,而不仅仅是在测试案例中。修复应广泛解决潜在问题,而不是为特定案例添加狭隘的补丁。
保持技能精益。更少、更好的指导通常优于详尽的规则。如果抄写员显示工作浪费 (不必要的验证、不必要的中间输出),就删除这些指导。如果尽管增加了更多规则,但仍然通过率保持不变,技能可能受到过度约束——尝试删除指导,看看结果是否保持不变或有所改善。
解释原因。基于推理的指令 (“做 X,因为 Y 往往会导致 Z”) 比严格的指令 (“始终做 X,永远不要做 Y”) 更有效。当模型理解目的时,它们会更可靠地遵循指令。
捆绑重复的工作。如果每个测试运行都独立地编写了类似的辅助脚本 (一个图表构建器、一个数据解析器),这就是将脚本捆绑到技能的脚本/目录中的信号。请参阅使用脚本来了解如何做到这一点。
(1)
循环
将 eval 信号和当前的 SKILL.md 给一个 LLM,并要求它提出改进建议。
检查并应用变更。
在新的迭代目录<N+1>/中重新运行所有测试用例。
对新结果进行评分和汇总。
与人类一起复习。重复。
当你对结果满意时停止,反馈始终空洞,或者在迭代之间不再看到有意义的改进。
技能创建器 Skill 可以自动执行大部分工作流程——运行评估、对断言进行评分、汇总基准以及提交结果供人工审核。
最后编辑:Ddd4j 更新时间:2026-05-14 23:27