Agent Skill超过100后调用命中率下降?四层工程优化法实践

📅 2026/8/27 1:40:59
Agent Skill超过100后调用命中率下降?四层工程优化法实践
Skill 数量超过 100 后Agent 调用命中率下降几乎是必然出现的问题。它不是某个模型能力不够导致的偶发现象而是 Skill 体系从“手写一堆小工具”演变成“管理一批庞大工具集”时必然要面对的工程设计问题。如果你的项目里 Skill 数量还不算多可能觉得命中率不是事可一旦数量破百你会发现它们之间的名称、描述、参数在不知不觉中互相“串味”Agent 开始选错工具、漏掉关键调用、甚至反复用错参数重试。这篇文章会直接围绕“Skill 数量过百后如何保证 Agent 调用命中率”这个点展开先讲清楚调用链路再给出四个可上手的优化层次最后配一套命中率评测方法和排查清单。1. 这篇文章真正要解决的问题先看一个很真实的场景。你负责一个 Agent 项目最初 Skill 只有 10 个左右Agent 每次都能准确选择需要的 Skill。随着业务推进你陆陆续续接入了搜索、浏览网页、写代码、操作数据库、发消息、读文档、调用内部 API 等各类 Skill。数量到 50 个时Agent 开始出现选择犹豫到 100 个以上问题集中爆发Agent 明明应该调用“查询订单”的 Skill却调用了“创建订单”的 Skill。多个名称相似的 Skill 共存描述也写得差不多Agent 根本分不清。某些 Skill 在 A 场景下好用在 B 场景下却被错误触发。参数填错导致执行失败Agent 反复重试但依旧报错。很多人第一反应是“换一个更聪明的模型”或者“调整 prompt”。但实际排查后你会发现问题往往出在 Skill 体系设计本身。Skill 数量一旦过百决定调用成功率的更多是工程结构命名是否规范、描述是否可区分、是否有合理的检索机制、是否有明确的适用范围。这篇文章要解决的就是这类问题。读完你会明确以下几点Agent 调用 Skill 时真正参与决策的环节有哪些。Skill 超过 100 个以后为什么靠“自然语言的描述”硬撑不可行。从描述、分类、检索、路由四个层次如何逐步提高命中率。怎么给 Skill 建立索引、做版本管理和作用域隔离。如何用样本集和指标量化评估“调用命中率”让问题可观测、可回归。2. Agent 调用 Skill 的基础概念与核心链路2.1 什么是 SkillSkill 在 Agent 体系中可以理解为一个“可被模型调用的能力单元”。它通常包含三部分明确的能力描述告诉模型这个 Skill 是做什么的何时应该调用。入参定义调用时需要哪些字段字段类型和含义是什么。执行逻辑实际执行时运行的代码、命令或 API 调用。从结构上看它非常像传统编程里的“函数”或“工具”但它和普通函数最大的区别在于调用它的决策者不是程序员而是大模型。模型会根据用户的当前意图、对话上下文以及 Skill 的描述来判断应该选哪个、传什么参数。这种异步的“模型来调函数”的架构在很多框架里被称为 Function Calling 或 Tool Calling。在 Claude 生态里被称为 Tool Use在 OpenAI 生态里是 Function Calling在 Agent 框架里则经常直接叫 Tool 或 Skill。文章后面统称 Skill。2.2 Agent 调用 Skill 的链路不管底层是哪种框架Agent 调用 Skill 的路径大体一致接收用户输入。Agent 根据输入和对话历史结合所有 Skill 的“描述信息”生成候选调用计划。从候选 Skill 中选择一个或几个按 Schema 填充参数。执行 Skill 代码拿到返回结果。Agent 把结果组织成回答返回给用户或继续下一轮调用。这里的关键环节是第 2 步和第 3 步。当 Skill 数量只有几个时模型可以把全部 Skill 信息塞进上下文直接做全局判断。但当 Skill 数量超过 100 个把所有 Skill 的描述同时塞进去已经不现实Token 消耗巨大模型也容易“看漏”或“看混”。所以工程上通常会在“候选生成”阶段做一次前置筛选从 100 个 Skill 中找出最相关的 10 到 20 个再让模型在这批候选里做最终决策。这个前置筛选的质量直接决定了最终命中的质量。2.3 命中率的真正决定因素命中率不只是“模型聪明不聪明”的问题它取决于多层因素描述信息是否准确、可区分。候选筛选策略是否合理。是否有作用域和权限约束避免无关 Skill 干扰。是否有冲突消解机制防止多个 Skill 描述重叠导致模型选择困难。是否有回退机制在第一步选错后能够自动纠正。只看表面很容易误以为“Skill 越多模型越容易选错”是模型问题。实际是系统的“区分度”和“过滤能力”没跟上数量增长。3. Skill 数量过百后平铺式管理必然失效3.1 平铺式管理的三个信号当 Skill 数量超过 100你会看到三个典型信号。第一个信号是“召回错乱”。用户在输入里提到“统计一下销售额”Agent 可能调用了一个叫“数据查询”的 Skill却没调用更贴近的“销售报表查询”。这不是模型笨而是候选列表里相似的描述太多模型在有限上下文里无法精细区分。第二个信号是“描述互相污染”。给 Skill A 写描述时你可能为了让它更通用写上了“可以获取数据”给 Skill B 写描述时也写了“可以获取数据”。结果两个 Skill 在语义空间里高度重叠模型只能靠猜。第三个信号是“新增 Skill 后老功能回归”。你加了一个“天气查询”结果原本能正确调用“位置查询”的场景反而被新 Skill 抢了。原因很简单新 Skill 的描述和原有 Skill 存在重叠模型的选择分布被改变。这三个信号都指向一个底层原因Skill 被当成了一组平铺的 JSON 描述而不是一套有结构、有索引、有边界的系统。3.2 为什么 100 是一个分水岭为什么不是 10 个、1000 个偏偏 100 个左右容易出问题主要原因有三点上下文窗口约束现代模型的上下文虽然不断变大但把所有 Skill 描述塞进 prompt 仍然很浪费而且数量越多注意力越分散。语义区分难度100 个 Skill 的描述如果按平均每个 500 字算总字数已到几万字其中相似词、同义词、模糊表达的概率大大增加。工程维护成本100 个 Skill 对应 100 份代码、100 份描述、100 组 Schema靠人工保证每一份都清晰、准确、不重叠已经超出人力的稳定输出范围。所以100 个 Skill 不是绝对的魔数而是一个“人工阶段”与“工程化阶段”的分水岭。低于这个数量你还可以靠写描述时多花心思来硬撑超过这个数量就必须引入结构化的管理方法。4. 提升命中率的四个层次与其只调 prompt不如把问题拆成四个层次逐层解决。这四个层次分别是描述层、分类层、检索层、路由层。每一层解决一个具体问题且越靠前越基础。4.1 描述层让每个 Skill 语义可区分描述层是最基础但最容易被忽视的一层。很多 Skill 被写错不是能力实现有问题而是描述写得“太像”。下面是一些常见问题描述过于模糊例如“这个工具用于获取数据”但没有说明是什么数据、从哪里获取、以什么方式返回。描述过于宽泛为了增加召回机会把所有可能场景都写进去结果和多个 Skill 重叠。描述缺少否定信息只写“能做什么”不写“不能做什么”。描述没有场景锚点没有写清楚“当用户提到 XXX 时应该调用”。在实际项目中更应该遵循以下几个原则每个 Skill 描述包含“触发条件”什么意图下应该调用。每个 Skill 描述包含“行为结果”调用后能拿到什么不能拿到什么。每个 Skill 描述包含“排除条件”哪些情况下不要调用。每个 Skill 的命名尽量带上领域前缀或功能类别例如finance_sales_report_query、finance_order_create避免直接叫queryData或tool1。示例# skill-finance-sales-report-query.yaml name: finance_sales_report_query description: | 查询销售报表数据。当用户需要查看销售额、销量、同比环比、按时间维度聚合的销售统计时使用。 仅适用于销售域数据不适用于订单明细、库存、客户信息查询。 如果用户想创建销售订单请使用 finance_order_create。 parameters: type: object properties: start_date: type: string description: 开始日期格式 YYYY-MM-DD end_date: type: string description: 结束日期格式 YYYY-MM-DD granularity: type: string enum: [day, week, month] description: 聚合粒度 required: [start_date, end_date]这段示例的核心价值在于描述里不仅写了“做什么”还写了“不做什么”并且给了一个相关 Skill 的名称作为排他提示。对模型来说这种“对比式描述”比“自我描述”更容易建立区分度。4.2 分类层为 Skill 建立领域边界当数量过百时单靠描述区分还不够。你需要把 Skill 归入不同的领域或类别让 Agent 在决策时先“选领域”再“选工具”。例如可以把所有 Skill 分成以下几类领域示例 Skill典型用途数据查询finance_sales_report_query、finance_order_query数据类需求数据操作finance_order_create、finance_order_update写操作需求消息通知message_send、message_schedule消息类需求日程管理calendar_create_event、calendar_query_events日程类需求文档处理document_parse、document_generate文档类需求系统运维system_status_check、system_restart运维类需求分类的价值在于Agent 可以先根据用户意图确定“域”再把候选 Skill 缩小到该域内。这个思路和传统软件开发里的“模块化”完全一致只是决策者是模型而已。在具体实现上分类不必只是“描述里的一个词”还可以体现在以下地方Skill 名称前缀带上领域缩写。Agent 的决策 prompt 里先列领域清单再让模型选领域。在检索阶段用户输入先匹配到某个领域再从该领域内召回 Skill。如果你的 Agent 框架支持“工作流级别”的 Skill 绑定那更好。可以把“客服场景”和“数据分析场景”拆成不同的 Agent 或不同的 Workspace每个场景只挂载相关的 Skill 子集。这相当于做了“场景级过滤”从源头减少候选数量。4.3 检索层把“全量塞给模型”改为“先召回再决策”Skill 数量超过 100 后不建议再让模型看全部 Skill 描述。正确做法是引入一个简单的检索层先根据用户输入召回 Top-K 个候选 Skill再把这 K 个候选交给模型做最终决定。检索的常见实现方式有三种基于关键词匹配适合具有明确业务术语的场景。基于 Embedding 语义检索适合描述丰富、语义复杂的情况。混合检索先做关键词命中和向量召回再做去重融合。下面是一个基于 Embedding 的简化示例演示如何从 Skill 注册表中召回候选# 文件路径skill_retriever.py import json import numpy as np from openai import OpenAI client OpenAI() def load_skill_registry(path: str) - list[dict]: with open(path, r, encodingutf-8) as f: return json.load(f) def embed_text(text: str) - np.ndarray: resp client.embeddings.create( modeltext-embedding-3-small, inputtext ) return np.array(resp.data[0].embedding) def retrieve_skills(user_input: str, skill_registry: list[dict], top_k: int 10) - list[dict]: # 对用户输入编码 query_vec embed_text(user_input) # 计算每个 skill 描述与用户输入的相似度 scored [] for skill in skill_registry: skill_vec np.array(skill[embedding]) score np.dot(query_vec, skill_vec) / ( np.linalg.norm(query_vec) * np.linalg.norm(skill_vec) ) scored.append((score, skill)) # 按相似度降序取 top_k scored.sort(keylambda x: x[0], reverseTrue) return [skill for _, skill in scored[:top_k]]使用前需要离线把每个 Skill 的描述和参数说明拼成一段文本然后批量生成 Embedding存入注册表[ { name: finance_sales_report_query, description: 查询销售报表数据支持按天/周/月聚合, embedding: [0.012, -0.033, 0.021] } ]这段示例只是演示思路实际生产环境还可以做以下增强将用户的输入先做意图识别再针对意图域做召回。加入关键词加权例如“订单”“销售”“日程”这样的领域词直接命中对应 Skill。对候选结果做重排序使用更精细的交叉编码器。检索层的引入本质上是把“模型从几百个选项里挑一个”变成“模型从十几个选项里挑一个”。前者是强人所难后者是正常工作。4.4 路由层用规则兜住高风险决策检索层解决的是“候选范围”问题但最终选哪个 Skill 仍然是模型的决策。在某些高风险场景中纯靠模型决策还不够需要加一层路由规则来做约束。路由层的典型策略有三种。第一种是领域路由。当用户输入中出现明确的领域词就直接进入对应的 Skill 列表不做跨域召回。比如输入中包含“订单号”就优先从订单域选择 Skill。第二种是白名单/黑名单。某些 Skill 只允许在特定场景下调用例如“删除数据库记录”这样的操作不能用普通对话触发。可以在路由层加一个前置判断当候选 Skill 属于高风险操作时必须先满足额外的条件才允许调用。第三种是显式确认。当候选 Skill 中有多个相似项时与其让模型猜不如先向用户确认意图。例如用户说“帮我查一下订单”但系统同时有“订单查询”和“订单详情查询”如果无法确定路由层可以生成一个澄清问题“您是想查订单列表还是查单个订单详情”路由层设计的总体原则是把能靠规则解决的问题交给规则把必须靠理解的问题交给模型。规则越清晰模型犯错的空间就越小。5. Skill 注册表与索引设计当 Skill 数量超过 100 个我强烈建议为 Skill 建立一份“注册表”而不是一堆散落的文件。注册表的核心作用是让 Skill 信息可以被程序读取、索引、检索和审计。5.1 注册表元数据设计一个相对完整的 Skill 注册表条目应该包含字段作用nameSkill 唯一标识命名带领域前缀namespace所属命名空间便于分组隔离version当前版本号用于兼容和回滚domain领域类别如 finance、message、calendardescription清晰的行为描述和触发条件parameters入参 Schema 定义required_permissions调用所需权限级别enabled_environments允许生效的环境列表如 dev、prodembedding_vector检索用的向量表示related_skills相关联或容易混淆的其他 Skill 名称update_time最近更新时间便于审计这个注册表可以存储为 JSON、YAML 或数据库记录并在 Agent 启动时加载到内存中。关键点是它不应该只是一个给人看的文档而是程序能直接读取的结构化数据。{ name: finance_order_create, namespace: finance.order, version: 1.2.0, domain: finance, description: 创建新的销售订单。当用户确认要下单并提供商品、数量、价格信息时使用。不要用于查询订单状态查询请用 finance_order_query。, parameters: { type: object, properties: { customer_id: {type: string}, items: {type: array} }, required: [customer_id, items] }, required_permissions: [order:write], enabled_environments: [dev, staging, prod], related_skills: [finance_order_query, finance_order_update] }这里的related_skills字段很有用。它相当于告诉检索系统这些 Skill 之间容易混淆模型做决策时需要额外注意。在检索阶段如果命中了某个 Skill可以顺便把它的关联 Skill 也拉出来让模型在“对比”中做选择。5.2 版本管理与冲突检测Skill 不是一次写完就永久不变的。业务调整、描述优化、参数改动都会产生新版本。为了不让旧版本残留干扰新版本的调用建议至少做到Skill 名称保持一致内容变更通过版本号表达。注册表里保留当前版本和上一个版本支持快速回滚。变更描述时自动触发一次“冲突检测”检查新描述是否和同领域的其他 Skill 描述存在高相似度。冲突检测可以用 Embedding 相似度来实现。当新旧描述之间的余弦相似度超过阈值时说明描述可能重叠需要人工审核。这比纯靠 review 靠谱得多。5.3 自动生成描述与人工复审写描述是非常费精力的工作。如果项目里 Skill 很多可以让模型辅助生成描述初稿但一定要人工复审。自动生成描述时建议提供以下上下文Skill 的实现代码。Skill 的调用示例。Skill 的输入输出样例。与已有 Skill 的对比信息。这样生成的描述才能贴合实际行为而不是泛泛而谈。人工复审重点检查三个点是否准确、是否可区分、是否包含触发条件和排除条件。6. 可控路由与作用域隔离6.1 按环境隔离 Skill很多团队在生产环境部署 Agent 时会把所有 Skill 一股脑注册进去这其实埋下了很多隐患。更稳重的做法是按环境隔离开发环境可以加载全部 Skill。测试环境只加载测试中或有变更的 Skill。生产环境只加载稳定版本且权限已验证的 Skill。一个简单的配置示例{ environments: { dev: { enabled_skill_namespaces: [finance, message, calendar, system] }, staging: { enabled_skill_namespaces: [finance, message] }, prod: { enabled_skill_namespaces: [finance, message], disabled_skills: [finance_order_delete] } } }通过环境配置可以避免新 Skill 在没有充分验证的情况下直接进入生产也可以避免高风险 Skill 在普通场景下被调用。6.2 按权限校验调用Skill 调用不该是“模型说调就调”的。对于有风险的操作应该加一层权限校验。例如查询类 Skill普通用户可用。写操作类 Skill需要用户显式确认。删除类 Skill需要额外的鉴权和审计。在 Agent 的调用链路里权限校验可以放在路由层和实际执行层之间。模型生成调用意图后先检查用户是否有权限调用该 Skill再放行。这样可以避免恶意 prompt 诱导 Agent 调用本地未授权能力。6.3 依赖与前置条件检查某些 Skill 依赖另一些 Skill 的输出结果。比如“生成日报”这个 Skill可能依赖“获取数据”和“生成图表”两个 Skill。如果 Agent 直接调用“生成日报”却没有先调用“获取数据”执行就会失败并浪费多轮重试。在注册表里可以为 Skill 增加depends_on字段{ name: report_daily_generate, depends_on: [data_fetch, chart_generate] }这样调度器在调用report_daily_generate前可以先检查依赖是否满足。前置条件不满足时直接提示 Agent 先补齐依赖而不是让它在执行阶段报错。7. 调用命中率的评测方法优化后的效果如何要靠数据说话。建议建立一套可复用的“命中率评测集”和评估脚本每次 Skill 变更后都跑一遍。7.1 构建评测样本集样本集要覆盖三类情况高频场景每个高频领域至少 10 到 20 个输入样例。容易混淆的场景特意设计一些语义相近的输入例如“查订单状态”和“改订单状态”。边界场景用户表达模糊、缺少参数、多意图混合等。每个样本标注“期望命中的 Skill”和“期望动作”。[ { user_input: 帮我查一下今天销售额, expected_skills: [finance_sales_report_query] }, { user_input: 把订单 1024 的收货地址改成上海市浦东新区, expected_skills: [finance_order_update] }, { user_input: 新建一个 11 月 5 日下午三点的会议, expected_skills: [calendar_create_event] } ]7.2 计算命中率指标在评测脚本里可以分别计算Top-1 命中率模型最终选择的 Skill 是否正确。Top-5 召回率候选列表里是否包含正确 Skill。参数正确率Skill 选对了参数是否正确。无效调用率触发了 Skill但执行失败或参数校验不通过。一个简化评估脚本示例# 文件路径evaluate_hit_rate.py import json def evaluate(samples, predictions): top1_hit 0 top5_recall 0 param_correct 0 total len(samples) for sample, pred in zip(samples, predictions): expected set(sample[expected_skills]) if pred[selected_skill] in expected: top1_hit 1 if len(expected set(pred[candidate_skills])) 0: top5_recall 1 if pred[selected_skill] in expected and pred[params_valid]: param_correct 1 return { top1_hit_rate: round(top1_hit / total, 4), top5_recall_rate: round(top5_recall / total, 4), param_correct_rate: round(param_correct / total, 4), total_samples: total } if __name__ __main__: with open(samples.json, r, encodingutf-8) as f: samples json.load(f) with open(predictions.json, r, encodingutf-8) as f: predictions json.load(f) result evaluate(samples, predictions) print(json.dumps(result, ensure_asciiFalse, indent2))运行方式python evaluate_hit_rate.py预期输出形如{ top1_hit_rate: 0.86, top5_recall_rate: 0.95, param_correct_rate: 0.81, total_samples: 120 }如果 Top-1 命中率低说明最终选择环节需要优化检查描述区分度和路由策略如果 Top-5 召回率低说明检索层没做好正确 Skill 根本没进候选列表如果参数正确率低说明 Schema 设计或参数抽取环节有问题。这种归因方式能把问题定位到具体环节而不是笼统地认为是“模型不行”。7.3 建立回归机制每次新 Skill 上线、旧 Skill 描述变更、模型版本升级都应该跑一遍评测集对比前后指标。如果命中率下降就需要找出是哪个 Skill 变化引起的。这个机制相当于传统软件工程里的回归测试只是测试对象从代码逻辑变成了 Agent 的工具选择行为。8. Skill 调用命中率常见问题与排查方法问题现象可能原因排查方式解决方案Agent 经常选错 Skill多个 Skill 描述语义重叠检查同一领域内描述的相似度建立冲突检测重写描述添加排除条件使用明确区分词候选列表里根本没有正确 Skill检索层召回策略不合理查看检索 Top-K 结果分析正确 Skill 的排名优化 Embedding 输入文本增加关键词命中规则技能命中但参数填错Schema 字段说明不清或字段类型不合理打印模型生成的参数 JSON与预期参数做比对补充参数示例为字段添加更细的描述新增 Skill 后老功能失灵新描述抢占旧 Skill 的语义空间对比新旧 Skill 的描述相似度为新 Skill 增加场景限制避免泛化同一个输入有时命中有时不命中模型随机性或候选排序不稳定多次运行同一输入观察波动降低 temperature或增加规则路由兜底调用执行失败但 Agent 不知道Skill 内部异常信息没有回流检查执行日志和返回值格式所有 Skill 统一返回结构化错误码高危 Skill 在普通对话中被触发缺少权限校验查看调用日志中高危 Skill 的触发上下文增加权限校验和显式确认机制上下文过长导致 Token 超限全量 Skill 描述都塞进了 prompt检查 prompt 中 Skill 描述数量改为检索式候选召回只放 Top-K9. Skill 体系最佳实践与工程建议写到这里把经验浓缩成一份可执行的清单。重视描述但不要只靠描述。描述是模型理解 Skill 的主要信息源但描述写得再好也不能解决“100 个候选塞进上下文”的问题。必须配合分类、检索和路由让模型在可管理的候选范围内做决策。先建立注册表再发展 Skill 数量。在 Skill 数量还没爆发的阶段就把注册表、元数据、版本管理和冲突检测做好后面扩容会顺畅很多。等数量过百再补你可能会面临大量重写工作。给 Skill 划分命名空间。一切按部门、业务或功能域划分不要让工具名是一锅粥。命名空间也便于在运维层面做隔离。把容易混淆的 Skill 成对暴露。如果两个 Skill 语义相近比如“查询订单”和“创建订单”主动在描述中告诉模型“如果用户是要查询千万别用创建”。这种对比式说明往往比单说“我是做什么的”更有效。用评测集量化一切。命中率高不高不能靠“感觉”。建立一个至少覆盖几十条典型输入的评测集每次改动后跑一遍让指标说话。为高风险 Skill 设置护栏。删除类、写入类、外部请求类 Skill一定加权限校验。即使模型选择了它也要满足条件后才真正执行。保留执行日志特别是模型生成的参数。一旦出现命中率下降日志是定位问题的第一手材料。不要只记录“调用了哪个 Skill”要记录模型的完整输出、候选列表、最终选择和执行结果。控制 Skill 粒度。一个 Skill 的功能尽量单一。如果一个 Skill 又查订单又改订单又删除订单它的描述会非常长且模型容易在使用意图不明确的时候做出错误调用。拆成三个细粒度 Skill配合分类和路由命中率会明显改善。注意新增 Skill 的回归风险。新 Skill 的语义空间如果和已有 Skill 重叠会影响旧 Skill 的调用命中。上线前用评测集跑一次回归不要直接推到生产。区分“可用”和“可靠”。100 个 Skill 都能被调用只能说明“可用”但要保证每一次调用都命中最合适的 Skill必须有完整的索引、排序、路由、评测和反馈机制。这也是 Agent 工程化程度的一个分水岭。10. 总结与后续学习方向Skill 数量超过 100 后Agent 调用命中率的瓶颈已经不在“模型能不能理解用户”而在“Skill 体系有没有被当成一套可管理的系统来设计”。描述层解决的是可区分性分类层解决的是候选范围检索层解决的是召回效率路由层解决的是风险控制。四者配合才能让模型在有限、精准的候选范围内做决策而不是在几百个相似的工具里盲猜。对正在做 Agent 项目的团队建议下一步优先做三件事把 Skill 注册表建起来把评测集跑起来把高风险 Skill 的护栏补上。这三件事做完你的 Agent 系统会明显更可控后续新增 Skill 时心里也有底。再往后可以继续深入的方向包括Skill 描述自动生成与质量检测、基于用户反馈的在线命中率监控、多模型场景下的 Skill 兼容层设计。Skill 体系一旦突破了 100 个规模它本身就是一个值得持续迭代的工程产品。