菜单

cuilinsu
cuilinsu
发布于 2026-04-04 / 1 阅读
0

Agent Skills 综述

摘要:本文围绕智能体技能创建的最佳实践展开,核心是技能需基于领域专业知识和项目实际细节,而非大模型通用知识;通过实战对话沉淀、现有资产合成、多轮实战优化等路径创建,注重精简留白、合理设计范围与指令,同时通过触发测试、质量评估和脚本规范,形成从创建、优化到验证的完整闭环,确保技能实用、精准、可复用。

技能创建者的最佳实践

从真正的专业知识开始

技能创建中一个常见的误区是,要求语言硕士(LLM)在不提供特定领域背景信息的情况下创建技能,而仅仅依赖语言硕士的通用培训​​知识。其结果是生成模糊、通用的流程(例如“妥善处理错误”、“遵循身份验证最佳实践”),而不是能够使技能真正有价值的具体 API 模式、特殊情况和项目规范。有效的技能源于真正的专业知识。关键在于将特定领域的背景知识融入到技能创造过程中。

摘自一项实践任务

与智能体进行实际对话,完成一项任务,并在对话过程中提供背景信息、纠正错误并表达偏好。然后将可复用的模式提取出来,形成一项技能。请注意以下几点:

  • ​行之有效的步骤​——最终取得成功的一系列行动

  • ​您所做的更正​——您引导代理方法的地方(例如,“使用库 X 代替 Y”、“检查边界情况 Z”)。

  • ​输入/输出格式​——输入和输出的数据格式

  • ​您提供的背景信息​——代理人原本不知道的项目特定事实、惯例或限制。

从现有项目成果中综合分析

当您拥有丰富的现有知识时,可以将其输入到学习型学习模型 (LLM) 中,并要求其综合生成一项技能。例如,根据团队实际的事件报告和运行手册综合生成的数据管道技能,将优于根据通用的“数据工程最佳实践”文章综合生成的技能,因为它包含了您团队的模式、故障模式和恢复流程。关键在于项目特定的材料,而不是通用的参考资料。优质素材包括:

  • 内部文档、操作手册和风格指南

  • API 规范、模式和配置文件

  • 代码审查意见和问题跟踪器(记录反复出现的问题和审查者的期望)

  • 版本控制历史记录,特别是补丁和修复程序(通过实际更改的内容揭示模式)

  • 现实世界中的失败案例及其解决方案

通过实际执行进行改进

技能的初稿通常需要完善。将技能应用于实际任务,然后将所有结果(不仅仅是失败的结果)反馈到创建过程中。问问自己:是什么导致了误报?遗漏了什么?哪些内容可以精简?即使只执行一次再修改,也能明显提高质量,而复杂的领域通常会受益于多次执行。

阅读代理的执行轨迹,而不仅仅是最终输出。如果代理在无效步骤上浪费时间,常见原因包括指令过于模糊(代理尝试多种方法才找到有效方法)、指令不适用于当前任务(但代理仍然执行),或者提供的选项过多而没有明确的默认值。

对于更结构化的迭代方法,包括测试用例、断言和评分,请参阅评估技能输出质量

明智地利用空间

技能激活后,其全部 SKILL.md​ 内容会与对话历史记录、系统上下文和其他已激活的技能一起加载到智能体的上下文窗口中。技能中的每个元素都会与窗口中的所有其他内容争夺智能体的注意力。

补充代理所缺乏的,省略其已知的。

重点关注代理人如果没有你的专业技能就无法了解的内容:项目特定的惯例、领域特定的流程、不易察觉的极端情况,以及要使用的特定工具或 API。你无需解释 PDF 是什么、HTTP 的工作原理或数据库迁移的作用。

<!-- Too verbose — the agent already knows what PDFs are -->
## Extract PDF text

PDF (Portable Document Format) files are a common file format that contains
text, images, and other content. To extract text from a PDF, you'll need to
use a library. pdfplumber is recommended because it handles most cases well.

<!-- Better — jumps straight to what the agent wouldn't know on its own -->
## Extract PDF text

Use pdfplumber for text extraction. For scanned documents, fall back to
pdf2image with pytesseract.

```python
import pdfplumber

with pdfplumber.open("file.pdf") as pdf:
    text = pdf.pages[0].extract_text()
```

针对每条内容,问问自己:“如果没有这条指令,智能体会出错吗?” 如果答案是否定的,那就删掉它。如果不确定,那就测试一下。如果智能体在没有这项技能的情况下也能很好地完成整个任务,那么这项技能可能并没有增加价值。请参阅“评估技能输出质量”了解如何系统地进行测试。

设计连贯单元

决定一项技能应该涵盖哪些内容,就像决定一个函数应该做什么一样:你希望它能封装一个连贯的工作单元,并能与其他技能良好地协同工作。技能范围过窄会导致单个任务需要加载多个技能,从而增加系统开销并导致指令冲突。技能范围过宽则难以精确激活。例如,查询数据库并格式化结果的技能可能是一个连贯的工作单元,而同时涵盖数据库管理的技能则可能试图承担过多的功能。

力求做到适度详细。

过于全面的技能反而会适得其反——智能体难以提取相关信息,并且可能会因为指令与当前任务无关而走上无效路径。简洁明了、循序渐进的指导,并辅以可运行的示例,往往比详尽的文档更有效。当你发现自己涵盖了所有极端情况时,不妨思考一下,大多数情况是否更适合由智能体自行判断处理。

通过逐步披露来构建大型技能

规范建议代码行数和令牌数控制 SKILL.md​ 在 500 行以内,仅包含代理每次运行所需的核心指令。如果某个技能确实需要更多内容,请将详细的参考资料移至单独的文件 references/​ 或类似目录中。关键在于告诉代理何时加载每个文件。“references/api-errors.md​ 如果 API 返回非 200 状态码则读取”比通用的“详情请参阅 references/”更有用。这使得代理可以按需加载上下文,而不是预先加载,这正是渐进式披露的设计理念。

校准控制

并非技能的每个部分都需要相同程度的指导。指导的具体程度应与任务的复杂性相匹配。

匹配特异性与脆弱性

当存在多种有效方法且任务允许一定程度的差异时,应赋予智能体一定的自由度。对于灵活的指令,解释其原因 可能比生硬的命令更有效——理解指令背后目的的智能体能够做出更符合上下文的决策。代码审查技能可以描述需要检查的内容,而无需规定具体的步骤:

## Code review process

1. Check all database queries for SQL injection (use parameterized queries)
2. Verify authentication checks on every endpoint
3. Look for race conditions in concurrent code paths
4. Confirm error messages don't leak internal details

当操作脆弱、一致性至关重要或必须遵循特定顺序时,要采取规范性措施:

## Database migration

Run exactly this sequence:

```bash
python scripts/migrate.py --verify --backup
```

Do not modify the command or add additional flags.

重视程序而非声明。

技能应该教会智能体如何解决一类问题,而不是针对特定实例生成什么。对比:

<!-- Specific answer — only useful for this exact task -->
Join the `orders` table to `customers` on `customer_id`, filter where
`region = 'EMEA'`, and sum the `amount` column.

<!-- Reusable method — works for any analytical query -->
1. Read the schema from `references/schema.yaml` to find relevant tables
2. Join tables using the `_id` foreign key convention
3. Apply any filters from the user's request as WHERE clauses
4. Aggregate numeric columns as needed and format as a markdown table

这并不意味着技能不能包含具体细节——输出格式模板(参见“输出格式模板”)、诸如“绝不输出个人身份信息”之类的约束以及工具特定的说明都非常有用。关键在于,即使个别细节很具体,方法也应该具有普遍性。

有效指导的模式

这些是构建技能内容的可复用技巧。并非每个技能都需要用到所有这些技巧——选择适合你任务的技巧即可。

陷阱部分

许多技能中最有价值的内容是一系列“陷阱”——特定环境下的、违背合理假设的事实。这些并非泛泛的建议(“妥善处理错误”),而是针对智能体在没有被告知的情况下仍会犯的错误的具体纠正措施:

## Gotchas

- The `users` table uses soft deletes. Queries must include
  `WHERE deleted_at IS NULL` or results will include deactivated accounts.
- The user ID is `user_id` in the database, `uid` in the auth service,
  and `accountId` in the billing API. All three refer to the same value.
- The `/health` endpoint returns 200 as long as the web server is running,
  even if the database connection is down. Use `/ready` to check full
  service health.

将陷阱信息放在 SKILL.md​ 代理程序在遇到问题之前就能读取的位置。如果告诉代理程序何时加载单独的参考文件,那么这样做是可行的;但对于一些不明显的问题,代理程序可能无法识别触发条件。

当代理人犯错需要纠正时,请将纠正措施添加到“陷阱”部分。这是迭代改进技能最直接的方法之一(参见“通过实际操作进行改进”)。

输出格式模板

当您需要代理以特定格式生成输出时,请提供一个模板。这比用文字描述格式更可靠,因为代理能够很好地匹配具体的结构。较短的模板可以内联存储在代码中 SKILL.md​;对于较长的模板或仅在特定情况下需要的模板,请将其存储在代码中 assets/​,并在需要时从代码中引用,SKILL.md​ 以便仅在需要时加载。

## Report structure

Use this template, adapting sections as needed for the specific analysis:

```markdown
# [Analysis Title]

## Executive summary
[One-paragraph overview of key findings]

## Key findings
- Finding 1 with supporting data
- Finding 2 with supporting data

## Recommendations
1. Specific actionable recommendation
2. Specific actionable recommendation
```

多步骤工作流程的检查清单

明确的检查清单有助于代理跟踪进度并避免跳过步骤,尤其是在步骤有依赖关系或验证门的情况下。

## Form processing workflow

Progress:
- [ ] Step 1: Analyze the form (run `scripts/analyze_form.py`)
- [ ] Step 2: Create field mapping (edit `fields.json`)
- [ ] Step 3: Validate mapping (run `scripts/validate_fields.py`)
- [ ] Step 4: Fill the form (run `scripts/fill_form.py`)
- [ ] Step 5: Verify output (run `scripts/verify_output.py`)

验证循环

指示代理在继续执行下一步操作之前验证自身的工作。流程如下:执行工作,运行验证器(脚本、参考清单或自检),修复任何问题,然后重复此过程直至验证通过。

## Editing workflow

1. Make your edits
2. Run validation: `python scripts/validate.py output/`
3. If validation fails:
   - Review the error message
   - Fix the issues
   - Run validation again
4. Only proceed when validation passes

参考文件还可以作为“验证器”——指示代理人在最终定稿前对照参考文件检查其工作。

计划-验证-执行

对于批量或破坏性操作,让代理以结构化格式创建中间计划,根据真理来源验证该计划,然后才执行。

## PDF form filling

1. Extract form fields: `python scripts/analyze_form.py input.pdf` → `form_fields.json`
   (lists every field name, type, and whether it's required)
2. Create `field_values.json` mapping each field name to its intended value
3. Validate: `python scripts/validate_fields.py form_fields.json field_values.json`
   (checks that every field name exists in the form, types are compatible, and
   required fields aren't missing)
4. If validation fails, revise `field_values.json` and re-validate
5. Fill the form: `python scripts/fill_form.py input.pdf field_values.json output.pdf`

关键在于步骤 3:一个验证脚本,用于将计划(field_values.json​)与真实数据源(form_fields.json​)进行比对。诸如“找不到字段‘signature_date’——可用字段:customer_name、order_total、signature_date_signed”之类的错误会为代理提供足够的信息来进行自我纠正。

打包可重用脚本

迭代开发某个技能时,比较代理在不同测试用例中的执行轨迹。如果您发现代理每次运行都在独立地重复相同的逻辑——构建图表、解析特定格式、验证输出——则表明需要编写一次经过测试的脚本并将其打包进去 scripts/​。有关设计和打包脚本的更多信息,请参阅“在技能中使用脚本”

优化技能描述

技能只有在被激活时才能发挥作用。前置元数据 description​ 中的字段 SKILL.md​ 是代理用来决定是否为特定任务加载技能的主要机制。描述不够具体会导致技能在应该触发时不会触发;描述过于宽泛则会导致技能在不应该触发时触发。本指南介绍了如何系统地测试和改进技能描述,以提高触发准确性。

技能触发机制

智能体采用渐进式披露来管理上下文。启动时,它们仅加载每个可用技能的描述 name​ 和 description​ 功能——仅足以判断技能何时可能相关。当用户的任务与描述匹配时,智能体会读取完整的描述 SKILL.md​ 并将其融入上下文,然后执行相应的指令。这意味着技能描述承担了触发该技能的全部责任。如果描述没有说明技能何时有用,角色就不会知道该如何使用它。一个重要的细节是:智能体通常只在需要超出其自身能力范围的知识或技能时才会调用相应的技能。例如,像“阅读此 PDF”这样简单的单步请求,即使描述完全匹配,也可能不会触发 PDF 技能,因为智能体可以使用基本工具完成。而对于涉及专业知识的任务——例如不熟悉的 API、特定领域的工作流程或不常见的格式——一份精心编写的任务描述就显得至关重要。

撰写有效的描述

在进行测试之前,了解好的描述是什么样的很有帮助。以下是一些原则:

  • 使用祈使句。将描述写成给行动者的指令:“在……时使用此技能”,而不是“此技能可以……”。行动者正在决定是否采取行动,所以要告诉它何时采取行动。

  • 关注用户意图,而非具体实现。描述用户想要达成的目标,而非技能的内部机制。智能体应与用户的需求进行匹配。

  • 宁可强势一些。明确列出该技能适用的情境,包括用户没有直接提及领域的情况:“即使他们没有明确提到‘CSV’或‘分析’。”

  • 务必简洁。通常几句话到一个短段落就足够了——既要足以涵盖技能范围,又要避免在涉及多个技能时造成智能体上下文信息过载。规范强制规定字数上限为 1024 个字符。

设计触发式求值查询

要测试触发功能,你需要一组评估查询——真实的用户提示,并标明它们是否应该触发你的技能。eval_queries.json(eval_queries.json)

[
  { "query": "I've got a spreadsheet in ~/data/q4_results.xlsx with revenue in col C and expenses in col D — can you add a profit margin column and highlight anything under 10%?", "should_trigger": true },
  { "query": "whats the quickest way to convert this json file to yaml", "should_trigger": false }
]

目标是编写大约 20 个查询:8-10 个应该触发,8-10 个不应该触发。

应该触发的查询

这些测试旨在检验描述是否涵盖了技能的范围。请从以下几个方面进行调整:

  • ​措辞​:有些正式,有些随意,有些有拼写错误或缩写。

  • ​明确性​:有些直接指出技能的领域(“分析此 CSV”),有些则描述需求而不指明领域(“我的老板想要根据此数据文件生成图表”)。

  • ​细节​:将简洁的提示与包含丰富上下文的提示结合起来——例如,简短的“分析我的销售 CSV 并制作图表”,以及包含文件路径、列名和背景信息的较长消息。

  • ​复杂性​:改变步骤数和决策点数。在多步骤工作流程中加入单步任务,以测试智能体能否在任务隐藏于更大流程中时,识别出相关技能。

最有用的“应该触发”查询是那些技能确实有用,但单凭查询本身却无法明显看出其作用的查询。在这种情况下,描述措辞至关重要——如果查询已经明确询问了技能的具体功能,那么任何合理的描述都能触发该查询。

不应触发的查询

最有价值的否定测试用例是那些“差一点就对”的测试用例——这些查询与你的技能共享关键词或概念,但实际上需要的是不同的内容。这些测试用例可以检验描述是否精确,而不仅仅是宽泛。对于 CSV 分析技能而言,较弱的反面例子包括:

  • ​"Write a fibonacci function"​— 显然无关紧要,测试不到任何东西。

  • ​"What's the weather today?"​— 没有关键词重叠,太容易了。

反面例子:

  • ​"I need to update the formulas in my Excel budget spreadsheet"​— 共享“电子表格”和“数据”概念,但需要 Excel 编辑,而不是 CSV 分析。

  • ​"can you write a python script that reads a csv and uploads each row to our postgres database"​— 涉及 CSV,但任务是数据库 ETL,而不是分析。

提高真实感的技巧

真实用户提示包含通用测试查询所缺乏的上下文信息。例如:

  • 文件路径(~/Downloads/report_final_v2.xlsx​)

  • 个人背景("my manager asked me to..."​)

  • 具体细节(列名、公司名称、数据值)

  • 语言随意,使用缩写,偶尔出现拼写错误

测试描述是否触发

基本方法:在安装了相应技能的代理程序中运行每个查询,并观察代理程序是否调用了该技能。确保该技能已注册并可被代理程序发现——具体实现方式因客户端而异(例如,技能目录、配置文件或命令行标志)。大多数代理客户端都提供某种形式的可观测性——执行日志、工具调用历史记录或详细输出——让您可以查看运行期间调用了哪些技能。请查阅客户端文档了解详情。如果代理加载了您的技能,则该技能会被触发 SKILL.md​;如果代理在未调用该技能的情况下继续执行,则不会触发该技能。如果满足以下条件,则查询“通过”:

  • ​should_trigger​ 是 true​ 并且该技能被调用,或者

  • ​should_trigger​ 是 false​,但该技能并未被使用。

多次运行

模型行为是不确定的——同一个查询可能在一次运行中触发该技能,但在下一次运行中却不会。多次运行每个查询(3 次是一个合理的起始次数),并计算​触发率​:即技能被调用的运行次数所占的比例。如果触发率高于某个阈值(0.5 是一个合理的默认值),则应触发查询通过;如果触发率低于该阈值,则不应触发查询通过。20 个查询,每个查询运行 3 次,总共需要 60 次调用。你需要编写脚本来完成这些操作。以下是大致结构——将 claude​ 调用和检测逻辑替换 check_triggered​ 为你代理客户端提供的具体逻辑:

#!/bin/bash
QUERIES_FILE="${1:?Usage: $0 <queries.json>}"
SKILL_NAME="my-skill"
RUNS=3

# This example uses Claude Code's JSON output to check for Skill tool calls.
# Replace this function with detection logic for your agent client.
# Should return 0 (success) if the skill was invoked, 1 otherwise.
check_triggered() {
  local query="$1"
  claude -p "$query" --output-format json 2>/dev/null \
    | jq -e --arg skill "$SKILL_NAME" \
      'any(.messages[].content[]; .type == "tool_use" and .name == "Skill" and .input.skill == $skill)' \
      > /dev/null 2>&1
}

count=$(jq length "$QUERIES_FILE")
for i in $(seq 0 $((count - 1))); do
  query=$(jq -r ".[$i].query" "$QUERIES_FILE")
  should_trigger=$(jq -r ".[$i].should_trigger" "$QUERIES_FILE")
  triggers=0

  for run in $(seq 1 $RUNS); do
    check_triggered "$query" && triggers=$((triggers + 1))
  done

  jq -n \
    --arg query "$query" \
    --argjson should_trigger "$should_trigger" \
    --argjson triggers "$triggers" \
    --argjson runs "$RUNS" \
    '{query: $query, should_trigger: $should_trigger, triggers: $triggers, runs: $runs, trigger_rate: ($triggers / $runs)}'
done | jq -s '.'

如果您的代理客户支持,您可以在结果明确后提前结束评估——无论是代理是否使用了该技能,还是已经开始不使用该技能进行工作。这可以显著减少运行完整评估集所需的时间和成本。

通过训练/验证集划分避免过拟合

如果针对所有查询优化描述,则有过度拟合的风险——即创建出一个适用于这些特定短语但对新短语无效的描述。解决方法是拆分查询集:

  • ​训练集(约 60%)​:用于识别故障和指导改进的查询。

  • ​验证集(约 40%)​:您预留出来仅用于检查改进是否具有普遍性的查询。

确保两个数据集都包含比例合适的触发查询和不触发查询——不要不小心把所有触发查询都放在一个数据集里。随机打乱数据集顺序,并在每次迭代中保持数据集的划分不变,这样才能确保比较的是同类数据。如果你使用像上面这样的脚本,你可以将查询拆分成两个文件——train_queries.json​ 和 validation_queries.json​——并分别对每个文件运行脚本。

优化循环

  1. 在训练集和验证集上评估当前描述。训练集的结果指导你的修改;验证集的结果告诉你这些修改是否具有泛化能力。

  2. 识别训练集中的失败案例:哪些应该触发的查询没有触发?哪些不应该触发的查询触发了?

    • 仅使用训练集失败来指导您的更改——无论您是自己修改描述还是触发 LLM,都不要将验证集结果纳入此过程。

  3. 修改描述。重点在于概括:

    • 如果“应触发”查询失败,则可能是描述过于狭窄。请扩大范围或添加有关该技能适用场景的上下文信息。

    • 如果“不应触发”查询出现误触发,则可能是描述过于宽泛。请具体说明该技能不包含哪些内容,或明确该技能与相邻能力之间的界限。

    • 避免直接添加失败查询中的特定关键词——这会导致过度拟合。相反,应该找到这些查询所代表的总体类别或概念,并针对该类别或概念进行优化。

    • 如果多次修改后仍然卡住,不妨尝试从结构上改变描述方式,而不是进行细微的调整。不同的框架或句式结构或许能突破常规的修改限制。

    • 请检查描述是否不超过 1024 个字符的限制——描述在优化过程中往往会变长。

  4. 重复步骤 1-3,直到所有训练集查询都通过或不再看到有意义的改进为止。

  5. 根据验证通过率(即验证集中通过的查询所占的比例)选择最佳迭代。请注意,最佳描述可能并非您生成的最后一个描述;较早的迭代可能比那些过度拟合训练集的后续迭代具有更高的验证通过率。

通常五次迭代就足够了。如果性能没有提高,问题可能出在查询上(太简单、太难或标签不清晰),而不是描述上。

skill-creator​ 技能可自动完成此循环的端到端操作:它拆分评估集,并行评估触发率,使用 Claude 提出描述改进建议,并生成一个实时 HTML 报告,您可以在运行过程中观看该报告。

应用结果

一旦你选定了最佳描述:

  1. 更新元数据 description​ 中的字段 SKILL.md​。

  2. 请确认描述字数不超过 1024 个字符

  3. 验证描述是否按预期触发。手动尝试几个提示进行快速检查。为了进行更严格的测试,编写 5-10 个新的查询(包含应该触发和不应该触发的查询),并通过评估脚本运行它们——由于这些查询从未参与优化过程,因此它们可以真实地检验描述是否具有普遍适用性。

前后对比:

# Before
description: Process CSV files.

# After
description: >
  Analyze CSV and tabular data files — compute summary statistics,
  add derived columns, generate charts, and clean messy data. Use this
  skill when the user has a CSV, TSV, or Excel file and wants to
  explore, transform, or visualize the data, even if they don't
  explicitly mention "CSV" or "analysis."

改进后的描述更具体地说明了该技能的功能(汇总统计、派生列、图表、清理),并且更广泛地说明了它的适用范围(CSV、TSV、Excel;即使没有明确的关键字)。

评估技能产出质量

你编写了一项技能,并尝试将其应用于某个题目,结果似乎有效。但它是否稳定可靠——在各种不同的题目中、在特殊情况下,是否比没有这项技能更好?进行结构化评估(评估)可以解答这些问题,并为你提供一个反馈循环,从而系统地改进这项技能。

设计测试用例

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

  • ​提示​:一条真实的用户消息——就像有人实际会输入的那种消息。

  • ​预期输出​:对成功应有的样子进行人类可读的描述。

  • ​输入文件​(可选):该技能需要处理的文件。

将测试用例存储在 evals/evals.json​ 您的技能目录中: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”)。

  • 涵盖特殊情况。至少包含一个测试边界条件的提示——例如格式错误的输入、不寻常的请求,或者技能指令可能存在歧义的情况。

  • 使用真实情境。真实用户会提及文件路径、列名和个人信息。像“处理此数据”这样的提示过于笼统,无法进行任何有意义的测试。

暂时不用担心定义具体的通过/失败检查——只需定义提示和预期输出即可。在查看首次运行结果后,再添加详细的检查(称为断言)。

运行评估

核心模式是将每个测试用例运行两次:一次​使用新技能​,一次不使用新技能(或使用之前的版本)。这样就能得到一个基准线,以便进行比较。

工作区结构

将评估结果整理到与技能目录并列的工作区目录中。每次完整评估循环都会生成一个单独的 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/       # 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 文件(grading.json​、timing.json​、benchmark.json​)是在评估过程中生成的——由代理、脚本或您生成。

生成跑

每次评估运行都应从一个干净的上下文开始——不包含之前运行或技能开发过程中的任何残留状态。这确保智能体只执行指令 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-path> <workspace>/skill-snapshot/​),将基准运行指向该快照,然后保存到而 old_skill/outputs/​ 不是 without_skill/​。

采集时间数据

计时数据可以帮助您比较技能相对于基准技能所需的时间和代币消耗——一项能显著提高产出质量但代币消耗量增加两倍的技能,与一项既更好又更便宜的技能,其权衡取舍截然不同。每次运行结束后,请记录代币数量和持续时间:timing.json(timing.json)

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

在 Claude Code 中,当子代理任务完成时,任务完成通知包含 <command> total_tokens​ 和 <command> duration_ms​。请立即保存这些值——它们不会持久保存到其他任何地方。

写作断言

断言是对输出结果应包含或达到的可验证陈述。在看到第一轮输出结果后再添加断言——通常情况下,在技能运行完毕之前,你无法确定“良好”的结果是什么样子。合理的论断:

  • ​"The output file is valid JSON"​— 可通过程序进行验证。

  • ​"The bar chart has labeled axes"​— 具体且可观察。

  • ​"The report includes at least 3 recommendations"​— 可数。

论断薄弱:

  • ​"The output is good"​——过于笼统,无法评分。

  • ​"The output uses exactly the phrase 'Total Revenue: $X'"​— 太脆弱了;用不同的措辞输出正确的结果会失败。

并非所有内容都需要断言。有些特质——例如写作风格、视觉设计、输出结果是否“合意”——很难分解成简单的合格/不合格判断。这些最好通过人工审核来发现。断言应该只用于那些可以客观验证的内容。在每个测试用例中添加断言 evals/evals.json​: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"
      ]
    }
  ]
}

评分输出

评分是指根据实际输出结果评估每一项论断,并记录“通过”或​“未通过”​,同时提供具体证据。证据应引用或参考输出结果,而不仅仅是陈述个人观点。最简单的方法是将输出和断言交给逻辑逻辑管理器 (LLM),让它逐一评估。对于可以通过代码检查的断言(例如有效的 JSON、正确的行数、文件存在且尺寸符合预期),可以使用验证脚本——脚本在机械检查方面比 LLM 的判断更可靠,并且可以在迭代中重复使用。grading.json(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
  }
}

评分原则

  • 必须提供确凿证据才能判定为合格。不要轻易相信任何信息。如果声明中写着“包含摘要”,而输出结果中只有一个名为“摘要”的部分,且内容含糊不清,那就是不合格——标签虽有,但内容空洞。

  • 不仅要检查结果,还要检查断言本身。评分时,注意哪些断言过于简单(无论技能水平如何都通过)、过于困难(即使输出良好也总是失败)或无法验证(仅凭输出无法验证)。在下次迭代中修正这些问题。

要比较两个技能版本,可以尝试​盲评​:将两个版本都提交给一位 LLM(法学硕士)评审,但不透露哪个版本来自哪个作品。评审会根据自己的评分标准,从整体质量(包括组织结构、格式、易用性和润色程度)进行评分,不受哪个版本“应该”更好的主观因素影响。这种方法可以补充对断言的评分:两个作品可能都符合所有断言,但在整体质量上却存在显著差异。

汇总结果

每次迭代运行结束后,计算每个配置的汇总统计信息,并将其保存到 benchmark.json​ 评估目录旁边(例如,csv-analyzer-workspace/iteration-1/benchmark.json​):基准测试.json(基准测试.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​ 会告诉你技能的成本(更多时间、更多代币)以及它带来的收益(更高的通过率)。一项增加 13 秒但能提升 50 个百分点通过率的技能可能值得学习。而一项代币消耗翻倍但仅提升 2 个百分点通过率的技能可能就不值得了。

标准差(stddev​)只有在每次评估运行多次时才有意义。在早期迭代中,如果只有 2-3 个测试用例且每次运行仅一次,则应关注原始通过次数和差值——随着测试集的扩展和每次评估运行次数的增加,这些统计指标才会变得有用。

分析模式

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

  • 移除或替换在两种配置下都始终通过的断言。这些断言没有任何实际意义——即使没有技能,模型也能很好地处理它们。它们虚高了有技能情况下的通过率,却没有反映技能的实际价值。

  • 调查在两种配置下都始终失败的断言。要么是断言本身有问题(要求模型无法执行的操作),要么是测试用例过于复杂,要么是断言检查的内容有误。在下次迭代之前修复这些问题。

  • 研究那些运用该技能后能通过而不用该技能则无法通过的论断。这清楚地表明了该技能的价值所在。理解​其中的原因​——哪些指令或脚本起到了关键作用?

  • 如果不同运行结果不一致,则需要收紧指令。如果同一评估有时通过有时失败(stddev​ 在基准测试中表现为高分),则可能是评估本身不稳定(对模型随机性敏感),或者技能指令过于模糊,导致模型每次的解读都不同。添加示例或更具体的指导以减少歧义。

  • 检查时间和令牌异常值。如果某个评估耗时是其他评估的 3 倍,则读取其执行记录(模型运行期间执行操作的完整日志)以找出瓶颈。

与人工审核结果

断言评分和模式分析可以发现很多问题,但它们只能检查你编写断言时预期的结果。人工审核则能带来全新的视角——发现你未曾预料到的问题,注意到输出结果虽然技术上正确但却偏离了重点,或者发现那些难以用简单的通过/失败检查来表达的问题。对于每个测试用例,请将实际输出结果与评分进行对比。记录每个测试用例的具体反馈,并将其保存在工作区中(例如,与 feedback.json​ 评估目录放在一起):feedback.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": ""
}

“图表缺少坐标轴标签”这样的反馈是可操作的;“看起来很糟糕”则不是。空的反馈意味着输出看起来没问题——该测试用例通过了你的审核。在迭代阶段,你应该重点改进那些你提出具体问题的测试用例。

不断改进这项技能

经过评分和审核后,您将获得三个信号来源:

  • 失败的断言指出了具体的缺陷——缺少步骤、指令不清楚,或者技能无法处理的情况。

  • 人类的反馈指出了更广泛的质量问题——方法错误、输出结构不良,或者技能产生了技术上正确但无用的结果。

  • 执行记录揭示了出错的​原因​。如果代理忽略了某条指令,则该指令可能存在歧义。如果代理在无效步骤上花费了时间,则可能需要简化或删除这些指令。

将这些信号转化为技能改进的最有效方法是,将这三个信号(包括当前信号)提供 SKILL.md​ 给语言学习模型(LLM),并要求其提出改进建议。LLM 可以综合分析失败的断言、审阅者的投诉以及文本记录行为中的模式,而手动关联这些模式将非常繁琐。在向 LLM 发出指令时,请遵循以下准则:

  • 根据反馈进行归纳总结。这项技能将应用于许多不同的提示,而不仅仅是测试用例。修复方案应着眼于根本问题,而不是针对特定示例添加狭隘的补丁。

  • 保持技能的精简。更少但更优质的指令通常比详尽的规则更有效。如果记录显示存在浪费时间的工作(不必要的验证、不必要的中间输出),请删除这些指令。如果即使增加规则后通过率仍然停滞不前,则该技能可能过于复杂——尝试删除一些指令,看看结果是否保持不变或有所提高。

  • 解释原因。基于推理的指令(“做 X 是因为 Y 往往会导致 Z”)比生硬的命令(“总是做 X,永远不要做 Y”)效果更好。当孩子们理解指令的目的时,他们会更可靠地遵循指令。

  • 将重复性工作打包。如果每次测试运行都独立编写了类似的辅助脚本(例如图表生成器、数据解析器),则表明应该将这些脚本打包到技能 scripts/​ 目录中。有关具体操作方法,请参阅“使用脚本”部分。

循环

  1. 将评估信号和电流输入 SKILL.md​ 到 LLM 中,并要求它提出改进建议。

  2. 审核并应用更改。

  3. 在新目录中重新运行所有测试用例 iteration-<N+1>/​。

  4. 对新结果进行评分和汇总。

  5. 请人工审核。重复此步骤。

最有用的“应该触发”查询是那些技能确实有用,但单凭查询本身却无法明显看出其作用的查询。在这种情况下,描述措辞至关重要——如果查询已经明确询问了技能的具体功能,那么任何合理的描述都能触发该查询。

不应触发的查询

最有价值的否定测试用例是那些“差一点就对”的测试用例——这些查询与你的技能共享关键词或概念,但实际上需要的是不同的内容。这些测试用例可以检验描述是否精确,而不仅仅是宽泛。对于 CSV 分析技能而言,较弱的反面例子包括:

  • ​"Write a fibonacci function"​— 显然无关紧要,测试不到任何东西。

  • ​"What's the weather today?"​— 没有关键词重叠,太容易了。

反面例子:

  • ​"I need to update the formulas in my Excel budget spreadsheet"​— 共享“电子表格”和“数据”概念,但需要 Excel 编辑,而不是 CSV 分析。

  • ​"can you write a python script that reads a csv and uploads each row to our postgres database"​— 涉及 CSV,但任务是数据库 ETL,而不是分析。

提高真实感的技巧

真实用户提示包含通用测试查询所缺乏的上下文信息。例如:

  • 文件路径(~/Downloads/report_final_v2.xlsx​)

  • 个人背景("my manager asked me to..."​)

  • 具体细节(列名、公司名称、数据值)

  • 语言随意,使用缩写,偶尔出现拼写错误

测试描述是否触发

基本方法:在安装了相应技能的代理程序中运行每个查询,并观察代理程序是否调用了该技能。确保该技能已注册并可被代理程序发现——具体实现方式因客户端而异(例如,技能目录、配置文件或命令行标志)。大多数代理客户端都提供某种形式的可观测性——执行日志、工具调用历史记录或详细输出——让您可以查看运行期间调用了哪些技能。请查阅客户端文档了解详情。如果代理加载了您的技能,则该技能会被触发 SKILL.md​;如果代理在未调用该技能的情况下继续执行,则不会触发该技能。如果满足以下条件,则查询“通过”:

  • ​should_trigger​ 是 true​ 并且该技能被调用,或者

  • ​should_trigger​ 是 false​,但该技能并未被使用。

多次运行

模型行为是不确定的——同一个查询可能在一次运行中触发该技能,但在下一次运行中却不会。多次运行每个查询(3 次是一个合理的起始次数),并计算​触发率​:即技能被调用的运行次数所占的比例。如果触发率高于某个阈值(0.5 是一个合理的默认值),则应触发查询通过;如果触发率低于该阈值,则不应触发查询通过。20 个查询,每个查询运行 3 次,总共需要 60 次调用。你需要编写脚本来完成这些操作。以下是大致结构——将 claude​ 调用和检测逻辑替换 check_triggered​ 为你代理客户端提供的具体逻辑:

#!/bin/bash
QUERIES_FILE="${1:?Usage: $0 <queries.json>}"
SKILL_NAME="my-skill"
RUNS=3

# This example uses Claude Code's JSON output to check for Skill tool calls.
# Replace this function with detection logic for your agent client.
# Should return 0 (success) if the skill was invoked, 1 otherwise.
check_triggered() {
  local query="$1"
  claude -p "$query" --output-format json 2>/dev/null \
    | jq -e --arg skill "$SKILL_NAME" \
      'any(.messages[].content[]; .type == "tool_use" and .name == "Skill" and .input.skill == $skill)' \
      > /dev/null 2>&1
}

count=$(jq length "$QUERIES_FILE")
for i in $(seq 0 $((count - 1))); do
  query=$(jq -r ".[$i].query" "$QUERIES_FILE")
  should_trigger=$(jq -r ".[$i].should_trigger" "$QUERIES_FILE")
  triggers=0

  for run in $(seq 1 $RUNS); do
    check_triggered "$query" && triggers=$((triggers + 1))
  done

  jq -n \
    --arg query "$query" \
    --argjson should_trigger "$should_trigger" \
    --argjson triggers "$triggers" \
    --argjson runs "$RUNS" \
    '{query: $query, should_trigger: $should_trigger, triggers: $triggers, runs: $runs, trigger_rate: ($triggers / $runs)}'
done | jq -s '.'

如果您的代理客户支持,您可以在结果明确后提前结束评估——无论是代理是否使用了该技能,还是已经开始不使用该技能进行工作。这可以显著减少运行完整评估集所需的时间和成本。

通过训练/验证集划分避免过拟合

如果针对所有查询优化描述,则有过度拟合的风险——即创建出一个适用于这些特定短语但对新短语无效的描述。解决方法是拆分查询集:

  • ​训练集(约 60%)​:用于识别故障和指导改进的查询。

  • ​验证集(约 40%)​:您预留出来仅用于检查改进是否具有普遍性的查询。

确保两个数据集都包含比例合适的触发查询和不触发查询——不要不小心把所有触发查询都放在一个数据集里。随机打乱数据集顺序,并在每次迭代中保持数据集的划分不变,这样才能确保比较的是同类数据。如果你使用像上面这样的脚本,你可以将查询拆分成两个文件——train_queries.json​ 和 validation_queries.json​——并分别对每个文件运行脚本。

优化循环

  1. 在训练集和验证集上评估当前描述。训练集的结果指导你的修改;验证集的结果告诉你这些修改是否具有泛化能力。

  2. 识别训练集中的失败案例:哪些应该触发的查询没有触发?哪些不应该触发的查询触发了?

    • 仅使用训练集失败来指导您的更改——无论您是自己修改描述还是触发 LLM,都不要将验证集结果纳入此过程。

  3. 修改描述。重点在于概括:

    • 如果“应触发”查询失败,则可能是描述过于狭窄。请扩大范围或添加有关该技能适用场景的上下文信息。

    • 如果“不应触发”查询出现误触发,则可能是描述过于宽泛。请具体说明该技能不包含哪些内容,或明确该技能与相邻能力之间的界限。

    • 避免直接添加失败查询中的特定关键词——这会导致过度拟合。相反,应该找到这些查询所代表的总体类别或概念,并针对该类别或概念进行优化。

    • 如果多次修改后仍然卡住,不妨尝试从结构上改变描述方式,而不是进行细微的调整。不同的框架或句式结构或许能突破常规的修改限制。

    • 请检查描述是否不超过 1024 个字符的限制——描述在优化过程中往往会变长。

  4. 重复步骤 1-3,直到所有训练集查询都通过或不再看到有意义的改进为止。

  5. 根据验证通过率(即验证集中通过的查询所占的比例)选择最佳迭代。请注意,最佳描述可能并非您生成的最后一个描述;较早的迭代可能比那些过度拟合训练集的后续迭代具有更高的验证通过率。

通常五次迭代就足够了。如果性能没有提高,问题可能出在查询上(太简单、太难或标签不清晰),而不是描述上。

skill-creator​ 技能可自动完成此循环的端到端操作:它拆分评估集,并行评估触发率,使用 Claude 提出描述改进建议,并生成一个实时 HTML 报告,您可以在运行过程中观看该报告。

应用结果

一旦你选定了最佳描述:

  1. 更新元数据 description​ 中的字段 SKILL.md​。

  2. 请确认描述字数不超过 1024 个字符

  3. 验证描述是否按预期触发。手动尝试几个提示进行快速检查。为了进行更严格的测试,编写 5-10 个新的查询(包含应该触发和不应该触发的查询),并通过评估脚本运行它们——由于这些查询从未参与优化过程,因此它们可以真实地检验描述是否具有普遍适用性。

前后对比:

# Before
description: Process CSV files.

# After
description: >
  Analyze CSV and tabular data files — compute summary statistics,
  add derived columns, generate charts, and clean messy data. Use this
  skill when the user has a CSV, TSV, or Excel file and wants to
  explore, transform, or visualize the data, even if they don't
  explicitly mention "CSV" or "analysis."

改进后的描述更具体地说明了该技能的功能(汇总统计、派生列、图表、清理),并且更广泛地说明了它的适用范围(CSV、TSV、Excel;即使没有明确的关键字)。

评估技能产出质量

你编写了一项技能,并尝试将其应用于某个题目,结果似乎有效。但它是否稳定可靠——在各种不同的题目中、在特殊情况下,是否比没有这项技能更好?进行结构化评估(评估)可以解答这些问题,并为你提供一个反馈循环,从而系统地改进这项技能。

设计测试用例

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

  • ​提示​:一条真实的用户消息——就像有人实际会输入的那种消息。

  • ​预期输出​:对成功应有的样子进行人类可读的描述。

  • ​输入文件​(可选):该技能需要处理的文件。

将测试用例存储在 evals/evals.json​ 您的技能目录中: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”)。

  • 涵盖特殊情况。至少包含一个测试边界条件的提示——例如格式错误的输入、不寻常的请求,或者技能指令可能存在歧义的情况。

  • 使用真实情境。真实用户会提及文件路径、列名和个人信息。像“处理此数据”这样的提示过于笼统,无法进行任何有意义的测试。

暂时不用担心定义具体的通过/失败检查——只需定义提示和预期输出即可。在查看首次运行结果后,再添加详细的检查(称为断言)。

运行评估

核心模式是将每个测试用例运行两次:一次​使用新技能​,一次不使用新技能(或使用之前的版本)。这样就能得到一个基准线,以便进行比较。

工作区结构

将评估结果整理到与技能目录并列的工作区目录中。每次完整评估循环都会生成一个单独的 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/       # 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 文件(grading.json​、timing.json​、benchmark.json​)是在评估过程中生成的——由代理、脚本或您生成。

生成跑

每次评估运行都应从一个干净的上下文开始——不包含之前运行或技能开发过程中的任何残留状态。这确保智能体只执行指令 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-path> <workspace>/skill-snapshot/​),将基准运行指向该快照,然后保存到而 old_skill/outputs/​ 不是 without_skill/​。

采集时间数据

计时数据可以帮助您比较技能相对于基准技能所需的时间和代币消耗——一项能显著提高产出质量但代币消耗量增加两倍的技能,与一项既更好又更便宜的技能,其权衡取舍截然不同。每次运行结束后,请记录代币数量和持续时间:timing.json(timing.json)

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

在 Claude Code 中,当子代理任务完成时,任务完成通知包含 <command> total_tokens​ 和 <command> duration_ms​。请立即保存这些值——它们不会持久保存到其他任何地方。

写作断言

断言是对输出结果应包含或达到的可验证陈述。在看到第一轮输出结果后再添加断言——通常情况下,在技能运行完毕之前,你无法确定“良好”的结果是什么样子。合理的论断:

  • ​"The output file is valid JSON"​— 可通过程序进行验证。

  • ​"The bar chart has labeled axes"​— 具体且可观察。

  • ​"The report includes at least 3 recommendations"​— 可数。

论断薄弱:

  • ​"The output is good"​——过于笼统,无法评分。

  • ​"The output uses exactly the phrase 'Total Revenue: $X'"​— 太脆弱了;用不同的措辞输出正确的结果会失败。

并非所有内容都需要断言。有些特质——例如写作风格、视觉设计、输出结果是否“合意”——很难分解成简单的合格/不合格判断。这些最好通过人工审核来发现。断言应该只用于那些可以客观验证的内容。在每个测试用例中添加断言 evals/evals.json​: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"
      ]
    }
  ]
}

评分输出

评分是指根据实际输出结果评估每一项论断,并记录“通过”或​“未通过”​,同时提供具体证据。证据应引用或参考输出结果,而不仅仅是陈述个人观点。最简单的方法是将输出和断言交给逻辑逻辑管理器 (LLM),让它逐一评估。对于可以通过代码检查的断言(例如有效的 JSON、正确的行数、文件存在且尺寸符合预期),可以使用验证脚本——脚本在机械检查方面比 LLM 的判断更可靠,并且可以在迭代中重复使用。grading.json(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
  }
}

评分原则

  • 必须提供确凿证据才能判定为合格。不要轻易相信任何信息。如果声明中写着“包含摘要”,而输出结果中只有一个名为“摘要”的部分,且内容含糊不清,那就是不合格——标签虽有,但内容空洞。

  • 不仅要检查结果,还要检查断言本身。评分时,注意哪些断言过于简单(无论技能水平如何都通过)、过于困难(即使输出良好也总是失败)或无法验证(仅凭输出无法验证)。在下次迭代中修正这些问题。

要比较两个技能版本,可以尝试​盲评​:将两个版本都提交给一位 LLM(法学硕士)评审,但不透露哪个版本来自哪个作品。评审会根据自己的评分标准,从整体质量(包括组织结构、格式、易用性和润色程度)进行评分,不受哪个版本“应该”更好的主观因素影响。这种方法可以补充对断言的评分:两个作品可能都符合所有断言,但在整体质量上却存在显著差异。

汇总结果

每次迭代运行结束后,计算每个配置的汇总统计信息,并将其保存到 benchmark.json​ 评估目录旁边(例如,csv-analyzer-workspace/iteration-1/benchmark.json​):基准测试.json(基准测试.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​ 会告诉你技能的成本(更多时间、更多代币)以及它带来的收益(更高的通过率)。一项增加 13 秒但能提升 50 个百分点通过率的技能可能值得学习。而一项代币消耗翻倍但仅提升 2 个百分点通过率的技能可能就不值得了。

标准差(stddev​)只有在每次评估运行多次时才有意义。在早期迭代中,如果只有 2-3 个测试用例且每次运行仅一次,则应关注原始通过次数和差值——随着测试集的扩展和每次评估运行次数的增加,这些统计指标才会变得有用。

分析模式

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

  • 移除或替换在两种配置下都始终通过的断言。这些断言没有任何实际意义——即使没有技能,模型也能很好地处理它们。它们虚高了有技能情况下的通过率,却没有反映技能的实际价值。

  • 调查在两种配置下都始终失败的断言。要么是断言本身有问题(要求模型无法执行的操作),要么是测试用例过于复杂,要么是断言检查的内容有误。在下次迭代之前修复这些问题。

  • 研究那些运用该技能后能通过而不用该技能则无法通过的论断。这清楚地表明了该技能的价值所在。理解​其中的原因​——哪些指令或脚本起到了关键作用?

  • 如果不同运行结果不一致,则需要收紧指令。如果同一评估有时通过有时失败(stddev​ 在基准测试中表现为高分),则可能是评估本身不稳定(对模型随机性敏感),或者技能指令过于模糊,导致模型每次的解读都不同。添加示例或更具体的指导以减少歧义。

  • 检查时间和令牌异常值。如果某个评估耗时是其他评估的 3 倍,则读取其执行记录(模型运行期间执行操作的完整日志)以找出瓶颈。

与人工审核结果

断言评分和模式分析可以发现很多问题,但它们只能检查你编写断言时预期的结果。人工审核则能带来全新的视角——发现你未曾预料到的问题,注意到输出结果虽然技术上正确但却偏离了重点,或者发现那些难以用简单的通过/失败检查来表达的问题。对于每个测试用例,请将实际输出结果与评分进行对比。记录每个测试用例的具体反馈,并将其保存在工作区中(例如,与 feedback.json​ 评估目录放在一起):feedback.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": ""
}

“图表缺少坐标轴标签”这样的反馈是可操作的;“看起来很糟糕”则不是。空的反馈意味着输出看起来没问题——该测试用例通过了你的审核。在迭代阶段,你应该重点改进那些你提出具体问题的测试用例。

不断改进这项技能

经过评分和审核后,您将获得三个信号来源:

  • 失败的断言指出了具体的缺陷——缺少步骤、指令不清楚,或者技能无法处理的情况。

  • 人类的反馈指出了更广泛的质量问题——方法错误、输出结构不良,或者技能产生了技术上正确但无用的结果。

  • 执行记录揭示了出错的​原因​。如果代理忽略了某条指令,则该指令可能存在歧义。如果代理在无效步骤上花费了时间,则可能需要简化或删除这些指令。

将这些信号转化为技能改进的最有效方法是,将这三个信号(包括当前信号)提供 SKILL.md​ 给语言学习模型(LLM),并要求其提出改进建议。LLM 可以综合分析失败的断言、审阅者的投诉以及文本记录行为中的模式,而手动关联这些模式将非常繁琐。在向 LLM 发出指令时,请遵循以下准则:

  • 根据反馈进行归纳总结。这项技能将应用于许多不同的提示,而不仅仅是测试用例。修复方案应着眼于根本问题,而不是针对特定示例添加狭隘的补丁。

  • 保持技能的精简。更少但更优质的指令通常比详尽的规则更有效。如果记录显示存在浪费时间的工作(不必要的验证、不必要的中间输出),请删除这些指令。如果即使增加规则后通过率仍然停滞不前,则该技能可能过于复杂——尝试删除一些指令,看看结果是否保持不变或有所提高。

  • 解释原因。基于推理的指令(“做 X 是因为 Y 往往会导致 Z”)比生硬的命令(“总是做 X,永远不要做 Y”)效果更好。当孩子们理解指令的目的时,他们会更可靠地遵循指令。

  • 将重复性工作打包。如果每次测试运行都独立编写了类似的辅助脚本(例如图表生成器、数据解析器),则表明应该将这些脚本打包到技能 scripts/​ 目录中。有关具体操作方法,请参阅“使用脚本”部分。

循环

  1. 将评估信号和电流输入 SKILL.md​ 到 LLM 中,并要求它提出改进建议。

  2. 审核并应用更改。

  3. 在新目录中重新运行所有测试用例 iteration-<N+1>/​。

  4. 对新结果进行评分和汇总。

  5. 请人工审核。重复此步骤。

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

在技​​能中使用脚本

技能可以指示代理运行 shell 命令,并将可重用的脚本打包到一个 scripts/​ 目录中。本指南涵盖一次性命令、具有自身依赖项的独立脚本,以及如何为代理设计脚本接口。

一次性命令

如果现有软件包已经满足您的需求,您可以直接在 SKILL.md​ 指令中引用它,而无需 scripts/​ 指定目录。许多生态系统都提供了在运行时自动解析依赖关系的工具。

uvx 在隔离环境中运行 Python 包,并采用积极的缓存策略。它随 uv 一起发布。

uvx ruff@0.8.0 check .
uvx black@24.10.0 .
  • 未与 Python 捆绑在一起——需要单独安装。

  • 速度快。缓存机制运行高效,因此重复运行几乎是瞬间完成。

技能中一次性命令的使用技巧:

  • ​固定版本​(例如,npx eslint@9.0.0​),以便命令在一段时间内表现相同。

  • 请在配置文件中明确列出先决条件 SKILL.md​(例如,“需要 Node.js 18+”),而不是假设代理环境已经具备这些条件。对于运行时级别的要求,请使用 compatibilityfrontmatter 字段

  • 将复杂命令移至脚本中。一次性命令在调用工具时非常有效,尤其是在只使用少量参数的情况下。但当命令变得足够复杂,难以一次性正确执行时,使用经过测试的脚本 scripts/​ 会更加可靠。

引用脚本 SKILL.md​

使用技能目录根目录的相对路径来引用捆绑文件。代理会自动解析这些路径,无需绝对路径。列出您系统中可用的脚本 SKILL.md​,以便代理商知道它们的存在:

## Available scripts

- **`scripts/validate.sh`** — Validates configuration files
- **`scripts/process.py`** — Processes input data

然后指示代理人运行它们:技能.md(技能.md)

## Workflow

1. Run the validation script:
   ```bash
   bash scripts/validate.sh "$INPUT_FILE"
2. Process the results:
```bash
   python3 scripts/process.py --input results.json

为代理用途设计脚本

当代理运行你的脚本时,它会读取标准输出(stdout)和标准错误输出(stderr)来决定下一步操作。一些设计上的选择使得代理使用脚本变得更加容易。

避免使用交互式提示

这是代理执行环境的硬性要求。代理在非交互式 shell 中运行——它们无法响应 TTY 提示符、密码对话框或确认菜单。如果脚本阻塞于交互式输入,则会无限期地挂起。接受通过命令行标志、环境变量或标准输入 (stdin) 的所有输入:

# Bad: hangs waiting for input
$ python scripts/deploy.py
Target environment: _

# Good: clear error with guidance
$ python scripts/deploy.py
Error: --env is required. Options: development, staging, production.
Usage: python scripts/deploy.py --env staging --tag v1.2.3

文档使用 --help​

​--help​ 输出是代理学习脚本界面的主要方式。请包含简要描述、可用标志和使用示例:

Usage: scripts/process.py [OPTIONS] INPUT_FILE

Process input data and produce a summary report.

Options:
  --format FORMAT    Output format: json, csv, table (default: json)
  --output FILE      Write output to FILE instead of stdout
  --verbose          Print progress to stderr

Examples:
  scripts/process.py data.csv
  scripts/process.py --format csv --output report.csv data.csv

保持简洁——输出结果会与代理正在处理的所有其他内容一起进入代理的上下文窗口。

编写有用的错误信息

当智能体收到错误信息时,该信息会直接影响其下一次尝试。一条晦涩难懂的“错误:输入无效”会浪费一次行动机会。相反,应该说明哪里出了问题,预期结果是什么,以及接下来应该尝试什么:

一次行动机会。相反,应该说明哪里出了问题,预期结果是什么,以及接下来应该尝试什么:

Error: --format must be one of: json, csv, table.
       Received: "xml"

使用结构化输出

优先使用结构化格式(例如 JSON、CSV、TSV),而非自由文本格式。结构化格式既可以被代理程序读取,也可以被标准工具读取(例如,,,jq​),从而使您的脚本能够在流水线中组合使用。cutawk​

# Whitespace-aligned — hard to parse programmatically
NAME          STATUS    CREATED
my-service    running   2025-01-15

# Delimited — unambiguous field boundaries
{"name": "my-service", "status": "running", "created": "2025-01-15"}

将数据与诊断信息分离:将结构化数据发送到标准输出 (stdout),将进度消息、警告和其他诊断信息发送到标准错误输出 (stderr)。这样,代理程序既可以捕获清晰、可解析的输出,又可以在需要时访问诊断信息。

进一步考虑

  • 幂等性。代理可以重试命令。“如果不存在则创建”比“创建但重复则失败”更安全。

  • 输入约束。对于含义模糊的输入,应给出明确的错误提示,而不是随意猜测。尽可能使用枚举和闭集。

  • 支持预运行。对于破坏性操作或有状态操作,可以通过一个 --dry-run​ 标志位让代理预览将会发生什么。

  • 使用有意义的退出代码。针对不同的失败类型(未找到、参数无效、身份验证失败)使用不同的退出代码,并在输出中进行说明,--help​ 以便代理程序了解每个代码的含义。

  • 安全默认设置。考虑破坏性操作是否应要求显式确认标志(--confirm​,--force​)或其他与风险级别相适应的安全措施。

  • 可预测的输出大小。许多代理程序会自动截断超过阈值(例如 10-30K 个字符)的工具输出,这可能会丢失关键信息。如果您的脚本可能会产生大量输出,请默认输出摘要或设置合理的限制,并支持类似 --offset​ 这样的标志,以便代理程序在需要时请求更多信息。或者,如果输出很大且不适合分页,则要求代理程序传递一个 --output​ 标志,该标志指定输出文件或 -​ 显式选择使用标准输出 (stdout)。