做了几年智能体应用,我越来越觉得路由(Routing)是整套系统里最容易被低估,却又最能决定上限的模块。智能体开发里所谓的路由,就是让系统在合适的时机、把合适的请求送到合适的处理方手里——无论是选大模型、选工具,还是选子智能体。这篇是智能体学习系列的第三篇,我想把路由这件事从头到尾拆透,包括它为什么存在、有哪些实现方式、怎么落地、怎么调试,以及我在真实项目里踩过的坑。适合正在搭建智能体知识库、工具调用或复杂任务编排的开发者参考。1. 智能体路由到底是什么——先厘清概念边界1.1 从一次请求说起假设你正在构建一个客服智能体,用户发来一句“我要改收货地址”。这句话进入系统后,并不是直接丢给一个超大模型去回答就结束了。系统内部需要做一连串判断:这句话是闲聊、是查询订单,还是需要调用修改地址的接口?如果调用接口,应该由哪个子智能体处理?用昂贵的大模型还是便宜的轻量模型?这一连串“往哪走”的决策,就是路由。路由在中文语境里常被翻译成“路由”,其实它和网络工程里的路由概念很像——网络路由决定数据包从哪个网口走,智能体路由决定请求经过哪些处理节点、调用哪些工具、命中哪条业务逻辑。只不过智能体路由处理的不是IP地址,而是自然语言、任务类型、上下文状态和用户意图。我在实际项目中通常把路由拆成三个层次来理解:第一层是入口路由,决定请求进入哪个处理通道;第二层是模型路由,决定用哪个模式、哪个模型来推理;第三层是工具/节点路由,决定后续调用哪些工具或者交给哪个子智能体。这三层可以独立存在,也可以嵌套实现。很多初学者把路由想成简单的if-else,但其实路由是一套完整的决策机制,它需要结合语义理解、规则配置、评分排序甚至动态规划。1.2 路由在智能体系统中的位置要理解路由的作用,得先看智能体系统的基本骨架。一个经典智能体流程通常包括:用户输入接收、上下文整理、意图和语义理解、规划决策、工具调用、结果生成与回复。路由往往发生在“语义理解”和“规划决策”的交界处,也可能直接参与“规划决策”本身。很多人会把路由和编排器(Orchestrator)混淆。我个人的区分方式是:编排器更像项目经理,负责把任务拆成步骤、追踪状态、协调结果;路由更像调度员,负责在分岔路口决定下一步走哪条路。实际工程中两者经常合并成一个组件,但思考时必须分开,否则代码会很快变成一团乱麻。路由还有一个容易被忽视的职责:隔离与保护。当某个子模块不稳定或者需要灰度发布时,路由可以根据条件把流量切到备用节点;当某个工具费率暴涨时,路由可以自动绕过它。这些能力让路由不只是“加速器”,更是系统韧性的关键节点。所以路由设计得好不好,直接决定你的智能体是一个稳固的产品,还是一个只在Demo里能跑的玩具。2. 为什么智能体离不开路由——四大驱动力2.1 模型选择:大模型不一定总是最优做智能体应用的人最容易犯的一个错误,就是所有请求都交给能力最强的大模型。这个做法在概念验证阶段没问题,但到了生产环境迟早会出问题。最强模型通常意味着最高的延迟、最贵的账单和最不确定的性能波动。而真实业务里,大量请求其实很简单,比如“你好”“谢谢”这种寒暄,或者“现在几点”这种固定问答。让这些请求走超大模型,等于用集装箱卡车送一封平信。路由的价值就在这里:通过路由识别请求复杂度,简单请求走轻量模型,复杂推理才升级到大模型。我做过一个实际测试,某客服智能体把简单问答路由到轻量模型后,整体成本下降了约四成,平均响应时间也缩短了一半以上。而复杂请求交给大模型后,回答质量并没有下降。这种“分级诊疗”式的路由设计,是智能体降本增效的第一杠杆。2.2 工具数量膨胀后必须分流当智能体只是调用两三个工具时,你甚至不需要路由,直接在代码里写死就行。但工具数量一旦超过十个、几十个,问题就来了:模型需要在一次推理里读完所有工具描述,再选出正确工具。这样不仅浪费token,还会因为“选择过多”导致准确率下降。有一种常见现象叫“工具迷失”,就是模型反复选择错误工具,似乎忘了自己有哪些工具可用。路由可以从源头上缩小选择范围。先用一个轻量的路由模块,根据用户问题判断“这个问题可能涉及订单、物流还是售后”,然后只把对应的五六个工具描述塞给主模型,主模型的选择压力就会小很多。这就像图书馆里先按分类找到书架,再在书架上找书,而不是在全馆一本书一本书地翻。我在开源社区看到不少智能体框架已经把“预路由工具”作为标准能力,就是基于这个思路。2.3 多智能体协作和任务分发你如果做过多智能体系统,一定会发现任务分发是个大难点。假设你有一个智能体团队,分别负责数据分析、文案生成、客户画像、知识问答等。用户提出一个复合需求:“把上周销售数据整理一下,写一份小结发给市场部。”这个任务需要数据分析智能体完成统计,再交给文案智能体做润色,其中可能还要判断是否需要客户画像智能体参与。谁来分配这个任务?就是路由。路由在这里承担了“人的管理者”的角色:它需要识别用户需求里包含几个子任务,决定每个子任务交给哪个智能体,甚至要决定子任务的执行顺序。如果路由做不好,就会出现几个智能体互相等待、重复执行、甚至抢同一个资源的情况。我在一个多智能体项目里,就因为路由对子任务边界识别不清晰,导致同一个数据被两个智能体各自处理了一遍,最后答案还不一致,排查了很久才定位到根因。2.4 成本与延迟的现实约束除了功能和准确率,成本和延迟是智能体落地必须面对的现实。一次多轮对话中,如果每一轮都调用同一个巨模型,单次会话成本可能是一位数的分币量级甚至更高,这在C端产品里几乎不可接受。同时,用户等的每一秒都在消耗耐心,如果所有复杂推理都串行,体验会变得很糟糕。路由是并行和降级的关键。通过路由,你可以在对话开始前就把“重型”任务和“轻型”任务分流,重型任务走异步队列,轻型任务实时返回;还可以在高峰时段,把非核心请求降级到简单模型。这种动态管控能力,不是靠后期调优能补上的,必须在路由设计阶段就预留。很多团队做智能体时只关注回答质量,忽略了路由策略,结果上线后成本崩盘,回头重构又伤筋动骨,实在不划算。3. 路由策略拆解:从规则到语义再到神经网络3.1 基于规则的路由:最快最稳,但需要显式枚举规则路由是门槛最低、最容易理解的一种方式。它本质上就是一组条件判断,比如:如果用户消息中包含“改地址”“换货”“退货”,则走“售后处理”。如果消息中包含“订单号”“物流”,则走“订单查询”。如果消息以“!help”开头,则走“帮助指令”。规则路由的优势是零成本、零延迟、完全可控。你清楚每一个分支的条件,不会出现意外分类,也方便做自动化测试。特别适合那些意图非常明确、关键词稳定、不需要过多语义理解的场景,比如指令型智能体、内部运维机器人、固定表单问答等等。规则路由的缺点也很明显:它无法处理“没提到关键词但意思一样”的表述。用户说“我的包裹怎么还没到”,里面没有“物流”二字,但明显是物流查询,这时候规则就失灵了。另一个问题是规则之间的优先级需要人工维护,规则一多,判断顺序就会变得非常脆弱,改一条可能引发连锁反应。所以我的建议是,规则路由适合做“保底盘”和“拦截器”,但不要把它当作唯一的智能手段,它更适合与其他策略结合。3.2 基于向量相似度的路由:让语义说话语义路由是目前比较流行的方案,核心思路是把用户输入和各个候选路由的“路由描述”都转换成向量,然后计算相似度,选最接近的那个。你可以把它理解为给每条路由通道写一段“标签文本”,比如“售后处理”的标签是“退换货、退款、保修、维修申请”,然后把用户问题映射到向量空间,计算与哪个标签最接近。实现上,这种方案依赖两件事:一是好的嵌入模型,二是足够的候选描述。嵌入模型的质量直接影响相似度计算的效果。如果你用的是通用嵌入模型,建议把候选描述写得具体一些,覆盖各种说法。比如不要只写“退换货”,要写“商品不满意想退货”“收到瑕疵品需要换货”等多种表达,这样向量空间的覆盖率才会更高。向量路由的优点是具备语义泛化能力,能够理解“我的包裹怎么还没到”是在查物流,即使里面没有任何关键词。缺点在于它是对“整句话”进行匹配,难以处理多意图混杂的长文本;“相似度阈值怎么定”也是玄学,阈值高了容易误杀全部请求,低了又会让路由乱跳。所以向量路由更适合意图边界清晰的场景,同时需要配合阈值校准和兜底策略。3.3 基于大模型分类的路由:最灵活但最贵如果你不满足于上述两种方式,还有更“硬核”的方案:直接用大模型来做路由分类。具体做法是,给模型一个列表,里面是各个路由选项及对应说明,让模型根据用户输入输出最适合的一个或多个选项。相比向量相似度,它考虑了上下文、隐含语义和推理链条,准确率通常更高,甚至能处理原本没有见过的表达方式。但代价同样明显。每一次路由决策都是一次额外的大模型调用,会引入延迟和费用。如果你已经有一个主模型在跑,再接一个大模型做路由,等于把推理开销叠了两层。在实际工程里,我会做一层“预判”:简单请求先用规则快速判断,拿不准的再交给大模型分类,尽可能减少额外调用。此外,大模型分类的结果是概率性的,因此最好在代码里加一个置信度校验,如果模型自己都模棱两可,则落到默认路由。我见过不少团队一上来就做基于大模型的“智能路由”,结果项目还没上线,账单先爆了。理性做法是,把大模型路由当作“疑难杂症专家门诊”,而不是所有人进门都先挂专家号。3.4 混合路由:生产环境的务实选择真实生产环境里,很少只用单一策略。成熟的智能体路由通常是一个管道:先用规则做快速拦截,比如封禁词、强制指令、特殊渠道;然后做意图粗分类,可以用小模型或嵌入模型计算相似度;对模糊请求,再调用大模型分类,并设置兜底。这种混合设计的核心思想是:大多数请求走最快最便宜的路径,只有少数复杂请求才进入昂贵路径。我在项目中常用的参数配置大致是:规则命中比例:30%~40%,这些请求毫秒级返回;语义向量命中比例:40%~50%,这些请求耗时在几十毫秒内;大模型分类比例:10%~20%,这些请求耗时在几百毫秒到数秒。你可能会问,为什么大模型分类的比例只占这么少?因为一旦规则和语义路由能覆盖大部分高频场景,剩下那些模糊请求量其实不多,但它们恰恰是最影响体验的部分,所以用大模型兜底是值得的。混合路由不仅实现了成本与效果平衡,还让系统具备“逐步降级”的能力——如果大模型服务超时,可以暂时关闭该层,退回向量/规则模式。4. 动手实现一个路由模块——从零开始的关键代码4.1 设计路由协议的输入输出动手写代码前,先把路由模块的输入输出定义清楚。我的习惯是定义一种“路由上下文(RouteContext)”,包含所有路由决策需要的信息:query:用户输入的原始文本;user_profile:可选,用户身份标签,比如是否VIP;channel:来源渠道,比如网页、小程序、企业微信;history:最近几轮对话记录;available_routes:当前可用的候选路由列表;context_meta:其他附加信息,比如时间、地区。输出则是一个“路由决策(RouteDecision)”对象,包含:selected_route:最终选中的路由标识;confidence:置信度,0到1;低于阈值时提示进入兜底;meta:记录路由来源,比如是规则命中、向量命中还是大模型分类,方便排查问题。这样的结构可以保证路由模块不依赖具体业务逻辑,新增一种路由只需要实现“输入RouteContext,输出RouteDecision”的函数即可。4.2 规则路由实现示例规则路由本质上是一张按优先级排序的规则表。我用Python写一个最简单的框架,方便你理解:from typing import Callable, List, Dict, Optional class RuleRouter: def __init__(self): self.rules: List[Dict] = [] def add_rule(self, name: str, keywords: List[str], priority: int = 100): self.rules.append({ "name": name, "keywords": keywords, "priority": priority }) self.rules.sort(key=lambda x: x["priority"], reverse=True) return self def route(self, context: dict) -> dict: query = context.get("query", "") for rule in self.rules: if any(kw in query for kw in rule["keywords"]): return { "selected_route": rule["name"], "confidence": 0.99, "meta": {"source": "rule", "rule": rule["name"]} } return { "selected_route": "default", "confidence": 0.1, "meta": {"source": "none"} } # 示例 router = RuleRouter() router.add_rule("after_sale", ["退货", "换货", "退款", "售后"]) router.add_rule("logistics", ["物流", "发货", "快递", "包裹"]) ctx = {"query": "我的包裹怎么还没到"} print(router.route(ctx))这段代码虽然简单,但它体现了规则路由的关键设计:按优先级排序,先命中先返回。你在实际写的时候,还需要考虑关键词的大小写、同义词扩展、否定词等。比如“不要退货”不能命中退货路由,这类边界情况需要在规则层做负向关键词过滤。4.3 语义路由实现示例语义路由需要一个嵌入模型。这里我用一个通用的接口来演示,不绑定具体模型供应商。核心流程是:先把每个候选路由的“描述文本”编码成向量,再把用户查询编码成向量,计算余弦相似度,最后选择相似度最高的候选。import numpy as np class SemanticRouter: def __init__(self, embed_fn): self.embed_fn = embed_fn # embed_fn(text) -> np.ndarray self.route_descriptions: Dict[str, str] = {} self.route_vectors: Dict[str, np.ndarray] = {} def register_route(self, name: str, description: str): self.route_descriptions[name] = description self.route_vectors[name] = self.embed_fn(description) def route(self, context: dict) -> dict: query = context.get("query", "") if not query: return {"selected_route": "default", "confidence": 0.0} q_vec = self.embed_fn(query) scores = {} for name, route_vec in self.route_vectors.items(): scores[name] = self._cosine(q_vec, route_vec) best = max(scores.items(), key=lambda x: x[1]) return { "selected_route": best[0], "confidence": float(best[1]), "meta": {"source": "semantic", "scores": scores} } @staticmethod def _cosine(a, b): a = a / (np.linalg.norm(a) + 1e-9) b = b / (np.linalg.norm(b) + 1e-9) return float(np.dot(a, b)) # 使用示例——假设embed_fn已经封装好嵌入模型 # embed_fn = lambda text: get_embedding(text) # router = SemanticRouter(embed_fn) # router.register_route("after_sale", "用户咨询退货、换货、退款、维修等售后服务问题") # router.register_route("logistics", "用户查询订单发货、快递运输、物流轨迹等配送问题")这里的register_route允许你动态添加路由,非常适合在管理后台配置新服务。关键点在于描述文本的质量,我建议对同一个路由提供多条描述,然后取平均向量作为该路由的向量表示,这样能提高覆盖率,减少因描述单一而导致的误路由。4.4 接入智能体主循环路由不能孤立存在,它应当被集成到智能体的主流程中。一个典型的主循环可能长这样:接收用户消息,整理成RouteContext;调用路由模块,得到RouteDecision;根据selected_route分发到不同的处理管道;管道执行结束后,汇总结果并返回给用户。伪代码如下:async def agent_loop(message, history, user): context = build_route_context(message, history, user) decision = router.route(context) if decision["selected_route"] == "after_sale": return await handle_after_sale(context) elif decision["selected_route"] == "logistics": return await handle_logistics(context) else: return await handle_default(context)这里要特别注意:路由决策应该是可观测的。每次路由决策都要记录selected_route、confidence、meta,方便后续排查和评估。如果路由偶尔出错,通过日志你能立刻看到是哪一层的问题——规则、向量,还是大模型分类。如果没有这样的记录,出问题时只能靠猜,排查效率会非常低。5. 路由在不同场景中的落地实践5.1 模型路由:按任务难度选模型在实际产品中,我经常需要在多个模型之间做路由。这里的“模型”不仅指不同品牌,还包括同一品牌的不同规格、不同温度采样、甚至本地部署的轻量模型。模型路由的核心指标是“复杂度”。一个简单的模型路由策略可以这样设计:问题长度很短,且不包含复杂推理词,走轻量模型;问题包含“为什么”“怎么证明”“分析一下”等深层推理词,走强大模型;用户身份是VIP或付费会员,即使问题简单也走高质量模型以保体验;系统当前负载较高,自动将非紧急请求降级到轻量模型。我建议给每个请求设定一个“服务质量等级”,比如L0/L1/L2,然后由路由模块根据上下文决定请求应获得的等级。模型路由的好处是,即便模型供应商升级或降价,你只需要调整路由映射表,而不用修改上游业务逻辑。5.2 工具路由:让智能体选择正确的API工具路由是智能体落地中最常见的场景。前面提到,工具数量增多后,主模型一次读取全部工具描述会浪费token且降低准确率。工具路由要做的就是提前过滤,只让主模型看到相关的少量工具。我见过一个比较成熟的实现方式:每个工具注册时,除了工具描述,还额外提供一份“路由标签”,比如长度在20字以内、包含常见触发词、关联业务域。路由模块根据用户问题,先召回最相关的5~8个工具,再拼接到主模型的系统提示词里。这样主模型的注意力更集中,选错工具的概率明显下降。工具路由还需要处理一个边界:当用户明确指定要某个功能时,路由必须尊重用户意图。比如用户说“我要看昨天的财务报表”,即使常规语义更接近“数据查询”,也应该把“报表生成”工具放到优先位置。这种优先级信息需要放在路由的上下文里,而不能只依赖纯语义匹配。5.3 多智能体路由:分配任务给专用子智能体多智能体场景下,路由的角色从“选工具”升级为“选同事”。团队里有多个子智能体,每个都有自己的专长和记忆库。路由要判断当前用户请求应该交给哪个子智能体,以及是否需要多个子智能体协作。我在设计多智能体路由时,通常会给每个子智能体准备一份“能力描述”,类似于角色卡片,并且明确输入输出规范。路由模块根据用户请求和这些卡片做匹配。如果用户请求包含多个子任务,还可以让路由输出一个有序的任务列表,比如:先调用数据分析智能体生成结果,再调用文案智能体润色输出。这里容易踩坑的是“循环触发”。假设路由把任务分给A,A在执行过程中又生成了一个新的子请求,这个子请求再次经过路由,又分给了B,B再分给A……如果不设置跳数上限,智能体之间会互相踢皮球,浪费大量资源。所以多智能体路由里一定要有“最大递归深度”和“已完成任务集合”的概念,防止任务在子智能体间无限循环。6. 路由效果评估与调试——不能靠感觉6.1 离线评估:构造测试集路由模块写完之后,如果不做评估就上线,后面一定会被各种“玄学”问题折磨。我会建议维护一个路由测试集,里面包含三类样本:正常样本:用户常见说法,覆盖每种路由通道;边界样本:一句话同时命中多个路由,比如“退款后多久发货”,应该走哪个通道;异常样本:空文本、语气词、未知语言、乱码,应该走兜底路由。评估指标可以看“路由准确率”和“兜底率”。准确率是期望路由与实际选中路由一致的比例;兜底率是异常样本被正确导向默认路由的比例。这个测试集会随着业务演化持续扩展,每次修改路由逻辑后都要跑一遍回归。我习惯把测试集放在一个JSON文件里,路由实现更新后直接执行测试脚本输出报告。如果你连基础测试集都没有,那我建议先别急着上线,至少把异常样本和每类几个正常样本补齐,哪怕手写十条也够用。6.2 在线观测:日志与追踪离线评估只能证明路由在历史数据上表现不错,但线上流量千变万化,必须靠观测发现异常。我在生产环境里会给每条路由决策打一条结构化日志,包含用户ID(脱敏)、时间、请求摘要、选中路由、置信度、各路由得分、耗时等。这些日志除了方便排查,还能定期做统计分析,看路由分布是否合理,有没有某个路由被过度集中或长时间没有命中。更进阶的做法是做一个“路由评审样本库”。每天抽样一定比例的请求,人工或半自动检查路由决策是否正确,把错误样本回填到测试集里,形成持续改进的闭环。这个机制做起来不复杂,但能明显提升路由质量。很多人忽略这一点,结果路由效果逐渐恶化却不自知。6.3 常见路由问题与排查技巧我把自己实际遇到的一些典型问题整理成了表格,供你对照排查。症状可能原因排查建议所有请求都落到同一个路由规则优先级设置不当,或者语义路由初始化时向量没加载成功检查路由表是否正常注册,打印每个请求的命中记录,确认是规则覆盖还是向量全为0语义路由命中结果飘忽不定嵌入模型版本不稳定,或者候选描述太模糊固定嵌入模型版本,增加描述文本的覆盖范围,对描述做均值归一化大模型分类路由超时模型调用链路过长,或并发过高给模型路由层加超时熔断,超时后自动降级到向量路由多个子智能体间任务重复执行路由没有保留历史执行记录,或子任务划分不清在上下文中维护“已执行节点”集合,路由决策时跳过已完成节点线上行为与离线评测不一致线上输入分布与测试集差异过大,或存在线上独有的上下文干扰丰富测试集,采样线上数据补充样本,做差异分析排查路由问题,最关键的是“可观测性”。如果路由模块没有日志、没有中间产物输出,那排查起来就像在黑箱里摸瞎。我会给路由模块增加debug模式,在调试接口里输出每个候选路由的得分、命中来源和阈值判断结果,这样肉眼就能看出问题出在哪一层。7. 路由设计的常见坑与实战心得7.1 路由规则与业务耦合过深有段时间我把很多业务判断直接写进了路由规则,比如“某个地区来的用户直接走当地客服队列”,后来业务一调整,路由模块里堆满了硬编码,改一处就要全量回归。这个教训让我明白,路由规则应该只做“通用决策”,具体业务参数应该通过上下文传入,而不是写死在路由代码里。比如地区、VIP等级、渠道这些信息,统一放进RouteContext,由外层决定,路由只负责消费这些字段,不负责解析来源。如果你发现自己开始在路由模块里写大段业务逻辑,建议停下来重新设计数据流。一个好的信号是:路由模块的代码可以独立测试,不依赖任何业务数据库,只需要给一份正确的上下文就能输出正确决策。7.2 语义路由的阈值玄学语义路由的相似度阈值,我调试过很多次。阈值设得低,什么请求都往一个路由里塞;阈值设得高,所有请求都掉进兜底通道,等于没有路由。后来我发现,与其执着于一个全局阈值,不如给不同路由设置各自的触发阈值,再叠加一个“最小接受置信度”。另一个经验是,不要直接使用原始余弦相似度作为置信度,因为不同嵌入模型产出的相似度分布差异很大。可以先采集一批历史请求,计算每个候选路由的相似度分布,然后用分位数来自动校准阈值。至少做到高于某一分位数的算命中,低于某一分位数的走兜底。这个方法比拍脑袋定一个0.7要靠谱得多。7.3 避免路由风暴和循环在多智能体系统里,路由产生循环是一个经典故障。我之前遇到过一种情况:A智能体处理完任务后,把结果重新包装成一个“新任务”丢回给路由,路由又把新任务分发给了A本身,导致A无限处理同一类请求。表面上看是路由选错了接收方,其实是路由缺少“任务来源标记”。我的解决方法是在路由请求里增加一个trace_id和parent_route字段,每次决策都记录当前请求是从哪个流程来的。如果发现parent_route和候选selected_route相同,而且循环次数超过限制,就强制走默认处理或直接报错,让上层流程接管。这就像交通路口的防兜圈机制,宁可让车辆绕远,也不能让它一直原地打转。7.4 最后分享一点个人经验回到最初那句话,路由是智能体系统的调度中枢,但它不需要一开始就做得非常复杂。我的建议是先把规则路由做扎实,配合日志和测试集,让系统先跑起来;等业务数据积累到一定程度,再逐步引入语义路由和大模型路由。不要为了炫技而堆砌高级策略,路由的最终目标是让智能体更高效、更稳定、更省钱。如果你正在做智能体开发,我特别建议你花点时间梳理一遍自己系统的路由决策路径:用户的每个请求究竟经过了哪些判断?这些判断是否可解释?有没有兜底?经过这样一轮梳理,你往往能发现不少可以立即优化的点。路由这个模块,越早重视,后面省的事越多。
做了几年智能体应用,我越来越觉得路由(Routing)是整套系统里最容易被低估,却又最能决定上限的模块。智能体开发里所谓的路由,就是让系统在合适的时机、把合适的请求送到合适的处理方手里——无论是选大模型、选工具,还是选子智能体。这篇是智能体学习系列的第三篇,我想把路由这件事从头到尾拆透,包括它为什么存在、有哪些实现方式、怎么落地、怎么调试,以及我在真实项目里踩过的坑。适合正在搭建智能体知识库、工具调用或复杂任务编排的开发者参考。1. 智能体路由到底是什么——先厘清概念边界1.1 从一次请求说起假设你正在构建一个客服智能体,用户发来一句“我要改收货地址”。这句话进入系统后,并不是直接丢给一个超大模型去回答就结束了。系统内部需要做一连串判断:这句话是闲聊、是查询订单,还是需要调用修改地址的接口?如果调用接口,应该由哪个子智能体处理?用昂贵的大模型还是便宜的轻量模型?这一连串“往哪走”的决策,就是路由。路由在中文语境里常被翻译成“路由”,其实它和网络工程里的路由概念很像——网络路由决定数据包从哪个网口走,智能体路由决定请求经过哪些处理节点、调用哪些工具、命中哪条业务逻辑。只不过智能体路由处理的不是IP地址,而是自然语言、任务类型、上下文状态和用户意图。我在实际项目中通常把路由拆成三个层次来理解:第一层是入口路由,决定请求进入哪个处理通道;第二层是模型路由,决定用哪个模式、哪个模型来推理;第三层是工具/节点路由,决定后续调用哪些工具或者交给哪个子智能体。这三层可以独立存在,也可以嵌套实现。很多初学者把路由想成简单的if-else,但其实路由是一套完整的决策机制,它需要结合语义理解、规则配置、评分排序甚至动态规划。1.2 路由在智能体系统中的位置要理解路由的作用,得先看智能体系统的基本骨架。一个经典智能体流程通常包括:用户输入接收、上下文整理、意图和语义理解、规划决策、工具调用、结果生成与回复。路由往往发生在“语义理解”和“规划决策”的交界处,也可能直接参与“规划决策”本身。很多人会把路由和编排器(Orchestrator)混淆。我个人的区分方式是:编排器更像项目经理,负责把任务拆成步骤、追踪状态、协调结果;路由更像调度员,负责在分岔路口决定下一步走哪条路。实际工程中两者经常合并成一个组件,但思考时必须分开,否则代码会很快变成一团乱麻。路由还有一个容易被忽视的职责:隔离与保护。当某个子模块不稳定或者需要灰度发布时,路由可以根据条件把流量切到备用节点;当某个工具费率暴涨时,路由可以自动绕过它。这些能力让路由不只是“加速器”,更是系统韧性的关键节点。所以路由设计得好不好,直接决定你的智能体是一个稳固的产品,还是一个只在Demo里能跑的玩具。2. 为什么智能体离不开路由——四大驱动力2.1 模型选择:大模型不一定总是最优做智能体应用的人最容易犯的一个错误,就是所有请求都交给能力最强的大模型。这个做法在概念验证阶段没问题,但到了生产环境迟早会出问题。最强模型通常意味着最高的延迟、最贵的账单和最不确定的性能波动。而真实业务里,大量请求其实很简单,比如“你好”“谢谢”这种寒暄,或者“现在几点”这种固定问答。让这些请求走超大模型,等于用集装箱卡车送一封平信。路由的价值就在这里:通过路由识别请求复杂度,简单请求走轻量模型,复杂推理才升级到大模型。我做过一个实际测试,某客服智能体把简单问答路由到轻量模型后,整体成本下降了约四成,平均响应时间也缩短了一半以上。而复杂请求交给大模型后,回答质量并没有下降。这种“分级诊疗”式的路由设计,是智能体降本增效的第一杠杆。2.2 工具数量膨胀后必须分流当智能体只是调用两三个工具时,你甚至不需要路由,直接在代码里写死就行。但工具数量一旦超过十个、几十个,问题就来了:模型需要在一次推理里读完所有工具描述,再选出正确工具。这样不仅浪费token,还会因为“选择过多”导致准确率下降。有一种常见现象叫“工具迷失”,就是模型反复选择错误工具,似乎忘了自己有哪些工具可用。路由可以从源头上缩小选择范围。先用一个轻量的路由模块,根据用户问题判断“这个问题可能涉及订单、物流还是售后”,然后只把对应的五六个工具描述塞给主模型,主模型的选择压力就会小很多。这就像图书馆里先按分类找到书架,再在书架上找书,而不是在全馆一本书一本书地翻。我在开源社区看到不少智能体框架已经把“预路由工具”作为标准能力,就是基于这个思路。2.3 多智能体协作和任务分发你如果做过多智能体系统,一定会发现任务分发是个大难点。假设你有一个智能体团队,分别负责数据分析、文案生成、客户画像、知识问答等。用户提出一个复合需求:“把上周销售数据整理一下,写一份小结发给市场部。”这个任务需要数据分析智能体完成统计,再交给文案智能体做润色,其中可能还要判断是否需要客户画像智能体参与。谁来分配这个任务?就是路由。路由在这里承担了“人的管理者”的角色:它需要识别用户需求里包含几个子任务,决定每个子任务交给哪个智能体,甚至要决定子任务的执行顺序。如果路由做不好,就会出现几个智能体互相等待、重复执行、甚至抢同一个资源的情况。我在一个多智能体项目里,就因为路由对子任务边界识别不清晰,导致同一个数据被两个智能体各自处理了一遍,最后答案还不一致,排查了很久才定位到根因。2.4 成本与延迟的现实约束除了功能和准确率,成本和延迟是智能体落地必须面对的现实。一次多轮对话中,如果每一轮都调用同一个巨模型,单次会话成本可能是一位数的分币量级甚至更高,这在C端产品里几乎不可接受。同时,用户等的每一秒都在消耗耐心,如果所有复杂推理都串行,体验会变得很糟糕。路由是并行和降级的关键。通过路由,你可以在对话开始前就把“重型”任务和“轻型”任务分流,重型任务走异步队列,轻型任务实时返回;还可以在高峰时段,把非核心请求降级到简单模型。这种动态管控能力,不是靠后期调优能补上的,必须在路由设计阶段就预留。很多团队做智能体时只关注回答质量,忽略了路由策略,结果上线后成本崩盘,回头重构又伤筋动骨,实在不划算。3. 路由策略拆解:从规则到语义再到神经网络3.1 基于规则的路由:最快最稳,但需要显式枚举规则路由是门槛最低、最容易理解的一种方式。它本质上就是一组条件判断,比如:如果用户消息中包含“改地址”“换货”“退货”,则走“售后处理”。如果消息中包含“订单号”“物流”,则走“订单查询”。如果消息以“!help”开头,则走“帮助指令”。规则路由的优势是零成本、零延迟、完全可控。你清楚每一个分支的条件,不会出现意外分类,也方便做自动化测试。特别适合那些意图非常明确、关键词稳定、不需要过多语义理解的场景,比如指令型智能体、内部运维机器人、固定表单问答等等。规则路由的缺点也很明显:它无法处理“没提到关键词但意思一样”的表述。用户说“我的包裹怎么还没到”,里面没有“物流”二字,但明显是物流查询,这时候规则就失灵了。另一个问题是规则之间的优先级需要人工维护,规则一多,判断顺序就会变得非常脆弱,改一条可能引发连锁反应。所以我的建议是,规则路由适合做“保底盘”和“拦截器”,但不要把它当作唯一的智能手段,它更适合与其他策略结合。3.2 基于向量相似度的路由:让语义说话语义路由是目前比较流行的方案,核心思路是把用户输入和各个候选路由的“路由描述”都转换成向量,然后计算相似度,选最接近的那个。你可以把它理解为给每条路由通道写一段“标签文本”,比如“售后处理”的标签是“退换货、退款、保修、维修申请”,然后把用户问题映射到向量空间,计算与哪个标签最接近。实现上,这种方案依赖两件事:一是好的嵌入模型,二是足够的候选描述。嵌入模型的质量直接影响相似度计算的效果。如果你用的是通用嵌入模型,建议把候选描述写得具体一些,覆盖各种说法。比如不要只写“退换货”,要写“商品不满意想退货”“收到瑕疵品需要换货”等多种表达,这样向量空间的覆盖率才会更高。向量路由的优点是具备语义泛化能力,能够理解“我的包裹怎么还没到”是在查物流,即使里面没有任何关键词。缺点在于它是对“整句话”进行匹配,难以处理多意图混杂的长文本;“相似度阈值怎么定”也是玄学,阈值高了容易误杀全部请求,低了又会让路由乱跳。所以向量路由更适合意图边界清晰的场景,同时需要配合阈值校准和兜底策略。3.3 基于大模型分类的路由:最灵活但最贵如果你不满足于上述两种方式,还有更“硬核”的方案:直接用大模型来做路由分类。具体做法是,给模型一个列表,里面是各个路由选项及对应说明,让模型根据用户输入输出最适合的一个或多个选项。相比向量相似度,它考虑了上下文、隐含语义和推理链条,准确率通常更高,甚至能处理原本没有见过的表达方式。但代价同样明显。每一次路由决策都是一次额外的大模型调用,会引入延迟和费用。如果你已经有一个主模型在跑,再接一个大模型做路由,等于把推理开销叠了两层。在实际工程里,我会做一层“预判”:简单请求先用规则快速判断,拿不准的再交给大模型分类,尽可能减少额外调用。此外,大模型分类的结果是概率性的,因此最好在代码里加一个置信度校验,如果模型自己都模棱两可,则落到默认路由。我见过不少团队一上来就做基于大模型的“智能路由”,结果项目还没上线,账单先爆了。理性做法是,把大模型路由当作“疑难杂症专家门诊”,而不是所有人进门都先挂专家号。3.4 混合路由:生产环境的务实选择真实生产环境里,很少只用单一策略。成熟的智能体路由通常是一个管道:先用规则做快速拦截,比如封禁词、强制指令、特殊渠道;然后做意图粗分类,可以用小模型或嵌入模型计算相似度;对模糊请求,再调用大模型分类,并设置兜底。这种混合设计的核心思想是:大多数请求走最快最便宜的路径,只有少数复杂请求才进入昂贵路径。我在项目中常用的参数配置大致是:规则命中比例:30%~40%,这些请求毫秒级返回;语义向量命中比例:40%~50%,这些请求耗时在几十毫秒内;大模型分类比例:10%~20%,这些请求耗时在几百毫秒到数秒。你可能会问,为什么大模型分类的比例只占这么少?因为一旦规则和语义路由能覆盖大部分高频场景,剩下那些模糊请求量其实不多,但它们恰恰是最影响体验的部分,所以用大模型兜底是值得的。混合路由不仅实现了成本与效果平衡,还让系统具备“逐步降级”的能力——如果大模型服务超时,可以暂时关闭该层,退回向量/规则模式。4. 动手实现一个路由模块——从零开始的关键代码4.1 设计路由协议的输入输出动手写代码前,先把路由模块的输入输出定义清楚。我的习惯是定义一种“路由上下文(RouteContext)”,包含所有路由决策需要的信息:query:用户输入的原始文本;user_profile:可选,用户身份标签,比如是否VIP;channel:来源渠道,比如网页、小程序、企业微信;history:最近几轮对话记录;available_routes:当前可用的候选路由列表;context_meta:其他附加信息,比如时间、地区。输出则是一个“路由决策(RouteDecision)”对象,包含:selected_route:最终选中的路由标识;confidence:置信度,0到1;低于阈值时提示进入兜底;meta:记录路由来源,比如是规则命中、向量命中还是大模型分类,方便排查问题。这样的结构可以保证路由模块不依赖具体业务逻辑,新增一种路由只需要实现“输入RouteContext,输出RouteDecision”的函数即可。4.2 规则路由实现示例规则路由本质上是一张按优先级排序的规则表。我用Python写一个最简单的框架,方便你理解:from typing import Callable, List, Dict, Optional class RuleRouter: def __init__(self): self.rules: List[Dict] = [] def add_rule(self, name: str, keywords: List[str], priority: int = 100): self.rules.append({ "name": name, "keywords": keywords, "priority": priority }) self.rules.sort(key=lambda x: x["priority"], reverse=True) return self def route(self, context: dict) -> dict: query = context.get("query", "") for rule in self.rules: if any(kw in query for kw in rule["keywords"]): return { "selected_route": rule["name"], "confidence": 0.99, "meta": {"source": "rule", "rule": rule["name"]} } return { "selected_route": "default", "confidence": 0.1, "meta": {"source": "none"} } # 示例 router = RuleRouter() router.add_rule("after_sale", ["退货", "换货", "退款", "售后"]) router.add_rule("logistics", ["物流", "发货", "快递", "包裹"]) ctx = {"query": "我的包裹怎么还没到"} print(router.route(ctx))这段代码虽然简单,但它体现了规则路由的关键设计:按优先级排序,先命中先返回。你在实际写的时候,还需要考虑关键词的大小写、同义词扩展、否定词等。比如“不要退货”不能命中退货路由,这类边界情况需要在规则层做负向关键词过滤。4.3 语义路由实现示例语义路由需要一个嵌入模型。这里我用一个通用的接口来演示,不绑定具体模型供应商。核心流程是:先把每个候选路由的“描述文本”编码成向量,再把用户查询编码成向量,计算余弦相似度,最后选择相似度最高的候选。import numpy as np class SemanticRouter: def __init__(self, embed_fn): self.embed_fn = embed_fn # embed_fn(text) -> np.ndarray self.route_descriptions: Dict[str, str] = {} self.route_vectors: Dict[str, np.ndarray] = {} def register_route(self, name: str, description: str): self.route_descriptions[name] = description self.route_vectors[name] = self.embed_fn(description) def route(self, context: dict) -> dict: query = context.get("query", "") if not query: return {"selected_route": "default", "confidence": 0.0} q_vec = self.embed_fn(query) scores = {} for name, route_vec in self.route_vectors.items(): scores[name] = self._cosine(q_vec, route_vec) best = max(scores.items(), key=lambda x: x[1]) return { "selected_route": best[0], "confidence": float(best[1]), "meta": {"source": "semantic", "scores": scores} } @staticmethod def _cosine(a, b): a = a / (np.linalg.norm(a) + 1e-9) b = b / (np.linalg.norm(b) + 1e-9) return float(np.dot(a, b)) # 使用示例——假设embed_fn已经封装好嵌入模型 # embed_fn = lambda text: get_embedding(text) # router = SemanticRouter(embed_fn) # router.register_route("after_sale", "用户咨询退货、换货、退款、维修等售后服务问题") # router.register_route("logistics", "用户查询订单发货、快递运输、物流轨迹等配送问题")这里的register_route允许你动态添加路由,非常适合在管理后台配置新服务。关键点在于描述文本的质量,我建议对同一个路由提供多条描述,然后取平均向量作为该路由的向量表示,这样能提高覆盖率,减少因描述单一而导致的误路由。4.4 接入智能体主循环路由不能孤立存在,它应当被集成到智能体的主流程中。一个典型的主循环可能长这样:接收用户消息,整理成RouteContext;调用路由模块,得到RouteDecision;根据selected_route分发到不同的处理管道;管道执行结束后,汇总结果并返回给用户。伪代码如下:async def agent_loop(message, history, user): context = build_route_context(message, history, user) decision = router.route(context) if decision["selected_route"] == "after_sale": return await handle_after_sale(context) elif decision["selected_route"] == "logistics": return await handle_logistics(context) else: return await handle_default(context)这里要特别注意:路由决策应该是可观测的。每次路由决策都要记录selected_route、confidence、meta,方便后续排查和评估。如果路由偶尔出错,通过日志你能立刻看到是哪一层的问题——规则、向量,还是大模型分类。如果没有这样的记录,出问题时只能靠猜,排查效率会非常低。5. 路由在不同场景中的落地实践5.1 模型路由:按任务难度选模型在实际产品中,我经常需要在多个模型之间做路由。这里的“模型”不仅指不同品牌,还包括同一品牌的不同规格、不同温度采样、甚至本地部署的轻量模型。模型路由的核心指标是“复杂度”。一个简单的模型路由策略可以这样设计:问题长度很短,且不包含复杂推理词,走轻量模型;问题包含“为什么”“怎么证明”“分析一下”等深层推理词,走强大模型;用户身份是VIP或付费会员,即使问题简单也走高质量模型以保体验;系统当前负载较高,自动将非紧急请求降级到轻量模型。我建议给每个请求设定一个“服务质量等级”,比如L0/L1/L2,然后由路由模块根据上下文决定请求应获得的等级。模型路由的好处是,即便模型供应商升级或降价,你只需要调整路由映射表,而不用修改上游业务逻辑。5.2 工具路由:让智能体选择正确的API工具路由是智能体落地中最常见的场景。前面提到,工具数量增多后,主模型一次读取全部工具描述会浪费token且降低准确率。工具路由要做的就是提前过滤,只让主模型看到相关的少量工具。我见过一个比较成熟的实现方式:每个工具注册时,除了工具描述,还额外提供一份“路由标签”,比如长度在20字以内、包含常见触发词、关联业务域。路由模块根据用户问题,先召回最相关的5~8个工具,再拼接到主模型的系统提示词里。这样主模型的注意力更集中,选错工具的概率明显下降。工具路由还需要处理一个边界:当用户明确指定要某个功能时,路由必须尊重用户意图。比如用户说“我要看昨天的财务报表”,即使常规语义更接近“数据查询”,也应该把“报表生成”工具放到优先位置。这种优先级信息需要放在路由的上下文里,而不能只依赖纯语义匹配。5.3 多智能体路由:分配任务给专用子智能体多智能体场景下,路由的角色从“选工具”升级为“选同事”。团队里有多个子智能体,每个都有自己的专长和记忆库。路由要判断当前用户请求应该交给哪个子智能体,以及是否需要多个子智能体协作。我在设计多智能体路由时,通常会给每个子智能体准备一份“能力描述”,类似于角色卡片,并且明确输入输出规范。路由模块根据用户请求和这些卡片做匹配。如果用户请求包含多个子任务,还可以让路由输出一个有序的任务列表,比如:先调用数据分析智能体生成结果,再调用文案智能体润色输出。这里容易踩坑的是“循环触发”。假设路由把任务分给A,A在执行过程中又生成了一个新的子请求,这个子请求再次经过路由,又分给了B,B再分给A……如果不设置跳数上限,智能体之间会互相踢皮球,浪费大量资源。所以多智能体路由里一定要有“最大递归深度”和“已完成任务集合”的概念,防止任务在子智能体间无限循环。6. 路由效果评估与调试——不能靠感觉6.1 离线评估:构造测试集路由模块写完之后,如果不做评估就上线,后面一定会被各种“玄学”问题折磨。我会建议维护一个路由测试集,里面包含三类样本:正常样本:用户常见说法,覆盖每种路由通道;边界样本:一句话同时命中多个路由,比如“退款后多久发货”,应该走哪个通道;异常样本:空文本、语气词、未知语言、乱码,应该走兜底路由。评估指标可以看“路由准确率”和“兜底率”。准确率是期望路由与实际选中路由一致的比例;兜底率是异常样本被正确导向默认路由的比例。这个测试集会随着业务演化持续扩展,每次修改路由逻辑后都要跑一遍回归。我习惯把测试集放在一个JSON文件里,路由实现更新后直接执行测试脚本输出报告。如果你连基础测试集都没有,那我建议先别急着上线,至少把异常样本和每类几个正常样本补齐,哪怕手写十条也够用。6.2 在线观测:日志与追踪离线评估只能证明路由在历史数据上表现不错,但线上流量千变万化,必须靠观测发现异常。我在生产环境里会给每条路由决策打一条结构化日志,包含用户ID(脱敏)、时间、请求摘要、选中路由、置信度、各路由得分、耗时等。这些日志除了方便排查,还能定期做统计分析,看路由分布是否合理,有没有某个路由被过度集中或长时间没有命中。更进阶的做法是做一个“路由评审样本库”。每天抽样一定比例的请求,人工或半自动检查路由决策是否正确,把错误样本回填到测试集里,形成持续改进的闭环。这个机制做起来不复杂,但能明显提升路由质量。很多人忽略这一点,结果路由效果逐渐恶化却不自知。6.3 常见路由问题与排查技巧我把自己实际遇到的一些典型问题整理成了表格,供你对照排查。症状可能原因排查建议所有请求都落到同一个路由规则优先级设置不当,或者语义路由初始化时向量没加载成功检查路由表是否正常注册,打印每个请求的命中记录,确认是规则覆盖还是向量全为0语义路由命中结果飘忽不定嵌入模型版本不稳定,或者候选描述太模糊固定嵌入模型版本,增加描述文本的覆盖范围,对描述做均值归一化大模型分类路由超时模型调用链路过长,或并发过高给模型路由层加超时熔断,超时后自动降级到向量路由多个子智能体间任务重复执行路由没有保留历史执行记录,或子任务划分不清在上下文中维护“已执行节点”集合,路由决策时跳过已完成节点线上行为与离线评测不一致线上输入分布与测试集差异过大,或存在线上独有的上下文干扰丰富测试集,采样线上数据补充样本,做差异分析排查路由问题,最关键的是“可观测性”。如果路由模块没有日志、没有中间产物输出,那排查起来就像在黑箱里摸瞎。我会给路由模块增加debug模式,在调试接口里输出每个候选路由的得分、命中来源和阈值判断结果,这样肉眼就能看出问题出在哪一层。7. 路由设计的常见坑与实战心得7.1 路由规则与业务耦合过深有段时间我把很多业务判断直接写进了路由规则,比如“某个地区来的用户直接走当地客服队列”,后来业务一调整,路由模块里堆满了硬编码,改一处就要全量回归。这个教训让我明白,路由规则应该只做“通用决策”,具体业务参数应该通过上下文传入,而不是写死在路由代码里。比如地区、VIP等级、渠道这些信息,统一放进RouteContext,由外层决定,路由只负责消费这些字段,不负责解析来源。如果你发现自己开始在路由模块里写大段业务逻辑,建议停下来重新设计数据流。一个好的信号是:路由模块的代码可以独立测试,不依赖任何业务数据库,只需要给一份正确的上下文就能输出正确决策。7.2 语义路由的阈值玄学语义路由的相似度阈值,我调试过很多次。阈值设得低,什么请求都往一个路由里塞;阈值设得高,所有请求都掉进兜底通道,等于没有路由。后来我发现,与其执着于一个全局阈值,不如给不同路由设置各自的触发阈值,再叠加一个“最小接受置信度”。另一个经验是,不要直接使用原始余弦相似度作为置信度,因为不同嵌入模型产出的相似度分布差异很大。可以先采集一批历史请求,计算每个候选路由的相似度分布,然后用分位数来自动校准阈值。至少做到高于某一分位数的算命中,低于某一分位数的走兜底。这个方法比拍脑袋定一个0.7要靠谱得多。7.3 避免路由风暴和循环在多智能体系统里,路由产生循环是一个经典故障。我之前遇到过一种情况:A智能体处理完任务后,把结果重新包装成一个“新任务”丢回给路由,路由又把新任务分发给了A本身,导致A无限处理同一类请求。表面上看是路由选错了接收方,其实是路由缺少“任务来源标记”。我的解决方法是在路由请求里增加一个trace_id和parent_route字段,每次决策都记录当前请求是从哪个流程来的。如果发现parent_route和候选selected_route相同,而且循环次数超过限制,就强制走默认处理或直接报错,让上层流程接管。这就像交通路口的防兜圈机制,宁可让车辆绕远,也不能让它一直原地打转。7.4 最后分享一点个人经验回到最初那句话,路由是智能体系统的调度中枢,但它不需要一开始就做得非常复杂。我的建议是先把规则路由做扎实,配合日志和测试集,让系统先跑起来;等业务数据积累到一定程度,再逐步引入语义路由和大模型路由。不要为了炫技而堆砌高级策略,路由的最终目标是让智能体更高效、更稳定、更省钱。如果你正在做智能体开发,我特别建议你花点时间梳理一遍自己系统的路由决策路径:用户的每个请求究竟经过了哪些判断?这些判断是否可解释?有没有兜底?经过这样一轮梳理,你往往能发现不少可以立即优化的点。路由这个模块,越早重视,后面省的事越多。