
Agent 的 Skill 数量一旦过百最先倒下的往往不是执行环节而是“调用命中率”。所谓调用命中率就是 Agent 在收到用户请求后能不能选中真正有用的那个 Skill而不是点到名字看着像、实际能力对不上的技能。这个话题在 Agent 项目里越来越常见尤其是用 Codex Skill、Claude Code Skill 这类把技能写成独立文件的方式管理技能时技能越多选择越难。这不是模型变笨。模型能力没有变但决策环境变了。上百个 Skill 的描述、参数、触发条件堆在一起模型需要在“看起来都对”的候选里做选择误选、漏选、反复选不出都开始出现。下面按实际调试的路径拆一遍先定位失败发生在哪个环节再优化 Skill 的命名和描述然后接入检索和路由最后用调用日志反推命中率。适合正在维护 50 个以上 Skill、或者刚把 Skill 数量做到三位数的开发者看。1. 先别急着堆 Skill命中率下降的根因在决策入口1.1 过百之后Agent 的“选择方式”完全变了如果只有 10 个 Skill最简单的方式是把所有 Skill 的说明都放进系统提示词让模型自己挑。这个阶段问题不大模型注意力足够覆盖偶尔一次误选很快就能发现。当 Skill 数量超过 100情况就完全不同了。全量注入会让上下文里充满技能描述关键的用户请求语义被稀释。模型先要读两三百行 Skill 元数据再判断现在该调用哪个这个过程又慢又容易错。实测里最常见的表现是单次响应时间变长、Tool call 选错、甚至 Agent 在多轮对话中不停重复选择同一个 Skill。这里的关键是“决策入口”不再是“模型直接读全部内容”而是要有人把 100 个 Skill 变成“有用的候选”。如果还在直接全量注入命中率下降是必然的。所以第一步不是修改某个 Skill 的描述而是确认当前的决策入口到底长什么样。1.2 过百之后常见的三种失败形态我把失败形态分成三类方便后面定位失败形态表现根因漏选该调 Skill 时不调直接输出了通用回答描述里没有覆盖触发场景或者候选集太大被忽略误选调了 Skill但不是最合适的那个多个 Skill 描述相似模型分不清边界循环/反复重试Agent 反复调用同一个 Skill报错后又换另一个再错Skill 前置条件不明确或执行结果不符合预期导致无法退出漏选往往要查 Skill 描述与用户意图的语义距离。误选更常见于同类 Skill 太多比如“生成图表”“生成流程图”“生成架构图”三个 Skill 描述都带“图”字但没有把输入格式和输出格式写清楚。循环重试则更多是 Skill 的执行合约有问题输入输出不稳定模型拿不到明确反馈就只能一直试。1.3 用调用记录先定位问题出在哪个环节不要凭感觉猜。把一次调用拆成三段注册阶段、检索/选择阶段、执行阶段。注册阶段Skill 是否被框架正确加载元数据是否完整。检索/选择阶段Agent 是否在候选集中找到了目标排序和过滤是否有效。执行阶段Skill 启动后是否成功完成结果是否返回给模型。我一般会在日志里给这三个阶段各打一条链路 ID然后统计“候选集大小”“被选中 Skill 名”“执行结果码”。只要连续跑几十条用例就能看出命中率是丢在选择阶段还是丢在执行阶段。很多时候Agent 其实已经选对了但 Skill 执行时报错模型为了完成用户任务就去试别的 Skill最后看起来像“选错”。这种问题如果不去看日志很容易被误判成命中率问题。注意落地时先不要直接改提示词先把日志链路补齐。没有日志后面所有的命中率优化都是盲调。2. Skill 的命名和描述决定了 Agent 第一眼看中谁2.1 名字要“场景化”不要“技术化”给 Skill 起名时不要用库名、类名或缩写。Agent 选择 Skill 时不是像程序员写代码那样按包路径找而是按用户自然语言意图匹配。名字本身就是一个强信号。比如fetch_weather_data不如get_current_weather_by_city清晰。text_processor不如extract_keywords_from_text有触发感。data_viz不如generate_bar_chart_from_csv明确。如果 Skill 数量过百命名风格最好统一成“动词 对象 场景/输出”让模型先通过名字过滤掉大量无关项。我自己遇到过非常典型的例子一个叫pdf_helper的 Skill描述里写了各种 PDF 操作结果 Agent 遇到“把合同转成 PDF”时总不调用它原因就是名字没有体现“转换”动作。所以在维护大量 Skill 时建议把命名规范当作接口规范来对待而不是随便起一个“方便自己记忆”的名字。名字好不好要看 Agent 能不能根据用户问题联想到它。2.2 描述里写清“什么时候用”和“什么时候不要用”描述是指导模型做调用决策的主要依据。一个合格的 Skill 描述至少包含四部分字段作用示例目标能力Agent 用它完成什么任务从 CSV 文件生成柱状图触发条件什么输入、什么场景下应该调用用户要求“画图”“可视化”“统计图表”输入要求需要哪些参数、哪些格式输入必须是 CSV 文件路径列名不能有中文不适用场景哪些情况不要调用用户只要文字描述图表不需要输出图片最后一条很容易被忽略。但“不适用场景”恰恰能抑制误选。100 个 Skill 里难免有相似能力你明确告诉模型“这个 Skill 不处理什么”比只写“能处理什么”更有效。在实际的项目里我见过很多 Skill 描述只写“这是一个强大的文档处理工具”这种话对 Agent 决策几乎没有帮助。模型不知道什么时候该调它也不知道输入输出是否符合当前场景结果就是碰运气式调用。描述必须写得像“给新手同事的任务交接说明”而不是写给领导看的产品介绍。2.3 给 Skill 配 few-shot 示例而不是只写抽象说明对 100 个 Skill 来说单靠抽象描述很难把所有边界讲清楚。给每个 Skill 补 2-3 个“用户问题 - 调用参数”的例子能让模型在向量相似度不高时也能靠示例对齐语义。示例要包括正常场景用户说“给我画一下本周销量的柱状图”Skill 收到chart_typebar,data_sourceweekly_sales.csv。边界场景用户说“我想看趋势”不应该直接生成图表而是先问清楚指标和时间范围。一个常见的 Skill 元数据可以写成类似下面的 JSON 结构{ name: generate_bar_chart_from_csv, description: 从 CSV 文件生成柱状图。适用于用户要求画柱状图、可视化、统计图表等场景。输入必须是 CSV 文件路径输出为 PNG 图片。, tags: [chart, csv, visualization], examples: [ { query: 画一下本周销量的柱状图, args: {data: weekly_sales.csv, chart_type: bar} } ], not_for: [用户只要文字趋势描述不需要图片] }示例不要写太长。Skill 数量过百之后单个 Skill 的元数据膨胀同样会拖慢选择。我建议一个 Skill 的元数据控制在 500 字以内示例保持在 2-3 个不追求把每个参数都解释一遍。3. 一百个 Skill 不能全部进上下文要加检索和路由3.1 从“全量注入”改成“按需加载”这是根子上解决命中率的方法。系统提示词里只放少量“入口 Skill”比如search_skill_index其余 100 个 Skill 的描述放到外部索引里。Agent 先通过入口 Skill 搜索索引再加载候选 Skill 的完整描述最后决定调用哪个。这样做的直接好处是上下文里不会同时出现 100 个技能说明它们的优先级不会被相互挤压。代价是增加了一次检索调用响应时间会多几十毫秒到几百毫秒但对命中率的提升是值得的。按需加载不是把检索逻辑藏在系统提示词里而是要有实际的中间层。你可以把 Skill 目录做成一个独立服务也可以做成 Agent 内部的工具函数但关键是模型每次决策看到的候选集必须是有限的而不是“全量”。3.2 用向量索引或关键词索引做候选召回构建 Skill 索引时不建议只依赖模型自己的记忆。可以用一个轻量向量库给每个 Skill 的 name、description、tags、examples 生成 embedding用户请求进来后先做 Top-K 召回把 100 个 Skill 缩小到 5-8 个候选再让模型做最终选择。没有向量库也可以用关键词召回。很多 Agent 框架本身支持 metadata filter你可以给 Skill 打 tag比如code,data,document,image,audio先按 tag 过滤再在剩下的里面做精确匹配。这种混合方式在质量稳定性和可解释性上更好。伪代码def recall_skills(query, skill_index, top_k8): # 先用规则/标签缩小范围 matched_tags extract_tags(query) candidates skill_index.filter_by_tags(matched_tags) # 再用向量相似度排序 scored [( skill, cosine_similarity(embed(query), skill.embedding) ) for skill in candidates] scored.sort(keylambda x: x[1], reverseTrue) return [skill for skill, score in scored[:top_k]]注意召回不是最终调用。召回的作用是“先圈定 8 个候选再让模型做最终选择”。如果你直接让模型从 100 个里选漏选率会很高如果只靠向量相似度硬选模型对复杂意图的判断优势就没有用上。所以我会把检索和模型选择串起来而不是互相替代。3.3 用规则路由兜底覆盖高频稳定场景向量检索适合处理开放场景规则路由适合处理你已经能枚举的高频场景。比如用户说“读取 URL”“下载图片”“生成 Excel”这些意图很明确完全可以用正则或意图分类器直接命中固定 Skill不走模型选择。规则路由还有一个好处可以处理模型容易混淆的场景。比如“把 Markdown 转成 PDF”和“把 Word 转成 PDF”是两个不同 Skill正则命中比让模型选更稳妥。规则路由不需要覆盖全部覆盖 30%-50% 的高频确定性场景就够了剩下的交给检索和模型选择。规则路由的维护成本不高但收益明显。它相当于把最稳定的一批调用从“概率问题”变成了“确定问题”。每次新增 Skill 时顺手补一条规则后续命中率优化压力会小很多。3.4 给 Agent 一个“找不到时怎么办”的出口即使做了检索和路由仍会遇到没有候选 Skill 的场景。这个出口必须提前定义好是返回一个通用问答能力还是明确告诉用户“当前 Skill 不支持需要安装对应插件”。很多 Agent 的问题是找不到合适 Skill 时模型硬选一个最接近的 Skill执行完发现不对再换另一个导致整个任务在错误循环里打转。可以在 Agent 的决策指令里加一句如果候选 Skill 的置信度都低于阈值直接拒绝调用并说明原因不要硬选。这句话能显著减少循环重试。注意这里不要一上来就开最大并发先用一条样例确认输入、输出和日志都正常。等单条 Skill 调用稳定后再调大并发或放宽阈值。4. 用评测数据和调用日志持续优化命中率4.1 建立 Skill 调用记录和指标想要提升命中率先要定义“命中”的标准Agent 选择了预期的 Skill并且执行成功算正命中。执行成功但结果不满足用户需求不算真正命中。选错了 Skill 但碰巧执行成功也不算命中。实际落地时我会给每个 Skill 记录调用次数、触发用户问题、是否成功、失败原因、替代 Skill。这些数据写到 JSONL 或数据库里作为后续优化依据。示例记录格式{ request_id: 8f3a, user_query: 把销售报告转成 PDF, selected_skill: convert_report_to_pdf, expected_skill: convert_docx_to_pdf, success: false, error: input_extension_not_supported, fallback_skill: null, candidate_count: 6 }candidate_count字段很关键。如果候选集里根本没有目标 Skill说明问题出在召回策略再怎么优化 Prompt 都没用。4.2 定期做命中率归因每收集几百条调用记录做一次归因。归因维度观察点归因方向候选集缺失检索规则、embedding 索引、Skill 标签候选集有但没选中描述、示例、命名、候选排序选中但执行失败Skill 本身输入输出合约、运行时异常执行成功但用户不满意Skill 功能边界和用户预期不匹配“候选集有但没选中”是最需要调核心描述的。这时候可以把未命中的用户 query 拿出来和当前 Skill 描述放在一起看通常会发现描述里缺少某个触发词或者多个 Skill 的描述没有把边界分开。4.3 通过 A/B 测试调整阈值和排序命中率优化不是一次性工作。调描述和调检索参数时不建议直接全局替换可以先用一个分流开关跑 A/B。比如同一批请求50% 走旧描述50% 走新描述连续跑 200 条数据再比较调用命中率和成功率。只看三五个案例很容易被偶然性带偏只有足够样本才说明问题。常见可调参数包括向量召回的 Top-K比如从 5 调到 8。规则路由的匹配阈值正则太严会漏太松会误。最终决策时的“置信度阈值”低于阈值就不调用任何 Skill。候选 Skill 在上下文中的展示顺序和详细程度。4.4 低质量 Skill 定期下架或合并Skill 数量过百后并不是每个 Skill 都值得保留。三个判断标准调用次数很少且没有不可替代价值。多个 Skill 描述高度相似合并成一个“多功能 Skill”并增加参数区分比让模型从两个相似描述里选更稳。错误率长期偏高且无法通过修复输入输出解决。不要因为写了代码就不舍得下架。一个高频误选的低质量 Skill 给系统带来的损失往往超过它偶尔成功带来的收益。我一般每两周清一次调用记录把“零调用、高错误、描述重复”三类 Skill 标记出来再决定是下架还是合并。5. 过百后的边界问题资源、成本、长尾场景和排查顺序5.1 资源与成本全量索引和每次检索的权衡Skill 数量过百不只是命中率问题还涉及启动速度和请求开销。如果每个 Skill 都在启动时加载首包时间会变长如果每次请求都做向量召回embedding API 或本地模型推理也会增加成本。实际项目里可以用缓存解决Skill 的 embedding 离线生成启动时加载到内存用户请求的 embedding 可以在同一个 session 内缓存避免多轮对话重复计算。如果检索服务允许也可以把完整的 Skill 目录做成静态索引文件每次部署时更新而不是每次请求实时从数据库读取。如果你的环境不允许使用外部向量库也可以退回到标签过滤加关键词匹配。虽然召回质量会比向量检索弱一些但只要标签体系设计得好命中率不会差太多。这种方案对中小型 Agent 项目很友好。5.2 长尾场景比高频场景更考验命中率高频场景被规则路由覆盖后剩余的长尾请求才是命中率最难的部分。这些请求往往没有标准化表达比如用户说“帮我把这个表弄好看一点”到底该调“图表美化”还是“表格样式调整”只有看过 Skill 示例才能判断。长尾场景不要指望一个方案解决。第一步是补充 Skill 描述的“同义触发词”比如给“图表美化”加“弄好看、美化、配色、样式”等第二步是把容易混淆的 Skill 做成一个组合 Skill让内部判断完再决定调用哪个子流程不要在 Agent 层面对外暴露多个相似入口。组合 Skill 的优势是Agent 首先只需要判断“用户是不是想美化表格”而不是纠结“三个美化类 Skill 该选谁”。判断粒度变粗了调用命中率反而会上升。5.3 典型报错和排查顺序按下面的顺序能解决大多数“调用命中率下降”问题先看日志确认 Skill 是没被召回、没被选中还是选中后执行失败。再看候选集确认目标 Skill 有没有进入候选列表。再看描述把用户 query 和 Skill 描述放在一起人工模拟一遍选择。再看 Skill 执行确认输入输出有没有硬性错误。最后再看检索参数比如 Top-K、阈值、标签过滤是否有过拟合。如果模型在报错后提示Agent execution terminated due to error或类似的执行终止信息先不要急着优化命中率先去看 Skill 运行时的异常堆栈。很多“命中率不高”其实是执行阶段崩了把异常修好命中率会自然回升。注意先修执行异常再调命中率。执行都不稳定的 Skill描述写得再清楚也不值得保留。5.4 什么时候才需要引入更重的 Skill 治理框架当 Skill 数量到 200、300甚至更多时单靠手写描述和简单向量库会有维护成本。此时可以考虑引入更完整的 Skill 注册中心或工具网关把 Skill 的 schema、版本、依赖、权限、日志统一管理。不过这套东西只有在团队协作和持续迭代时才值得做。如果只是个人项目或早期验证先把手动元数据规范做好用 JSON/JSONL 管理索引就够了。不要一开始就上一个大规模框架那样反而会把命中的问题从模型选择变成平台配置问题。我更建议做 Skill 治理时把顺序理清楚先定义命中指标再缩小候选集最后优化描述和示例。不要一上来就改 Prompt也不要盲目录入更多 Skill。每次把一个 Skill 加入系统前先问一句它会不会让 Agent 在选择时更难判断如果会那就先把命名、描述和检索索引补齐再上线。真正让命中率稳定下来的不是模型变强而是 Skill 被组织得足够清晰。