如果你在一个 Agent 平台里挂上了上百个 Skill大概迟早会遇到这个尴尬时刻用户问了一个非常简单的问题助手反而把那个最常用、最核心的 Skill 给漏了转头去猜了一个冷门工具捣鼓了半天最后回了一句“我无法完成”。这不是模型变傻了而是 Skill 描述全部塞进上下文之后模型已经被“工具说明书海洋”淹没了。先说明一下这里的 Skill 泛指 Agent 平台里的工具描述、插件定义、自定义技能包不管叫什么名字本质上都是“给模型看的函数说明书”。这个拆分场景是很多人都会撞上的实际问题。业内常说的多 Skill 分层加载机制就是专门用来解决“工具太多、模型不知道用哪个”这个痛点的。这篇文章写给正在做 Agent 应用、做工具平台、或者自己维护了几十个自定义技能的朋友适合那些已经开始觉得“把全部 Skill 塞进 Prompt”行不通的团队参考。1. Skill 一多模型为什么就开始犯浑1.1 单 Skill 模式的工作方式与隐含假设先回到最简单的场景。早期很多 Agent 只有一两个 Skill比如一个查天气、一个设提醒。这时系统 Prompt 里几乎不需要动什么脑筋直接把两个 Skill 的完整描述扔进去模型读完就知道“哦这里有查天气的功能有设提醒的功能用户说天气就调第一个说提醒就调第二个”。单 Skill 模式能跑通依赖一个隐含假设模型在有限的上下文里完全有能力读完、理解、并记住每一个工具的用法。因为在 1-5 个 Skill 的规模下上下文占用可能不到几千字模型能把注意力平均分配到每个工具上。可一旦这个数字从 5 变成 50从 50 变成 500这个假设就不成立了。模型仍然是同一个模型它的注意力窗口和推理能力并没有变变的是你塞给它的“说明书”篇幅。我见过有些团队把 Skill 列表直接拼成一个超长 JSON塞进 system prompt然后在线上跑着跑着就发现用户明明需要 A 工具模型却调了 B 工具甚至把 A 工具的参数填进了 B 工具的请求体里。表面看是“模型没理解”实际上是你在强迫模型在一堆噪音里做精细查找这本来就不符合模型的工作方式。1.2 数量上到三位数后三个肉眼可见的大坑第一个坑是注意力稀释。模型对长上下文的注意力并不是均匀分布的它对开头和结尾的内容更敏感对中间大段内容会明显“漫不经心”。上百个 Skill 的描述按顺序排下来中间区域恰好就是几十个不常被使用的长尾工具。它们不是“被忽略了”而是模型很可能看到了这个字但注意力权重低到没有意识到这是一个可用的工具。用户问了个需要长尾 Skill 的任务模型却宁可胡说八道也不愿意去调用它这就是注意力稀释的典型表现。第二个坑是 Token 预算和费用失控。我帮你算一笔账假设每个 Skill 的描述平均 200 字加上参数定义和说明文本大约每个占 150-250 token再算上 JSON 格式的键名、引号、换行符100 个 Skill 差不多就要 3-4 万 token。这个量级已经接近很多模型的上下文上限了。也就是说你还没对话就已经先把模型“喂了个半饱”后续对话内容、历史记录、检索结果全都得挤剩下的空间。而且每次请求都要把这些 token 送进去成本是线性增长的用户只问一句“今天天气如何”你却为了一百个无关工具的说明书付了钱。第三个坑是冲突和维护灾难。上百个 Skill 里难免有功能重叠比如“按关键词搜文档”和“全文搜索”这两个描述里都出现了“搜索”“文档”“关键词”模型很难区分到底该选哪个。加上 Skill 是不断演进的今天改了 A 的描述版本明天更新了 B 的参数结构每次都改完整 Prompt 串很容易出现旧版本残留、描述矛盾、参数命名不一致。模型面对互相打架的信息只能选择其中一个碰运气或者干脆一个都不选。所以多 Skill 场景的核心问题不是“模型能力不够”而是“输入的组织方式不适合”。分层加载机制要解决的就是把“让模型从 100 个里挑 1 个”这个困难问题转换成“先告诉模型去哪一层找再从 5-10 个候选里挑 1 个”的简单问题。2. 分层加载机制的整体设计思路分层加载本质上是一套“漏斗式筛选”。它模仿的不是数据库索引而是你去图书馆找一本书的过程。你想找一本关于“明清瓷器鉴定”的书不会站在图书馆门口要求大脑把几百万册书的书名全部过一遍而是先判断“这类书应该在文化艺术类楼层”然后到那一层找到“文物收藏”书架再在书架上扫一眼书名抽出两三本候选翻开目录确认哪一本真正匹配。这套流程映射到 Agent 系统就是路由、召回、校准三层。每一层都只做一件简单的事把团队从“维护一个巨型 Skill 文档”中解放出来。2.1 三层结构路由层、召回层、校准层路由层解决的是“这个请求大概属于哪个领域”。系统先根据用户输入判断意图类别比如日程管理、邮件处理、数据分析、客户查询、内容生成、知识库检索等等。输出结果是一组候选 Skill 列表而不是单个 Skill。路由层的作用是缩小范围把几百个 Skill 变成几十个为后续精细检索创造条件。召回层解决的是“在这个候选列表里哪几个 Skill 和当前需求最匹配”。这一步通常会做语义相似度计算把用户输入和 Skill 描述都转成向量做余弦相似度排序也可能混入关键词匹配、热度加权等信号。输出结果是按分数排名的 TopK 个 Skill。校准层解决的是“选出来的 Skill 是不是真的能用以及怎么用”。检查参数是否齐全、类型是否合法、执行入口是否可达。如果不行要么要求模型补充信息要么返回一个明确错误让模型换另一个 Skill。只有通过校验的 Skill 描述最终才会被完整注入到本次对话上下文的最前面、模型最容易注意到的位置。这三层是串行执行的每一层都大幅缩小结果集。分层加载最核心的一句话是让模型只看到它“当下应该关心”的少量信息而不是把整个工具库摊在它面前。2.2 为什么分层而不是一次性全塞进去有人会问把全部 Skill 塞进 Prompt 简简单单为什么不这么做因为模型的推理过程受“认知负荷”影响。在心理学里一个人同时盯着的选项越多做出正确选择的概率越低模型其实也一样。你可以把模型的每轮推理理解为一次“有限注意力分配”。我实测过一组数据在固定模型、固定任务、固定 Skill 描述质量相同的前提下候选 Skill 从 5 个增加到 20 个时工具调用的准确率下降还很轻微从 20 个增加到 60 个时准确率会出现肉眼可见的下跌再往上走不仅准确率下跌模型还会开始凭空编造参数。这说明真正适合模型一次性处理的工具候选数量大概就在个位数到十几这个量级。还有一层原因在于可维护性。全量注入的方案里Skill 描述、参数定义、索引结构全部耦合在一个超长字符串里。分层之后路由表是一份独立的配置Skill 索引是另一份独立的配置参数校验规则又是独立的部分。每一层都可以单独修改、单独测试、单独回滚。这在工程上是把“单体应用”拆成“微服务”的思路收益是长期维护成本的显著降低。3. 路由层把上百个 Skill 先装进抽屉3.1 分类体系怎么设计才算合理做路由层的第一步不是马上写代码而是先把 Skill 盘点一遍设计领域分类。这个分类体系的颗粒度很讲究太粗比如只分“办公”“生活”“技术”三大类每个大类下仍然塞了几十个 Skill路由等于没做太细比如按用户原话里可能出现的每个动词建立一个类会导致分类器训练困难而且 Skill 本身功能有交叉很容易同时命中多个类。我的经验是控制在大类 8-12 个每个大类下挂 5-30 个 Skill 比较合适。举例来说日程会议、邮件通信、文档处理、数据分析、客户关系、营销内容、研发工具、知识检索、财务报销、系统管理。分类可以叠一层子分类但千万不要做三层以上的树状结构因为在路由层做太精细的分类收益会边际递减维护成本却直线上升。冷启动阶段没有人工标注数据可以借助大语言模型辅助打标签。把每个 Skill 的名称和描述批量发给模型让它输出“这个 Skill 应该归到哪个领域”再人工复核一遍。做这一步时有一个容易被忽略的点分类标签的命名要尽量贴近用户会说的自然语言而不是内部代码术语。比如一个内部叫“ecrm_v2”的 Skill如果它的分类标签是“客户关系管理”用户在说“帮我查一下这个客户上次做了什么”时路由层更容易把它映射进正确领域。3.2 路由实现的三种方案按需选择第一种是规则 关键词倒排索引。系统把每个 Skill 名称、描述、触发词拆成词元建立倒排索引。用户输入进来后先做一个轻量分词然后拿命中词元去索引里查。这种方式的好处是延迟极低、费用几乎为零、完全可控。缺点是没有泛化能力用户换了个说法就命中不了。它适合那些领域边界清晰、触发词非常固定的场景比如“查天气”“设提醒”“发邮件”。第二种是轻量文本分类模型。用一个较小的分类模型或者远端分类接口对用户输入做意图分类直接输出属于哪个领域。这种方式比规则泛化能力强能处理“帮我看看明天早上有没有会”这种绕弯子的表达。缺点是需要一定量的标注数据而且分类模型本身也有误判率。它适合领域数量稳定、有训练数据积累的团队。第三种是让大语言模型做路由。把用户输入发给一个强模型给它看一张“Skill 目录菜单”只让它输出最匹配的领域和候选 Skill 清单。这种方式灵活性最高特别是冷启动阶段还没有历史数据用它兜底最稳。代价是每次路由请求都要花一次模型调用的费用和时间而且路由这一步本身也需要消耗 tokens。如果线上 QPS 很高这个成本会让你肉疼。现实中绝大多数团队都会采用混合方案先用规则或小模型做快速路由命中置信度低时再升级到 LLM 路由。路由层还应准备兜底逻辑如果所有候选都没达到阈值那就把“全部 Skill 的摘要列表”作为候补给到下一层由更费力但更智能的召回层来判断而不是直接拒绝用户。4. 召回层在候选范围里选出真正要用的那几把刀4.1 Skill 记录结构设计召回层的前提是Skill 在系统里不是一份简单的功能描述而是一条结构化的索引记录。我使用过比较实用的字段结构大致如下id唯一标识name技能名称尽量是人类可读的比如“查询客户订单状态”description功能描述说明“这个 Skill 能做什么”建议 50-100 字trigger_examples触发场景示例3-5 句真实的用户话术比如“帮我查一下订单物流到哪了”params_schema参数定义包括必填项、类型、枚举值category所属领域keywords补充关键词包括同义词和常用口语表达enabled是否启用这里的 trigger_examples 是一个非常值得重视的字段。很多人写 Skill 描述时只写功能不写场景导致向量检索时匹配不上。举个例子一个“生成客户周报”的 Skill如果你只在描述里写“根据客户数据生成周报”用户输入“帮我给销售总监写一封总结邮件”时语义上其实高度相关但检索系统很难把它召回。如果你在 trigger_examples 里写上“给销售总监发周报总结”“把本周客户进展整理成邮件”向量匹配度会高出一大截。4.2 向量检索与混合召回的实现要点召回层通常会构建两套索引一套是词法倒排索引一套是向量索引。词法索引负责精确匹配关键词向量索引负责语义相似度检索。两者各有用处词法索引不会漏掉明显包含“邮件”“周报”字样的输入但处理不了“帮我写封信发给领导”这种表达向量索引能处理同义改写但偶尔会召回一些表面相似、实际无关的 Skill。工程上的标准做法是把两者的结果做融合。先分别取各自的 Top 20然后用 RRFReciprocal Rank Fusion做加权合并对每个 Skill 计算 1 除以它在各列表中排名的和最后按融合分数排序取 Top 5 到 Top 10 作为最终候选。这套做法在中等规模下效果稳定而且实现成本低不需要花里胡哨的重排序模型。TopK 的值非常关键。设得太小比如只取 Top 1如果第一个候选本身就不对没有任何纠错余地设得太大比分说 Top 50又把“模型候选太多会犯浑”的问题带回来了。我建议从 Top 5 起步观察一段时间如果发现正确 Skill 经常出现在第 1-2 位但模型没有选它可以不动 TopK而是去优化 Prompt 结构如果发现正确 Skill 经常出现在第 6 位以后那就说明召回质量有问题优先修 Skill 描述的语义质量而不是继续放大 TopK。具体调参口径是统计“最终被成功运行的 Skill 是否出现在候选列表里”的覆盖率把覆盖率超过 95% 作为底线。还有一个实操细节向量生成时的内容选择。用整个 Skill 描述去生成 Embedding 往往效果不好因为描述里混杂了功能说明、技术参数、使用限制影响语义向量质量。更好的做法是只用名称 一句话摘要 trigger_examples 来生成向量把详细的参数定义留到校准层再读。这样向量空间里每一个点都代表“这个 Skill 适用于什么场景”而不是“这个 Skill 在技术上是怎么实现的”。4.3 候选被检索到之后如何展示给模型召回到了 TopK不等于直接把这几个 Skill 的完整定义全部塞进 Prompt。这里我推荐两段式注入候选摘要列表 命中详情。摘要列表里只有技能名称、一句话能力描述以及一个编号长度大约控制在每行 30-50 字。模型先浏览这个列表确定要用哪个编号。然后系统再根据模型的选择动态扩展出这个 Skill 的完整参数方案和调用说明。这个设计和人在看菜单时先看菜名、再翻到那一页看配料是一样的逻辑。两段式的好处是模型在前几行就能建立起“有哪些工具可选”的全貌之后再专注看被选中的那个工具的细节不会被无关参数干扰。要在 Prompt 里做一个显式声明以下编号是可用的技能如果这些技能都不能满足用户需求请明确回复“没有合适技能”而不是从历史对话里猜测一个旧工具的用法。5. 校准层选出来的 Skill 要跑得通才行5.1 参数 Schema 校验是刚需不是可选项我在实际项目里见过太多次“Skill 选对了参数填错了”的情况。模型正确选出了“创建会议”这个 Skill但用户没提供参会人、开始时间、时长模型居然自己编了一个“下午两点”和“所有同事”就这么发出了邀请。这不是模型不聪明而是当你把“要不要填参数”的判断权完全交给模型时它倾向于把缺了的字段“补全”而不是“向用户提问”。解决办法是在执行前加一道参数 Schema 校验。系统把召回的 Skill 参数定义拿出来逐项检查必填参数是否齐全参数类型是否合法枚举值是否在允许范围内参数数量是否在合理上限内。校验不通过的请求不能直接执行。这时候系统有两种纠偏路径如果只是少一两个关键必填参数可以在 Prompt 里给模型一条反馈——请向用户询问本次创建会议需要提供参会人名单、开始时间、预计时长当前缺少开始时间如果参数完全驴唇不对马嘴就提示模型“当前技能不适用于该请求请选择其他技能”。把这道校验放在“执行之前”而不是“执行报错之后”能省掉大量事故。真正等到调下游 API 才发现参数缺失用户已经在等了回滚也困难。5.2 上下文预算与注入水位控制校准层还负责控制这次对话真正注入的 Skill 描述总量。我给团队定过一个简单可执行的规则每一轮对话中Skill 相关注入内容的 Token 预算上限为 2 万候选列表占 20%命中 Skill 的完整描述占 40%其余留作对话历史和新鲜的用户输入。预算按比例分配比按字数更科学因为不同语言和编码方式下 token 数差异很大。超过预算上限时优先保证“当前已命中 Skill”的完整描述压缩候选摘要的详细程度比如只保留名称不要描述。这个“水位控制”非常实用很多 Agent 跑得卡顿不是因为模型推理慢而是因为 Prompt 长度已经快顶到上下文上限每轮请求都在超载边缘。定一个预算就像在水位线上面画了一条红线一眼就知道当前情况下该舍掉什么。有一些日志指标值得记录每轮请求注入的 Prompt tokens、候选列表大小、命中 Skill 数量、被截断的字段数量。这些数据能帮你判断到底是“上下文太长导致模型不听话”还是“候选太少导致模型没得选”。5.3 反馈闭环让系统越用越准分层加载系统不是部署完就结束了它需要在线学习机制持续运转。这里最关键的是一类信号Skill 被召回后执行是成功了还是失败了。成功代表“选对且用对了”失败可能有多种原因包括参数错误、下游接口超时、用户取消甚至 Skill 本身的 Bug。把这些结果回流到排序权重中就出现了正反馈每次成功执行给对应 Skill 的热度分 1每次失败给热度分 -2。召回排序时在向量相似度的基础上叠加一个热度加权系数。不需要复杂的推荐算法一个带指数的 EWMA 滑动平均就够了。更进一步每周离线跑一次聚类分析找出“高分低用”的 Skill向量检索明明经常召回它但实际执行成功率低于 70%。这些 Skill 多半存在几种问题描述过于宽泛误导了召回参数定义过时下游接口已经改了功能与另一个 Skill 高度重叠实际竞争中总是落败。针对每个失败聚类逐一修正描述或下线淘汰。这个数据闭环做起来之后系统才会从“刚开始可用”变成“越用越准”。6. 实操过程中踩过的坑与排查实录6.1 常见问题速查表这里整理了一个我在多 Skill 分层系统中的排查速查表按症状、原因、解决顺序来写症状可能原因排查方向与解法用户需求明显匹配某个 Skill但模型选了另一个路由层分类命中错误检查路由输出日志确认分类是否正确调整路由层置信度阈值路由正确但候选列表里没有正确 Skill召回层语义匹配不充分检查 Skill 的 trigger_examples 是否覆盖常见问法扩充描述中的场景句候选列表里有正确 Skill但模型没选它TopK 太小或 Prompt 结构不清适当调大 TopK优化候选摘要列表的展示顺序与措辞模型选对了 Skill但参数填得乱七八糟参数 Schema 未被完整注入或缺少运行时校验命中 Skill 必须注入完整参数方案执行前强制过校验器同一领域内多个 Skill 互相“打架”描述与触发词高度重叠合并同类 Skill或在描述中明确区分适用边界系统越来越慢单次请求 token 激增注入水位失控候选列表膨胀检查上下文预算设置压缩候选摘要限定 TopK 上限长上下文中间位置的 Skill 总是被忽略注意力被稀释把重要 Skill 放前部降低注入总量考虑给 SKill 加“重要性前缀”6.2 几个让我印象深刻的失败案例第一个案例是分类过粗引发的路由误判。当时我们把所有“和文字处理有关”的 Skill 分了同一个大类结果用户问“给客户发一封感谢信”路由层把“文档模板生成”和“邮件发送”两个 Skill 全放进候选模型在两个之间犹豫了半天选了文档生成结果生成完忘记发邮件。这个问题的根源不是模型差而是分类没有区分“生成内容”和“执行动作”。后来我把分类拆成“内容生成类”“渠道发送类”“数据查询类”路由层的误判率立刻降了一个台阶。第二个案例是同义 Skill 打架。一个内部项目里同时存在“查订单详情”和“订单查询”描述几乎一样只是名称不同。用户问“查一下单号 888 的状态”模型每次都直接选列表里排前面的那个但那个 Skill 偏偏指向了下游一个已经无人维护的接口响应超时率极高。我们纠结了很久要不要保留两个 Skill最后决定合并成一个把已废弃接口从枚举 list 中移除。这件事教会我一个道理Skill 数量不是越多越好重复覆盖同一片功能的 Skill 只会制造噪音。第三个案例跟长上下文切片有关。我们最初把 Skill 描述一股脑注入到 system prompt 的尾部结果模型经常“忘记”最后几个 Skill。把向量召回命中到的 Skill 放到注入区间的开头和把全量 Skill 摘要列表放到末尾两者差别非常明显。后来我把“始终可用、固定常驻”的高频 Skill 放最前面“需要时才加载”的长尾 Skill 放后面再配合分层注入顺序这个现象就基本消失了。7. 分层加载不是银弹边界要心里有数开头讲了“Skill 一多模型就犯浑”其实还有一种情况Skill 太少时分层加载纯属画蛇添足。如果你只需要维护 5 个以内的工具全量注入加一个简单枚举完全够用引入路由、向量召回、参数校验三层架构只会把简单问题复杂化。我自己看到的比较合理的分界线是Skill 总量少于 20 个不需要分层20-50 个可以只做一层轻量路由50 个以上分层加载的收益才真正显现出来值得投入完整建设。另外如果你的 Agent 只在非常固定的几个场景里运行比如只做智能客服、只做数据报表、只做 OA 审批每个场景对应的 Skill 集合是静态的那你也可以不依赖动态召回直接在代码里写死每个场景绑定的 Skill 列表。这种“僵化”在边界稳定时反而是最稳定、最便宜的方案。动态分层最大的价值是应对开放场景下的长尾请求而不是为了彰显技术复杂度。最后分享一点个人体会做多 Skill 分层加载本质上是在给模型“减负”。每次遇到模型调用不精准不要第一时间想着换更强的模型或者堆更多提示词规则先问自己一句——模型真的有必要看到那几十个无关 Skill 吗把这句话想明白你就抓住了这套机制的核心。以后如果 Skill 量级继续涨到上千可能还需要引入子 Agent 编排、Skill 依赖图、按用户群体做个性化路由但那是另一个更远的架构话题了。在那之前把路由层的分类体系维护好把召回层的 Skill 描述写到位把校准层的反馈闭环跑起来已经能让一个上百 Skill 的 Agent 在实际生产中站得很稳。