这两年做AI应用,避不开一个尴尬:本地部署的开源模型省钱、可控、数据不出内网,但智力天花板就摆在那儿;云端大模型能力强、上下文长、升级快,可一是费用涨得快,二是敏感数据不敢送出去。
于是越来越多团队开始走"混合大模型"路线——本地跑一套开源底座,云端挂一套商业API,外层用多Agent编排调度。模型一多、Agent一多,请求怎么分、故障怎么切、记忆怎么共享,这些问题如果不提前设计,做大了一定会爆雷。这套系统的核心,其实不是模型本身,而是中间那层"路由与高可用"。
下面这篇内容不聊PPT架构,我把在混合大模型工程化里踩过的坑、验证过的方案全部整理出来,覆盖多Agent协作、本地/云端混合部署的路由设计、健康检查与自动切换,给正在搭这类系统的同学一份能直接参考的实践笔记。内容偏工程向,需要你了解基本的LLM API调用和一点分布式系统常识。
1. 混合部署的底层逻辑:本地和云端为什么要共存
1.1 本地模型和云端模型从来不是同一种"物种"
先说个判断:不要指望本地开源模型的API接口做得比云厂商好,也不要指望云端模型能解决你的合规问题。两者从一开始就是互补关系,混着用不是将就,而是性价比和业务约束共同作用下的最优解。
我列一个对比表,这是每次给新同学讲混合架构时必用的底稿:
| 维度 | 本地模型(vLLM/Ollama) | 云端模型(商业API) |
|---|---|---|
| 单次调用成本 | 固定硬件投入,边际成本低 | 按token计费,量大了惊人 |
| 数据合规 | 数据不出内网,安全可控 | 数据出网,要过隐私审批 |
| 能力上限 | 受模型参数和量化级别限制 | 综合能力普遍更强 |
| 延迟 | 内网延迟低,但受显存和排队影响 | 公网延迟高,峰值时段不稳 |
| 可定制 | 可微调、可与本地知识库融合 | 只能用上下文注入 |
实际项目里最常见的组合是:常规链路放本地,高难度任务放云端,用小流量探云端的效果,跑通之后再逐步放开。千万别一上来就把所有请求都压到一条链路上,两边都会出事。
1.2 多Agent场景把路由问题放大了
单模型单出口的时代,你只需要在代码里写死一个endpoint。但一旦引入多Agent,每个Agent各自有角色、工具、会话上下文,还要互相传递结果,事情就变了:
- 同一个Agent在不同步骤里需要不同能力的模型;
- 不同Agent对数据敏感度、延迟、成本的要求完全不一样;
- Agent之间要共享对话历史和事实记忆,不能各记各的,否则聊着聊着就上下文漂移了。
相当于原来只有一条路,现在有了一个城市级的交通网。没有统一的路由层,最后的结局一定是每个Agent自己接云厂商SDK、自己写重试、自己管理API Key,运维直接变成灾难。我见过一个项目,三个Agent分别用了三种不同的模型调用方式,上线第二周就因为配额问题互相抢资源,这就是典型的缺少路由治理。
1.3 把"路由"从网络工程里借过来
做这套系统的过程中我发现,网络领域的路由思想比"调用分发"这个词更值得借鉴。
- 静态路由:写死的模型映射,适合稳定不变的场景;
- 动态路由:根据健康状态、负载、延迟动态选路,对应网络里的OSPF/BGP那套收敛逻辑;
- 策略路由(PBR):不仅看目标地址,还看来源、端口、应用类型,对应我们按任务类型、数据敏感度、用户级别做分流;
- 默认路由/兜底路由:所有没匹配到的请求,统一走一个保底出口。
我后面的所有设计,本质都是把这套网络路由的思维搬到LLM调用链上。先有路由思想,再谈工具选型,顺序不能反。
2. 路由层选型与核心设计:先定规则,再定工具
2.1 先别急着选框架,想清楚你的流量模型
网上讲LiteLLM、LangChain这类路由工具的文章很多,但工具永远是次要的。动手之前,先回答三个问题:
- 你的请求是按什么维度分流的?数据敏感级别?任务类型?成本预算?
- 你允许多大的失败概率?故障切换时间容忍多少秒?
- 团队有没有能力长期维护一个自研组件?
常见的三种选型路径:
- LiteLLM:快速起步,支持大量Provider统一成OpenAI格式,适合中小团队、路由规则不复杂的场景;
- 自研轻量网关:用FastAPI等框架实现路由配置、健康检查、熔断降级,适合路由规则复杂、需要深度定制切流逻辑的公司;
- 通用API网关(Kong/APISIX/Envoy):适合已经有微服务网关体系的团队,但LLM特定的参数(token预算、流式透传、模型权重)要做不少扩展开发。
我这边最终选的是自研轻量网关。原因很简单:我们的分流规则里有数据敏感字段,有成本预算控制,还有按Agent维度的优先级调度,通用网关反而束手束脚。如果你们团队没有后端人手,用LiteLLM起步完全够,先把链路跑通再逐步替换。
2.2 设计一张"三层路由表"
不要让所有规则堆在一个大if-else里,太容易烂。我把路由拆成三层,每一层只关心一件事:
- 实例路由(Instance Route):决定请求到达哪台物理推理节点,解决本地GPU集群多节点负载均衡的问题;
- 模型路由(Model Route):决定用哪个模型家族,解决"同一任务可以用本地32B也可以用云端旗舰"的问题;
- 业务路由(Policy Route):决定按什么策略条件匹配,由业务方配置,解决"敏感请求走本地"的问题。
每一层本质上都是一张表。下面是一段我实际在用的简化版YAML配置:
routes: - name: "敏感数据强隔离" condition: "request.metadata.sensitivity == 'high'" action: model_route: "local-qwen-32b" instance_route: "gpu-pool-a" priority: 100 - name: "高难度推理走云端旗舰" condition: "task.type in ['complex_reasoning','long_context'] and quota.remaining > 1000" action: model_route: "cloud-gpt-4o" priority: 80 - name: "默认走本地" condition: "*" action: model_route: "local-qwen-32b" priority: 1注意优先级:条件写得再细,优先级不对等于白写。我一般把"安全合规"类规则优先级设最高,"成本控制"其次,"可用性兜底"放最低。高优先级的规则一旦命中就短路,不再往下匹配。
2.3 策略路由:把"该走哪条路"写成规则
策略路由的核心经验是:规则条件必须来自请求的元数据,而不是模型返回的内容。你可以在请求头、请求体里带上业务字段,例如:
- sensitivity: high/medium/low(数据敏感度)
- task_type: chat/code/extraction/reasoning(任务类型)
- max_cost: 0.01(本次调用成本上限)
- timeout_budget: 3000(延迟预算,毫秒)
网关统一读取这些字段来做匹配。规则必须有分组和版本。比如"月度成本预算用掉80%之后,所有code任务从云端切到本地旗舰模型",这类规则要允许运营同学在后台直接改,而不是改代码重新发版。我在第一个版本里把规则硬编码在Python里,后来每次调一个阈值都要走发版流程,身心俱疲,改成配置化之后才解脱。
2.4 动态路由:权重、健康度与探活
静态路由只能解决"初始怎么分",解决不了"某台节点坏了怎么办"。所以上面的表只是基础,真正要跑起来必须有健康度感知。
- 主动探活:网关定期请求推理服务的/health接口,或者发一个极小的生成请求(比如max_tokens=1),连续失败N次就标记不健康;
- 被动探活:统计最近1分钟的错误率、P95延迟,超过阈值就自动摘除流量;
- 权重分配:按实例的剩余显存、并发水位做加权轮询,而不是简单round-robin。
还应该做"优雅摘除"(graceful drain):标记不健康的节点不再接收新请求,但已经建立的流式连接不掐断,让正在生成的请求自然结束。这个细节很多团队会漏,实际体验差别很大。我最早没有做优雅摘除,节点一重启,正在生成的长回答直接断流,用户端报错一堆。
3. 高可用建设:故障切换、熔断与降级
3.1 三种高可用模型怎么选
高可用不是一个开关,而是一组权衡。我见过三种落地形态:
- 冷备:本地主实例挂了,人工或脚本拉起备用实例。成本最低,切换分钟级,适合非核心链路。
- 热备/主备:主实例故障后自动切到备用实例,秒级到十秒级。成本略高,适合核心场景。
- 双活/多活:两路同时承载流量,故障时只把失败流切走。成本最高,但用户体验最平滑。
网络工程里讲究主备切换和双上行链路,LLM场景其实一模一样。我的建议:对外提供统一网关,网关后面挂两个Provider(本地集群、云API),平时按权重分流,出故障时靠健康状态自动收敛。不要做成"平时只用本地、云端永远闲置",那样云端链路长期不验证,真正故障时反而切不过去。
3.2 健康检查与探活参数
健康检查看起来简单,但参数设置不好就会误判。我常用的探活配置:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 探活间隔 | 10s | 太频繁会占显存和API配额 |
| 探活超时 | 3s | 超过即认为本次探活失败 |
| 不健康阈值 | 连续3次失败 | 避免单次抖动误摘除 |
| 恢复阈值 | 连续2次成功 | 恢复不要太激进 |
| 探活请求 | max_tokens=1的小请求 | 顺带测首token延迟 |
流式大模型的健康检查有个坑:很多服务的/health接口是静态的,进程活着就返回200,但实际推理队列已经堵死了。所以至少要有一个探活打到推理接口本身,或者暴露一个"当前排队长度+平均首token延迟"的指标给网关。只看进程存活,等于没做高可用。
3.3 超时、重试与熔断参数设计
这块参数表基本是根据线上事故一点点调出来的:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 连接超时 | 3s | 云端API握手慢 |
| 首token超时 | 20-30s | 超过说明服务端排队严重 |
| 总请求超时 | 180s(非流式)/ 自定义(流式) | 长文本生成需要 |
| 重试次数 | 1-2次 | 重试太多会放大故障 |
| 重试退避 | 500ms + 随机jitter | 防踩踏 |
| 熔断阈值 | 最近10个请求中错误率>50% | 连续观察窗口至少5个请求 |
重试和熔断必须配合:只重试不熔断,失败请求会反复打在已经故障的服务上,形成"重试风暴"。熔断器一般有三种状态:关闭(正常)、打开(快速失败)、半开(试探恢复)。半开状态下放少量流量过去测试,成功了再闭合。这个套路和分布式系统里的保护策略完全一致,只不过被保护对象换成了模型服务。
3.4 降级路径设计:从云端切回本地
高可用的最后一环是降级。降级不是简单的failover,而是一条明确的"优先链路链":
cloud:gpt-4o → cloud:claude → local:qwen-32b → local:qwen-14b
每一级都对应不同的能力与成本。当成本预算耗尽或者云端API不可用时,请求能自动落到本地模型上。这里要额外注意:降级后的模型返回质量可能明显下降,所以网关要在响应头里带上实际使用的路由信息,方便你回溯"是哪一层降级了、为什么降级"。
我第一次做生产切换测试时,故意在管理接口把云端Provider标记为不可用,结果网关在30秒内自动把流量切到本地,在线业务只看到少量超时重试,没有报错。那一刻才觉得这套折腾值了。后来我把这个"故意故障"做成了定期演练,每次都能发现一些配置上的小问题。
4. 多Agent协作中的路由与共享记忆
4.1 Agent之间的服务发现
多Agent系统里,每个Agent可以理解成一个微服务,Agent之间也要互相调用。所以Agent网关承担两个职责:对外接收用户请求做统一路由;对内做Agent注册与服务发现。
每个Agent启动时向注册中心登记自己的名称、能力描述、调用地址、依赖的模型路由。调用方发请求时带上目标Agent名称,网关里的Agent路由表负责找到正确的目标。这个设计和微服务的服务注册/发现没有本质区别,只不过多了一层"模型路由"的叠加。
这就有点像网络里的路由重分布:新Agent上线或模型路由变化后,注册中心要把最新的路由信息同步给所有相关方。我给路由配置加了一个版本号,Agent缓存里带上版本,每次调用时如果发现版本不一致就重新拉取,避免一边是旧配置一边是新配置造成流量错乱。
4.2 共享记忆的三种实现
热词里反复出现"多Agent共享记忆",我重点讲这个。Agent记忆不是一个Redis就完事的,至少要分三层:
| 记忆类型 | 存储 | 用途 | 路由策略 |
|---|---|---|---|
| 会话短期记忆 | Redis | 当前会话的上下文、中间结果 | 按会话ID读取 |
| 长期语义记忆 | 向量库(Qdrant/Milvus) | 历史案例、知识片段 | 按相似度召回 |
| 结构化事实记忆 | PostgreSQL | 用户档案、订单状态、任务结果 | 按业务主键读取 |
共享记忆的关键不是存储选型,而是"写入一致性":多个Agent同时更新同一份会话状态时,要以哪个版本为准?我的做法是加一个简单的版本号,每次写入带上version,版本冲突时后写覆盖前写但记录日志;重要的结构化状态(比如订单状态、任务状态)则用PostgreSQL行锁或CAS,保证最终一致。别小看这个版本号,少了它,共享记忆很快就会变成"共享脏数据"。
4.3 上下文路由:把请求引导给最合适的Agent
还有一个容易被忽略的路由维度:按意图选择Agent。用户一条消息进来,先在网关做一次轻量意图识别,再决定交给哪个Agent串联。
这一步我建议用小模型做,比如本地部署的7B级别模型,只做分类任务,速度快、成本低。等分发链路走通后,再把需要深度的复杂请求用策略路由切给云端或更大的本地模型。这就像网络里的单臂路由,所有子网流量先经过一个公共网关,统一打上标签再分别转发。用7B模型做意图识别还有一个好处:请求量再大也不心疼成本,可以放心地让网关每次请求都跑一遍分类。
5. 完整落地参考:一套可以抄的混合部署方案
5.1 组件清单与部署拓扑
我当前生产环境比较稳定的组合是:
- 本地推理:vLLM部署Qwen2.5-72B和Qwen2.5-32B两个实例,分别服务不同任务;
- 轻量模型:Ollama或vLLM部署7B模型,专门做意图识别和策略路由分类;
- 路由网关:自研FastAPI服务,读YAML路由配置,支持健康检查和熔断;
- 共享记忆:Redis存会话,Qdrant存向量记忆,PostgreSQL存结构化事实;
- 监控:Prometheus采集网关指标,Grafana可视化,AlertManager告警;
- 云API:统一通过网关接入,网关只维护一份API Key和配额管理。
部署形态上,网关和本地推理集群放同一个内网,云API走公网出口。这里有个网络层面要提前规划的点:本地网关到云API之间的出口带宽、稳定性和DNS解析质量,直接决定了云端链路的可用性上限。很多云端链路看起来"不可用",其实是出口网络抖动导致的。
5.2 本地推理实例部署要点
vLLM启动最基础的命令:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-32B-Instruct \ --tensor-parallel-size 4 \ --gpu-memory-utilization 0.85 \ --port 8001 \ --served-model-name local-qwen-32b几个关键参数的含义:
- --tensor-parallel-size 4:4卡张量并行,小于1显存不够,大于4通信开销上升;
- --gpu-memory-utilization 0.85:给显存预留15%余量,防止长上下文OOM;
- --served-model-name:对外暴露的模型名,网关路由表里匹配的就是这个名字。
如果是多实例部署,每台机器起不同的端口,网关通过实例路由表把流量分发过去。显存分配要按任务预估:长上下文任务要留足KV Cache,短对话任务可以把并发开高。另外实测下来,vLLM对同一批卡跑多个小模型实例,比单实例跑一个大模型更容易出现显存碎片,建议一个实例至少占一整张卡。
5.3 网关核心配置示例
前面已经给了路由表配置,这里补一个完整的网关配置骨架,包括健康检查和熔断参数:
providers: - name: local-qwen-32b type: openai_compatible base_url: http://10.0.0.11:8001/v1 api_key: local health: interval: 10s timeout: 3s unhealthy_threshold: 3 recovery_threshold: 2 circuit_breaker: error_rate_threshold: 0.5 window: 10 min_requests: 5 - name: cloud-gpt-4o type: openai_compatible base_url: https://api.example.com/v1 api_key_env: CLOUD_LLM_KEY timeout: connect: 3s first_token: 30s total: 180s retry: max_attempts: 2 backoff: 500ms jitter: 200ms global: default_route: local-qwen-32b fallback_chain: [cloud-gpt-4o, local-qwen-32b]网关内部最核心的方法就是对OpenAI格式的适配:不管后端是vLLM还是云端API,统一转成OpenAI /v1/chat/completions格式返回给上层Agent。这一步能省掉Agent层大量的兼容代码。如果你们的Agent框架本身支持多Provider,网关也可以简化成只做策略路由和成本控制,模型调用直接透传。
5.4 监控与告警
高可用系统如果没有监控,等于裸奔。我至少会盯这几个指标:
- 网关QPS与分模型QPS;
- P50/P95首token延迟和总延迟;
- 错误率(按HTTP状态码、按熔断次数分桶);
- 路由切换次数(这是最重要的信号,切换频繁说明配置或资源有问题);
- 成本日报:按天、按模型、按Agent维度统计token消耗与费用。
告警规则我一般设三条:错误率连续5分钟超过5%;P95延迟超过基线2倍;路由切换次数超过阈值。这三条能覆盖大部分"高可用失效"的场景。另外我强烈建议把"路由切换事件"本身也记成日志,每次切换都要能回答清楚:谁触发的、什么时候触发的、从哪条链路切到哪条链路。
6. 常见问题与排查技巧实录
6.1 路由不走预期规则
症状:请求本应走"敏感数据走本地"的规则,结果走到了云API。
排查顺序:
- 先看网关日志里的匹配结果,确定命中了哪条rule;
- 检查condition里的字段是否真的传进来了,很多问题是请求头字段名不一致;
- 检查优先级,是不是低优先级的规则覆盖了高优先级,通常因为配置下发顺序不对。
我犯过的错:把condition写成了等值判断而不是包含判断,导致sensitivity字段等于字符串'high'时正常,等于列表['high']时直接跳过,走了默认规则。规则匹配逻辑一定要写单元测试,把常见组合都覆盖到,不然上线后出问题全靠人肉翻日志。
6.2 高可用切换失败或来回抖
症状:节点故障后没有自动切走,或者切走后又切回来,流量抖动。
排查顺序:
- 看健康检查探活是否真的能触达故障节点,网络隔离、防火墙经常会挡住内网探活;
- 看恢复阈值是不是设得太激进,节点刚要恢复就被判定正常,又把流量引过去了;
- 确认是否有多个网关实例同时做切换,彼此的熔断状态是否同步。
来回抖的经典场景:探活间隔10s、恢复阈值2次,一个半故障节点每20秒好一下,网关流量切过去又超时,然后再切回来,造成P95毛刺。解决办法是把恢复阈值调大,或者给刚恢复的节点一个"冷启动期",先放5%流量热身再逐步放开。
6.3 多Agent互相调用死循环
症状:Agent A调用Agent B,B又回调A,两边拿着上下文反复处理,token消耗爆炸。
排查顺序:
- 在Agent调用链路的请求头里加trace_id和深度计数;
- 超过调用深度上限(比如5层)直接拒绝;
- 对同类Agent之间的循环调用做去重,检查是否同一会话内对同一条消息已经处理过。
我后来在网关里加了一个简单的"任务令牌":同一个任务令牌在未完成前不允许重复进入同一个Agent,这招基本能杜绝大部分循环嵌套。排查时记得看网关日志里的trace_id链路,很快就能定位到是哪两个Agent在互相拉扯。
6.4 流式请求在网关上的坑
症状:流式输出在网关中转后,到客户端出现断流、乱码或延迟增大。
原因一般是网关使用了缓冲逻辑,没有把SSE事件即时透传。正确做法是:网关收到上游的第一个字节后,立即开始向下游转发,不要等完整响应。同时流式场景的超时时间不能按普通请求的total timeout来算,否则长回答生成超过180秒就被掐断了,应该只限制"首token超时"和"相邻两个chunk的最大间隔"。我一开始用同步HTTP客户端做流式转发,结果首字节延迟直接翻倍,换成异步流式转发后问题消失。
6.5 成本超支但排查不到原因
症状:云端API账单突然翻倍,但QPS看起来没涨。
原因大概率是重试逻辑和Agent死循环叠加:某条链路持续超时,每个请求都被重试了2次,多个Agent又各自独立发起请求,同一个用户问题最终在云端被调用了5到6次。解决方法是给每个请求生成一个全局request_id,在网关层做同id去重,并在成本统计里按request_id聚合,马上就能看出来是哪些任务在重复消耗配额。
最后再分享一个我个人的习惯:每次调整路由规则或高可用参数,我都会在非高峰期手动做一次故障演练,方法很简单——在网关的管理接口把某个Provider标记为不可用,然后观察流量是否按预期切走、是否出现错误率飙升、切回时有没有抖动。这套动作我已经重复了很多次,每次都总能发现一些配置上的小问题。故障不是会不会来的问题,而是什么时候来的问题,多演练一次,线上就多一分底气。