混合模型部署与多Agent编排:路由、高可用与落地实践
发布时间:2026/9/8 18:11:49 锦皓数字建站

做混合模型路由这套东西之前我一直以为最大的难点在“选模型”或者“Agent怎么分工”。真正跑起来才发现难点全在没人明说的那层多Agent怎么编排本地和云端的请求怎么按规则分流出故障时怎么在不打断线上任务的情况下自动切换。这篇文章我把整条链路拆开讲覆盖从“为什么需要混合部署”到“路由如何落地”“高可用怎么保底”最后附带我自己踩过的几个坑适合正在做Agent应用、又卡在本地模型和云端API之间反复横跳的团队参考。1. 为什么说混合部署不是把两个模型拼在一起很多团队一开始的思路很简单本地放一个开源模型省钱云端放一个商用大模型保效果两边都能用就行。但这种做法在单次问答场景里问题不大一旦进入多Agent场景就会立刻暴露一个尴尬点Agent的每一步都可能调不同模型有的步骤需要高智商推理有的只是从上下文中摘几个字段混合部署的收益根本不在“两个模型”而在于“路由能按步骤把请求送到合适的地方”。还有一个更现实的问题在主从模式上。最近多Agent设计里主从模式越来越流行本质上是把subagent当作一种另类的tool来做调用主Agent负责拆任务、汇总结果subagent只负责完成自己那一小块。如果所有subagent都只想抢最强的云端大模型成本和延迟会迅速失控而且很多子任务其实并不需要全能模型。比如“从聊天记录里抽取关键词”这种任务本地的小模型就够了硬上云端旗舰模型只会让账单和响应时间双双爆炸。混合部署真正要解决的是让多Agent体系中的每一次模型决策都能“按需供给”。它包含几层含义便宜的本地模型优先承接高频简单请求云端模型负责复杂理解、工具调用和长流程推理同时在路由层做兜底——本地挂了转云云限流了回本地两边都脆弱的时候至少保证错峰和重试不雪崩。我之前在一个客服工单辅助场景里实测过一组数据每天大约十万次请求约七成的请求是摘要、抽取、改写这类中低难度任务用本地量化模型就能处理出可用结果剩下三成涉及多文档对比、复杂逻辑推理才真正需要云端大模型。架构调整前所有请求都打云端每月推理账单高得吓人调整后费用降了一半以上首Token时延也从平均两秒多降到了本地模型的四五百毫秒。多Agent场景同理只要路由设计得当混合部署的收益不是加法而是乘法。2. 多Agent编排的核心主从模式、共享记忆与子路由设计2.1 把subagent当作tool来封装我在项目里很快接受了一个理念最新的多Agent设计中主从模式不是“让一个Agent去指挥另一个Agent”那样模糊而是把subagent明确地当作tool去调用。这种设计最大的优点是接口统一。主Agent不关心subagent内部是怎么做推理的它只要知道输入能传什么、输出能拿回什么。既然接口等于tool calling的schema那么路由层就可以像调度工具一样调度Agent比如指定某个task必须走本地模型、某个task只能走云上模型。实际封装中我会给每个subagent定义非常窄的职责边界。比如“字段抽取Agent”只负责从一段文本里抽结构化字段“报告生成Agent”只负责把结构化结果扩展成自然语言段落。注意不要让主Agent把原始上下文直接丢给subagentsubagent不会拒绝超长输入它只会默默把所有内容都塞进上下文然后消耗大量Token。我通常会把主Agent的上下文压缩成精简摘要或者字段化的中间结果后再传给子Agent这样既省预算也让子Agent的输出更稳定。把subagent理解为tool还有一个天然的好处可观测性。之前多个Agent互相调用来调用去日志里根本分不清是哪一步出的错。封装成tool之后每次调用都对应一个类似function_name的标识你可以很自然地做链路追踪哪个Agent失败了、失败时用了什么模型、走了本地还是云端一目了然。2.2 共享记忆不能简单做成“所有Agent共用一个聊天记录”多Agent共享记忆是个说起来容易、做起来全是坑的设计。最先踩的坑是“共享”两个字被理解成全局广播。团队里一度把所有子Agent的输入里都塞入当前会话的完整历史结果Prompt长度快速增长模型在大量无关信息里迷失回答质量反而下降。我把共享记忆拆成了两层处理。第一层是会话级短期记忆对应Agent流水线中需要跨步骤传递的临时状态比如一个任务ID、当前已经检索到的文档列表、上一次模型输出的结构化结果。这一层用带TTL的Redis或者内存状态表就够了不需要做成向量库。第二层是长期记忆例如用户偏好、历史工单的处理模式、项目中沉淀下来的领域知识这种适合在规模化后丢进向量数据库做召回也不用每次请求都全局读一遍。我建议每个子Agent执行前都做一次“最小记忆拉取”只取和自己任务相关的上下文片段。比如字段抽取Agent只需要当前这轮用户输入和上一轮的抽取JSON完全不需要用户三个月前的一段闲聊。主Agent更像是一个记忆协调者它来决策谁能看到什么。这样既控制了上下文长度也间接减少了路由到云端模型的Token消耗因为不需要每个Agent都把巨大上下文重新发给云端推理。2.3 LangGraph里的条件路由与分支控制要点LangGraph这类图编排框架最大的价值是把原先藏在代码if else里的Agent流转逻辑变成了显式的图结构。你不需要靠模型自由发挥去决定下一步调谁而是可以在每条边上写明确的判断条件。这种设计在混合部署场景里非常省心因为路由决策可以完全和模型推理解耦。条件路由建议写成结构化判断函数而不是把一长段上下文拼进Prompt里让模型决定下一步去哪。LangGraph的conditional_edge允许你在某个节点执行完后根据当前state里的字段决定下一个要执行的节点。例如在state中专门维护一个字段叫route_decision由调度层独立计算不要直接让Agent在自由文本里“顺便指定下一步”。我的经验是路由意图必须是显式字段比如task_type、need_cloud、is_sensitive这些判断函数里宁可多写几个硬规则也不要依赖模型自由生成指令。模型偶尔会在长输出里自己脑补一个不存在的目标节点造成隐性问题。处理分支并行时subgraph是很好用的工具。把一整条“检索-生成-校验”链路封装成子图可以单独测试、单独注入依赖也可以在主图里复用。特别是同一任务需要多个专家Agent并行处理时比如一个Agent负责市场分析一个Agent负责技术方案对比它们之间互不依赖可以并行执行。但要注意subgraph内部的循环同样消耗主图设置的step上限如果并行分支里某个子Agent内部循环出不来整个编排都会卡住。因此我倾向于在每个subgraph入口放一个循环计数节点把最大执行轮数写成明确参数宁可任务失败重来也不让它在循环里空转。3. 本地/云端混合部署的路由设计与落地细节3.1 路由层要解决的真实问题你可能会问既然本地和云都有模型为什么不直接在代码里判断一下该调谁原因是真实业务的路由条件远不止“该调谁”这一个维度。你还需要考虑当前本地推理服务是否过载、云端API的配额是否快用完、这个请求是否属于敏感数据必须在本地处理、一个会话之前使用了哪个模型现在能否平滑切换、预算还剩多少。我把这类逻辑统一收敛到一个模型路由服务里对外暴露唯一的openai兼容接口。上层Agent不需要知道底层是vLLM本地推理还是云端API它只要传一个请求路由服务按预置规则分发。好处是上层代码不会散落大量的if else模型升级、新增部署节点的时候只需要改路由配置不需要Agent层改动。这是整个高可用方案的主要前提你不能让每个Agent自己决定调用哪个模型否则出故障的时候根本没法集中切换。这一步类似计算机网里面的策略路由(PBR)。PBR的精髓在于选路不能只看目标IP而要根据源地址、端口、业务类型等多种条件做判断模型路由也一样不能只看一句话是否困难还要看调用来源Agent、上下文预估Token数、任务类型、合规约束等。网络工程师不会把静态路由写死在每条数据包里我们也不该把目标模型写死在Agent代码里。3.2 路由规则的四类来源我整理了自己在项目里真正用到的四类路由信号按优先级从上到下依次是显式指令主Agent经过判断后在内部字段里写入route_hint例如“这是一个代码生成任务需要走cloud_code_model”这类显式指令应具备最高优先级。数据合规约束凡是请求里带有用户手机号、企业内部文档等敏感内容无论其他条件如何一律走本地或私有化实例不能让数据离开可控环境。意图分类当没有显式指令时由一个小型意图分类模型或规则引擎对输入做粗分类识别它属于简单抽取、工具调用还是复杂推理。成本和性能优化若以上都没有命中默认走本地模型并设置一个可信度阈值当输出质量不稳定或用户对当前结果发起了反复编辑则动态升级到云端模型。为了让这些规则可配置我会把它们写成一个单独的路由配置文件。示例里简单设计成YAML格式route_rules: - name: sensitive_local_only priority: 1 condition: contains_sensitive: true target: local_llm - name: explicit_cloud_hint priority: 2 condition: route_hint: complex_reasoning target: cloud_flagship - name: local_first_default priority: 99 condition: fallback: true target: local_llm上面这组配置虽然简单但它把“敏感数据强制本地”“云端只接收显式复杂任务要求”“其他默认走本地”这三个核心策略表达得很清楚。后面做切换时只需动态调整配置不需要改任何Agent代码。3.3 会话级粘性路由防止模型来回横跳路由还有一种很隐蔽的稳定性问题同一个会话的不同请求因为没有状态被路由到不同模型结果用户能明显感觉到模型“性格分裂”。上一轮用本地模型回答得很简短下一轮因为路由去了云端格式和语气完全变掉。我处理这个问题用的是会话级粘性路由同一会话一旦在某一时段命中了某个模型就在一段时间内把后续请求尽量绑定到同一种模型上。只有出现报错、超时、显式升级要求时才允许切换。但粘性也不能做成永久绑定否则用户问题复杂度已经在增加模型却还留在不适合的本地小模型上。我规定如果会话的上下文Token长度超过某个阈值或检测到明显的复杂推理需求可以允许路由升级但从云端切回本地则要更加谨慎。比如本地服务在会话中途若发生一次重启全部状态丢失此时如果粘性地继续走本地用户体验会中断。这种情况应当立刻切到云端备用通道让会话继续。简单说粘性路由应该遵循“支持平滑升级拒绝无脑回切”的原则。单看一个路由规则很直白但很多规则叠加在一起就会出现意外比如敏感数据强制本地和云上限流降级本地同时命中虽然最终是一致的但要考虑事件日志怎么留痕。我建议路由服务在开始执行时就把当前命中的规则名称、决策理由、候选模型列表写入日志这样后续调优规则时不至于对着无法解释的结果猜来猜去。4. 高可用方案健康检查、熔断、降级与容量保护4.1 健康检查不只是“进程活着”本地模型的健康检查我一开始只写了端口通不通结果很天真。vLLM的进程可能还活着但GPU显存已经快要爆掉或推理队列已经堆积了几百个请求每个新请求都会等半分钟才返回。这种状态在传统健康检查里是“健康”的但在路由系统里已经算是“亚健康”了。我给本地推理节点做了更细粒度的状态上报除了进程存活外还会把自己当前的排队长度、平均首Token时延、GPU利用率、显存余量一起上报。路由服务根据这些指标给节点打一个动态分数当分数低于阈值时先减少新流量分配而不是直接把节点摘掉。如果持续恶化再摘除节点进入重建流程。一个实用性很强的健康检查响应示例大概是{ status: ok, queue_length: 3, avg_first_token_ms: 420, gpu_util: 0.67, vram_free_gb: 8.2 }路由侧拿着这份数据就可以决定当queue_length大于20或avg_first_token_ms超过1500时即使status是ok也暂时把该节点标记为“不接收新请求”。这比单纯等进程退出再转移要靠谱得多因为它能在故障真正发生之前减小爆炸半径。4.2 重试、超时和熔断要配合使用高可用不能只靠“失败后换一个模型重新调”这种简单重试。重试若没有上限流量高峰时段会在云端API和本地模型之间互相放大请求形成雪崩。我定的原则是只在超时、5xx、网络抖动这类瞬时故障时重试4xx参数类错误重试没有意义。重试次数默认为2次采用指数退避第一次重试间隔0.5秒第二次间隔1.5秒。超过两次后直接进兜底通道宁可让单次任务失败也不能把系统整体拖死。熔断机制在高可用链路里同样关键。如果一个通道连续失败次数达到阈值它应当进入“断开”状态不再接收新请求而不是每个请求都在那苦等超时。例如本地推理服务在30秒内错误率超过40%我就对本地节点做熔断新请求一律引导到云端模型过了30秒后放一小部分探活流量进去如果探活成功再逐渐恢复。这个降级模式的核心是让路由服务能感知“熔断结果”并调整策略。我在服务里维护着一张通道状态表通道名、状态、冷却到期时间、最近连续失败次数。路由判断时先看这张状态表如果目的通道已熔断就直接跳到备选通道不再层层重试。对用户而言他感知到的只是可能慢了一点点但不会被连续错误卡死。4.3 容量规划与流量高峰的保护本地模型的扩容速度比云端API慢得多。云端大模型只要账户有配额并发基本随买随用但本地GPU服务要加载新的权重哪怕用vLLM的预加载机制也需要数十秒甚至更久。因此混合部署的高可用不能只靠实时扩缩容必须提前做容量预估和冷备节点。我的经验是按峰值流量的50%预留本地推理算力余量多余流量靠路由分摊到云端。如果峰值流量突然翻倍路由会自动减少发往本地的比例而不是让本地队列无限膨胀。同时把云端通道作为天然的“弹性缓冲”但还要注意云API自身都有并发限额超限后会返回限流错误。遇到这种情况我的方案是给云端调用增加一层客户端令牌桶允许突发但限制总速率避免路由层把流量一次性灌给云API导致限流进一步加剧。多Agent场景对容量的影响往往被低估。一个用户问题在Agent编排初期可能只产生一次主模型推理但后续可能演化出六七个并行子Agent请求每个子Agent又可能触发多个模型的连续调用。若路由层没有任何并发限制Agent编排对底层模型服务的压力会被放大数倍。所以我在路由层设置了优先级队列对属于同一任务链路的请求做聚合限制同一条链路能同时占用多少个后台推理任务防止一个复杂任务吃光所有推理资源。5. 可观测性建设与排障实战记录5.1 用trace_id贯穿整个混合链路可观测性是这类系统调优的基础。多Agent和混合部署叠加之后一次用户请求可能横跨主Agent图、多个subgraph分支、本地模型、云端API没有trace_id的话排障基本靠猜。我专门设计了一个上下文对象在路由入口处生成唯一请求ID之后每次模型调用、每个Agent执行都自动带上这个ID写入结构化日志。日志行会记录很多关键信息当前节点名、命中的路由规则、实际模型名、部署通道是本地还是云端、tokens用量、首Token延迟、是否发生过重试、最终错误码。排障时就按trace_id聚合所有日志能直接看到一条请求在哪里耗时最长、从哪里开始走了降级、失败后的兜底结果是什么。这类信息的价值在系统稳定运行时感觉不到但一旦线上出问题它能帮你把排查时间从小时级压缩到分钟级。5.2 典型的坑和解决记录我把自己项目里真实出现过的几类问题整理成了速查表适合有类似架构的人对照排查现象可能原因处理办法本地节点日志显示队列堆积但路由还在大量分发健康检查只看进程存活没看队列长度细化健康检查增加queue_length首Token延迟等指标评分评分低时动态降权同一个会话的回复风格来回切换会话级路由没有粘性每条独立决策增加会话级sticky路由按TTL阶段绑定同一种模型支持升级但谨慎回切云端API偶发5xx导致整条任务失败对瞬时错误没有重试或重试策略过于粗暴区分可重试错误和不可重试错误设置指数退避最多重试两次子Agent内循环依赖导致主流程超时subgraph的递归上限被全局限制没单独设置每个subgraph入口加循环计数并把recursion_limit放宽到合理的有限值本地模型在显存还剩一点时性能急剧下降忽略了碎片化和并发调度损耗把显存余量阈值设为保守值低于阈值即停止新请求进入除了这几类可以直接按表排查的问题有些问题还需要从根上修正设计思路。例如条件路由的判断不应当把决策全权交给主模型我早期犯过一个错让模型在自然语言回复里顺便指定下一步要执行的subagent结果模型偶尔会放飞自我生成了一个根本不在图里的节点名LangGraph直接报错。后来我把所有路由决策收敛成显式字段比如task_type和route_hint只允许模型在有限选项里选择绝不能自由创造节点。这类约束在多Agent场景里非常关键它把系统的确定性从模型的可控性中剥离出来。5.3 子图并行与死循环的预防实践使用LangGraph这类图编排框架时会遇到一个隐藏很深的坑并行分支中的子图内部一旦发生循环会影响整条链路的完成时间因为并行节点要等所有分支都结束后才能进入下一步。如果一个分支因为某种边界情况陷入死循环其他分支即使一秒钟就完成了整体也会卡住。为避免这种局面我给每一个subgraph都设计了迭代预算。类似于给子图发一张“只能跑五步”的令牌每经过一个节点就减一减到零时无论结果如何都强制退出并把当前部分结果返回给主Agent。主Agent在拿到这个不完整结果后会根据结果质量判断是重新执行一次子任务还是简化目标继续往下走。该策略需要配合全局限流和重试机制一起使用否则预算耗尽后反复重入子图也会造成类似故障。我做条件路由时还会主动增加一层入环检测。例如在一个检索Agent子图里设计上是“检索-判断是否找到答案-未找到则改写关键词-再到检索”如果改写关键词后仍然没有答案就应当在“判断是否找到答案”的边界上直接跳出。如果判断条件写得太宽松模型总觉得“再改一次关键词可能能搜到”它就会在一次请求中循环很多轮。这种问题排查起来很隐蔽因为从单轮日志看都是正常调用只有把同一trace_id的数据拉出来聚合才发现同一个节点重复执行了八次。解决办法是在条件判断里同时加入一个硬性目标比如“检索次数达到两次仍未找到就返回失败”而不是让模型自由决定要不要继续。经历了这一路的折腾我个人最大的体会是混合大模型架构真正难的地方不在于单个模型多聪明而在于系统在整个调用链路上能保持多少确定性。多Agent把任务拆碎了混合部署把执行通道拆成了两条如果没有一套能让路由、重试、降级、熔断像轨道交通信号灯一样协作的控制面最终体验会非常不稳定。而一旦把这块做好你会得到一个同时具备成本可控、效果可用、故障可修的系统。最后再分享一个建议如果刚起步不要急着把路由规则做得很复杂先把“敏感本地、默认本地、云上兜底”这三条策略跑通再逐步增加升级条件和可观测性细节远比一开始搭一个庞大但不可控的路由中心要可靠得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。