ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多模型混合调用架构:统一网关设计与路由策略实践

多模型混合调用架构:统一网关设计与路由策略实践 前几天一位朋友问我你们线上为什么用三个不同家的模型 API一个做对话一个做结构提取一个跑批量分析全部堆在一起到底是怎么管的这是个很典型的问题。现在很多团队已经到了“手里有新 API 就接入进来试试”的阶段模型能力参差不齐、计费规则又天差地别没有统一管理层项目就会变得极其混乱。这篇文章想把我搭建多模型混合调用架构时的核心思路、分层设计、路由策略和踩过的坑完整整理出来给正在做同样事情的你一个可以复现的参考。这里的关键词是“统一管理”。不是说用一个 SDK 把调用封装完就结束了而是把模型 API 当成基础设施来治理——注册、路由、鉴权、限流、观测、成本核算每一块都要有对应的解决思路。也就是说所谓多模型混合调用架构本质上是一个“中间层”一个位于业务代码和不同大模型服务中间的网关。1. 为什么不能把模型 API 直接写死在业务代码里直接从“为什么需要统一层”这个问题说起。我见过不少项目最开始只接了一个大模型 API代码里哪个模块要生成内容就各自调用。等第二、第三个模型进场情况就开始失控每次调用要单独处理 key、单独处理超时、单独解析返回格式上游一换版本所有调用方都要跟着改。所以业务一复杂第一件事就是要有中间层。1.1 单一供应商的风险不是“等出事才知道”大模型服务本质上也是第三方基础设施。它可能限流可能故障可能调整价格也可能突然把某个模型下线。如果你的业务代码直接依赖某一家的 API任何一朵乌云都会直接砸到你的头上。我之前遇到过一次比较大的故障某个供应商在高峰期连续出现 5xx 错误持续了近两个小时。业务方如果只靠这一家这两小时就是完全不可用的状态。但只要做了多模型混合调度只是在路由配置里把优先级下调流量立刻切到次选模型对业务的影响基本可以忽略。这其实是很多团队最容易忽略的一点多模型接入不是为了“炫技”而是为了把单点故障变成可切换的备选方案。1.2 不同模型在不同任务上各有所长现在主流模型各自的能力画像差异挺明显的。有的上下文窗口大适合文档分析有的在复杂推理上表现好适合代码生成还有一些小参数模型速度快、成本低适合做分类、抽取这类结构化任务。如果只用一家最强的模型跑所有类型任务既慢又费钱如果只用最便宜的小模型复杂任务效果又达不到。所以“按任务分层、按能力分流”是刚需。这背后需要一套清晰的调度维度任务类型对话、推理、抽取、分类、向量化效果要求高、中、低延迟敏感度实时交互还是离线处理成本预算付费额度还是免费额度而这些维度最终都要落到“路由”这一步上。没有统一网关这个逻辑就只能散落在各个业务方那里重复实现维护成本会非常高。1.3 免费 API 在整体架构里的地位顺便说一下最近大家讨论很多的“免费大模型 API”。免费额度无论是新账号赠送、特定模型的免费试用还是开发者套餐里包含的免费调用次数在架构里的价值容易被高估也容易被低估。高估的团队把免费 API 当正式生产资源用结果发现并发受限、配额用完毫无预警、服务条款里对商用有限制最后手忙脚乱。低估的团队则完全无视免费额度全部走付费通道白白浪费成本优化的空间。我的建议是免费 API 最适合放在“原型验证”和“低优先级离线任务”这两类场景。比如某个新任务类型需要快速试一下效果用免费额度跑一批样例验证可行了再决定要不要接入付费 API或者做数据增强、批量打标这类对延迟不敏感、对稳定性要求不高的任务把免费额度作为次要路由目标用完自动切换。同时在统一管理平台里免费 API 更应该被记录成一个独立的路由目标而不是和付费 API 混在同一个配置项里。这样一方面方便追踪真实成本另一方面也方便在配额耗尽时自动降级。2. 统一网关的核心设计不是代理是基础设施如果只是“转发请求”那这个中间层就没有意义了。真正有价值的统一网关要解决三件事收敛接入差异、集中调度策略、沉淀观测数据。这也决定了它的分层方式。2.1 三层架构拆解接入层、策略层、共用能力层我习惯把多模型调用网关拆成三层方便脑子里面理清边界接入层负责把不同模型 API 的外部差异“抹平”——不同供应商的鉴权方式、请求路径、参数格式、返回结构在这一层统一转换成内部标准格式。策略层是调度的核心负责决定一次请求由哪个模型处理包括路由规则、优先级、配额检查、降级逻辑。共用能力层则是横切能力包括统一鉴权、限流、日志、指标采集、费用归因和告警。这三层不一定对应三个微服务可以是一个服务里的三个模块。但边界一定要清晰。如果连边界都没有后续加一个模型就要改十几个地方。2.2 把 OpenAI 兼容协议作为“默认方言”现在很多模型服务商都提供了 OpenAI 兼容的接口这给统一接入带来很大便利。因为只要按一个标准先打通就能够快速接入一批模型。所以在设计内部标准请求格式时我的建议是直接以 OpenAI 的 ChatCompletion 协议为蓝本做一层扩展。也就是说内部统一用model messages temperature max_tokens这套结构在接入层去映射各家模型的差异。但大家要注意兼容不代表完全一致。有些模型用/chat/completions有些用/v1/messages有些 temperature 支持的最小步长是 0.1有些直接给整数有些参数叫max_tokens有些叫max_output_tokens。接入层就要把这些差异全部消化掉。真正完全一样的协议很少反而最容易出问题的就是那些“看似一致、实则不同”的字段。2.3 配置驱动的模型注册表统一管理的第一步是让“接入一个新模型”变成“改一段配置文件”而不是“改代码、发版、重启”。我一般用一个配置驱动的模型注册表来解决。注册表里每条记录包含这些字段字段说明实际选择时的注意点model_id内部模型标识用语义化命名比如chat-llm-a不要把供应商型号直接当内部标识provider供应商标识同一个供应商下可以挂多个 model_idendpointAPI 地址注意区分基础地址和完整路径api_key_ref密钥引用不要明文存 key要用密钥管理服务引用max_context最大上下文直接影响路由时的长度判断max_output最大输出长度部分供应商输出上限远小于官方宣传值capabilities能力列表比如 json_mode、function_call、vision、streamingprice_info价格模型用于成本预估与路由quota配额限制免费额度和付费额度的剩余量status状态在线、离线、降级等把模型信息收敛到一个地方后面的路由、观测、成本分析就有了一套统一的元数据来源。我强调“配置驱动”的原因很简单模型的更新频率比业务代码高得多供应商今天上线新模型、明天调整参数上限你不希望为了换个模型就动生产代码。设计的时候可以约定一个配置文件的格式比如 YAML 或 JSON发布时走配置中心热更新。早期项目没必要直接上重量级的服务网格先保证“配置能热更新”就行。3. 路由策略从简单任务到动态调度网关有了接下来最核心的问题就是来了一个请求交给谁这块策略我认为是很多人最先会低估的地方——他们以为路由就是“轮询 随机”。3.1 最简单的任务级硬路由起步阶段可以不用做太复杂在业务请求头上带一个scene标记比如chat、extract、classify网关根据场景路由到固定模型。对就这么简单。硬路由的价值在于“可预期”。每个场景的行为完全一致排查问题时思路也非常清楚这个场景配的就是这个模型谁也不许抢。这个模式在任务类型稳定、模型选型明确的场景下已经够用。但硬路由也有明显短板一旦某个模型故障或有突发流量硬路由无法自动切换你需要人工改配置。所以硬路由适合当“默认底线”不适合当唯一方案。3.2 加权路由与灰度发布想在新旧模型之间做灰度对比或者想把流量按比例分配到不同模型上加权路由最直接。例如把一个场景 70% 流量落在主力付费模型、30% 落在备选模型观察一段时间的效果差异再调整比例。实现上我给一个很轻的思路在网关里维护一个权重表每个目标模型有一个权重值。分配时可以用随机权重算法import random def route_by_weight(groups): total sum(item[weight] for item in groups) r random.uniform(0, total) upto 0 for item in groups: upto item[weight] if upto r: return item[model_id]这个算法无论权重总和是多少都能正确按概率返回模型而且实现只有几行。生产上要注意用random.uniform而不是random.randint因为随机区间的边界会导致概率偏差这算我踩过的细节坑。加权路由特别适合“让新模型先接 10% 流量观察一段时间”的场景。但要注意对用户可感知质量的场景10% 的用户可能体验到差异化结果需要提前想好这是否可接受。3.3 成本感知与免费配额优先调度当模型比较多的时候“按规则配死”就不够用了。我见过一个很朴素的调度需求“同样的任务如果免费配额还有剩优先走免费模型免费配额用完了就走付费模型。”这个需求看起来简单实现起来要小心。免费 API 的限额可能是按分钟、按小时、按天甚至按并发数来算的。你没法只靠“还剩几次调用”这种粗粒度计数最好做一个配额管理模块定期同步各个“模型-配额维度”的用量。举一个真实的例子某家的免费额度是每分钟 20 个请求。如果你的系统峰值是每秒 5 个请求那么按分钟限额来计算你的配额会在几秒内烧光。这时候就要在路由里加一个“配额池”的判断如果当前分钟窗口内已经接近上限就提前把流量切走而不是等返回 429 了再重试。这一类的实时判断逻辑建议放在策略层里面用滑动窗口计数来做class SlidingWindowQuota: def __init__(self, limit, window_seconds60): self.limit limit self.window_seconds window_seconds self.buckets {} def allow(self, key, now): bucket self.buckets.setdefault(key, []) # 清理窗口外的记录 while bucket and bucket[0] now - self.window_seconds: bucket.pop(0) if len(bucket) self.limit: bucket.append(now) return True return False把“还有没有配额”当成路由的一个前置判断条件再配合权重、优先级才能做到真正有意义的成本感知调度。3.4 降级链路底线模型 must have我在前面提到过故障切换这里再展开说。降级链路是“不成文但必须要有的设计”。每个核心场景至少要配置两级模型主模型和备用模型。主模型故障、超时或连续报错时网关自动把请求降级到备用模型。降级不是简单的 try-except。一个请求失败后直接换个模型重试可能导致结果不一致、费用翻倍甚至卡死业务。我的做法是区分“可降级”和“不可降级”可降级面向非用户实时场景如后台异步生成、批量摘要失败重试成本低。不可降级用户实时交互、涉及支付或关键流程的场景降级前要有明确的策略比如降级到规则引擎或者返回明确错误提示而不是“神秘地”换一个模型硬答。这里再强调一个观点降级的核心目的是“让系统在异常时也能输出一个可用结果”而不是“找一个更聪明的模型”。备选模型在能力上允许弱于主模型但要稳定响应要快。4. 适配差异真正难处理的往往是细节模型接入表面上是“调通接口”实际上真正的工作量全在边界情况和细节差异。我在多个项目的接入过程中整理了一些高频坑挑几个有代表性的聊聊。4.1 超时、重试与熔断参数不是拍脑袋填的不同模型的响应延迟差异极大。小模型往往 1 秒内就能出结果大的推理模型可能需要几十秒。如果你给所有请求统一设置 5 秒超时那么用大模型的任务基本全部失败。合理的做法是在模型注册表里给每个模型配置独立的超时参数。连接超时、读取超时与会话超时分开设置。重试也要区别对待。什么情况下值得重试网络抖动、5xx、429配额不足这些情况可以重试但你收到的 4xx 业务错误比如参数格式不对、内容审核拦截重试只会浪费一次调用。这一点必须要区分开。429 的重试要非常小心。大部分供应商在 429 响应里会带retry-after头有的甚至精确到秒。只管 sleep 重试而不看这个头会形成“重试风暴”把自己打死。另外免费 API 的 429 尤其常见重试策略更应该保守。熔断器建议按“供应商维度”做而不是按模型维度做。因为同一个供应商故障时往往是整体故障按模型维度熔断可能该模型已经恢复、其他模型还在禁飞名单里白白把可用流量杀掉。4.2 流式返回的统一问题很多场景要求流式输出打字机效果。这个特性在统一网关这里会碰到一个麻烦流式响应的格式不统一。有的供应商在流式 chunk 里提供完整增量文本有的只给一个 token ID还有一种情况是同一家的不同模型流式行为都不一样。网关屏蔽这些差异时最简单的做法是先把每家流式数据解析成统一的delta结构再往外推。但要注意你不能在网关层“憋”流式响应——比如等整个响应结束后再一次吐出那样延迟表现会变得非常差失去了流式的意义。正确做法是逐 chunk 转发同时兼容各家不同的结束标记。这里我建议一开始就定义一套内部统一的流式协议各模型接入时全部转换到这个协议上业务侧永远只认这一套。{ id: request_id, delta: ..., finish_reason: null, stat: { input_tokens: 12, output_tokens: 3 } }token 统计在流式场景里容易漏。很多供应商在流式结束时带一个 usage 字段但有些则需要你自己累计每个 chunk 的长度。统一网关最好在接入层就把 usage 归一化处理掉落到公共日志里否则成本核算时你会发现数字对不上。4.3 模型名映射、输入上限与隐蔽参数差异这是许多人会忽略的一层如果你在网关内部统一用“语义化 model_id”跨模型切换时业务传入的模型名就得做映射。比如业务写chat-llm-a路由层最终解析成某个具体供应商的实际模型编号。这样换来的是“业务代码和具体供应商型号解耦”我在第 2 节强调的 model_id 原则就在这里发挥作用。另外不同供应商的上下文上限计算方式也可能不同。有的按 token 算有的按字符算还有的是按“大约 4 个字符 1 个 token”粗估。网关在路由前做长度预判时千万不能混用单位。我见过因为按字符数预估 token 数导致一批超长文档请求全部失败的事故调了一整天才发现是单位不一致。还有温度、top_p、presence_penalty 等采样参数不同供应商的默认值和取值范围差异也很大。你在兼容层传一个超出目标模型支持范围的参数服务商可能选择忽略也可能直接报错。统一网关要保证“传参到边缘模型时经过一次参数合法化校验”宁可少传一个业务不依赖的参数字段也别冒被拒的风险。5. 观测与费用多模型架构最容易失控的两件事统一管理的最后一块重要拼图是“看清每一笔调用”。没有观测的多模型架构出了问题就像在黑暗里找东西。5.1 统一日志和链路追踪比你想的更必要请求从业务进入网关再打到不同供应商中间每一跳都需要有 trace 能力。网关作为统一入口天然是日志和链路追踪的最佳落点。建议每个请求在网关生成一个内部 request_id并把它透传到下游模型调用和业务返回体里。日志至少要包含这些字段request_id、scene、model_id、实际命中供应商模型请求 tokens、返回 tokens、总耗时首 token 延迟流式场景尤其关键响应状态码、错误类型、重试次数路由判定依据命中权重还是配额判断这些字段凑齐后两个很重要的分析就出来了“不同模型的真实首 token 延迟对比”和“不同场景下实际 token 成本分布”。后者比供应商给的账单更接近真实业务视角。链路追踪如果不想上太重的基础设施可以用简单的日志打点加请求 ID 关联。项目体量上来之后再考虑完整 trace 系统。因为网关是所有流量的必经点做一个日志聚合就能解决大部分问题。5.2 限流与配额免费 API 是重灾区统一网关的另一项必要能力是“总入口限流”。业务上可能同时对接多个账号、多个模型不统一做限流可能某个渠道的额度一瞬间被某个大任务打爆或者把某个供应商的 API 触发整体封禁。限流维度建议至少支持场景级别、API key 级别、供应商级别。再配合配额管理模块让“某供应商今日剩余多少调用量”成为路由可以感知的维度。免费 API 在限流和配额方面尤其容易出现“表面有额度、实际上限很低”的情况。比如有的免费模型对并发数限制非常严格只允许 2 个并发请求。在没做并发限制的情况下业务层启动一个并发为 10 的批量任务等于当场就触发限流。应对方法一个是上面提到的滑动窗口另一个是每个供应商单独做一套并发信号量把并发控制在安全线内import threading semaphores {} def get_semaphore(provider_key, max_concurrency2): if provider_key not in semaphores: semaphores[provider_key] threading.Semaphore(max_concurrency) return semaphores[provider_key]不要小看这个小东西它经常是“免费 API 试用时翻车”的最后一道防线。5.3 成本核算必须穿透到业务场景多模型架构的账单是很令人头疼的。供应商 A 的账单打出来是一堆 model 级别的调用费用你很难看出这些调用都服务了哪些业务场景。只有在网关层面把每次调用的 scene、model_id、usage 都打了标记事后才能精准回答“某场景上个月花了多少钱”。成本核算这里我给一个非常实用的建议每周导一次网关的 usage 汇总按 scene model 两个维度做一个成本透视表。你很快会发现成本大头往往集中在一两个场景和一两个模型上这比盲目换模型要有效得多。另外一个与“免费大模型 API”相关的点有些免费 API 名义上是免费的但不代表使用成本为零比如不可控的排队延迟、更低的命中率、被迫反复重试导致的时间成本以及数据使用政策可能不适用于某些业务。因此在做成本比较时除了单价之外把“免费模型的失败重试率、平均首 token 延迟”这些指标也算进去才能做出合理的路由决策。6. 一个轻量级实现的路径从最小可用网关开始最后想分享的是一个我认为具备可行性、结构也不算重的最小实现思路。如果你刚开始做多模型接入不用一上来就上重量级服务用几个简单的模块就能搭一个可用的网关。6.1 最小系统建议FastAPI 配置文件用 FastAPI 写一个单服务网关内部挂三个模块配置加载模块、路由决策模块、供应商适配模块。整个服务可以在一两天内搭完关键是把接口定义清楚app.post(/v1/gateway/chat) async def gateway_chat(req: ChatRequest): route decide_route(scenereq.scene, task_typereq.task_type) provider adapters[route.provider] result provider.invoke(req) log_usage(route, result) return result这个接口跟上层业务方约定好一个统一的请求/响应结构业务方完全不需要关心模型来自哪家供应商。想要扩展新模型时配置注册表加一条记录再写一个适配器或用通用兼容适配器就完成了。6.2 路由决策的顺序不要搞反很多做多模型网关的人容易把路由逻辑写得非常复杂又是规则引擎、又是权重计算、又是 AI 判断。我自己的经验是第一个版本用“顺序判断”就够了先查场景是否指定了独占模型硬路由再查该模型配额是否充足不足则跳到下一候选然后查当前模型是否在熔断状态或被限流最后按权重从候选模型里随机选一个这个顺序能覆盖绝大部分生产场景逻辑也简单。等量级和复杂性到达一定水平后再考虑引入更复杂的动态评估。6.3 开始阶段别追求大而全很多人看多模型网关相关文章容易犯一个毛病第一版就要把自动重试、熔断、流式聚合、费用报表、可视化监控全部做齐。事实上早期最应该做的是把配置文件搭好、把模型接入流程标准化、把日志里该打的字段打全。这三个地基搭好后续各种高级功能都会很顺利地长出来。反过来如果一开始就把时间花在复杂的可视化看板上地基反而不稳后面每接入一个新模型都是一次鬼门关。我在多个项目里的实际体会是统一管理多个大模型 API 的收益不会在接入第一个模型的时候体现而是在你接入第二个、第三个并且能够随心所欲地调整路由和配置的时候才真正爆发。有一段时间我甚至养成了一个习惯每次做新任务效果对比不是找业务方改代码而是后台改一段路由配置就能让同样的输入同时跑在三个不同模型上。这种自由度就是统一管理架构带给整个团队最大的红利。以后如果公司要接入新的模型哪怕模型 API 风格完全不同我的步骤也依然是注册表加一条配置适配器补齐差异灰度切量看效果然后决定去留。这一套流程下来项目接入一个新模型的时间能压到两三个小时而不是两周。最后再提醒一次做这个架构容易掉进去的坑永远不要在业务代码里直接拼模型供应商的 key。统一网关把“模型接入”这件事变成一个平台的公共能力之后你会发现团队的研发效率、成本控制和故障应对能力都会有一个量级层面的提升。
返回列表