Agent Skill 数量过百后命中率下降?一套路由优化方案

Agent Skill 数量过百后命中率下降?一套路由优化方案 先问一个很现实的问题当你的 Agent 项目里只有 5 个 Skill 时模型基本不会选错当 Skill 数量到 30 个时开始偶尔答非所问当 Skill 数量突破 100 个时你会发现模型经常“视而不见”——明明某个 Skill 就是为当前任务设计的Agent 却跑去调用了另一个似是而非的工具甚至直接拒绝执行。这个现象在我维护的 Agent 项目中非常典型。团队早期热衷于把各种能力拆成 Skill从发邮件、查工单、生成图表到拉取指标数据数量很快过了百。结果 Agent 的调用命中率从 90% 以上一路掉到不足 70%线上反馈最多的一句话是“它怎么不用我新做的那个 Skill”这篇文章不打算讲玄学也不走“加一个更大的模型”这种思路。我会从 Agent 调用链路的实际痛点出发整理一套可落地的 Skill 治理和路由优化方案包含命名规范、描述模板、分层路由、检索召回和评估回归的具体做法。如果你也在维护一个 Skill 数量快速增长、命中率逐步下滑的 Agent 项目这篇文章应该能帮你找到明确的优化方向。1. 背景与核心概念1.1 什么是 Agent 中的 Skill在 Agent 开发中Skill 是一个被反复提及但定义并不统一的概念。简单理解Skill 是 Agent 可调用的一组能力封装它让模型不再只是“生成文本”而是能真正完成某个操作或任务。一个 Skill 通常包含三部分触发条件描述什么场景下应该使用这个 Skill。执行逻辑可以是代码、脚本、API 调用链也可以是另一个子 Agent。输入输出约束定义需要哪些参数、返回什么结构。从工程角度看Skill 类似于传统软件开发中的“工具函数”但它的特殊之处在于调用方不是人类而是大语言模型。Agent 需要根据用户输入、上下文、系统提示词和 Skill 描述自主决定调用哪个 Skill。这就引出了调用命中率的问题。1.2 什么是调用命中率调用命中率指的是Agent 在应当调用某个 Skill 的场景下是否准确选出了正确的 Skill。举个例子用户说“帮我查一下订单 OD 20250110 的物流状态”系统中有 5 个 Skill查询订单、查询物流、查询售后、查询库存、查询价格。如果 Agent 正确选择了“查询物流”这就是一次命中如果选成了“查询订单”或者随机编造了一个不存在的 Skill就是未命中。命中率是一个复合指标通常由三部分构成指标说明召回率正确 Skill 是否出现在候选结果中选择准确率模型是否从候选中选中了正确项执行成功率Skill 被调用后是否能正确执行完成很多团队只关注选择准确率忽略了召回率。实际上当 Skill 数量超过百级后最先出问题的往往是召回——正确 Skill 的描述根本没能进入模型的决策上下文后续选择准确率自然无从谈起。1.3 为什么 Skill 过百后命中率会明显下降Skill 从几十个增长到上百个不是简单的“数量变多”而是调用决策问题发生了质变。第一个原因是上下文膨胀。如果把所有 Skill 的描述都塞进系统提示词100 个 Skill 至少需要 3000 到 5000 个 token。模型在处理长上下文时注意力会被大量无关描述分散和当前任务真正相关的 Skill 反而不够突出。第二个原因是描述歧义。当 Skill 数量少时每个 Skill 的描述都能写得非常“个性化”。一旦数量增多很多 Skill 在功能上天然相似比如“生成销售周报”和“生成销售月报”如果描述不够精确模型很难区分。第三个原因是缺少分层机制。全部 Skill 放在同一个平面里让模型做选择本质上是一个“100 选 1”的难题。但如果先做一次分类变成“10 个分类中选 1 个再在 10 个 Skill 中选 1 个”决策难度会大幅下降准确率也会明显提升。2. 问题拆解Skill 数量过百后的五大痛点2.1 上下文膨胀与注意力稀释大语言模型的注意力机制决定了输入越长模型对每个 token 的“关注权重”越分散。把 100 个 Skill 的描述全部塞进上下文会导致模型对每个 Skill 的记忆都变得模糊。这种场景下经常出现的问题是模型记住了 Skill 描述中的共性关键词却忽略了每个 Skill 的差异化约束条件。比如多个 Skill 都包含“生成报告”这几个字模型可能随机选中其中一个而不是根据报告类型、时间范围、输出格式等细节做判断。解决思路不是“删掉一些 Skill”而是改变加载策略。真正有经验的 Agent 实践者会采用按需加载、分层检索的方式只把和当前意图最相关的少量 Skill 放进决策上下文而不是让模型背负全部 Skill 的描述。2.2 描述语义重叠导致选择歧义“描述语义重叠”是 Skill 数量过百后最隐蔽的敌人。举一个真实场景假设系统中有这两个 Skill——Skill A获取用户订单列表支持按状态筛选返回订单编号、金额、商品名称。Skill B查询用户历史订单支持按时间范围筛选返回订单编号、支付状态、物流单号。如果只看描述这两个 Skill 在功能上高度相似。模型面对用户问题“帮我查一下我上个月的订单”可能选择 A也可能选择 B甚至两个都不选。要解决这个问题不能只靠模型“更聪明”而是要在 Skill 描述编写阶段就做好语义隔离。每个 Skill 的描述要明确回答三个问题这个 Skill 是做什么的什么场景下必须用它什么场景下不应该用它2.3 缺少分层路由机制当 Skill 数量较少时把所有 Skill 放在同一个清单里让模型选择是可行的。但超过 100 个之后平面式选择就会出问题。原因很简单模型在“100 个选项中选 1 个”的准确率远低于“先在 10 个分类中选 1 个再在 10 个选项中选择 1 个”。这是由大语言模型本身的决策特性决定的。分层路由的核心思路是给 Skill 增加“目录”维度。类似在文件系统中你不会把所有文件都丢在根目录下而是按功能建立文件夹。Skill 管理也应该这样否则无论怎么优化单个描述模型都会在高相似度的 Skill 之间摇摆。2.4 召回与排序能力缺失很多 Agent 项目的架构是“将所有 Skill 描述拼接到提示词中让模型一次选择”。这种做法把召回和排序全部交给了模型缺少系统层面的“预筛选”。当 Skill 数量超过百级更合理的做法是引入检索层先用关键词或向量检索从 100 多个 Skill 中召回 Top-K 个候选再把这 K 个候选的描述输出给模型做最终选择。这样做有几个好处减少了模型需要处理的上下文长度。过滤掉了大量明显无关的 Skill。将决策难度从“100 选 1”降为“5 选 1”或“10 选 1”。可以在召回阶段强制加入业务规则比如权限过滤、环境过滤、灰度过滤。2.5 版本与依赖管理混乱Skill 数量过百后另一个被忽视的痛点是管理混乱。同一个 Skill 可能被迭代了多个版本不同环境开发、测试、生产使用不同的配置Skill 之间还可能存在依赖关系。如果这些信息没有结构化维护Agent 调用时会出现“描述是对的代码已经变了”的情况。比如模型根据旧描述选择了 Skill但实际执行时底层 API 已经变更导致调用失败或返回异常数据。因此Skill 治理不只是“把描述写好”还需要建立统一的注册表和元数据规范把名称、版本、依赖、权限、适用范围都管理起来。3. 环境准备与示例场景说明3.1 运行环境说明本文的示例以 Python 为主建议环境如下Python 3.10 或以上版本。需要安装 numpy、pydantic、PyYAML 等常用库。Agent 框架部分以伪代码和结构示意为主核心思路可以迁移到 LangChain、AutoGen、自研框架等不同实现中。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路和路由机制不绑定特定框架版本。3.2 示例项目目录结构为了让后面的实战案例更直观我们先约定一个项目结构。这是一个典型的 Agent Skill 治理项目并不是完整生产代码而是演示如何组织 Skill 定义和路由模块。agent-skill-routing/ ├── skills/ │ ├── order/ │ │ ├── query_order.yaml │ │ ├── query_logistics.yaml │ │ └── refund_order.yaml │ ├── report/ │ │ ├── generate_sales_report.yaml │ │ └── generate_user_report.yaml │ └── customer/ │ ├── get_customer_info.yaml │ └── update_customer_tag.yaml ├── router/ │ ├── __init__.py │ ├── indexer.py │ ├── retriever.py │ ├── classifier.py │ └── selector.py ├── registry/ │ ├── skill_registry.py │ └── skill_schema.py ├── config/ │ └── routing_config.yaml └── examples/ └── demo_router.py这个结构体现了前面提到的分层思想skills目录按业务分类组织router目录负责从 Skill 列表中找出最合适的候选registry目录管理 Skill 元数据。4. 提升命中率的完整策略体系4.1 第一步规范 Skill 命名与描述当 Skill 数量超过百级时你最先要做的不是写代码而是制定一套统一命名规则。因为模型对 Skill 的理解完全来源于你给它的名字和描述文本。如果这些文本本身充满歧义后续所有工程优化都会打折扣。推荐结构[动作]_[对象]_[场景]。例如query_order_logistics查询订单物流。generate_sales_report生成销售报告。update_customer_tag更新客户标签。命名规范的意义有两点。第一模型看到 Skill 名称时能快速理解这个 Skill 的核心功能第二在检索召回阶段关键词匹配会更准确。试想一下如果你把一个查询物流的 Skill 命名为track_package_flow而用户习惯说“订单物流”模型可能根本不会联想到这个 Skill。描述文本建议使用结构化模板而不是自由发挥。一个标准描述模板可以包含以下字段name: query_order_logistics description: 查询订单物流状态 keywords: [订单, 物流, 快递, 收货, 签收, tracking, logistics] application_scene: 用户询问某个订单的物流进度、快递位置时使用 input_parameters: order_id: type: string required: true description: 订单编号 logistics_company: type: string required: false description: 物流公司名称可选 output_format: 返回物流轨迹列表包含时间、地点、状态 when_not_to_use: 不能用于查询订单支付状态、退款进度用户询问价格时不要调用特别注意when_not_to_use字段。这是很多团队忽略的却非常有效。模型在选择 Skill 时除了要知道“什么时候用”还需要知道“什么时候不用”。负向约束能显著减少相似 Skill 之间的混淆。4.2 第二步建立 Skill 分类与分组分类是降低选择难度的最有效手段。当 Skill 数量超过 100 个时建议按两级分类管理第一级是业务域第二级是具体 Skill。例如一个电商 Agent 的 Skill 分组可以是一级分类包含 Skill 示例订单域查询订单、查询物流、申请退款、修改收货地址商品域查询商品详情、查询库存、商品比价用户域获取用户信息、更新用户标签、查询会员等级报表域生成销售报表、生成用户报表、生成库存报表营销域创建优惠券、查询活动列表、发送营销短信分组之后Agent 的决策流程就从“100 个 Skill 里选 1 个”变成了“先判断属于哪个业务域再在 10 个 Skill 里选 1 个”。4.3 第三步引入两级路由机制两级路由是提升命中率的核心策略。第一级是意图分类第二级是 Skill 选择。具体流程如下用户输入到达 Agent。路由层先根据用户输入判断当前意图属于哪个业务域。加载该业务域下的所有 Skill 描述。模型在这些 Skill 中选出最匹配的一个。执行该 Skill并返回结果。两级路由的优势在于它把一次“大海捞针”式选择变成了两次小范围选择。每一次选择的候选集都很小模型的决策负担大幅降低。实际落地时第一级分类可以有两种实现方式构建一个轻量级意图分类器使用 embedding 相似度匹配。让 Agent 模型自己先做一次分类判断再进入具体的 Skill 选择。如果团队已经有标注数据建议用第一种方式效果更稳定如果数据量少可以采用第二种方式但要记得在系统提示词中明确分类规则。4.4 第四步使用向量召回提升检索效率分级路由解决了“候选集过大”的问题但在某些场景下用户输入不够明确或者一个业务域下的 Skill 数量仍然很多还需要引入向量召回机制。向量召回的基本思路是为每个 Skill 生成 embedding 向量存到向量库中。用户请求到来时将用户的输入也转换为 embedding 向量。计算用户输入向量与所有 Skill 向量的相似度。取 Top-K 个相似度最高的 Skill作为候选输出给模型。使用向量召回时需要注意一个问题如果只靠向量相似度可能会召回一批“看起来相关但实际不匹配”的 Skill。比如用户说“帮我查一下昨天卖得最好的商品”向量召回可能同时返回“查询商品详情”和“生成销售报表”因为这两个功能在语义上都有一定相关性。所以向量召回适合做“粗排”不能直接作为最终决策。正确的做法是向量召回 Top-10再让模型在 Top-10 中做精排选择。4.5 第五步优化模型选择策略与输出约束当候选 Skill 列表已经缩放到 5 到 10 个之后如何让模型“稳定地选对”就成了关键。这里有一个工程经验不要只给模型一串 Skill 名称要把每个候选的高质量描述都呈现出来并且要求模型输出结构化结果而不是自由文本。典型的提示词片段如下你是 Agent 路由决策器。请根据用户输入从以下候选 Skill 中选择最合适的一个。 候选 Skill 列表 1. query_order_logistics 功能查询订单物流状态 适用场景用户询问物流、快递、签收进度 参数order_id, logistics_company 2. query_order_info 功能查询订单基本信息和状态 适用场景用户询问订单详情、订单状态、订单金额 参数order_id 请只输出 JSON 格式不要添加额外说明 {skill: 选中 Skill 的名称, confidence: 0.0-1.0, reason: 选择理由}输出结构化约束有两个好处第一方便程序解析结果不会出现模型“嘴上说选择 A实际执行 B”的情况第二要求模型写出选择理由能让模型在决策时更谨慎减少随机选择。另外一个重要技巧是温度参数调低。在选择题场景中温度过高会导致模型“创意性发挥”这是灾难性的。建议将路由决策的 temperature 设置为 0 或接近 0。4.6 第六步动态加载与上下文裁剪前文反复提到不要把全部 Skill 描述塞进上下文。实际工程中Skill 描述应当分层加载全局保留一份精简的“Skill 目录”只包含 Skill 名称和一句话用途。当意图判断完成后再加载该分类下的完整 Skill 描述。向量召回出的 Top-K 候选也只需要加载这几个 Skill 的详细描述。这套机制可以用一个简单的流程图表示用户输入 ↓ 意图判断轻量分类器或模型第一步判断 ↓ 定位业务域 ↓ 加载该域 Skill 描述列表 ↓ 向量召回 Top-K 候选 ↓ 模型精排选择最终 Skill ↓ 执行 Skill返回结果动态加载不仅能提升命中率还能显著降低 token 消耗和响应延迟。对于生产环境的 Agent 项目这是一项必要的优化。5. 实战案例从无序 Skill 列表到高命中率路由层5.1 创建项目结构按照前面第 3 节的目录结构创建项目。如果你是在自己的 Agent 工程中改造可以只新增registry和router两个模块不需要动已有的 Skill 定义。创建目录mkdir -p agent-skill-routing/{skills/{order,report,customer},router,registry,config,examples}5.2 定义 Skill Schema为了让 Skill 描述结构化我们需要定义一个统一的数据模型用 pydantic 来做字段校验。这样后续在加载 Skill 时能提前发现描述残缺问题。# 文件路径registry/skill_schema.py from typing import List, Optional from pydantic import BaseModel, Field class SkillParameter(BaseModel): name: str type: str string required: bool False description: str class SkillDefinition(BaseModel): name: str Field(..., descriptionSkill 唯一名称) display_name: str Field(, description展示名称) description: str Field(..., descriptionSkill 一句话描述) keywords: List[str] Field(default_factorylist, description触发关键词) category: str Field(..., description一级分类如 order、report、customer) application_scene: str Field(, description适用场景详细说明) when_not_to_use: str Field(, description负面约束什么时候不应该调用) input_parameters: List[SkillParameter] Field(default_factorylist) output_format: str Field(, description输出格式说明) version: str Field(1.0.0, description版本号) owner: str Field(, description负责人)这个 Schema 看起来很基础但它解决了一个实际问题当 Skill 数量过百后靠人肉检查每个描述是否规范是不现实的。有了 Schema就能在注册阶段统一校验。5.3 编写 Skill 注册表注册表负责加载所有 Skill 定义并提供按分类检索的能力。# 文件路径registry/skill_registry.py import yaml from pathlib import Path from typing import Dict, List from registry.skill_schema import SkillDefinition class SkillRegistry: def __init__(self, skills_dir: str): self.skills_dir Path(skills_dir) self._skills: Dict[str, SkillDefinition] {} self._categories: Dict[str, List[SkillDefinition]] {} def load_all(self) - None: 扫描 skills 目录下所有 yaml 文件并加载。 for yaml_file in self.skills_dir.rglob(*.yaml): with open(yaml_file, r, encodingutf-8) as f: raw_data yaml.safe_load(f) skill SkillDefinition(**raw_data) self._skills[skill.name] skill self._categories.setdefault(skill.category, []).append(skill) def get_skill(self, name: str) - SkillDefinition: return self._skills.get(name) def list_by_category(self, category: str) - List[SkillDefinition]: 按一级分类返回 Skill 列表。 return self._categories.get(category, []) def list_categories(self) - List[str]: return list(self._categories.keys()) def total_count(self) - int: return len(self._skills)这个注册表的核心价值是集中管理所有 Skill并且支持按分类快速获取候选集。在两级路由中第一级分类完成后只需要把该分类的 Skill 列表交给模型。5.4 编写简单的检索召回模块下面实现一个不依赖外部向量库的简易召回模块。这里使用 TF-IDF 风格的关键词命中 覆盖度计算作为向量召回之前的快速粗筛。如果你已经在使用向量数据库可以直接替换retrieve方法的内部实现。# 文件路径router/retriever.py import jieba from typing import List from registry.skill_schema import SkillDefinition def tokenize(text: str) - set: 简单分词实际项目中可替换为更专业的分词器或 embedding。 return set(jieba.lcut(text)) def key_tokenize(keywords: List[str]) - set: 将关键词列表展开为 token 集合。 tokens set() for keyword in keywords: tokens.update(jieba.lcut(keyword)) return tokens class KeywordRetriever: 基于关键词覆盖度的 Skill 召回器。 def __init__(self, registry): self.registry registry def retrieve(self, user_input: str, top_k: int 5) - List[SkillDefinition]: input_tokens tokenize(user_input) scored_skills [] for skill in self.registry._skills.values(): skill_tokens key_tokenize(skill.keywords) # 取并集避免除零 union input_tokens | skill_tokens if len(union) 0: continue # 计算 Jaccard 相似度交集 / 并集 overlap len(input_tokens skill_tokens) score overlap / len(union) if overlap 0: scored_skills.append((skill, score)) scored_skills.sort(keylambda item: item[1], reverseTrue) return [skill for skill, _ in scored_skills[:top_k]]这里需要注意真实生产环境建议使用 embedding 向量召回效果会好很多。上面的 KeywordRetriever 只是一个轻量示例用于说明“召回层”的职责边界它不决定最终调用哪个 Skill只负责缩小候选范围。5.5 编写两级路由决策器下面进入核心实战环节。我们要实现一个路由决策器它的工作流程是将用户输入与一级分类匹配。确定分类后从注册表中获取该分类下的 Skill 列表。若分类下 Skill 仍然超过阈值先做关键词召回。把候选 Skill 描述组装成提示词交给模型选择。解析模型输出返回最终 Skill 名称和置信度。为了让示例不依赖具体的大模型 SDK我们把模型调用封装成一个函数。实际项目中你可以在这里接入 OpenAI 接口、Claude 接口或者自研模型的 HTTP 服务。# 文件路径router/selector.py import json from typing import Optional from registry.skill_registry import SkillRegistry from router.retriever import KeywordRetriever from registry.skill_schema import SkillDefinition class Router: def __init__(self, registry: SkillRegistry, category_embeddingsNone): self.registry registry self.retriever KeywordRetriever(registry) # category_embeddings 可用于基于向量的一级分类这里先保留接口 self.category_embeddings category_embeddings def classify_category(self, user_input: str) - str: 第一级路由判断用户输入属于哪个业务域。 示例中使用简单关键词匹配实际项目建议使用分类模型或 embedding 相似度。 category_keywords { order: [订单, 物流, 退款, 收货, 发货, 购买, 下单], report: [报表, 报告, 统计, 汇总, 环比, 同比, 销售], customer: [客户, 用户, 会员, 标签, 画像, 联系方式], } best_category None best_score 0 for category, keywords in category_keywords.items(): score sum(1 for kw in keywords if kw in user_input) if score best_score: best_score score best_category category if best_score 0: # 如果无法判断默认返回 None由上层决定如何处理 return unknown return best_category def build_prompt(self, user_input: str, candidates: List[SkillDefinition]) - str: lines [ 你是一个 Agent 路由决策器。请根据用户输入从候选 Skill 中选择最合适的一个。, 候选 Skill 列表, ] for idx, skill in enumerate(candidates, start1): lines.append( f{idx}. {skill.name}\n f 功能{skill.description}\n f 适用场景{skill.application_scene}\n f 参数{[p.name for p in skill.input_parameters]}\n f 不要用于{skill.when_not_to_use or 无} ) lines.append(请只输出 JSON 格式{skill: Skill 名称, confidence: 0-1, reason: 选择理由}) return \n\n.join(lines) def call_model(self, prompt: str) - dict: 调用大模型接口的核心函数。 这里需要替换为你实际使用的模型 SDK。 示例返回一个固定的 JSON仅用于演示提示词构造逻辑。 # 实际项目中这里会调用模型接口 # response openai.ChatCompletion.create(...) # return json.loads(response[choices][0][message][content]) return { skill: query_order_logistics, confidence: 0.92, reason: 用户询问物流状态该 Skill 描述最匹配, } def route(self, user_input: str) - Optional[str]: # 第一级分类 category self.classify_category(user_input) print(f[路由] 一级分类结果{category}) if category unknown: return None # 第二级获取该分类下所有 Skill candidates self.registry.list_by_category(category) print(f[路由] 分类 {category} 下共有 {len(candidates)} 个 Skill) if len(candidates) 10: # 如果候选过多先做关键词召回 candidates self.retriever.retrieve(user_input, top_k5) print(f[路由] 候选超过阈值召回 Top-{len(candidates)}) if not candidates: return None # 第三级构建提示词并交给模型做最终决策 prompt self.build_prompt(user_input, candidates) result self.call_model(prompt) skill_name result.get(skill) confidence result.get(confidence, 0) print(f[路由] 模型选择{skill_name}置信度{confidence}) # 置信度过低时可以返回 None由上层 Agent 继续兜底 if confidence 0.6: print([路由] 置信度低于阈值返回 None) return None return skill_name上面的代码演示了完整的两级路由机制。需要注意classify_category中的关键词表只是示意真实项目建议换成意图分类模型。call_model中需要接入实际的大模型 API。置信度阈值 0.6 可以根据业务场景调整。在关键业务中宁可不调用也不要乱调用。5.6 运行与验证我们写一个简单的主程序来演示路由流程。# 文件路径examples/demo_router.py import sys sys.path.append(..) from registry.skill_registry import SkillRegistry from router.selector import Router def main(): registry SkillRegistry(../skills) registry.load_all() print(f已加载 Skill 数量{registry.total_count()}) print(f一级分类列表{registry.list_categories()}\n) router Router(registry) test_input 帮我查一下订单 OD20250110 的物流状态 skill_name router.route(test_input) print(f\n最终选择 Skill{skill_name}) if __name__ __main__: main()预期的运行输出大致如下已加载 Skill 数量7 一级分类列表[order, report, customer] [路由] 一级分类结果order [路由] 分类 order 下共有 3 个 Skill [路由] 模型选择query_order_logistics置信度0.92 最终选择 Skillquery_order_logistics这个示例虽然简单但已经包含了和一致性相关的关键设计Skill 按分类管理不是平铺在一个大文件里。路由过程分为“分类 → 召回 → 精排”三个阶段。模型输出是结构化 JSON方便后续程序化解析。置信度低于阈值时路由层可以主动拒绝返回避免错误调用。5.7 效果评估与数据说明当我们把这一套机制部署到线上后不能只看“感觉变好了”需要建立可量化的评估指标。建议为每个 Skill 建立一条测试用例集至少包含正向用例明确应当调用该 Skill 的用户输入。负向用例不应调用该 Skill 的相似输入。评估指标包括指标计算方式优化目标召回率正确 Skill 进入候选的比例越高越好精确率最终选中的 Skill 是正确的比例越高越好兜底率路由层返回 None 的比例结合业务确定平均延迟一次路由决策耗时越低越好平均 token 数单次路由消耗的 token越低越好在实际项目中我建议每新增 20 个 Skill 就做一次全量回归测试。否则等到命中率跌到无法忍受时再排查定位问题的成本会高很多。6. 常见问题与排查思路Skill 数量过百后命中率下降的原因往往是多个因素叠加。以下整理了几个高频问题和排查建议。问题现象常见原因解决思路模型总是选错相似 Skill描述语义重叠缺少负向约束为每个 Skill 增加when_not_to_use字段并检查相似 Skill 描述是否足够差异化新加的 Skill 从未被调用新 Skill 未进入候选集或命名与用户习惯不一致检查注册表是否加载成功检查分类关键词是否覆盖用户常用说法用户输入与 Skill 关键词差异较大召回层不够强仅依赖关键词匹配引入向量召回将用户输入和 Skill 描述做 embedding 相似度计算上下文过长导致响应变慢全量加载 Skill 描述到提示词改为动态加载优先加载分类或召回后的候选 Skill某些用户输入完全无法路由一级分类无法匹配或置信度过低被拒绝增加兜底策略例如返回通用 Agent 处理检查分类关键词覆盖度测试集命中率正常线上命中率低测试用例与真实场景分布不一致定期抽取线上真实用户输入作为回归测试集同一输入多次路由结果不一致模型温度过高或提示词中候选顺序不稳定将 temperature 调低候选 Skill 排序固定避免随机性7. 最佳实践与工程建议7.1 描述与命名规范Skill 命名和描述是命中率的根基。建议团队内建立统一的写法规范并写入代码评审流程。以下是一份可参考的模板name: 动词_对象_场景 description: 一句话说明功能20 字以内 keywords: 至少 3 个用户可能使用的同义表达 application_scene: 什么业务场景下使用写清楚触发条件 when_not_to_use: 明确什么场景不要调用强调一句写描述时不要只站在开发视角要站在用户视角。比如用户会说“包裹到哪了”而不是“查询物流轨迹记录”。7.2 配置与注册表管理当 Skill 数量过百后靠文件目录管理会逐渐吃力。建议引入配置中心或者一份集中的 Skill 注册表记录每一条 Skill 的生命周期创建、上线、下线、废弃。注册表中应该包含这些信息Skill 名称和版本号。负责人和变更历史。依赖的 API、权限范围。所属分类和标签。当前生命周期状态开发中、已上线、已废弃。这样做的价值在于当路由层需要过滤 Skill 时可以快速跳过“未上线”和“已废弃”的项避免模型选中一个不可用的 Skill。7.3 日志、监控与灰度发布命中率不是配置完就不变的。随着线上用户输入变化、模型版本升级、Skill 描述修改命中率都会波动。因此必须把路由决策过程记录成日志。每个路由日志至少包含用户输入原文。一级分类结果。召回候选列表和分数。模型最终选择和置信度。实际执行结果成功或失败。用户是否重新提问或纠正。有了这些日志你才能回答一个关键问题模型选错是因为描述不清、召回不到还是因为候选太相似。灰度发布建议修改 Skill 描述、调整路由策略时先在 10% 到 20% 的流量上验证命中率确认无回退后再全量上线。这是避免“一改描述就掉点”的有效手段。7.4 安全与权限边界Skill 数量增多后权限管理也是不可回避的问题。首先要确保路由层只加载当前用户有权限访问的 Skill。例如普通用户不能调用“管理员重置密码”的 Skill即使模型在候选集中看到了它也不应该执行。其次Skill 的描述中不要包含敏感的内部 API 地址、密钥、数据库连接串。模型在长上下文中可能无意间泄露这些信息一旦日志被外部访问就会造成安全事故。最后涉及删除、修改、资金操作等高风险 Skill建议增加二次确认机制。核心原则是模型可以“建议”执行但关键动作必须有人工确认。7.5 性能优化命中率和性能经常被放在一起考虑因为很多团队为了提升命中率把更多内容塞进上下文结果延迟暴涨。推荐的性能优化路径是给每个 Skill 维护一个“短描述”用于路由决策详细说明只在执行阶段加载。一级分类尽量用轻量分类器或向量检索避免每次都调用大模型。向量召回结果做缓存相同或相似输入直接命中缓存。控制候选 Skill 数量在 5 到 10 个之间不要贪多。7.6 避免在 Skill 描述中写出不存在的接口这是一个容易踩的坑Skill 描述中的参数名、接口路径必须与真实实现保持一致。如果模型根据描述选择了 Skill但参数名对不上执行阶段就会失败。建议在注册表加载时增加一个校验逻辑读取 Skill 描述中的input_parameters与代码中的函数签名做对比不一致时直接报错而不是等到线上调用时才暴露。8. 总结与后续学习方向Skill 数量过百后的命中率问题本质上是“Agent 在大量候选能力面前如何做正确决策”的工程问题。它不是单靠换一个更强的模型就能解决的而是需要从 Skill 定义规范、路由架构、召回策略、模型输出约束、评估体系几个方向系统优化。回顾本文的核心内容Skill 描述必须结构化包含关键词、适用场景、负向约束。Skill 必须分层管理通过一级分类缩小候选范围。两级路由机制是提升命中率的关键架构。当候选过多时引入向量召回做粗排模型精排做最终决策。模型输出必须结构化并设置置信度阈值。建立测试集和评估指标新增 Skill 后必须回归。如果你正在搭建或维护 Agent 项目我的建议是先做两件事第一把现有 Skill 的描述全部按模板重写一遍加上when_not_to_use字段第二给 Skill 建立分类目录实现两级路由。这两步做完命中率通常会有明显提升。下一步可以深入学习的方向包括向量数据库的选型与实践、Few-shot 提示词在路由决策中的应用、多轮对话下的 Skill 上下文记忆管理、以及如何用自动评估框架持续回归命中率。Skill 数量增长本身是好事说明 Agent 的能力在扩展关键是如何让这些能力被“正确地想起来、正确地用起来”。所谓命中率优化本质上是给模型的决策过程装上导航系统让它从“凭感觉选”变成“有依据地选”。如果这篇文章对你有帮助可以收藏备用后续团队扩展 Skill 时按里面的模板和步骤走一遍能少走不少弯路。