Agent技能路由:检索与大模型混合方案实战解析
最近在准备 Agent 相关面试题时经常看到一个问题“Agent 技能路由到底用检索还是大模型” 如果面试现场直接回答“让模型自己选”大概率会被追问一句“那模型选错了怎么办” 然后整个回答就容易卡住。这个问题看起来是二选一实际上是在考察你对 Agent 工程链路的理解深度。技能路由不是一道单选题它需要结合技能数量、token 成本、延迟、准确率、可观测性来做分层设计。本文不打算给你一个标准答案而是从原理、对比、完整代码和工程落地四个层面把“检索式路由”和“大模型式路由”讲透最后给出一套可落地的混合路由方案。1. Agent技能路由是什么为什么会被面试官盯上1.1 从一个Agent调用链说起先看一个典型的 Agent 处理用户请求的链路用户请求 - 意图理解 - 技能路由 - 调用技能 - 生成回复假设你正在做一个智能客服 Agent系统里有查天气、查订单、查物流、查优惠券、申请售后五个技能。用户说一句“我的快递到哪了”Agent 不可能每次都把五个技能全部执行一遍而是要先判断这句话应该交给哪个技能。这个“判断交给谁处理”的环节就是技能路由Skill Routing / Tool Routing。在传统软件里我们会写一个if-else或switch-case来分发请求if 快递 in user_input or 物流 in user_input: skill check_logistics elif 订单 in user_input or 购买 in user_input: skill check_order else: skill unknown这种方式在技能很少时没有问题但一旦技能数量增长到几十个甚至几百个规则判断会变得非常脆弱。用户表达千变万化同一个技能可能对应完全不同的说法比如“我的包裹怎么还没到”“卖家发货了吗”“快递在哪了”其实都是物流查询。规则一多维护成本就会爆炸。于是 Agent 系统需要一个更智能的路由层它要做的事情是给定用户输入和技能集合选出一个最合适的技能去执行。这个选择过程可以用检索式方案也可以用大模型方案也可以两者结合。1.2 技能路由到底解决什么问题很多人把技能路由等同于“意图识别”两者有重叠但侧重点不同。意图识别Intent Recognition关注的是“用户想做什么”通常是一个分类问题输出一个意图标签。技能路由Skill Routing关注的是“系统应该调用哪个能力”不仅包含意图判断还包含工具选择、参数规划、调用合法性判断。举个例子用户说“我要退货”。在技能体系里退货可能归属“申请售后”技能但这个技能本身包含“填写退货单”“查询退货进度”“撤销退货申请”等多个内部动作。技能路由只需要决定把请求交给“申请售后”技能至于技能内部怎么处理属于后续流程路由层不关心。这就像网络中的路由器它不负责生成数据内容只负责把数据包分发到正确的目标地址。一套好的技能路由方案应该做到以下几点准确在技能数量多、描述相似的情况下能选对技能。高效时间开销不能太大不能让用户明显感觉到“卡顿”。可控技能列表新增、下线时路由逻辑能及时调整。可解释线上出了问题时开发人员能快速定位为什么路由到了 A 技能而不是 B 技能。可降级当某个路由组件不可用时系统有后备方案而不是整体不可用。1.3 为什么“让模型自己选”是危险答案面试中问到技能路由很多人的第一反应是“把所有技能描述放进 prompt让大模型选择”。这种思路并不是完全不能用但如果作为面试回答的唯一方案很容易暴露工程经验不足。让大模型直接选通常有四个问题。第一是 token 开销。假设你有 100 个技能每个技能的描述平均 100 个 token光技能列表就要 10000 个 token。每次用户请求都把这 10000 个 token 发给大模型成本会非常高而且请求越多这个成本越不可控。第二是上下文窗口限制。技能描述会越写越详细尤其是字段、参数、约束条件都会往里塞。当技能数量增长到几百个累计 token 数很容易超过大模型的上下文窗口。即使模型窗口足够大把大量不相关信息塞进去模型很容易被无关技能干扰反而降低准确率。第三是幻觉问题。当我们让模型在开放式候选中做选择时模型可能编造一个不存在的技能名或者在两个相似技能之间反复横跳。你可以用强约束 prompt 缓解这个问题但无法根除。第四是稳定性。模型输出天然带有随机性同样的用户问题两次请求可能路由到不同技能。如果缺少校验机制和降级策略这种随机性在线上就是事故隐患。所以“让模型自己选”这个答案的问题不在于“模型大不大”而在于“把路由决策完全押注在模型的一次开放式输出上”既不可控也不可观测。2. 两种典型路由方案的原理解读2.1 检索式路由先缩小范围再决策检索式路由的核心思想是把“技能路由”转换成“文本相似度匹配”问题。具体做法是给每个技能准备一段结构化的描述文本用户请求进来后计算用户输入与技能描述之间的相似度选相似度最高的技能。检索式路由最常见的实现方式有三种第一种是关键词召回如 BM25BM25 是一种基于词频和逆文档频率的排序函数最早用于搜索引擎。它不依赖模型纯统计计算速度极快。它的核心逻辑是一个词在某个文档中出现的次数越多这个词对于该文档的重要性越高但同时要减去词在整个语料中过于常见带来的干扰。BM25 适合中文场景下的精确关键词匹配比如“退货”“物流”这种技能标签词。但它对同义词、口语化表达不够敏感比如“我的包裹有点问题”这种说法字面上并不包含“物流”关键词BM25 可能召回不到物流技能。第二种是向量召回如 Embedding 相似度向量召回是用预训练模型如 BERT、Sentence Transformer 等把文本转成一个向量然后计算用户输入向量与技能描述向量的余弦相似度。这种方式能捕捉语义层面的相似性哪怕用户输入和技能描述没有一个字重复只要语义接近也能召回。比如技能描述是“查询用户订单的付款状态、配送情况”用户输入是“我买的东西状态咋样”两者没有明显公共关键词但向量空间里距离很近。向量召回的缺点是依赖 Embedding 模型的质量同时需要维护向量索引。常见的落地方案有 faiss、Milvus、Qdrant 等。对于技能数量较多的场景还需要考虑索引更新策略。第三种是混合召回混合召回的核心思路是把关键词召回和向量召回结合起来通常采用“多路召回 加权融合”的方式。这也是现在工业界比较常用的方案比如“向量混合检索加 BM25 多路召回”的思路先分别用 BM25 和向量检索召回 top N 结果再通过分数归一化、加权融合得到最终的候选排序。混合召回的优势在于关键词能为精确术语兜底向量能为语义表达兜底两个通道互为补充。2.2 大模型式路由把选择权交给语义理解大模型式路由的出现是为了解决检索式路由难以处理复杂语义的问题。检索式路由本质上还是“匹配”但用户表达往往是模糊的、隐含的有时候一句话里包含多个意图或者需要结合上下文才能判断。大模型式路由通常有两种做法。第一种是让大模型从候选列表中选择把技能列表和描述拼进 prompt要求模型只能从给定技能中选择一个输出技能 ID 或技能名。这种方式适合技能数量较少、描述本身具有较强区分度的场景。第二种是让大模型先思考再调用将路由决策作为 ReAct 或 Tool Calling 的一部分让大模型在生成回复的过程中自行决定是否调用某个技能以及调用哪个技能。这种方式更接近 Agent 的完整形态但路由只是其中的一环模型自由度和不可控性会更大。大模型式路由的优点是强大的语义理解能力能处理口语化、省略、歧义等检索式难以覆盖的输入。缺点则是前文提到的成本、延迟、幻觉和随机性。2.3 直接对比检索式和模型式的适用边界对比维度检索式路由大模型式路由原理文本相似度匹配语言模型语义决策延迟低毫秒级高秒级成本低高按 token 计费技能数量扩展性强增加技能只需更新索引弱技能增多会撑爆 prompt语义泛化能力一般依赖向量模型强能处理复杂语义幻觉/随机性风险无存在需要强约束可解释性高能回溯相似度分数低模型推理过程不可控适合场景技能数量多、低延迟、低成本技能数量少、语义复杂、允许一定延迟从对比中能看出两者并不是替代关系而是互补关系。检索式路由擅长“缩小范围”大模型路由擅长“最终决断”。把两者结合起来正是工程上最稳妥的思路。3. 实战目标与基础环境准备3.1 示例场景智能客服技能路由这一节我们实现一个可运行的“检索召回 大模型精排/兜底”混合路由实战项目。示例场景是一个智能客服 Agent包含五个技能查天气查订单查物流查优惠券申请售后目标输入用户问题输出应该路由到的技能名并显示本次路由走的是“检索直出”还是“大模型兜底”。3.2 环境依赖本地环境建议使用 Python 3.9 及以上版本。核心依赖如下pip install jieba scikit-learn openai说明一下各依赖的作用jieba中文分词库。BM25 和向量化都需要先对中文文本分词不安装的话无法处理中文输入。scikit-learn用于计算 TF-IDF 向量示例中用 TF-IDF 模拟向量召回通道。生产环境建议替换为 SentenceTransformer faiss 或 Milvus。openai用于调用大模型接口。示例中通过 OpenAI 兼容接口调用本地方案如果你使用的是云端模型只需修改base_url和model即可。如果你的本机没有安装 Ollama又不想配置云端模型可以先跳过 4.3 节的真实模型调用改为模拟返回后续再接入真实模型。3.3 项目结构router_demo/ ├── main.py # 入口演示完整路由过程 ├── skill_registry.py # 技能注册表 ├── retriever.py # 检索召回层BM25 向量混合 ├── llm_router.py # 大模型路由层 └── hybrid_router.py # 混合路由主流程下面的代码按文件拆分读者可以按路径创建文件直接运行。4. 完整实战搭建一套“检索召回大模型精排”的混合路由4.1 定义技能注册表技能注册表是路由系统的元数据中心。你能路由哪些技能取决于这里注册了什么。# 文件路径router_demo/skill_registry.py from dataclasses import dataclass, field from typing import List dataclass class Skill: 技能定义 name: str # 技能唯一标识 description: str # 技能描述用于向量检索 keywords: List[str] # 关键词用于 BM25 召回 parameters: List[str] field(default_factorylist) # 技能所需参数 def load_default_skills() - List[Skill]: 初始化技能列表 return [ Skill( namecheck_weather, description查询指定城市的天气情况包括温度、降雨、风力等天气信息, keywords[天气, 温度, 下雨, 降雨, 晴天, 风力, 气温], parameters[city], ), Skill( namecheck_order, description查询用户在电商平台下的历史订单包括订单状态、商品信息、订单金额, keywords[订单, 购买, 下单, 交易, 买了], parameters[order_id], ), Skill( namecheck_logistics, description查询快递物流进度包括包裹所在位置、运输状态、预计送达时间, keywords[物流, 快递, 包裹, 到哪, 发货, 配送, 物流单号], parameters[tracking_number], ), Skill( namecheck_coupon, description查询用户账户中的优惠券包括面额、有效期、使用条件, keywords[优惠券, 满减, 折扣, 优惠, 代金券], parameters[coupon_id], ), Skill( nameafter_sale, description处理用户售后申请包括退货、退款、换货、投诉维权等, keywords[退货, 退款, 售后, 换货, 投诉, 退款申请], parameters[order_id, reason], ), ]技能描述是整个路由系统的“第一质量防线”。描述写得越清晰、越有辨识度检索结果就越准确。比如不要把“检查订单状态”和“检查物流状态”都写成“查询订单进度”否则两个技能在向量空间里会非常接近路由会频繁混淆。4.2 实现检索召回层BM25 向量混合检索召回层是路由系统的第一道关卡。它的任务不是一次性选出最终技能而是快速把候选技能从 5 个缩小到 top 3供后续决策使用。下面实现一个同时包含 BM25 和 TF-IDF 向量相似度的混合检索器。生产环境中BM25 部分可以保持不变TF-IDF 部分可以替换成真正的 Embedding 模型和向量数据库。# 文件路径router_demo/retriever.py import math import jieba from typing import List, Tuple from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity class BM25: 简单的 BM25 实现 def __init__(self, docs: List[str], k1: float 1.5, b: float 0.75): self.corpus [jieba.lcut(doc) for doc in docs] self.doc_len [len(doc) for doc in self.corpus] self.avg_len sum(self.doc_len) / len(self.corpus) self.k1 k1 self.b b self.doc_freq self._calc_doc_freq() self.idf self._calc_idf() def _calc_doc_freq(self): freq {} for doc in self.corpus: for word in set(doc): if word not in freq: freq[word] 0 freq[word] 1 return freq def _calc_idf(self): n_docs len(self.corpus) idf {} for word, freq in self.doc_freq.items(): idf[word] math.log((n_docs - freq 0.5) / (freq 0.5) 1) return idf def score(self, query: str) - List[float]: query_tokens jieba.lcut(query) scores [0.0] * len(self.corpus) for q in query_tokens: if q not in self.idf: continue q_idf self.idf[q] for i, doc in enumerate(self.corpus): if q not in doc: continue tf doc.count(q) numerator q_idf * tf * (self.k1 1) denominator tf self.k1 * (1 - self.b self.b * self.doc_len[i] / self.avg_len) scores[i] numerator / denominator return scores class HybridRetriever: 混合检索器BM25 向量相似度 def __init__(self, skill_docs: List[str], skill_names: List[str]): self.skill_names skill_names # BM25 通道 self.bm25 BM25(skill_docs) # 向量通道用 TF-IDF 模拟向量召回生产环境可替换为真正的 Embedding 向量库 self.vectorizer TfidfVectorizer(tokenizerjieba.lcut, token_patternNone) self.skill_vectors self.vectorizer.fit_transform(skill_docs) def search(self, query: str, top_k: int 3) - List[Tuple[str, float]]: 多路召回 加权融合 # 1. BM25 召回并做归一化 bm25_scores self.bm25.score(query) bm25_norm _normalize(bm25_scores) # 2. 向量召回 query_vec self.vectorizer.transform([query]) vector_scores cosine_similarity(query_vec, self.skill_vectors).flatten() vector_norm _normalize(vector_scores.tolist()) # 3. 加权融合 final_scores [ 0.4 * bm25_norm[i] 0.6 * vector_norm[i] for i in range(len(self.skill_names)) ] # 4. 返回 top_k 候选 ranked sorted( zip(self.skill_names, final_scores), keylambda x: x[1], reverseTrue, ) return ranked[:top_k] def _normalize(scores: List[float]) - List[float]: 最小最大归一化避免两个通道量纲不一致 min_v min(scores) max_v max(scores) if max_v - min_v 1e-9: return [0.0] * len(scores) return [(s - min_v) / (max_v - min_v) for s in scores]这里有几个设计点值得展开说明。首先是“为什么用混合召回而不是只用向量检索”。向量检索对语义相似的表达非常有效但它在处理精确术语时可能不如 BM25。比如技能关键词中恰好包含“满减券”用户输入也包含“满减券”BM25 会给一个很高的分数而向量模型可能把“满减券”映射到其他近似语义上。两个通道融合后这类精确匹配不容易丢失。其次是“为什么 TF-IDF 可以模拟向量召回”。实际项目中真正的向量召回会用 SentenceTransformer 生成句向量再存进 faiss 或 Milvus 做相似度检索。示例工程选择 TF-IDF 是为了保证读者在没有 GPU 的情况下也能运行完整链路。路由的框架不会变化变化的只是底层特征表示。最后是“融合权重为什么是 0.4 和 0.6”。这个权重不是固定的需要根据具体场景调参。通常的做法是准备一批标注好的测试数据分别跑 BM25 通道、向量通道和不同融合权重下的 top1 准确率选择效果最好的权重组合。这也是离线评测要做的事情后文会单独说明。4.3 接入大模型精排与兜底决策检索层负责缩小候选范围大模型层负责在候选范围不够明确时做出最终决策。这句话是理解整个混合路由的关键。我们不对每一次用户请求都调用大模型那样成本太高而是先看检索层给出的最高分是否足够可信。如果可信直接返回如果不可信再把检索到的 top 3 候选交给大模型精排。# 文件路径router_demo/llm_router.py import json import os from typing import List, Tuple from openai import OpenAI # 使用 OpenAI 兼容接口base_url 改成你实际使用的服务地址 # Ollama 默认端口为 11434也可以改为 vLLM、云端模型等 client OpenAI( base_urlos.getenv(LLM_BASE_URL, http://localhost:11434/v1), api_keyos.getenv(LLM_API_KEY, ollama), ) SYSTEM_PROMPT 你是一名 Agent 技能路由决策器。 你的任务是根据用户问题从给定的候选技能中选择唯一一个最合适的技能。 只能返回 JSON 格式不要输出其他内容。 JSON 格式示例{skill: check_weather} def build_user_prompt(query: str, candidates: List[Tuple[str, float]]) - str: 构造带候选技能的用户 prompt lines [f用户问题{query}, 候选技能] for name, score in candidates: lines.append(f- {name}相关度分数{score:.4f}) lines.append(请从候选技能中选择一个最合适的技能。) return \n.join(lines) def llm_select(query: str, candidates: List[Tuple[str, float]]) - str: 用大模型从候选技能中选择一个 valid_names [name for name, _ in candidates] resp client.chat.completions.create( modelos.getenv(LLM_MODEL, qwen2.5:7b), temperature0, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: build_user_prompt(query, candidates)}, ], ) content resp.choices[0].message.content.strip() # 防止模型输出多余内容尝试提取 JSON try: result json.loads(content) skill result.get(skill) except json.JSONDecodeError: # 兜底去掉可能的 markdown 代码块标记 content content.replace(json, ).replace(, ).strip() skill json.loads(content).get(skill) if skill not in valid_names: # 安全校验模型输出了候选外的技能拒绝执行并返回第一个候选 return candidates[0][0] return skill代码中有两个容易被忽视的点。第一是temperature0。路由决策属于确定性任务原则上不应该引入随机采样。把 temperature 设置为 0 可以最大限度保证“同一个问题多次调用路由结果一致”。虽然部分模型在 temperature0 时仍有一定随机性但这是目前工程上最合理的选择。第二是“输出校验”。模型返回的技能名必须包含在我们传入的候选列表中否则直接拒绝。这一步非常关键。你希望大模型做的是“从范围内选择”而不是“自由发挥”。如果模型输出的技能不在候选范围说明模型已经出现了幻觉此时宁可回退到检索层的 top1也不能接受一个不存在的技能。4.4 串联完整路由链路现在把检索层和大模型层组合起来形成一个完整的混合路由。# 文件路径router_demo/hybrid_router.py from typing import List, Dict from skill_registry import Skill from retriever import HybridRetriever from llm_router import llm_select class HybridRouter: 检索召回 大模型兜底的混合技能路由 def __init__( self, skills: List[Skill], top_k: int 3, confidence_threshold: float 0.55, ): self.skills {skill.name: skill for skill in skills} # 构造检索用的技能描述文本 skill_docs [] for skill in skills: doc skill.description .join(skill.keywords) skill_docs.append(doc) self.retriever HybridRetriever(skill_docs, list(self.skills.keys())) self.top_k top_k self.confidence_threshold confidence_threshold def route(self, query: str) - Dict: # 第一步检索召回 top_k 候选 candidates self.retriever.search(query, top_kself.top_k) top1_name, top1_score candidates[0] # 第二步置信度足够高直接走检索直出 if top1_score self.confidence_threshold: return { strategy: retrieval, skill: top1_name, confidence: round(top1_score, 4), candidates: candidates, } # 第三步置信度不足调用大模型兜底决策 llm_skill llm_select(query, candidates) return { strategy: llm, skill: llm_skill, confidence: round(top1_score, 4), candidates: candidates, }路由逻辑可以概括为三步。第一步用混合检索器从全部技能中召回 top 3 候选。这里的核心思想是“降维”把全量技能空间压缩成一个很小的候选集合避免大模型把所有技能都看一遍。第二步检查 top1 分数是否超过置信度阈值。如果用户输入和某个技能描述高度相关比如直接包含技能关键词“退货”“退款”检索器给出的分数会很高这时直接返回检索结果即可不需要调用大模型。第三步如果置信度不足说明用户输入比较模糊或者几个候选技能之间区分度不够。这时候再让大模型在 top 3 候选里做精排。由于候选只有 3 个prompt 很短成本很低准确率也会比从 100 个技能里选要高得多。4.5 运行与验证最后写一个入口文件用几个典型的用户问题验证整个路由链路。# 文件路径router_demo/main.py from skill_registry import load_default_skills from hybrid_router import HybridRouter def main(): skills load_default_skills() router HybridRouter(skills, top_k3, confidence_threshold0.55) test_queries [ 今天上海下雨吗, 帮我查一下我的快递到哪了, 我想申请退货退款, 我之前买的那件衣服发货了吗, 你们平台有什么优惠券能领吗, ] for query in test_queries: result router.route(query) print(f用户问题{query}) print(f路由结果{result[skill]}策略{result[strategy]}) print(f候选列表{[(name, round(score, 4)) for name, score in result[candidates]]}) print(- * 60) if __name__ __main__: main()运行命令cd router_demo python main.py如果没有配置真实大模型运行到需要大模型兜底的 case 时会报连接错误。你可以把hybrid_router.py中的llm_select临时替换成一个模拟函数来验证检索部分的输出。下面是一个示例运行结果基于未接入真实大模型时的检索直出用户问题今天上海下雨吗 路由结果check_weather策略retrieval 候选列表[(check_weather, 1.0), (check_logistics, 0.0), (check_order, 0.0)] -------------------------------------------------------------------- 用户问题帮我查一下我的快递到哪了 路由结果check_logistics策略retrieval 候选列表[(check_logistics, 1.0), (check_order, 0.46), (check_coupon, 0.0)] --------------------------------------------------------------------可以看到当用户问题中包含明显的技能关键词时检索层就能直接完成路由不需要大模型介入。这也验证了“先检索、后精排”的分层思路在成本控制上的优势。5. 常见问题与排查思路技能路由在工程落地中经常遇到各种问题下面整理了一张高频问题排查表。问题现象常见原因解决思路检索 top1 经常选错技能技能描述区分度不够或向量模型效果不佳重写技能描述增加区分性关键词更换更强的 Embedding 模型调整 BM25 与向量的融合权重BM25 召回到不相关结果关键词列表设置过宽包含太多泛化词清理技能关键词保留强区分词为关键词设置权重大模型路由幻觉输出不存在的技能模型自由输出缺少约束严格使用候选枚举校验把候选技能名放进 prompt 并要求 JSON 输出输出不在列表时回退到 top1每次路由结果不稳定未设置 temperature0模型随机采样固定 temperature0对同一问题做多次测试确认随机性范围技能数量增加后准确率下降向量索引未更新或技能间相似度变大新增技能后立即重建索引定期对技能描述做相似度分析合并或拆分冲突技能线上延迟偏高每条请求都调用大模型提高 confidence_threshold增加高频问题缓存检索层直出率监控并优化token 成本上涨明显大模型 prompt 中包含过多候选技能控制 top_k 大小优化技能描述长度把候选技能列表限制在 top 3 或 top 5排查技能路由问题建议先分清楚故障出现在哪一层。如果检索 top1 正确但最终路由错误问题大概率在大模型精排层。如果检索 top1 本身就不正确问题大概率在技能描述或检索算法层。如果同样的输入线上表现和本地测试不一致优先检查模型版本、temperature 参数、缓存机制是否存在差异。6. 工程化落地的最佳实践6.1 技能描述是路由的第一质量防线很多团队在技能路由上反复调参却忽略了最基础的事情技能本身写得不好。技能描述的质量直接决定了检索上限。如果描述中写满了没有区分度的套话比如“为用户提供优质的查询服务”那不管用多好的检索模型效果都很有限。建议每个技能描述至少包含三部分技能触发场景什么情况下用户会用到这个技能。技能核心能力具体能查什么、能做什么。关键技术词检索时能快速命中的词。对于相似技能要有意制造区分度。比如“查订单”和“查物流”描述中都要写明边界“查订单”关注订单创建、支付、商品明细“查物流”关注运输节点、包裹位置、送达时间。这样两个技能在向量空间里才会拉开距离。6.2 分层路由如何做降级混合路由的核心是“检索兜底模型模型兜底检索”这句话反过来也成立。当大模型服务不可用时路由系统不应该整体崩溃。合理的做法是设置一个开关当 LLM 服务的错误率达到阈值时自动切换为“纯检索模式”只靠检索结果返回 top1 技能。虽然准确率可能下降但至少服务可用。同样当检索层因故障失败时可以采用“纯大模型模式”把全部技能描述拼进 prompt 让模型选择。当然这是最坏的降级方案技能多的时候成本很高但总比完全不可用要好。降级策略可以用配置中心动态管理不用修改代码。6.3 从离线评测到线上灰度技能路由上线前一定要做离线评测不要直接凭感觉发布。离线评测的关键是数据。准备一份标注数据集每条数据包含“用户问题”和“期望技能”。跑完路由系统后分别计算检索直出、大模型直出、混合路由这三种模式的 top1 准确率。如果混合路由的 top1 准确率达不到业务要求需要回到技能描述和权重调优上。线上上线时建议先做灰度。比如先让 10% 的流量走新路由观察路由分布、平均延迟、大模型调用占比、线上用户满意度。确认无异常后再逐步放量到 50%、100%。6.4 可观测性与安全边界路由层是整个 Agent 的入口一定要埋点。每个路由请求至少记录以下字段用户问题原文。检索候选及分数。最终命中的技能。路由策略retrieval / llm / fallback。大模型调用耗时和 token 消耗。是否触发降级。这些日志不仅是排查问题的依据也是后续优化路由策略的数据来源。比如你发现“大模型兜底”占比越来越高说明检索层的置信度阈值可能设置得太高或者技能描述质量不够需要回去优化。安全边界方面路由层还需要注意技能参数校验不要在技能内部直接信任大模型输出的参数要对参数名和参数值做白名单校验。最小权限原则每个技能只授予完成任务所需的最小权限。如果“查物流”技能内部没有删除数据的权限路由到了它也不会触发危险操作。敏感操作二次确认对于“退款”“转账”“删除”等高危技能路由层命中后需要增加用户确认环节防止恶意 prompt 被路由到高危技能。7. 总结回到开头那个面试题“Agent 技能路由该用检索还是大模型”现在你应该有了自己的答案不要无脑让模型自己选也不要固守纯检索方案。更合理的工程方案是“检索召回 大模型精排/兜底”的分层路由结构。技能多时先通过 BM25 和向量混合召回缩小候选范围。检索置信度高时直接返回检索结果控制成本和延迟。检索置信度不足时再让大模型在候选范围里做最终选择。上线前后通过离线评测、线上灰度、可观测埋点来持续优化路由效果。面试中如果你能把这个链路讲清楚并且说明每一步的设计理由就已经远胜于简单回答“让模型自己选”了。动手把本文的示例代码跑通再尝试把技能数量从 5 个扩展到 50 个你会更深刻地理解为什么技能路由需要分层设计。这套思路不仅适用于技能路由也适用于 Agent 中工具调用、知识库检索等各类路由场景。
