
当面试官问起 Agent 技能路由该用检索还是大模型时很多人的第一反应是直接让大模型自己选不就行了我见过不少候选人这样回答然后下一句就被追问得哑口无言。因为这不是一个“选边站”的问题而是一个工程决策问题。真正合理的答案不是二选一而是分层设计检索负责确定性和效率大模型负责复杂意图和兜底。先别急着反驳。如果你负责过一个真实的上线 Agent大概率遇到过这些场景用户说“帮我算一下 23*17”模型非要先分析一段再调计算器用户说“设个明天早上 8 点的闹钟”模型把提醒技能和闹钟技能搞混技能列表从 20 个涨到 200 个之后把所有技能描述塞进 prompt模型开始自己编技能名。这些问题不是模型不够聪明而是你把一个本可以用确定性方式解决的映射问题硬生生变成了一个概率生成问题。这篇文章不打算帮你背面试答案而是把“技能路由”这个系统设计问题拆开讲清楚。看完你可以判断什么场景该用检索什么场景该用大模型以及为什么“检索优先、模型兜底”才是大多数 Agent 的正确打开方式。1. 先搞清楚“技能路由”到底在解决什么问题1.1 技能路由不是搜索是决策分发Agent 系统里技能路由通常出现在意图识别之后、工具调用之前。用户输入进来之后系统要先判断“这句话应该交给哪个技能处理”。比如天气查询、定时提醒、订单查询、知识库问答、代码执行每个都对应一个独立技能。把输入正确分发到对应技能就是路由要做的事。它和传统的信息检索不一样。检索解决的是“哪些文档和 query 相关”输出是一堆候选路由解决的是“这个输入应该由哪个处理模块执行”输出是唯一决策或有序候选。你可以把路由想象成公司的前台来访者说明来意后前台要决定带到哪个部门。如果带错了后面的部门能力再强也没用。所以路由的核心指标不是“检索到了相似内容”而是“分发对了模块”。这也是为什么很多团队在做 Agent 时一开始会把路由做成一个独立模块而不是让模型在工具调用里自由发挥。1.2 从“让模型自己选”的直觉陷阱说起现在的大模型确实支持 function calling你给它几个工具的 JSON Schema它能输出应该调用哪个工具。这给人的直觉是技能路由根本不用单独做模型天然会选。但这个直觉忽略了一个关键前提function calling 是一个生成任务不是映射任务。生成任务的特点是模型会基于概率预测输出最可能的 token。当工具定义清晰、语义边界明显、工具数量少时这个概率预测很准一旦工具数量变多、工具描述之间出现重叠、用户表达比较口语化模型就可能输出一个“看起来合理但实际不对”的工具名。我见过一个典型例子技能列表里有“日历查询”和“日程创建”两个工具描述分别是“查询用户的日历安排”和“为用户创建新的日程”。用户说“看看我这个周末有没有空”模型直接选中了“日程创建”因为它把“有空的安排”理解成了要创建东西。这种错误在检索式路由里很容易避免因为你会发现“看看有没有空”和“查询”更相关并且规则层可以直接把“有没有空”“哪天有空”映射到日历查询。1.3 为什么说“无脑让模型选”是错的无脑让模型选有三个问题每一个在上线后都会变成严重的事故。第一不可观测。模型给出了一个工具名但你不容易知道它为什么选这个。它不会告诉你“因为我看到用户提到了周末”你只能猜。一旦路由错误排查链路就断了。第二不稳定。同一个用户输入模型可能这次选技能 A下次选技能 B。不是模型“变了”而是采样温度、上下文长度、工具描述顺序都可能影响输出。放到生产环境里这会导致用户体验完全无法预测。第三成本不可控。模型每次路由都是一次推理延迟普遍在几百毫秒到秒级。如果有 10 个技能你可能要传几千 token 的工具描述如果有 100 个技能呢要么放弃部分工具描述要么被 token 和延迟拖死。这里要区分一下我并不是说大模型完全不能用于路由。我是说不能“无脑”让它选。面试官想要的恰恰是你对这三个问题的识别和应对而不是一句“用模型选”。2. 检索式路由和模型式路由的本质差异2.1 检索式路由规则、倒排索引、向量匹配、意图槽位检索式路由的核心思路是把用户输入和技能定义都变成可比较的内容然后用确定性的方式做匹配。最基础的是规则路由。关键词、正则、词典都是规则。比如用户输入包含“天气”且包含城市名就路由到天气技能。规则的好处是快、可控、好解释缺点是覆盖不了自然语言的变体。用户说“今天适合出门吗”你如果不维护“适合出门”这种口语化关键词规则就会漏。再进一步是倒排索引和 BM25。把每个技能的描述拆成关键词建一个倒排索引用户 query 进来后计算相关性得分。BM25 对精确词匹配友好所以它适合你明确知道用户会怎么说的场景比如“查询订单”“查快递”。然后是向量检索。将 query 和技能描述都做 embedding计算余弦相似度取 top-k。向量检索的优点是能处理语义相似但用词不同的表达比如“今天适合出门吗”和“天气”在语义上相关。缺点是需要维护 embedding 模型和向量库并且相似度分数不是一个绝对可解释的“概率”。实际工程中大家通常会用“多路召回”这套思路既有 BM25 的字面匹配又有向量的语义匹配再做结果融合。这也是很多人提过的“向量混合检索加 BM25 多路召回”。它不是某个特定库的名字而是一种常见工程组合。2.2 大模型路由自然语言理解、开放意图、复杂场景大模型路由的本质是把“选择哪个技能”变成一个阅读理解加推理任务。你给模型一段用户输入、技能列表和约束让它输出技能 ID。模型的优势在于它能处理很复杂的意图。比如用户说“帮我订一个周三下午能顺便见客户的商务餐厅”这里至少包含“订餐厅”“筛选时间”“结合客户位置”三个隐含动作。如果用规则和检索你需要提前定义大量槽位和组合逻辑用模型它可以从语义里推断出应该调用“餐厅预订”技能甚至可以把“周三下午”“客户位置”作为参数提取出来。模型还擅长多轮上下文。用户上一轮说“帮我查一下北京的天气”这一轮问“那上海呢”模型结合上下文知道这还是天气查询检索式路由如果只处理当前轮就会丢失“上海也要查天气”这个隐含信息。2.3 核心维度对比维度检索式路由大模型路由延迟通常毫秒级几百毫秒到秒级成本几乎为零每次推理都有 token 成本可控性规则明确可精确调整依赖模型输出结果概率性可解释性能追溯到命中的关键词/分数只能看到“模型认为”长尾/开放意图弱需要维护特征强能泛化技能数量很多时用索引扩展性能稳定Prompt 膨胀选择准确率下降维护方式维护规则、描述、索引维护样本、Prompt、评测集这个表格看起来是“各有优势”但注意最后一行的“维护方式”检索式路由更接近传统工程大模型路由更接近机器学习项目。你如果所在团队缺少算法经验就要格外慎重地让模型承担路由主责。3. 实际落地时怎么选先走确定性再上模型3.1 第一步把技能定义成可检索的路由表不要直接写代码调模型。先把所有技能整理成一个结构化路由表这是整个方案的地基。每个技能至少需要这些字段{ skills: [ { id: weather_query, name: 天气查询, description: 查询指定城市当前天气和未来几天天气预报, keywords: [天气, 气温, 下雨, 降温, 适合出门], required_entities: [city], domain: 生活服务 }, { id: alarm_set, name: 设置闹钟, description: 为用户设置一个指定时间的闹钟提醒, keywords: [闹钟, 叫我, 提醒我, 起床], required_entities: [time], domain: 生活服务 } ] }这个路由表不只是给程序看的也是给大模型看的。后面所有检索和模型推理都依赖 description 写得准不准确。描述里要写“这个技能负责什么”而不是“这个技能能够做什么”。负责什么更接近意图边界能够做什么容易让模型误选。3.2 第二步用规则处理高频确定意图上线最怕的是把所有流量都丢给路由模块。建议先看历史日志找出出现频率最高的 5 到 10 个意图用规则直接命中。比如“闹钟”技能你可以维护一个正则集合import re def find_skill_by_rules(text: str): pattern r(设置|定|调).{0,4}(闹钟|提醒|叫我) if re.search(pattern, text): return alarm_set return None这里要注意两点。第一规则要留别名和同义词比如“叫醒我”“喊我起床”都应该进闹钟。第二规则不等于“最好”它是为了守住高频带。真正的长尾才交给后面的层。为什么先上规则因为规则层可以做到零延迟、零成本、完全可解释。你不需要模型来解释为什么“设置闹钟”匹配到了 alarm_set正则就是证据。用规则兜住 70% 的高频流量剩下 30% 才需要更复杂的路由逻辑。3.3 第三步用向量检索召回应题当规则层没有命中就可以进入召回层。将用户 query 和路由表中的技能描述分别做 embedding计算相似度。Python 里的大致过程是from sentence_transformers import SentenceTransformer model SentenceTransformer(your-embedding-model) query_emb model.encode(明天早上叫我起床) desc_emb model.encode(为用户设置一个指定时间的闹钟提醒) similarity query_emb desc_emb.T这里有两个关键问题。第一embedding 模型不是越贵越好而是越匹配你的领域分布越好。通用模型在电商、客服、办公场景里表现差异很大一定要拿真实 query 样本去验证。第二不要只用向量检索。我建议做 BM25 加向量检索的多路召回因为两路补充BM25 擅长精确词匹配向量擅长语义泛化。你可以各取 top 20然后按加权分数融合。这个融合函数一开始用简单加权就够了不要一上来就上模型排序。3.4 第四步不确定/复杂意图才交给大模型并设置兜底召回层会给你一个带分数的候选列表。这时候你该做阈值判断而不是直接把候选全部丢给大模型。我的经验是分三段处理如果 top1 分数大于 0.9且领先第二名较大直接返回 top1不调用大模型。如果 top1 分数在 0.6 到 0.9 之间说明有点模糊但还不是完全无信号可以把 top3 候选交给大模型做最终裁决。如果 top1 分数低于 0.6说明用户意图很可能不在技能表里不要硬选走兜底流程。注意0.9 和 0.6 不是标准值不同 embedding 模型的分数分布差异很大。上线前要拿一批标注数据画出分数分布再确定阈值。兜底流程通常包括返回“我没理解你的意思请换一种说法”或者推荐几个可能相关的技能让用户选择。硬选一个错误技能比不选择更糟糕。4. 一个可复用的混合路由设计4.1 四层路由架构把前面的步骤汇总就是一个四层路由架构规则层、召回层、裁决层、兜底层。规则层处理高确定性意图召回层缩小候选范围裁决层做模糊意图终选兜底层保护不发生错误分发。这四层不是并列关系而是顺序执行的关系。每层都有出口也都有转下层的条件。用导航来类比规则层像高速路主路正常情况下直接到终点召回层像下了高速后的城市道路会经过多个路口大模型裁决层像复杂环岛需要交警人工指挥兜底层像导航失效时提醒司机停车问路。4.2 特征工程和候选召回在实际工程里路由不能只看当前一句话。我建议把以下特征加入考虑归一化后的 query 文本去掉标点、统一大小写、替换同义词。对话上下文前几轮的用户输入和系统响应有时候单轮缺失关键信息。用户静态属性比如地域、会员等级会影响技能候选。会话状态是否已经在某个技能内部如果用户在“天气技能”里追问“那明天呢”应该直接继续走天气技能而不是重新路由。候选召回这块可以用一个函数把规则、BM25、向量检索统一起来def recall_skills(query, skill_table, top_k20): candidates set() candidates.update(rule_match(query, skill_table)) candidates.update(bm25_search(query, skill_table)) candidates.update(vector_search(query, skill_table, top_k)) return ranked_candidates(candidates)这个函数不要做得太重。先确保召回率足够高后面裁决模型才有意义。如果召回层已经把正确技能排除了大模型再怎么选也选不回来。4.3 大模型裁决大模型裁决的输入应该是“候选技能列表 用户输入 必要的上下文”而不是全量技能表。全量技能表会让 Prompt 膨胀模型容易看到自己更熟悉的技能名产生误导。一个常见的 Prompt 结构是给定用户输入和候选技能请从候选中选择一个技能并给出理由。 只能输出 JSON格式为 {skill_id: ..., reason: ...} 用户输入{query} 候选技能 {skill_candidates}这时你可以强制要求模型输出 JSON然后解析。要注意的是模型输出不一定合法一定要做格式校验和重试。我一般会加一个简单函数def llm_judge(query, candidates): result llm_call(propmt(query, candidates)) try: return json.loads(result)[skill_id] except: return fallback_rule(query, candidates)这个 fallback_rule 很重要。模型一旦拒绝输出、输出乱码、或者超时你必须有一个规则兜底。比如返回候选里相似度最高的那一个或者让用户确认。4.4 兜底策略和异常处理兜底不是一个简单的“未识别”而是有层次的设计技能内部兜底如果当前候选里有一个是默认技能比如闲聊或通用问答可以直接路由过去。用户确认返回“你是想查天气还是设置提醒”让用户选择。人工接管对于客服式 Agent如果置信度极低可以转人工客服。全链路日志把每一层的输入、候选、分数、最终选择、用户后续反馈记录下来。这些日志不只是为了排查更是为了迭代。你可以每周统计一次哪些 query 被路由错了错误发生在规则层、召回层还是模型裁决层然后针对性更新。5. 面试中怎么回答才能不踩坑5.1 面试官真正想考察的东西回到开头的面试场景。面试官问“Agent 技能路由该用检索还是大模型”他大概率不是在找你背结论而是看你脑子里有没有一个系统设计的框架。他想知道你能不能意识到大模型不是万能的你能不能区分确定性逻辑和概率生成你有没有处理过“技能数量变多之后路由退化”的问题你会不会为了追求智能而牺牲可控性和可观测性如果你回答“用大模型自己选”等于告诉对方我把一个工程问题简化成了一个模型问题。很多做 Agent 的团队就是这样被坑的Demo 阶段看起来很好上线后用户问得稍微偏一点路由就错然后所有能力都跟着错。5.2 回答框架和关键词一个比较稳的回答框架是“我会先把技能整理成结构化路由表用规则层处理高频确定意图然后用 BM25 加向量检索做多路召回候选控制在 top3 到 top5只有候选之间分数接近、或者用户表达明显是多轮复杂意图时才把候选交给大模型做最终裁决最后保留置信度阈值和兜底流程并把所有路由日志用于后续评测和更新。”这句话覆盖了五个关键词结构化路由表、规则层、多路召回、模型兜底、可观测性。面试官大概率会顺着这些关键词继续问而你只要把每一层为什么存在讲清楚就比只回答“混合”要高一个层次。5.3 常见追问和应对追问一如果大模型选错了怎么办回答要点不要只靠模型。先看日志定位是哪一层的问题是召回层没把正确答案召回来还是模型裁决错了。然后更新技能描述、调整阈值、补充规则或增加负面样本。一句话模型选错不可怕可怕的是没有观测和回流机制。追问二技能有 100 个甚至 1000 个怎么处理回答要点把所有技能描述塞进 prompt 是不现实的。要靠索引和召回。先建技能描述的倒排索引和向量索引query 进来先快速召回几十个候选再用更精细的层裁决。技能多的时候反而更需要检索优先而不是模型优先。追问三有些意图必须用模型比如“帮我综合分析一下”怎么办回答要点这是“复杂意图”场景可以用模型但要给模型限定输出范围比如只能从可用的分析类技能里选。同时这种高成本路由只用于长尾或高价值流量而不是所有流量。追问四怎么评测路由效果回答要点构建一个路由评测集包含用户 query、期望技能 ID、场景标签。离线测准确率、召回率、延迟、成本、兜底率线上加 A/B 或者按日志抽检。注意评测集要持续更新把线上真实错误样本回流进去。6. 真实项目中的边界与长期维护6.1 适合检索优先的场景如果满足以下条件我建议先不要上大模型路由技能集合相对固定比如 20 个以内。用户输入命令感强比如“查天气”“设闹钟”“查订单”。延迟要求高比如对话机器人要求秒回。业务要求强可控比如金融、医疗、客服领域。这些场景里规则和检索能覆盖掉绝大多数情况而且出了问题可以快速定位和修复。6.2 必须用模型判断的场景也有一些场景检索式路由很难独立承担用户表达高度开放没有固定句式比如“帮我安排一下这周的效率计划”。需要多轮上下文当前指令不完整比如“换一个更近的”。技能之间边界模糊需要综合语义判断。用户可能同时触发多个技能需要做组合路由。这些场景可以把大模型放进决策链路但依然建议用候选召回限制范围而不是让模型面向整个技能库做自由选择。6.3 从规则到检索再到模型演进不是替代我的观点是这不是一个“非此即彼”的选型而是一个持续演进的路径。新系统往往从规则开始因为规则最快、最透明技能多了以后引入向量检索和 BM25再往后当长尾意图占比明显增加才把大模型作为一种裁决器放进去。每一层增加都不是为了替换上一层而是在上一层覆盖不了的边界上补齐能力。这种“确定性护栏 模型智能”的结构比“把所有希望押在模型上”要稳定得多。6.4 排查链路路由不准时先查什么路由出问题的时候不要直接调 Prompt也不要先去骂模型。按这个顺序查先看现象是漏路由没命中任何技能、错路由命中了错误技能还是不稳定同一句话结果不同再看输入 query 有没有被正常清洗上下文有没有传对实体有没有少提取再看规则层是不是有规则误杀高频意图有没有匹配到规则优先级对不对再看召回层BM25 和向量各自有没有命中正确技能阈值是不是太严或太松再看模型层候选列表是否正确Prompt 有没有歧义输出格式是否被正确解析最后看反馈层有没有用户点了“不是这个”这些反馈有没有回流成样本和规则这个排查顺序的核心是先定位问题发生在哪一层再去修那一层。否则你改 Prompt 可能白改改阈值可能影响其他意图最后越调越乱。回到最开始的面试问题。真正让候选人拉开差距的不是“知道检索”还是“知道大模型”而是你能不能把这两者放进一个可演进、可观测、可维护的工程框架里。技能路由的本质是决策分发不是模型炫技。先想清楚哪些问题需要智能判断哪些问题只需要高效映射答案自然就清楚了。