优化 Skill 描述#

如何改进 Skill 的描述,使其在相关提示词下能够可靠地触发。

Skill 只有在被激活时才能发挥作用。SKILL.md 的 Frontmatter 中的 description 字段是智能体决定是否为给定任务加载该 Skill 的主要机制。描述不足会导致 Skill 在该触发时未触发;描述过宽则会导致其在不该触发时触发。

本指南介绍了如何系统地测试和改进 Skill 的描述,以提高触发准确性。

Skill 触发的工作原理#

智能体使用渐进式披露来管理上下文。在启动时,它们仅加载每个可用 Skill 的 namedescription —— 这足以让它们决定某个 Skill 何时可能相关。当用户的任务与描述匹配时,智能体就会将完整的 SKILL.md 读取到上下文中并遵循其中的指令。

这意味着描述承担了触发的全部重任。如果描述没能传达出 Skill 何时有用,智能体就不知道要调用它。

一个重要的细微差别是:智能体通常只在任务需要的知识或能力超出了它们单独处理的范围时才会咨询 Skill。一个简单的、单步骤的请求(如“读取此 PDF”)可能不会触发 PDF Skill,即使描述完美匹配,因为智能体可以使用基础工具处理它。只有涉及专业知识的任务 —— 不熟悉的 API、特定领域的流转或不常见的格式 —— 才是精心编写的描述能发挥作用的地方。

编写有效的描述#

在测试之前,了解一份好的描述是什么样的会很有帮助。几个原则:

  • 使用祈使句。 将描述框定为给智能体的指令:“当…时使用此技能”,而不是“此技能可以…”。智能体在决定是否采取行动,所以告诉它何时采取行动。
  • 专注于用户意图,而非具体实现。 描述用户试图实现的目标,而不是 Skill 的内部机制。智能体会根据用户要求的任务进行匹配。
  • 宁可稍微“推销”一点。 明确列出 Skill 适用的场景,包括用户没有直接点名该领域的情况:“即使他们没有明确提到‘CSV’或‘分析’。”
  • 保持简练。 几句话到一小段通常是合适的 —— 长度足以覆盖 Skill 的范围,又短到不会在智能体面对众多 Skill 时使其上下文膨胀。规范强制限制为 1024 个字符。

设计触发评估查询#

要测试触发情况,你需要一组评估查询 —— 标记了“应该触发”或“不应该触发”该 Skill 的真实用户提示词。

// eval_queries.json
[
	{
		"query": "我有一个电子表格在 ~/data/q4_results.xlsx,C 列是收入,D 列是支出 —— 你能增加一个利润率列并高亮显示任何低于 10% 的内容吗?",
		"should_trigger": true
	},
	{
		"query": "将此 json 文件转换为 yaml 的最快方法是什么",
		"should_trigger": false
	}
]

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

“应该触发”的查询#

这些查询用于测试描述是否捕捉到了 Skill 的范围。可以从以下几个维度进行变化:

  • 表述方式:有些正式,有些随意,有些带有拼写错误或缩写。
  • 明确程度:有些直接点出 Skill 的领域(“分析这个 CSV”),有些描述需求但不直接点名(“我老板想要根据这个数据文件做一个图表”)。
  • 详细程度:混合使用简短的提示词和带有大量上下文的提示词 —— 比如一个简短的“分析我的销售 CSV 并制作图表”,对比一个包含文件路径、列名和背景故事的长消息。
  • 复杂程度:改变步骤的数量和决策点的数量。包括单步骤任务和多步骤流转,以测试智能体在任务埋藏在更大的链条中时,是否能辨别出该 Skill 的相关性。

最有用的“应该触发”查询是那些 Skill 可以提供帮助但连接并不显而易见的查询。在这些情况下,描述的措辞决定了成败 —— 如果查询已经要求了 Skill 完全对应的内容,那么任何合理的描述都会触发。

“不应该触发”的查询#

最有价值的负面测试案例是擦边球 —— 即与你的 Skill 共享关键词或概念,但实际上需要其他东西的查询。这些查询用于测试描述是否精确,而不仅仅是宽泛。

对于一个 CSV 分析 Skill,弱负面示例会是:

  • "编写一个斐波那契函数" —— 明显不相关,没有测试意义。
  • "今天天气怎么样?" —— 没有关键词重叠,太容易了。

强负面示例:

  • "我需要更新我的 Excel 预算电子表格中的公式" —— 共享了“电子表格”和“数据”概念,但需要的是 Excel 编辑,而不是 CSV 分析。
  • "你能写一个 python 脚本来读取 csv 并将每一行上传到我们的 postgres 数据库吗" —— 涉及 CSV,但任务是数据库 ETL,而不是分析。

真实性技巧#

真实的用户提示词包含通用测试查询所缺乏的背景信息。请包含:

  • 文件路径(~/Downloads/report_final_v2.xlsx
  • 个人背景("我经理让我..."
  • 具体细节(列名、公司名称、数据值)
  • 口语化表达、缩写和偶尔的拼写错误

测试描述是否触发#

基本方法:在安装了该 Skill 的情况下,将每个查询输入到智能体中,并观察智能体是否调用了它。确保该 Skill 已在智能体中注册并可被发现 —— 具体运作方式因客户端而异(例如,Skill 目录、配置文件或 CLI 标志)。

大多数智能体客户端都提供某种形式的可观察性 —— 执行日志、工具调用历史或详细输出 —— 让你看到运行期间咨询了哪些 Skill。查看你客户端的文档了解详情。如果智能体加载了你 Skill 的 SKILL.md,则表示触发成功;如果智能体在未咨询它的情况下开始工作,则表示未触发。

一个查询在以下情况下视为“通过”:

  • should_triggertrue 且 Skill 被调用,或者
  • should_triggerfalse 且 Skill 未被调用。

多次运行#

模型的行为具有非确定性 —— 同一个查询可能在一次运行中触发 Skill,而在下一次运行中不触发。将每个查询运行多次(建议从 3 次开始)并计算触发率:调用该 Skill 的运行次数所占的比例。

如果“应该触发”的查询触发率高于阈值(建议默认 0.5),则视为通过。如果“不应该触发”的查询触发率低于该阈值,则视为通过。

20 个查询,每个运行 3 次,总共 60 次调用。你会想要通过脚本来实现自动化。以下是通用结构 —— 将 check_triggered 中的 claude 调用和检测逻辑替换为你智能体客户端提供的任何方式:

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

# 此示例使用 Claude Code 的 JSON 输出检查 Skill 工具调用。
# 请将此函数替换为你智能体客户端的检测逻辑。
# 如果 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 '.'

如果你的智能体客户端支持,你可以在结果明朗后尽早停止运行 —— 只要智能体咨询了 Skill 或是在未咨询它的情况下开始工作即可。这可以显著减少运行全量评估集的时间和成本。

使用训练/验证集划分避免过拟合#

如果你针对所有查询来优化描述,你可能会面临过拟合的风险 —— 即制作了一份适用于这些特定措辞但在新措辞下失效的描述。

解决方案是拆分你的查询集:

  • 训练集 (~60%):你用来识别失败并指导改进的查询。
  • 验证集 (~40%):你放在一边,仅用于检查改进是否具备普适性的查询。

确保两组都包含比例均衡的“应该触发”和“不应该触发”的查询 —— 不要不小心把所有的正向案例都放在一组里。随机洗牌并在不同迭代中保持划分固定,这样你才是在进行公平的对比。

如果你使用像上面那样的脚本,你可以将查询拆分为两个文件 —— train_queries.jsonvalidation_queries.json —— 并分别针对每个文件运行脚本。

优化循环#

  1. 训练集和验证集评估当前的描述。训练结果指导你的更改;验证结果告诉你这些更改是否具备普适性。
  2. 训练集识别失败案例:哪些应该触发的查询没有触发?哪些不应该触发的查询误触发了?
    • 仅使用训练集的失败案例来指导你的更改 —— 无论你是自己修改描述还是向 LLM 寻求建议,都不要将验证集的结果引入该过程。
  3. 修改描述。 专注于通用化:
    • 如果“应该触发”的查询失败了,描述可能太窄了。扩大范围或添加关于 Skill 何时有用的背景说明。
    • 如果“不应该触发”的查询误触发了,描述可能太宽了。添加关于该 Skill 做什么的特异性描述,或阐明该 Skill 与相邻能力之间的界限。
    • 避免直接添加来自失败查询的具体关键词 —— 那叫过拟合。相反,找到那些查询所代表的通用类别或概念并针对其进行处理。
    • 如果在多次迭代后卡住了,尝试采用一种结构上截然不同的方式来编写描述,而不是进行增量调整。不同的叙事角度或句子结构可能在渐进式完善行不通的地方取得突破。
    • 检查描述是否保持在 1024 个字符限制内 —— 描述往往在优化过程中会逐渐增长。
  4. 重复步骤 1-3,直到所有训练集查询都通过,或者你不再能看到明显的改进。
  5. 通过验证集的通过率来选择最佳迭代版本 —— 即验证集中通过的查询比例。请注意,最好的描述可能不是你最后产出的那一个;之前的某个迭代版本可能比后期针对训练集过度优化的版本具有更高的验证通过率。

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

skill-creator Skill 自动化了这一整个端到端循环:它会拆分评估集、并行评估触发率、使用 Claude 提出描述改进建议,并在运行时生成一个你可以实时查看的 HTML 报告。

应用结果#

一旦选定了最佳描述:

  1. 更新 SKILL.md Frontmatter 中的 description 字段。
  2. 验证描述是否在 1024 个字符限制内。
  3. 验证描述是否按预期触发。手动尝试一些提示词作为快速的健全性检查。对于更严格的测试,编写 5-10 个全新的查询(混合“应该触发”和“不应该触发”),并让它们通过评估脚本运行 —— 由于这些查询从未参与过优化过程,它们可以给你一个公平的检查,看看描述是否具备普适性。

修改前后对比:

# 修改前
description: Process CSV files.

# 修改后
description: >
  分析 CSV 和表格数据文件 —— 计算汇总统计信息、增加衍生列、生成图表以及清理杂乱数据。
  当用户拥有 CSV、TSV 或 Excel 文件并希望探索、转换或可视化数据时,使用此技能,
  即使他们没有明确提到“CSV”或“分析”。

改进后的描述对 Skill 的功能(汇总统计、衍生列、图表、清理)描述得更加具体,并对其适用范围(CSV、TSV、Excel;即使没有显式关键词)描述得更加宽泛。

下一步#

一旦你的 Skill 可以可靠地触发,你就需要评估它是否能产出高质量的输出。参见评估 Skill 输出质量了解如何设置测试案例、评分结果以及进行迭代。