大家做混合大模型架构,多数是被现实逼的。我自己最早接触这个方向,是因为手上的业务方拿着纯云端API方案来找我说"这个月账单又超了,而且客户要求数据不出内网",那时候才意识到,单靠一个云厂商的模型服务根本撑不起生产级系统。本地/云端混合部署不是赶时髦,而是成本、隐私、延迟、可用性这几个硬约束在背后互相拉扯之后,必然长出来的形态。这篇文章就是我踩了大半年坑之后的完整复盘,重点讲清楚三件事:多Agent到底怎么协同才不会乱、路由层怎么像网络交换机一样把请求精准送到对的模型、以及高可用体系怎么搭才能扛住真实故障。内容偏工程实践,适合正在搭LLM基础架构的团队参考,也适合刚接触这一块、想搞明白"为什么不能所有请求都走同一个API"的开发者。
1. 混合部署的价值坐标:成本、隐私与延迟互相拉扯
1.1 纯云端API的单点脆弱性与账单焦虑
先说为什么不能All in云端。很多团队第一版系统都是直接接商业大模型API,跑Demo阶段确实爽,代码量少、效果还行、上线快。但一旦进入生产,第一个炸的点就是成本。我见过一个客服问答项目,上线三个月以后月度Token消耗翻了几十倍,账单从几千跳到十几万,业务方直接看傻了。更麻烦的是预算不可控,你要是不做请求级别的管控,模型API的计费根本不是一个能预测的量。
第二个问题是数据隐私。金融、医疗、企业内部知识库这类场景,客户会明确要求数据不能出内网,或者至少核心数据不能发往第三方模型服务。这时候纯云端方案直接出局,不是你技术选型的问题,是合规上根本不给你选。第三个是延迟。云端模型再好,一次完整的流式响应也要经过公网,遇到高峰时段、跨地域链路抖动,响应时间飘到用户不可接受的程度。这三个痛点叠在一起,"本地部署一部分模型+云端保留一部分模型"就成了非常自然的妥协方案。
1.2 本地小模型能接住哪些真实业务
本地部署不是让你把Llama 3 405B这种级别的稠密模型硬扛在单机上的。真正务实的做法,是把业务请求按难度分级,简单任务用本地小模型消化,复杂推理再上云。
拿我自己经手的实际业务举例:意图识别、实体抽取、信息分类、格式整理这一类结构化任务,7B到14B的开源模型跑在单张A100或者双卡4090上,效果和云端大模型差距已经很小,有些场景甚至因为微调过反而更好。真正需要云端出手的是长文总结、复杂推理、代码生成、角色扮演这类对"聪明程度"有硬性要求的任务。还有一类是流式语音交互,对首token延迟极其敏感,用本地模型效果显著优于公网转发。
所以我在设计混合架构的时候,第一原则就是"能力分级、流量分层"。这句话听起来简单,落到工程上就是要在请求入口做一次完整的分类和路由,让80%的简单流量沉淀在本地,剩下20%需要重火力的请求再上云。这跟计算机网络里的路由转发思路本质上是同构的,只不过这里路由的对象不是IP包,而是带业务语义的推理请求。
1.3 "先路由再调度"才是混合架构的核心动作
很多人把混合部署理解成"本地一套服务、云端一套服务、中间用代码判断一下转发",这个理解太浅了。真正生产级的设计,必须在本地和云端前面加一层独立的模型网关(Model Gateway / LLM Router),所有请求统一进网关,由网关根据路由策略决定发去本地还是云端。这一层承担的不只是转发,还包括协议转换、负载均衡、健康检查、熔断降级、流量观测。没有这一层,你的高可用和成本控制都是空谈。
我自己最开始图省事,直接在业务代码里写了个if-else判断模型归属,前两周跑得挺欢,第三周本地推理服务一重启,业务代码全部报错,排查了半天才发现是一个服务依赖另一个服务,中间没有任何隔离。从那以后我就把"路由层必须独立成服务"这件事列为铁律。业务方只认一个统一的推理接口,至于请求去了本地还是云端、用的哪个模型、有没有降级,都是网关内部的事。
2. 多Agent协同:三种编排模式与并发控制的真实参数
2.1 流水线、编排器与联邦式:选型逻辑
多Agent是混合架构里最容易做得失控的部分。Agent不是一个模型,而是一个"模型+提示词+工具调用+记忆"的组合体。多个Agent一起工作的时候,首先要回答的问题是它们怎么组织。我在实践里把常见的编排模式分为三类。
第一类是流水线模式,Agent按固定顺序执行,前一个Agent的输出交给后一个Agent,适合流程非常稳定的场景,比如"先做意图识别,再调用对应技能Agent生成回答"。优点是简单可靠,缺点是灵活性差,一旦流程中间有分支判断,流水线就僵住了。第二类是编排器模式,也叫路由器或指挥官模式,一个主Agent负责任务规划和分解,把子任务分发给其他Agent,再把结果汇总返回。OpenAI后来的function calling思路本质也是这个。这种模式适合任务复杂度高、需要动态拆分的场景,但主Agent很容易成为瓶颈,它自己的能力直接决定整个系统上限。第三类是联邦式模式,多个Agent各自独立运行、通过消息总线或共享黑板机制协作,没有一个中心节点,适合异步、松耦合的场景,但是调试难度大,我在实践里用得最少。
我的选型建议很朴素:业务方只要一个稳定输出的服务,就别上太复杂的编排;如果你对"Agent自由协作产生涌现能力"没有确切的业务验证,也别硬上联邦式。先把流水线跑通,再用编排器模式做动态扩展,是一个不容易翻车的路线。
2.2 上下文传递:Agent之间到底传什么
多Agent的第二个核心问题是上下文传递。这里我踩过的坑特别多,最典型的是把"用户原话"从头传到尾,导致每个Agent都在处理大量无关信息,Token消耗翻倍,而且上下文一长,模型很容易丢失关键信息。
正确的做法是设计一个标准化的Agent消息结构,包含任务ID、角色来源、意图标签、需要传递的结构化数据字段、以及裁剪后的摘录文本。我在实际项目里用的是类似OpenTelemetry的trace模型思路:一个根任务会展开成多个子任务span,每个span里记录子任务输入输出的结构化摘要,而不是把原文一路带着跑。子Agent需要的背景信息由父Agent显式拼接,最重要的原则是"每个Agent只看到它完成任务所需的最小上下文"。
通信方式上,我倾向于在Agent之间通过一个轻量级消息队列(RabbitMQ或者Redis Stream都可以)传递任务消息,而不是直接HTTP互相调。原因有两点:一是解耦,Agent实例可以独立扩缩容而不互相感知;二是天然支持异步和重试,消息失败不会把整条链路拖死。当然了,如果你的Agent数量很少、逻辑都在同一个进程内,那直接函数调用也没问题,别过度设计。
2.3 并发配置:线程池、信号量与超时的联动
多Agent的并发控制,是"agent多并发配置"热搜背后真正的工程难点。很多团队把并发数简单理解成"线程池大小调大点",结果一压测就炸。我之前线上事故有一半跟并发配置相关。
我的经验是,多Agent并发要同时管理三个维度:Agent内部的模型请求并发、Agent之间的任务队列并发、以及对外部依赖(本地模型服务、云端API)的流量控制。以一次请求进来为例,编排器Agent可能需要并发调用三个子Agent,每个子Agent又要调本地模型服务,如果内层模型服务只支持10个并发,而你给编排器配了50个并发,那么排队和超时必然发生,然后用户侧表现为接口变慢,重试一上来,系统直接雪崩。
所以我在生产里给出的初始参数通常是这样的:编排器线程池大小为20,子Agent线程池大小为50,本地模型服务并发上限为16(根据GPU显存和推理引擎实际压测得出),云端API并发上限为32,总入口并发用信号量控制为100。超时配置上下两层联动,子Agent的调用超时为30秒,编排器总超时为60秒,重试次数不超过1次,且只在可重试错误码上重试。这些数字不是拍脑袋,而是压测环境下一轮轮调出来的,不同团队的硬件不同,但"分层限流、先内后外"的思路是通用的。
3. 模型路由层:把网络路由的思路搬进大模型网关
3.1 从静态路由到策略路由:规则怎么定
模型路由是混合架构里最像"网络工程"的部分。我经常和团队说,大模型网关本质就是一张路由表,只不过表项不是目的网段,而是"请求特征到模型服务的映射"。
最低级的路由是静态路由,直接在网关里写死HOST、路径和模型名的对应关系。比如业务A的查询请求一律走本地模型A,业务B的生成请求一律走云端模型B。这种方案适合模型数量少、业务变化缓慢的阶段,跟我们做静态路由实验一样,能在ensp上配通就算入门。但生产环境很快会暴露问题:模型有多个版本、有灰度发布的诉求、有故障时需要切换、不同的请求负载时段成本策略不同,静态表项根本扛不住。
这时候就要上策略路由(Policy-Based Routing,PBR)。PBR的核心是路由决策不只依赖目的地址,而是根据一组策略条件进行判断。在模型网关里,这些条件包括:请求类型标签、Token预估成本、响应延迟目标、数据敏感级别、当日预算消耗比例、模型可用状态等。我设计的路由策略表大概是这样的:
| 优先级 | 匹配条件 | 目标模型 | 动作 |
|---|---|---|---|
| 10 | 请求类型=意图识别 | 本地小模型A | 转发 |
| 20 | 请求类型=代码生成 且 来源=内部研发平台 | 云端大模型B | 转发 |
| 30 | 数据敏感级别=高 | 本地大模型C | 转发 |
| 40 | 预算消耗比例>80% 且 请求类型=摘要 | 本地小模型A | 降级转发 |
| 50 | 本地模型A 不可用 | 云端大模型D | 转发(故障转移) |
表项从上往下匹配,命中即停。这套规则放在一个YAML配置文件里,网关启动时加载,配置热更新走etcd订阅,改策略不需要重启服务。落地的时候有几个细节必须注意:一是规则顺序很重要,和防火墙规则一样是先匹配先生效;二是每条规则必须能解释"为什么命中",方便问题排查;三是每个目标模型必须关联独立的健康状态,规则匹配到不健康的服务时必须能跳到下一个可用目标。
3.2 按能力标签与成本因子做动态权重
静态策略之外,我还做了基于权重的动态路由,主要用于两个场景:版本灰度发布和成本倾斜。
版本灰度发布的场景很常见。新微调版本的本地模型上线,不能直接百分百流量,可以先切5%的请求过去观察指标,没有问题再逐步放大。这个在网关里实现很简单,就是为同一能力配置多个模型地址,各带一个weight权重,网关按权重负载均衡。我实际使用的权重配置:
models: - name: local-model-a-v3 endpoint: http://10.0.1.10:8001/v1 weight: 5 - name: local-model-a-v2 endpoint: http://10.0.1.11:8001/v1 weight: 95成本倾斜是另一件事。云端模型价格高、本地模型价格低,两者的真实时延和效果差异在不同业务下表现不同。我让路由层定期从监控系统读取每个模型的平均响应时间和每百万Token成本,再按一个可调的"成本敏感系数"计算综合得分,把流量按得分比例分发。说白了,这跟链路负载均衡里的加权轮询是同一个思路,只是权重不只是固定配置,而是动态计算的。
还有一个容易被忽略的点:动态权重不能只盯成本和延迟,还要看效果指标。我在网关里额外接入了每个模型在黄金测试集上的效果得分,效果跌到阈值以下的路由自动摘除。这样做的好处是,即使新模型发布后看着快又便宜,但如果回答质量崩了,系统也能及时发现并切回旧版本。
3.3 路由失效兜底:降级链路必须提前设计
路由层做得再完善,也要面对一个残酷的事实:你依赖的所有模型服务,本地也好云端也好,都可能不可用。本地推理机器会宕机,显存会OOM,云端API会限流,甚至会直接报5xx。所以路由层如果没有兜底设计,一切转发规则都是空中楼阁。
我的经验是给每个业务请求预设一个降级链路。所谓降级链路,就是一条按"最优->次优->保底"排列的路径。最理想的路径是本地能力足够的模型,次选是云端同能力模型,保底是规则引擎或者固定模板回答。比如某个智能客服请求,正常走本地微调模型,本地故障时切云端通用模型,云端也故障时返回一个预设的安抚话术并转人工。这个降级链路不是临时想的,而是在架构设计阶段就定义好,并且每个环节都要做压测。理由很简单:故障发生时你没有时间冷静设计,只能执行预案。
兜底设计还有一个边界要注意:降级不能盲目。如果业务是交易场景,模型回答错了会造成真金白银的损失,那么宁可快速失败返回错误码给用户,也不要拿一个低质量回答糊弄过去。在我负责的系统里,降级策略是分等级配置的,L0业务禁止自动降级到低质量模型,L1业务可以降级但必须记录日志并告警,L2业务允许任意降级。把降级权和责任边界定义清楚,比把所有请求都强制高可用更有工程意义。
4. 高可用矩阵:探活、熔断、限流与故障转移的配合
4.1 三层高可用:模型服务、网关、业务侧各管什么
高可用是个系统工程,不是某一层能独立扛住的。我在混合大模型架构里把高可用拆成三层,各管一段。
第一层是模型服务层,管的是本地推理服务和云端API本身的可用性。本地侧要做多副本部署、GPU健康监测、进程守护和自动拉起;云端侧能做的就是请求重试、区域切换和账号切换。第二层是网关层,管的是探活、熔断、限流、优雅降级和流量切换,这一层是我投入精力最多的,也是路由和高可用交汇的地方。第三层是业务侧,管的是超时控制、重试策略、缓存和用户可见的降级提示。三层之间通过标准化错误码和健康状态接口联动。
跟搭建MySQL MGR或PostgreSQL高可用集群的思维类似,模型服务也需要一个"副本状态同步+主从切换"的机制。不过模型服务和数据库有一个关键区别:模型服务大多是纯无状态的(如果你不做会话记忆),切换成本低得多。这意味着我们甚至可以不做复杂的主从选举,只要有一个健康检查机制能发现实例挂了,流量能自动转移到剩余实例就行。但也要反过来想,正因为切换成本低,很多团队反而忽视了自动化,靠人肉改配置切换,这在半夜故障时就是灾难。
4.2 健康检查与熔断参数怎么定才不误伤
健康检查是高可用的眼睛。我在生产里用的探活方式不是简单的ping端口,而是有业务语义的"推理探活":定期向模型服务发一个极小的推理请求,判断响应是否正常、耗时是否在阈值内。之所以不用ping或者HTTP HEAD,是因为很多模型服务进程活着但推理功能已经退化(比如显存泄漏导致OOM前兆、推理引擎线程卡死),端口探活根本发现不了。
探活参数我建议这样设置:每10秒探活一次,连续3次失败标记为不健康,连续2次成功恢复健康。超时阈值设为正常推理耗时的3倍以上,避免偶发慢请求造成误判。这里有个特别容易踩的坑:探活请求必须走和真实请求一样的数据路径,不能走旁路或者内部短链。我踩过一次,探活脚本直接访问模型实例的内网IP,绕过了网关的限流队列,结果探活全绿,真实请求却因为队列积压大量超时,最终被用户投诉了才查出来。
熔断器的参数同样需要精细化。我用的参数组合是:滚动窗口10秒内错误率超过50%触发熔断,熔断持续时间30秒,半开状态下放行少量试探测请求,成功比例超过70%则恢复全量。这些参数不是从书上抄的,是通过模拟故障压测定出来的。关键点是熔断阈值要分模型设置,云端大模型API的错误容忍度可以放高一点(网络抖动造成的偶发错误常见),本地模型服务的阈值要严格得多(内部链路不应该频繁出错,出错就说明有问题)。
4.3 一次完整故障转移的时序复盘
聊完参数,我拿一次真实的故障转移流程来复盘,这样更直观。某个工作日下午,运行本地模型A的两台GPU服务器中的一台出现故障,显存ECC报错导致推理服务崩溃。
故障转移的完整时序是这样的:第0秒到第3秒,客户端开始出现部分超时错误,网关的负载均衡器把请求打到宕机实例上,报错率上升。第5秒,健康检查连续3次失败,网关把宕机实例标记为不健康,从服务池摘除。第5秒到第8秒,剩余流量全部打到存活实例,单机负载上升,但还在承受范围内。第12秒,进程守护脚本尝试重启失败,确认实例彻底不可用。第20秒,存活实例的请求延迟开始超过告警阈值,触发了网关的过载保护,部分非核心任务的请求被降级到云端模型B。第35秒,运维收到告警,开始介入排查硬件问题。整个过程用户的体感是少量请求变慢、极少数请求失败,业务没有中断。
这个流程能跑通,靠的是每个环节都提前设好了触发条件和动作。如果健康检查周期太长(比如60秒),故障持续的时间就会拉长到分钟级;如果没有过载保护,单实例强撑可能导致连锁雪崩。我后来把这段复盘的结论写进了团队的故障演练文档,每个月会做一次故障注入演练,随机杀掉一个模型实例,看整个链路能否自动恢复。这种事前演练比事后补窟窿高效得多,强烈建议做。
5. 生产环境排障手记:五个典型事故的根因与修复
5.1 多Agent并发飙升导致上游限流
第一个想写的事故,是某次做活动推广,流量突然涨到平时的5倍,多Agent编排器的并发策略没扛住,导致它的所有子任务都开始排队,最终把上游云端API的限流阈值打满了。表面上看是上游限流问题,实际根因是我们自己的编排器并发数没有随流量自动伸缩,导致请求在内部无限排队,而这些排队请求又拿着占用的连接不放,最终拖死了整个服务。
修复动作有两个:一是给编排器加上基于队列长度的自动扩缩容机制,队列积压超过阈值就自动增加Agent实例;二是在网关层对所有业务请求设置一个排队上限,超出上限直接快速失败,绝不无限等待。快速失败虽然会让部分用户看到错误,但总比整个系统打挂好。
5.2 路由优先级写反导致的成本翻倍
第二个事故非常丢人。某次我们上线了一条新的成本优化策略,本意是低优先级的摘要请求全走本地模型,结果配置的时候把路由表项的优先级数字写反了,导致所有摘要请求都先命中云端大模型,跑了一整天才在账单上发现异常。那一天的成本是平时的3倍。
这个事故给我的教训是:路由配置必须做上线前验证和灰度发布。后来我加了一个路由规则的"影子模式":新规则先在真实流量上跑影子,只记录"如果这条规则生效,请求会被路由到哪",不实际转发,跑一段时间对比实际路由结果和期望路由结果,一致了才正式切换。另外,成本监控必须做到小时级别,别等日报,日报出来的时候钱已经烧完了。
5.3 缓存误导健康检查引发雪崩
第三个事故和缓存有关。我们在网关层做了一层语义缓存,相同意图的请求直接命中缓存返回,不打到模型服务。这本是降本的好手段,但问题出在健康检查的探活请求也符合缓存命中条件,于是一个已经挂掉的模型服务,因为探活请求全部命中缓存返回了正常结果,被误判为健康。等缓存过期后真实请求瞬间涌入,这个"假健康"的实例彻底被击穿,连锁引发雪崩。
修复方案是在探活请求里加一个特殊的no-cache标记,同时探活响应必须校验模型服务实例自身的标识信息,确保这个探活响应真的是这个实例生成的,而不是缓存伪造的。从那以后,我把所有监控探活路径都梳理了一遍,凡是经过缓存的路径全部加白名单绕开,这条经验我认为值得每个做模型网关的团队抄一遍。
5.4 超时设置一刀切拖垮本地推理
第四个事故是超时配置过于粗暴。当时为了省事,我把网关到所有模型服务的超时都统一设成了60秒,结果本地模型处理短任务只需2秒,云端大模型处理复杂任务需要40秒,两者混在一个超时池里,本地服务的线程迟迟不释放,造成大量线程被无效占用,GPU利用率上不去,整体吞吐反而下降。
修复方法很直接:按模型服务能力分别设置超时。本地小模型8秒、本地大模型30秒、云端大模型60秒、流式任务单独用空闲超时控制。超时粒度越细,资源利用越充分。这个事让我明白一个道理:在LLM架构里的所有时间参数都必须按路径单独设置,任何"统一配置"的偷懒都会在流量上来时加倍偿还。
5.5 模型版本漂移与路由规则脱节
第五个坑比较隐蔽:路由规则里写死了模型版本号,而模型服务端已经灰度更新了新版本,两边脱节了。比如路由规则说"意图识别请求走model-a-v2",实际上model-a-v2已经被v3替代下线了,但是网关还在往旧地址转发,旧地址的上游负载均衡器做了兼容处理,导致系统没有立刻报错,但行为已经和预期不一致。等到旧版本彻底下线时,才爆发大量404错误。
这类问题的根因是配置管理没有跟上发布流程。我的修复方案是给模型版本建立注册表,模型服务的注册信息里必须包含版本号和兼容能力标签,路由规则只匹配"能力标签+最低版本号",不写死具体版本。这样模型服务升级时,只要兼容能力标签不变,路由规则就无需修改。这套机制类似于动态路由协议相比静态路由的核心优势:网络拓扑变了,路由能自动收敛,而不是靠人肉改表。
6. 可复用的基线配置与最终建议
6.1 网关路由与高可用的最小配置
最后给出一份我在生产里落地过的核心配置骨架,可以直接作为起点。模型网关用的是基于FastAPI自研的轻量网关,路由引擎和熔断逻辑自己实现,配置存在etcd里。
router: rules: - priority: 10 match: { type: intent, sensitivity: low } target: local-small-a fallback: cloud-large-b - priority: 20 match: { type: codegen, source: internal } target: cloud-large-b fallback: local-large-c - priority: 30 match: { sensitivity: high } target: local-large-c fallback: none weights: local-small-a: { v3: 5, v2: 95 } cloud-large-b: { primary: 90, backup: 10 } healthcheck: interval: 10s timeout: 8s fail_threshold: 3 success_threshold: 2 no_cache: true circuit_breaker: window: 10s error_rate_threshold: 0.5 cooldown: 30s half_open_requests: 5 recovery_success_rate: 0.7 rate_limit: global_qps: 200 per_business_qps: 50 queue_max: 100 overflow_action: fast_fail6.2 Agent并发参数的推荐初始值
多Agent侧我建议从以下初始值开始压测调优,不要一上来就抄大厂公开的方案,大厂的硬件和业务模型跟你不一样。
| 参数 | 推荐初始值 | 调整依据 |
|---|---|---|
| 编排器线程池 | 20 | 压测时观察CPU和依赖调用延迟 |
| 子Agent线程池 | 50 | 根据模型服务并发上限联动调整 |
| 本地模型服务并发上限 | 16 | 用压测工具打到GPU利用率80%为止 |
| 云端API并发上限 | 32 | 根据云厂商配额和预算设置 |
| 子Agent调用超时 | 30s | 正常P95延迟的2-3倍 |
| 编排器总超时 | 60s | 必须大于子任务链路总耗时 |
| 重试次数 | 1 | 只在429/5xx/连接错误上重试 |
这些数字跑通后,要按月复盘调整。模型升级、数据量增长、业务变化都会让最优参数漂移,高可用体系不是"配置一次就完事"的静态系统。
6.3 我对这套架构的总体评价与后续演进
做了大半年混合架构,我的整体感受是:这套方案的复杂度的确比单纯接云端API高一个量级,但投入到一定程度后,回报非常明显。成本上,本地模型承接了大约65%的请求量,整体推理成本降了接近70%;稳定性上,有了网关这层缓冲,云端API的抖动对业务的影响基本被隔离了;最大的隐性问题——多Agent编排的调试复杂度——则要靠完善的链路追踪来缓解。
我个人在实践中最想强调的一点是:不要把路由和高可用拆成两件事分别做,它们本质是同一个系统的一体两面。路由负责"把请求送到该去的地方",高可用负责"当那个地方不可用时还能送到别的地方",没有可观测性的路由是盲目的。所以如果你只从这篇文章带走一个行动项,我会建议先在你的模型调用入口前面加一层带全链路追踪的网关,哪怕规则很简单,也比没有强。先把这条链路立起来,成本、稳定性、多Agent协同的能力,都从这一层长出来。