Skip to main content
技能只有被激活才有帮助。SKILL.md frontmatter 中的 description 字段是代理决定是否为给定任务加载技能的主要机制。描述指定不足意味着技能在应该触发时不会触发;描述过于宽泛意味着技能在不应该触发时会触发。 本指南涵盖如何系统地测试和改进你的技能描述以提高触发准确性。

技能触发是如何工作的

代理使用 渐进式披露 来管理上下文。启动时,它们只加载每个可用技能的 namedescription —— 仅足以决定技能何时可能相关。当用户的任务与描述匹配时,代理将完整的 SKILL.md 读入上下文并遵循其指令。 这意味着描述承担了触发的全部负担。如果描述没有传达技能何时有用,代理就不会知道去使用它。 一个重要的细微差别:代理通常只针对需要超出其单独处理能力的知识或能力的任务咨询技能。像“阅读此 PDF”这样的简单单步请求可能不会触发 PDF 技能,即使描述完全匹配,因为代理可以使用基本工具处理它。涉及专门知识的任务——不熟悉的 API、特定领域的工作流或不常见的格式——才是写得好的描述可以产生差异的地方。

编写有效的描述

在测试之前,了解好的描述是什么样的是有帮助的。一些原则:
  • 使用祈使句短语。 将描述框架为给代理的指令:“当…时使用此技能”,而不是“此技能做…”。代理正在决定是否行动,所以告诉它何时行动。
  • 关注用户意图,而非实现。 描述用户试图实现什么,而不是技能的内部机制。代理匹配的是用户要求的内容。
  • 倾向于更主动。 明确列出技能适用的上下文,包括用户没有直接命名领域的情况:“即使他们没有明确提及’CSV’或’analysis’。”
  • 保持简洁。 几句话到一小段通常是对的——足够长以覆盖技能的范围,足够短以至于不会在许多技能中膨胀代理的上下文。规范 强制执行 1024 个字符的硬限制。

设计触发评估查询

要测试触发,你需要一组评估查询——标记了是否应该触发你的技能的真实用户提示。
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 并制作图表” alongside 带有文件路径、列名和背景故事的较长消息。
  • 复杂性:变化步骤数和决策点。包括单步任务以及多步工作流,以测试代理是否能在任务埋藏在更大链中时辨别技能是相关的。
最有用的应该触发查询是那些技能会有帮助但仅从查询本身来看连接不明显的查询。这些是描述措辞产生差异的情况——如果查询已经要求了技能所做的确切内容,任何合理的描述都会触发。

不应该触发的查询

最有价值的负面测试用例是 接近失误 —— 与你的技能共享关键字或概念但实际上需要不同内容的查询。这些测试描述是否精确,而不仅仅是宽泛。 对于 CSV 分析技能,弱的负面示例将是:
  • "Write a fibonacci function" — 显然无关,测试不了什么。
  • "What's the weather today?" — 没有关键字重叠,太容易。
强的负面示例:
  • "I need to update the formulas in my Excel budget spreadsheet" — 共享”spreadsheet”和”data”概念,但需要 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...")
  • 具体细节(列名、公司名、数据值)
  • 随意语言、缩写和偶尔的拼写错误

测试描述是否触发

基本方法:通过安装的技能运行每个查询并通过代理观察代理是否调用它。确保技能已注册并可被你的代理发现——这如何工作因客户端而异(例如,技能目录、配置文件或 CLI 标志)。 大多数代理客户端提供某种形式的可观察性——执行日志、工具调用历史或详细输出——让你看到运行期间咨询了哪些技能。查看客户端文档了解详情。如果代理加载了你的技能的 SKILL.md,则技能触发;如果代理在没有咨询它的情况下继续,则未触发。 如果满足以下条件,查询“通过”:
  • should_triggertrue 且技能被调用,或
  • should_triggerfalse 且技能未被调用。

多次运行

模型行为是不确定的——相同的查询可能在一次运行中触发技能而在下一次不触发。多次运行每个查询(3 是一个合理的起点)并计算 触发率:技能被调用的运行比例。 如果应该触发的查询的触发率高于阈值(0.5 是一个合理的默认值),则通过。如果应该不触发的查询的触发率低于该阈值,则通过。 20 个查询每次运行 3 次,那是 60 次调用。你将想要脚本化这个。这是一般结构——用你的代理客户端提供的任何内容替换 check_triggered 中的 claude 调用和检测逻辑:
#!/bin/bash
QUERIES_FILE="${1:?Usage: $0 <queries.json>}"
SKILL_NAME="my-skill"
RUNS=3

# 此示例使用 Claude Code 的 JSON 输出检查 Skill 工具调用。
# 用你的代理客户端的检测逻辑替换此函数。
# 如果调用了技能应返回 0(成功),否则返回 1。
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.jsonvalidation_queries.json——并分别针对每个文件运行脚本。

优化循环

  1. 评估 当前描述在 训练和验证集 上。训练结果指导你的更改;验证结果告诉你这些更改是否泛化。
  2. 识别失败训练集 中:哪些应该触发的查询没有触发?哪些不应该触发的查询触发了?
    • 仅使用训练集失败来指导你的更改——无论你是自己修改描述还是提示 LLM,保持验证集结果不在过程中。
  3. 修改描述。 专注于泛化:
    • 如果应该触发的查询失败,描述可能太窄。扩大范围或添加关于技能何时有用的上下文。
    • 如果应该不触发的查询错误触发,描述可能太宽。添加关于技能 做什么的具体性,或澄清此技能与相邻能力之间的边界。
    • 避免添加来自失败查询的特定关键字——那是过拟合。相反,找到这些查询代表的一般类别或概念并解决它。
    • 如果在几次迭代后卡住,尝试结构上不同的描述方法而不是增量调整。不同的框架或句子结构可能在细化无法突破的地方突破。
    • 检查描述是否保持在 1024 字符限制以下——描述在优化期间倾向于增长。
  4. 重复 步骤 1-3 直到所有 训练集 查询通过或你停止看到有意义的改进。
  5. 选择最佳迭代 通过其验证通过率——验证集 中通过的查询比例。注意最佳描述可能不是你产生的最后一个;早期迭代可能比后来过拟合训练集的迭代有更高的验证通过率。
五次迭代通常足够。如果性能没有提高,问题可能在于查询(太容易、太难或标记错误)而不是描述。
skill-creator 技能自动化了这个循环端到端:它拆分评估集,并行评估触发率,使用 Claude 提议描述改进,并生成一个你可以观看运行的实时 HTML 报告。

应用结果

一旦您选择了最佳描述:
  1. 更新您 SKILL.md frontmatter 中的 description 字段。
  2. 验证描述是否在 1024 字符限制 之内。
  3. 验证描述是否按预期触发。手动尝试几个提示作为快速健全性检查。为了更严格的测试,编写 5-10 个新查询(混合应触发和不应触发的查询)并通过 eval 脚本运行它们——由于这些查询从未成为优化过程的一部分,它们可以诚实地检查描述是否具有泛化能力。
前后对比:
# 之前
description: Process CSV files.

# 之后
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;即使没有明确的关键字)。

后续步骤

一旦您的技能可靠地触发,您将需要评估它是否产生了良好的输出。请参阅 评估技能输出质量 以了解如何设置测试用例、评分结果并进行迭代。