ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

多模型混合调用架构:大模型API统一接入与治理实践

多模型混合调用架构:大模型API统一接入与治理实践 这两年做了几个AI应用后我最深的体会是真正拉开项目复杂度的不是某个模型效果好不好而是能不能把一个多模型混合调用架构搭好把多个大模型API统一管理起来。模型越来越多各家能力差异又大如果业务代码直接散装接入前期确实“爽”等要切换、降级、算成本的时候整个团队都会被拖进泥潭。这篇文章就把我在某个内部AI应用项目里沉淀下来的统一接入方案写出来包括API抽象层、路由策略、容错机制、成本观测和几个踩过的坑适合正在做Agent平台、智能客服、内容生成服务的开发者参考。1. 当项目里同时接入三五个大模型API之后混乱是怎么滋生出来的1.1 每个模型一套接入方式代码里到处是分支我最早接手的那套系统逻辑非常简单业务方指定一个模型我们调一个接口把结果返回给前端。后来需求多了有的场景要便宜快速有的场景要强推理有的场景要视觉理解于是陆陆续续接了好几个不同厂商的API。最开始大家没当回事每个新模型都按SDK文档在业务代码里单独写一个方法没多久代码里就出现了大量if provider xxx的分支。更难受的是各家API差异比想象中大得多。请求参数有的叫temperature有的叫temp最大值还不一样流式返回有的用SSE有的用自研事件格式错误码更是五花八门有的返回HTTP 429表示限流有的返回200但里层error_code是限流。每次联调一个新模型光参数转换和错误解析就要折腾两三天等接入的模型数量超过三个这套代码基本没人敢动了。1.2 统一管理不只是“开发方便”更是线上稳定性问题当时很多人觉得多接几个模型不就是为了买到更便宜的服务吗开发麻烦一点忍忍就过去了。真正让我下决心做统一架构的是一次线上事故某个主力模型服务商发公告说当天要升级我们想切到备用的另一个模型结果发现备用模型虽然早早就接好了但SDK版本不一致、请求参数没适配生产环境一压测就报错。最后只能顶着风险继续用老服务整晚没睡踏实。从那以后我想明白了分管多个大模型API的核心价值不在于“集成了多少家”而在于三个能力透明替换切换模型不应该是改代码、重新上线而是配置层面的动作。策略路由不同请求能自动选择合适模型比如简单的摘要走低价模型复杂的推理走强模型。稳定兜底某个模型故障时流量能自动转移到健康模型而不是让用户看到超时。1.3 我最终确定的架构目标统一管理多个大模型API本质上是给业务层和模型服务商之间加一层“中间人”。这层中间人要做的事情不复杂但边界要清晰对业务层暴露一套稳定、中立的统一接口业务方不需要关心后端到底是哪家模型。对模型服务商通过适配器做隔离新增一个模型只需要实现一个适配器不改业务代码。对运维侧提供路由、熔断、降级、计费的统一配置入口。架构上就分成了四块统一抽象层、适配器层、路由调度层、可观测与成本层。下面我逐个讲清楚设计和实现思路。2. 先把API抽象拉齐一个Request/Response模型吃下所有差异2.1 为什么必须先统一协议而不是直接做网关很多人一听“统一管理多个大模型API”第一反应是搭一个API网关把各家请求转发一下。我觉得这是本末倒置。如果网关的转发逻辑还是基于各家私有协议的每接一个模型就要在网关里写一层透传参数那网关反而成了又一个维护地狱。正确的做法是先定义一套中立的请求/响应模型把所有模型的差异全部收敛在适配器里。业务方只认这一套模型网关和路由也只认这一套模型。只要协议层统一了后面做路由、做缓存、做成本统计才有共同的抓手。我定义统一请求时坚持几个原则字段用中性语义不偏向任何一家。比如对话历史统一叫messages模型名统一叫model随机性统一叫temperature。能力按最小可用集设计先覆盖对话、流式、工具调用、JSON模式这四个高频能力其他特殊能力走扩展字段。参数范围做归一比如有的厂商温度支持0到2有的只支持0到1统一层直接约束为0到1适配器内部做缩放。这样看起来“牺牲”了一些厂家特性但换来的是业务逻辑的高度一致。我在实践里最大的体会是协议统一得越彻底后面的路由和成本代码就越省力。2.2 供应商适配器的接口拆分统一协议定义好之后每个模型服务商对应一个适配器。适配器的核心接口非常收敛class ModelAdapter(Protocol): def get_model_info(self) - UnifiedModelInfo: ... def chat(self, request: UnifiedRequest) - UnifiedResponse: ... def stream_chat(self, request: UnifiedRequest) - Iterator[UnifiedEvent]: ...get_model_info返回模型元数据包括模型ID、支持哪些能力、上下文长度、计费单价等。chat走非流式stream_chat走流式。业务方永远只面向这两个方法编程具体HTTP调用、鉴权、SDK封装全部在适配器内部。每个模型的元信息长这样class UnifiedModelInfo(BaseModel): model_id: str supports_tools: bool False supports_json_mode: bool False supports_vision: bool False max_context_tokens: int 0 prompt_price_per_1k: float 0.0 completion_price_per_1k: float 0.0这里有个很容易忽略的细节模型元信息不是写死的常量而是每个适配器必须返回的真实值。因为后面路由要根据supports_tools、max_context_tokens、prompt_price_per_1k做筛选和排序。如果元信息不准确路由算出来的结果全是错的。2.3 模型能力差异的归一化处理统一协议不难真正花时间的是处理“大家都说自己支持工具调用但格式完全不一样”这类问题。比如工具调用某个服务商返回的是结构化的tool_calls数组另一个服务商要求把工具定义写成一段特殊格式的字符串还有一个服务商把工具调用结果嵌在Markdown代码块里。统一层的做法是定义自己的UnifiedToolCall结构class UnifiedToolCall(BaseModel): call_id: str function_name: str arguments: dict适配器负责把各家返回格式转换成这个结构反向时再把业务方的工具结果转换成厂商要求的格式。这样业务层永远不会看到厂商相关的字段。JSON mode也是重灾区。有的用response_format{type: json_object}有的叫structured_output有的干脆不支持只能靠提示词约束。归一化之后统一层只认一个response_formatjson的开关适配器内部实现“真支持就用参数不支持就用提示词拼一段强约束文本”。我当时的一个判断是能靠适配器抹平的差异就不要让业务方感知只有“支持还是不支持”这种硬能力差异才需要放到元信息里交给路由层决策。比如视觉请求只能路由到支持视觉的模型工具调用请求只能路由到支持工具的模型。3. 路由与调度按成本、延迟、质量、配额做动态决策3.1 四种路由策略的适用场景模型多了以后真正让业务跑得又稳又省的是路由策略。我沉淀下来最常用的四种如下表策略核心逻辑典型场景需要注意的问题固定主备优先打主模型失败才切备模型对稳定性要求极高的核心链路主模型故障切换时可能有秒级抖动权重比例按配置比例分配流量同时用多个模型分摊成本无法感知实时质量和可用性延迟优先从健康节点里选延迟最低的模型客服、实时助手等对时延敏感场景低成本慢模型容易被饿死成本优先估算成本后选最便宜的合格模型批处理、摘要、分类等海量低频请求需要先定义“合格”的底线这里最关键的是明确一点任何单一策略都不可能通吃。更合理的做法是“先过滤后打分”。过滤阶段用硬性条件把不满足的模型剔除比如上下文不够、不支持视觉、配额已耗尽打分阶段再用业务关心的维度排序比如成本权重0.4、延迟权重0.3、质量权重0.3。3.2 路由权重与自动降级的联动只做静态权重是不够的因为真实线上环境随时在变化。某个模型突然限流了如果还按原比例把流量打进去那批请求就会大量失败。我后来做了一个联动逻辑路由系统实时统计每个模型的滑动窗口错误率当错误率超过阈值时自动把该模型标记为“不健康”并把原本分配给它的流量按剩余模型的比例重新分配。这就是路由和容错的结合点。一个看起来简单的改动但效果非常明显。之前某个模型故障时业务方经常反馈“部分请求超时”做了动态降级之后故障期间的成功率能稳定维持在99%以上只是响应速度略有波动。联动逻辑还要考虑“恢复”不能一健康就立刻把流量全部切回来那会造成抖动。我采取的做法是“缓慢回升”恢复后的模型先接收一小部分流量观察错误率稳定后才逐步恢复原权重。3.3 落到代码的简易路由引擎我把路由引擎的骨架简化后基本长这样class ModelRouter: def __init__(self, registry: ModelRegistry, metrics: MetricsCollector): self.registry registry self.metrics metrics def route(self, req: UnifiedRequest) - ModelAdapter: candidates self.registry.filter_by_capabilities(req) candidates self.filter_healthy(candidates) scored [self.score_model(m, req) for m in candidates] scored.sort(keylambda x: x.score, reverseTrue) return scored[0].adapter打分函数里会把元信息和实时指标结合起来比如成本分数来自元信息里的价格字段延迟分数来自最近5分钟的P50延迟健康分来自滑动窗口错误率。分数权重通过配置文件调整改配置就能调整路由倾向不用动代码。这样我后面做实验就非常方便今天想让请求多走便宜模型改一下权重重启即可。4. 容错与降级重试、熔断、超时一个都不能少4.1 不同失败类型的处理优先级很多团队的容错就是“失败就重试三次”这在大模型API场景下会出大问题。大模型请求不像普通RPC那样无状态重试可能产生重复计费而且很多生成任务不是幂等的。我按错误类型把处理策略分成了几档4xx参数错误不重试。这是代码或配置问题重试只会浪费额度应该直接报错并告警。429限流可以先小退避重试一次但不宜反复重试。如果连续多次遇到429说明路由层应该把流量切走了。5xx服务端错误可以重试但前提是确认请求没有副作用。如果业务侧已经接收了用户输入重试前最好跟业务约定是否允许重复生成。连接超时和读超时必须设置阈值。我见过有请求挂在某个模型上整整90秒没返回用户早就跑了。4.2 实现了熔断器之后的真实效果统一接入层最适合做的事就是熔断。我给每个模型节点都配了一个熔断器状态机遵循经典的关闭、打开、半开三态。当某个模型的连续失败率超过阈值熔断器打开后续请求直接拒绝进入该模型快速失败经过冷却时间后进入半开状态放少量试探请求成功率达到要求再关闭。我最初犯过一个错只按服务商维度做熔断没有把模型节点拆开。结果有一次某个具体模型因为配额问题连续报错同厂商的其他模型是好的但整个服务商的流量都被切走了白白损失了可用的算力。后来改成“按模型节点熔断”每个模型的熔断状态独立维护问题就解决了。熔断器对用户体感的提升也很明显。以前某个模型挂了用户会一直转圈等超时现在熔断后立刻返回错误前端可以马上提示重新选择服务或者走兜底方案用户端的感知变成“稍后再试”而不是“服务卡死”。4.3 兜底方案设计兜底方案不是随便写一段“服务繁忙”就完事了。我在实践中把兜底分成三级模型级降级优先路由到同能力的备选模型比如强推理挂了切到中档模型。结果级缓存如果是同质化请求之前有高质量结果可以直接把缓存结果返回。这个对摘要、分类这类任务尤其有效做法是按文本向量相似度或哈希做缓存命中率能做到20%到30%。提示级兜底以上都不可用时返回预设的降级文案并记录一条告警。这样设计之后线上很少有用户真正看到“AI服务不可用”的裸奔状态绝大部分异常都在前两级被消化掉了。5. 上线后必须盯的指标与成本分摊5.1 Token计价口径天生不统一怎么拉齐说到成本第一道坎就是“各家对token的统计口径不一样”。有的按token计费有的按字符计费有的把缓存命中按折扣计费有的直接不区分有的token数在返回体里明明白白有的要自己去估算。如果按厂商返回的数字直接加总算成本最后对账一定对不上。我的做法是在统一层自己维护一套成本口径适配器返回统一响应时除了业务字段必须附带input_tokens和output_tokens。这个数字不一定要跟厂商完全一致但要保证系统内部所有模型用的是同一套统计规则。比如可以用一个通用分词器统一估算也可以直接采信厂商但必须明确记下“本数字来自厂商估算可能存在10%以内偏差”。然后成本就很简单了cost req_input_tokens / 1000 * model_info.prompt_price_per_1k resp_output_tokens / 1000 * model_info.completion_price_per_1k统一层会在每个请求日志里写清楚用了哪个模型、消耗了多少token、估算成本是多少。这样财务同事要对账时能直接把日志拉出来不用再问研发“这两个模型价格是怎么算的”。5.2 观测面板上的关键指标模型多了以后只看“整体成功率”完全不够。我上线后长期盯的是四个维度维度关键指标踩坑提示模型状态调用量、成功率、错误码分布错误码分布要区分服务端错误和限流延迟P50、P95、流式首Token延迟流式请求必须单独统计首Token耗时路由各策略命中比例、降级次数降级次数突增说明上游模型有波动成本每请求成本、日成本、场景成本按场景分摊别只看总体大盘这里最容易被忽视的是“流式首Token延迟”。非流式接口的延迟很好理解但流式场景下用户感知到的等待时间其实是首Token之前的时间。如果不单独统计你会发现延迟指标很漂亮但用户还是觉得慢因为你把整个流式拉满的时间也算进去了。5.3 成本预算控制在什么粒度比较合理预算控制我建议按“场景”切而不是按模型切。比如客服问答、内容生成、文档总结各给各的预算每个场景有独立的日预算和月预算。这样做有两个好处一是某个场景的流量突然爆炸时能快速定位到是哪个功能在烧钱二是预算限制可以精细控制该场景的请求如果预估成本超标直接降级到更便宜的模型。我在统一层实现了一个简单的预算预检每次路由前根据请求类型和模型价格估算成本如果该场景当日预算剩余不足就强制走低成本模型甚至走缓存。这套机制上线后整体模型成本下降了大约30%因为没有钱花在“不需要高质量模型”的请求上。6. 从这套架构里沉淀出的几条实战经验6.1 先跑通最小闭环再考虑完美抽象我见过不少团队一上来就画很大的架构图要做模型市场、要做自动调优、要做全链路追踪结果做了三个月还没接完第一个模型。我的建议是反着来先选两个差异最大的模型一个闭源强模型、一个开源低成本模型做一个最简单的适配器加网关把“代码不感知厂商”这个核心链路跑通。第一版哪怕只支持chat和stream_chat都行工具调用后面再补。因为协议统一这件事不做永远不知道自己的接口设计哪里有问题只有真实接了两个模型才会发现温度范围、错误码、流式格式这些差异比你想象得要多。最小闭环跑通后再逐步补充能力元信息、路由策略、预算控制。6.2 别忽略流式接口对统一层的冲击如果一开始只支持非流式后面补流式支持会是一场灾难。因为流式不是“多返回几个chunk”那么简单它牵扯到SSE连接管理、心跳、客户端断连取消、事件解析、工具调用增量拼接、最终计费统计等一大堆逻辑。等业务量大了再改兼容成本极高。我最早设计Protocol时就同时要求实现chat和stream_chat哪怕第一版只有少数模型支持流式。后来新接入的厂商几乎没有额外改动因为流式差异全部在适配器里被吸收掉了。这个决定后来被证明是最省时间的一个选择。6.3 模型版本切换要留审计记录模型API不只是模型本身在变提示词、参数、路由策略也经常调。以前排查问题时常碰到这种对话业务方说“我让模型做了个总结结果输出格式不对”研发说“是不是提示词改了”两边扯皮半天。后来我在统一层里给每个请求都加了版本号和切换记录写到日志里。这里说的版本号不是一个数字而是一套“链路快照”请求命中了哪条路由策略、最终选了哪个模型、用什么版本的提示词模板、适配器做了什么参数转换。只要有了这套审计数据线上任何一次异常输出都能回溯到具体的推理链路而不是猜。实际排查问题时这个能力比多写十个监控指标都管用。这套多模型混合调用架构做下来我最大的体会就是“统一层不是中转站而是业务和模型之间的契约”。契约定得清晰后面加模型、换模型、控成本都只是适配器层面的工作量契约定得含糊所有复杂性都会反向渗透到业务代码里越往后越难收拾。如果你正在处理多个大模型API的接入问题可以从一个统一协议和两个适配器开始先把链路跑通再一步步把路由、熔断、预算这些能力填进去。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表