当 Agent 选错 Skill 时:四个修复方法
Skill 库只有 12 个 Skill 时,一切都很顺利。等数量超过 100 个,Agent 却开始绕过那个明明最合适的 Skill,转而选择一个只是看起来相关的 Skill。模型没换,Skill 也没动。你唯一改变的是菜单大小。可随着菜单扩张,Agent 面对的不再是一份短清单,而是一套从未针对正确选择 Skill 优化过的搜索机制。变差的不是 Skill,而是检索。

路由就是检索,描述就是索引条目
Agent 在选择阶段根本不会读取 Skill 正文。它看到的是一份描述列表,然后判断你的请求最像其中哪一项。
Skill 按三级渐进式披露机制加载。启动时,Agent 只会把每个已安装 Skill 的名称和描述载入系统提示词。某个请求看起来与其中一项匹配时,它才会读取该 Skill 的完整说明。只有任务确实需要,Agent 才会继续打开 Skill 附带的脚本或参考文件。

真正决定一切的是第一级,但多数作者优化的却是另一层。他们反复打磨 Skill 内部的说明,尽管 Agent 此时根本还没看到这些内容,却把真正参与选择的描述写得含糊不清。在选择阶段,Agent 并不是根据 Skill 的实际能力进行推理,而是拿请求里的词去匹配描述里的词。
所以,你写的不是文档,而是索引条目。用户的请求就是查询,必须能够命中它。接受这个视角之后,本文余下的内容其实只讲一件事:把搜索工程的方法用到你自己的 Skill 目录上。

匹配失败只有两种方式。 该触发的 Skill 没有响应,这是一次漏检。本该保持安静的 Skill 却响应了,这是一次误命中。请记住这两个概念,因为下面每种修复方法针对的都是其中之一。
从磁盘上看,一个 Skill 只是一个文件。发现机制只读取顶部的两个字段。Skill 激活之前,下面的内容一概不会读取。因此,打磨正文对选择没有任何帮助。
SKILL.md
---
name: spreadsheet
description: 用于处理电子表格和数据文件的 Skill。
---
# 此标记以下的所有内容都是 Skill 正文。
# 发现机制从不加载它。启动时只读取名称和描述。
# 所以这部分可以稍后打磨,它并不是让 Skill 被选中的那一层。
为什么十几个 Skill 时路由顺畅,到了上百个却会崩溃
只有十几个 Skill 时,描述写得潦草也无需付出代价。模型可以暴力遍历一份短清单。可随着菜单扩大,潦草的代价也会增加,而且比你预想得更快。
目录越大,两种压力越明显。第一种是 Token 成本。Agent 开始工作前,所有描述就已经放进提示词里,这笔成本实实在在:Anthropic 测得,优化前的工具定义大约消耗 134,000 个 Token,仅一个连接器就占掉了其中很大一部分。第二种是区分难度。条目越多,功能相近、描述雷同的条目也越多,彼此就越难区分。
已有故障报告正好印证了这一点。最常见的问题是选错 Skill 或工具,以及用错参数;名称越相似,问题越严重。典型情况是两个工具一个向用户发送内容,另一个向频道发送内容。只有少数几个 Skill 时,这类冲突几乎不会发生;数量达到几百个后,冲突便成了常态。
可以想象这样一条曲线。大约 12 个 Skill 以内,无论描述怎么写,影响都不大,因为模型可以看完菜单再选择。到 30 个左右,描述质量开始决定命中还是漏检。超过 100 个,近似重复项开始碰撞,目录本身也会撑大上下文。超过 1,000 个,模型已经无法稳定看到正确条目,扁平选择随之失效。

这四个区间并不是随意划分的。每个区间都意味着上一种方法开始失效,也需要不同的修复方案。下面就按这个顺序逐一展开。

Token 成本不是抽象概念。Agent 开始做任何事之前,每条描述都会拼进提示词。因此,每次请求都要承担整个目录累加起来的成本。
把描述写成查询,而不是摘要
解释 Skill 能做什么的是文档,说明何时该调用它的是触发器。只有后者能让它被选中。
这是投入产出比最高的一项修复,成本只有你的注意力。Anthropic 的官方建议说得很直接:描述必须同时写清 Skill 能做什么,以及哪些具体情境应该触发它。 要把摘要变成触发器,关键是写明“何时使用”,再列出模型应当留意的情形。毕竟,模型才是这段描述的读者。

四个动作就能解决大部分问题,但它们很容易混在一起,所以需要分开看。第一是位置:第一句话就写明何时使用,因为会话上下文拥挤时,描述可能被截断,而你无法预知截断位置。第二是人称:使用第三人称,因为描述会注入系统提示词,第一人称会让说话者身份含混,影响发现。第三是具体性:写出真实请求会出现的文件类型、动词和领域术语,不要只写“有帮助”“强大”这类形容词。第四是立场:描述可以写得稍微强势一点。
最后一点可能有悖直觉。模型往往不会充分触发 Skill,即使 Skill 明明合适,也可能直接跳过。因此,Anthropic 的 Skill Creator Skill 建议把描述写得更主动一些:只要用户提到仪表盘或指标,就使用这个 Skill,即使用户没有明确说要做仪表盘。这样做是在纠正模型已知的沉默倾向,也就是前面两种失败模式中的第一种。
同时,你能用的字数也有严格上限。开放规范把描述限制在大约 1,000 个字符以内,客户端列表则会在描述与触发文本合计约 1,500 个字符处截断。正向触发条件、排除条件和关键词都要争夺这点空间,所以触发条件要放在最前面,行文也要精简。

用代码表示,就是下面这样。Agent 只看到这份列表,看不到它下面的任何内容,然后只根据描述文本判断请求与各项的匹配程度。
# 发现机制交给模型的是一份扁平的 (name, description) 列表,没有其他内容。
catalog = [
Skill("spreadsheet",
# 触发条件优先、第三人称、语气稍微强势
"当用户打开、清理 xlsx 或 csv 文件,或将其中的数据制成图表时使用,"
"用户提到数据透视表时也应使用,即使他们从未说过‘电子表格’。"),
Skill("pdf",
"读取和填写 PDF 文档。当用户提到 PDF、"
"表单或文档提取时使用。"),
]
def select(request, catalog):
# 模型根据每条描述而非 Skill 正文,对请求进行评分
return max(catalog, key=lambda s: match(request, s.description))
告诉模型哪里不该去
一个过于容易触发的 Skill 不会主动告诉你这是路由错误。它只会产出糟糕的结果,让你花上一整天修输出质量,可真正的问题是:根本不该让这个 Skill 回答。
这就是第二种失败模式,也就是过度触发。它的修复方法也是全文最直接的:描述写得太宽泛,就会被并非为该 Skill 设计的请求触发,产出低质量结果,并把路由问题伪装成质量问题。你改说明、改示例,结果还是不见好,因为这个 Skill 从一开始就不该被选中。
一位开发 Kubernetes 策略 Skill 的从业者就遇到了这个问题。一条过于宽泛的描述,让该 Skill 在普通故障排查请求上也会触发,随后给出质量很差的回答。修复只需要增加一个否定条件:不要用于常规 Kubernetes 故障排查。此前他花了一整天排查错误的层级,加上这句话后,一轮迭代就解决了问题。

这个诊断方法值得养成习惯。准备 10 条应当触发 Skill 的提示词,再准备 10 条不应触发的提示词。在评估输出质量之前,先跑完这组测试。过度触发会立刻暴露出来,通常加一个否定条件就能补上缺口。这样,触发问题和质量问题就分开了,而两者需要在完全不同的位置调试。
写这些条件时还要注意一点。MUST、ALWAYS、NEVER 这类全大写的绝对措辞,是需要重新表述的警示信号。它们读起来更像僵硬的命令,而不是指导,尤其容易在本该具体判断的边界情况中被过度套用。

这套测试足够小,可以和你发布的每个 Skill 放在一起。
should_fire = [...] # 该 Skill 预期处理的 10 个提示词
should_not_fire = [...] # 它必须忽略的 10 个相近提示词
def triggering_test(skill):
misses = [p for p in should_fire if not fires(skill, p)] # 触发不足
false_hits = [p for p in should_not_fire if fires(skill, p)] # 过度触发
return misses, false_hits
# 在查看输出质量之前先运行此测试
misses, false_hits = triggering_test(policy_skill)
if false_hits:
# 修复描述,而不是 Skill 正文
policy_skill.description += " 不要用于常规故障排查。"
搜索之前,先缩小搜索空间
模型并不难从 3 个 Skill 中选出一个,难的是一次面对 300 个 Skill。所以,别再让它一次从 300 个选项里挑。
扁平列表本身出了问题,就改用分层路由。先判断类别,再从该类别中选择 Skill。每一步,模型面对的都只是少数几个选项,而不是几百个。每经过一层,近似重复项的范围都会缩小,两类错误率也会随之下降。

这是一种需要自行搭建的模式,不是打开某个开关就能得到的功能。原生 Skill 发现采用扁平结构,所以层级要体现在你组织和呈现目录的方式中。相关研究已经充分验证了这种架构:先粗后细检索,依次定位类别、工具和接口;也可以使用父级聚合,先结合关键词匹配与稠密向量检索,再选出排名靠前的少数几个父节点并展开。
这些研究考察的是真实规模,不是玩具数据集。近期一项基准测试覆盖了 70 个服务器和 527 个工具,使用的也是真实多步骤问题,正好处在扁平列表已经失效的区间。结论是,两级决策并不是取巧。只要目录大到无法被单个提示词清楚容纳,它就应该成为默认架构。

它确实有代价,但并不高。你要多走一步,也要维护分类体系,确保每个 Skill 位于正确的分支。换来的好处是,模型再也不用在一次决策中面对整个目录。这正是分层的意义。
写成代码,就是对同一个选择器调用两次,而且每次面对的都是短列表。
def route(request, tree):
domain = select(request, tree.categories) # 从约 10 个中选 1 个,例如 "data"
skill = select(request, tree.skills_in(domain)) # 从约 10 个中选 1 个,例如 "xlsx"
return skill
# 用两个小决策替代一次几乎不可能完成的百选一
当文字已经无能为力,就在下面加一层检索器
Skill 超过 1,000 个,再好的描述也不够,因为模型根本看不到其中大多数。解决办法不是继续打磨文字,而是不要再把所有条目都展示给模型。
到了超大规模阶段,必须调整架构。不要把每个条目都塞进提示词,而是针对请求先召回一小批候选项,再让模型从候选列表中选择。还可以加入一个可选的重排步骤,在候选项交给模型之前先调整顺序。这就是普通检索,也就是文档搜索沿用多年的“先召回、再排序”流程,只不过如今检索对象换成了你的 Skill 目录。
收益很大,而且有实测数据。在 RAG-MCP 研究中,面对大型工具集,一层基础检索把工具选择准确率从 13.62% 提高到 43.13%,同时将平均提示词 Token 从约 2,134 个降到 1,084 个,接近减半。这不是调参带来的小幅改善,而是让系统的表现跨上了一个量级,并且只有在目录大到无法一次展示时才会出现。同一项研究也如实说明了它的上限:注册表达到数千项后,检索精确率本身也会开始下降。这时应当加入重排,而不是继续只依赖召回。

Anthropic 已经为工具发布了这样的机制。把工具定义标记为延迟加载后,模型只会看到一个搜索工具和少数几个关键工具。需要其他工具时,它会按关键词排名进行搜索,返回 3 到 5 个候选引用,再将其展开成完整定义。公开结果显示:Token 用量约减少 85%,一个模型的选择准确率从 49% 提升到 74%,另一个模型则从 79.5% 提升到 88.1%。在编程客户端中,只要工具描述超过上下文窗口的 10%,系统就会自动切换到搜索索引。

工具和 Skill 的现状并不一样,这直接决定了精力该花在哪里。目前,检索器已经可用于工具。Skill 发现仍会把所有描述扁平注入上下文,下面没有重排阶段为质量不佳的条目兜底。因此,前几节所说的文字功夫对 Skill 更重要,恰恰因为没有其他机制可以挽救一条糟糕的标签。关键词召回之后还有更前沿的方向,比如训练一个专门学习选择的模型,但这仍属于研究阶段。应把它看作下一步,而不是今天的默认方案。
整个流程分成三个阶段,目录始终不会完整进入提示词。
def route_at_scale(request, index):
candidates = index.recall(request, k=5) # 候选清单,而不是整个目录
candidates = rerank(request, candidates) # 可选:在模型看到之前排序
return model.select(request, candidates) # 模型从 5 个而不是 5000 个中选择
上面是抽象结构。下面则是 Anthropic 已经为工具推出的具体做法:把长尾工具标记为可发现,而不是预加载。
tools = [
{"type": "tool_search_tool_bm25_20251119", "name": "tool_search"},
{"name": "get_weather", "description": "...", "defer_loading": True},
# 保持少数关键工具已加载,其余工具延迟加载
]
# 搜索会返回 3 到 5 个候选项,并按需展开为完整定义
按自己的规模处理,不要照搬别人的方案
代价最高的错误,是为 40 个 Skill 构建检索器。代价第二高的错误,是为 4,000 个 Skill 逐条手动调整描述。
修复方案要与你在曲线上的位置匹配。Skill 大约少于 30 个时,仅靠打磨描述就足够了。把触发条件放在开头,使用第三人称,语气稍微强势一点,以抵消模型倾向于保持沉默的问题。你不需要更重的方案,加上它们只会拖慢进度。
超过 100 个后,条目冲突与 Token 成本会同时出现。给那些覆盖范围过大的 Skill 增加否定条件,再加入类别层,让模型每一步只需从少数几个选项中决策。多数成规模的内部目录其实都处在这个区间,前面那套两类错误模型也正是在这里最有价值。
超过 1,000 个后,文字能做的已经做完了。构建先召回再重排的检索层,或者在平台提供工具搜索时直接采用,让模型从候选列表而不是整个目录中选择。描述依然重要,因为检索器索引的就是这些描述,但不再只靠描述把请求引向正确的 Skill。

贯穿全文的逻辑很简单:在问题需要调整架构之前,你始终能控制的是自己写下的文字。如果本周只做一件事,就挑出最常用的 3 个 Skill,分别跑一遍“10 条应触发、10 条不应触发”的测试。用 20 分钟测试路由,你得到的信息会比花一周修改说明更多。

整篇文章可以压缩成一个函数。看清你的 Skill 数量,就知道下一步该做什么。
def next_move(skill_count):
if skill_count < 30:
return "写好描述,然后停手"
if skill_count < 1000:
return "增加否定条件和类别层"
return "构建先召回再重排的流程,或使用平台工具搜索"
致谢与延伸阅读
- Anthropic,高级工具使用:工具搜索、延迟加载,以及实测 Token 与准确率收益。
- Anthropic,Skill 编写最佳实践:第三人称描述、触发条件,以及如何在多个 Skill 中进行选择。
- Anthropic Skill Creator Skill:为什么描述应当稍微强势,以抵消触发不足。
- RAG-MCP:检索如何把工具选择准确率从约 13% 提升到 43%,同时减少提示词 Token。
- Red Hat,Tool RAG:召回、重排与分层工具选择全景。
- 一个真实案例:只加一个否定条件,就在一轮迭代中修复了过度触发。
下面是一篇与本文标签相关的文章。
一位资深工程师的担忧,暴露了 AI 时代最关键的角色
Agentic AI 的瓶颈已经不只是模型能力,而是模型周围的系统设计:业务目标、工作流、持久化执行、协议、安全边界和可观测性。
继续阅读……