ARTICLE DETAIL

资讯详情

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

AI网关实战:多模型统一管理与协议适配架构设计

AI网关实战:多模型统一管理与协议适配架构设计 1. 多模型接入的乱局为什么统一管理成了刚需我最早接触多模型接入是在两年前当时团队同时用了三家厂商的模型服务一家做通用对话一家做代码补全还有一家专门跑文档理解。最开始大家各写各的调用代码每个项目里散落着不同的 SDK、不同的鉴权方式、不同的返回格式。三个月后问题集中爆发——某家厂商调整了接口字段三个业务线同时挂掉财务那边要统计各模型的调用成本我们花了整整两天才把账单对齐更离谱的是有个同事把测试环境的密钥提交到了公开仓库虽然及时发现没造成损失但那次之后我就下定决心要把所有模型调用收口到一层统一的管理层。这就是AI网关要解决的核心问题。所谓统一管理多家大模型 API本质上是在业务代码和各家模型服务之间插入一个中间层由这个中间层负责鉴权、路由、限流、计费、日志和格式转换。业务侧只认一套接口底层换模型、加模型、下线模型业务代码一行都不用改。这件事听起来简单做起来坑不少。我见过太多团队一开始觉得不就是包一层 HTTP 转发吗结果做到一半发现要考虑流式响应、要考虑不同厂商的 token 计算方式差异、要考虑超时重试策略、要考虑密钥的轮换和隔离。所以我打算把这两年踩过的坑和最终沉淀下来的方案完整讲一遍从架构设计到落地实现再到日常运维中的排查技巧尽量让准备做这件事的团队少走弯路。这篇文章适合几类人看正在被多模型接入搞得焦头烂额的后端工程师、需要为团队搭建 AI 基础设施的架构师、以及想搞清楚AI网关到底值不值得做的技术负责人。如果你只是个人项目调一两个模型可能用不上这么重的方案但如果你所在的组织有多个业务线、多个模型供应商、多个环境那这套东西迟早要建。2. 整体架构设计统一管理层到底该管什么2.1 先想清楚要解决哪些问题在动手写代码之前我建议先把要解决的问题列清楚。根据我的经验多模型接入的痛点通常集中在这么几个方面鉴权分散。每个厂商一套密钥体系有的用 Bearer Token有的用 AK/SK 签名有的还要额外的 AppID。密钥散落在各个项目的配置文件、环境变量甚至代码里轮换一次要改十几个地方。接口异构。同样是对话补全OpenAI 风格的接口和国内厂商的接口在请求体结构、字段命名、流式返回格式上都有差异。业务代码如果直接对接换一个模型就要改一遍解析逻辑。成本失控。没有统一的计量谁在调、调了多少、花了多少钱全靠各家后台分别看汇总起来极其痛苦。稳定性无保障。某家服务挂了业务直接报错没有降级、没有重试、没有备用模型切换。合规与审计。哪些数据发给了哪个模型、有没有敏感信息外泄、调用日志能不能追溯这些在统一管理层缺失的情况下基本无从谈起。把这五个问题对应到架构上就得到了统一管理层需要具备的核心能力统一鉴权、协议适配、计量计费、路由容灾、日志审计。这五块是骨架其他的都是锦上添花。2.2 分层架构怎么切我最终采用的架构分三层从下往上依次是接入层负责对外暴露统一接口处理业务侧的鉴权我们自己的 API Key不是厂商的做初步的参数校验和限流。这一层用 Nginx 或者专门的网关组件都行我们用的是自研的轻量网关因为需要做一些定制化的限流策略。核心层是整个系统的大脑包含路由引擎、协议适配器、计量模块和缓存模块。路由引擎根据请求的模型标识、业务标签、当前各厂商的健康状态决定把请求发到哪里协议适配器负责把统一格式的请求翻译成各家厂商的格式再把响应翻译回来计量模块记录每次调用的 token 数和预估成本缓存模块处理那些可以复用的响应。厂商层是对各家 API 的封装每个厂商一个适配器实现统一的接口。新增一个厂商只需要写一个适配器注册到路由引擎即可不影响其他部分。这个分层的好处是职责清晰。接入层的变化比如换网关组件不影响核心逻辑核心层加一个新策略不影响厂商适配器厂商层加一个新模型不影响业务侧。2.3 为什么不用现成的开源方案你可能会问市面上不是有现成的 AI 网关开源项目吗为什么不直接用我试过几个说下我的判断。现成方案的优势是开箱即用基础的转发、鉴权、限流都有。但问题在于计量计费这块几乎没有能直接满足企业需求的。不同厂商的 token 计算方式差异很大有的按字符算有的按 token 算有的输入输出分开计价有的有阶梯价格。开源方案通常只做一个粗略的统计真要跟财务对账根本对不上。另外企业内部的鉴权体系往往和现有的账号系统、权限系统打通开源方案的鉴权模型不一定能直接套用。所以我的建议是如果只是小团队内部用开源方案够用如果是要作为企业级基础设施核心的计量和鉴权部分最好自研转发和适配部分可以参考开源实现。3. 核心细节拆解鉴权、路由与协议适配怎么做3.1 统一鉴权体系的设计鉴权这块我踩过最大的坑是一开始把业务侧鉴权和厂商侧鉴权混在一起做。后来想明白了这是两个完全独立的问题。业务侧鉴权解决的是谁在调用我们的网关。我们采用的是 API Key 签名的方式每个业务线分配一个 Key请求时带上 Key 和时间戳网关用 HMAC 验签。Key 本身有权限范围比如 A 业务线只能调对话模型B 业务线可以调所有模型。这样即使某个 Key 泄露影响范围也是可控的。厂商侧鉴权解决的是网关怎么向厂商证明身份。这部分对业务侧完全透明业务侧不需要知道底层用的是哪家厂商的密钥。厂商密钥统一存在配置中心支持热更新轮换时不需要重启服务。这里有个细节值得说厂商密钥的隔离。我们给每个环境开发、测试、生产分配独立的厂商密钥而不是共用一套。原因是开发环境经常有调试性质的调用如果和生产共用密钥一旦开发环境出问题比如死循环调用会直接影响生产环境的配额。独立密钥虽然管理成本高一点但出问题时能快速定位和止损。提示厂商密钥千万不要写进代码或提交到仓库。我们用配置中心加环境变量双重保障配置中心里的密钥是加密存储的服务启动时解密加载到内存。3.2 路由引擎的策略设计路由引擎要回答一个核心问题这个请求应该发给哪家厂商的哪个模型。最基础的策略是按模型名直连业务侧指定model: gpt-4网关就发给对应的厂商。但实际生产环境往往需要更复杂的策略按业务标签路由。业务侧不指定具体模型只指定用途比如purpose: code-completion网关根据配置决定用哪家模型。这样业务侧和具体模型解耦我们换模型时业务侧无感知。按成本路由。同样的任务优先用便宜的模型只有在便宜模型失败或质量不达标时才升级到贵的模型。这个策略在批量任务场景下能省不少钱。按健康状态路由。网关持续探测各厂商的可用性某家连续失败就临时摘除请求自动切到备用厂商。这里要注意熔断的粒度不能一家厂商挂了就把所有请求都切走因为不同模型可能部署在不同的集群上要按模型维度做熔断。按配额路由。每家厂商都有调用配额网关要实时跟踪剩余配额快用完时提前切换到备用厂商避免请求打到配额上限被拒绝。这几种策略可以组合使用我们最终实现的是一个策略链先按业务标签筛选候选模型再按健康状态过滤再按配额过滤最后按成本排序选最优的。3.3 协议适配的关键难点协议适配看起来是纯体力活但有几个难点必须处理好。流式响应的格式差异。OpenAI 的流式返回是 SSE 格式每个 chunk 是data: {...}最后以data: [DONE]结束。国内某厂商的流式返回是分行的 JSON字段名也不一样。适配器要做的不仅是字段映射还要处理结束标志、错误事件的格式差异。我建议在适配器里统一转换成一种内部流式格式业务侧只认这一种。Token 计算的差异。这是最容易被低估的部分。不同厂商对 token 的定义不同同一个文本在不同厂商那里算出来的 token 数可能差 20% 以上。如果要做精确的计量计费必须为每个厂商实现对应的 tokenizer。我们用的是各家开源的 tokenizer 库对于没有开源 tokenizer 的厂商用近似算法并在计量时标注估算。错误码的映射。每家厂商的错误码体系都不一样有的用 HTTP 状态码有的在响应体里放业务错误码。适配器要把这些统一映射成一套内部错误码业务侧根据内部错误码做处理。比如配额超限这个语义不同厂商可能有五六种不同的表达统一映射后业务侧只需要判断一种。参数兼容性。不同厂商支持的参数集不同比如有的支持temperature有的叫top_p有的两个都支持但默认值不同。适配器要做参数的白名单过滤和默认值填充避免把不支持的参数透传给厂商导致报错。3.4 计量与计费的实现细节计量这块我单独拿出来讲因为它直接关系到钱做错了财务会找你麻烦。每次调用要记录的信息包括请求 ID、业务线标识、厂商、模型、输入 token 数、输出 token 数、调用时间、耗时、是否成功。这些信息异步写入日志系统同时累加到实时统计里。成本计算要维护一张价格表每个厂商每个模型的价格可能不同而且会变。价格表要支持版本管理因为调价后历史账单不能受影响。我们用的是调用时快照价格的方式每次调用记录当时的价格这样即使后续调价历史账单也是准确的。对账是每个月最痛苦的事。厂商给的账单和我们自己的统计总会有差异差异来源通常是厂商的计费粒度和我们不同比如按 1000 token 为单位取整、失败请求是否计费、缓存命中是否计费。我的经验是不要追求 100% 对齐追求差异可解释。把差异控制在 1% 以内并且能说清楚差异来源财务那边就能接受。4. 实操落地从零搭建一套统一管理层4.1 技术选型与部署形态技术栈方面我们用的是 Go 做网关核心原因是并发性能好、部署简单单二进制、生态里有成熟的 HTTP 和 SSE 处理库。如果你团队更熟悉 Java 或 Python用这些也完全可以核心逻辑不依赖特定语言。部署形态上我们采用的是无状态服务 配置中心的模式。网关本身不存任何状态所有配置厂商密钥、路由策略、价格表都从配置中心拉取支持热更新。这样网关可以水平扩展加机器就能扛更高并发。数据库方面调用日志写入时序数据库我们用的是 ClickHouse因为日志量大且主要是按时间范围查询和聚合。配置和价格表存在关系型数据库里量小但要求强一致。4.2 核心代码结构网关的核心接口设计成这样type Provider interface { Name() string Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error) ChatStream(ctx context.Context, req *ChatRequest) (StreamReader, error) CountTokens(text string) int EstimateCost(inputTokens, outputTokens int) float64 }每个厂商实现这个接口。路由引擎持有所有 Provider 的注册表根据策略选择 Provider 并调用。请求处理的主流程func (g *Gateway) HandleChat(w http.ResponseWriter, r *http.Request) { // 1. 业务侧鉴权 biz, err : g.auth.Verify(r) if err ! nil { writeError(w, ErrUnauthorized) return } // 2. 解析统一格式请求 req, err : parseChatRequest(r) if err ! nil { writeError(w, ErrBadRequest) return } // 3. 路由选择 provider, err : g.router.Select(biz, req) if err ! nil { writeError(w, ErrNoAvailableProvider) return } // 4. 调用厂商 resp, err : provider.Chat(r.Context(), req) if err ! nil { g.metrics.RecordFailure(provider.Name(), err) // 触发重试或降级 resp, err g.fallback(r.Context(), biz, req, provider) } // 5. 计量 g.meter.Record(biz, provider.Name(), req, resp) // 6. 返回统一格式响应 writeResponse(w, resp) }这个结构看起来简单但每一步都有细节。比如第 4 步的重试不能无脑重试要区分错误类型网络超时可以重试参数错误重试也没用配额超限要换厂商而不是重试同一家。4.3 配置中心的数据结构配置中心里存的核心配置有这么几块厂商配置厂商名、接口地址、密钥引用、支持的模型列表、超时设置。模型配置模型名、所属厂商、价格输入/输出分开、tokenizer 类型、最大上下文长度。路由策略业务标签到模型列表的映射、优先级、降级链。业务配置业务线标识、API Key 哈希、权限范围、配额限制。这些配置的变更要能实时生效不能重启服务。我们用的是配置中心的长轮询机制配置变更后网关在秒级内感知到。4.4 灰度与回滚机制新接入一个厂商或者调整路由策略时一定要有灰度机制。我们的做法是新配置先只对内部测试业务线生效观察一段时间通常是一天的调用成功率、延迟、成本确认没问题后再逐步放量到其他业务线。回滚要能做到一键。所有配置变更都记录版本出问题时回滚到上一个版本即可。这里有个经验配置变更和代码发布要分开。配置变更通过配置中心代码发布通过 CI/CD两者的回滚机制独立避免耦合。5. 常见问题与排查技巧实录5.1 典型问题速查表问题现象可能原因排查方向解决方法某厂商调用全部超时厂商服务故障或网络问题检查厂商状态页、网关到厂商的网络连通性触发熔断切到备用厂商计量数据与厂商账单差异大tokenizer 不一致或计费粒度不同对比同一批请求的 token 数校准 tokenizer记录差异原因流式响应中断网关超时设置过短或厂商侧断流检查网关超时配置、厂商侧日志调整超时增加心跳保活密钥轮换后部分请求失败配置未完全生效或缓存未刷新检查配置中心推送状态、网关缓存强制刷新缓存确认所有实例已更新某业务线配额异常消耗业务侧死循环调用或密钥泄露查看该业务线的调用日志和来源 IP临时禁用该 Key排查业务侧代码响应格式解析错误厂商接口变更或适配器 bug抓取原始响应对比适配器逻辑更新适配器增加格式校验5.2 几个我踩过的坑坑一忽略厂商的并发限制。有家厂商的 API 有并发上限超过就直接拒绝。我们一开始没做并发控制高峰期大量请求被拒。后来在网关层加了针对每个厂商的信号量超过并发上限的请求排队等待问题解决。坑二流式响应的超时设置。流式响应如果按普通请求的超时来设置长文本生成很容易超时中断。我们的做法是流式请求设置一个较长的总超时比如 5 分钟同时设置一个 chunk 间隔超时比如 30 秒没有新 chunk 就认为断流两个超时配合使用。坑三错误重试导致的重复计费。早期我们的重试逻辑没有区分错误类型网络超时后重试结果厂商那边其实已经处理了导致重复计费。后来改成只有明确的连接失败才重试超时的情况先查询厂商的请求状态再决定是否重试。坑四配置热更新时的竞态。配置更新是异步的更新过程中如果有请求进来可能读到一半新配置一半旧配置。我们的解决方法是配置整体替换而不是逐字段更新用原子指针切换保证请求读到的配置是一致的。5.3 监控与告警怎么配监控指标分几个层次业务层各业务线的调用量、成功率、平均延迟、成本。这些是给业务方看的。厂商层各厂商的调用量、成功率、延迟分布、配额使用率。这些是给运维看的。系统层网关自身的 QPS、CPU、内存、GC 情况。这些是给 SRE 看的。告警规则我建议设置这几条某厂商成功率低于 95% 持续 5 分钟、某业务线成本日环比增长超过 50%、网关 P99 延迟超过 3 秒、配额使用率超过 80%。告警要分级P0 的打电话P1 的发消息P2 的记工单。提示告警阈值不要设得太敏感否则天天被误报骚扰最后大家都不看告警了。我见过一个团队把成功率阈值设成 99.9%结果每天几十条告警最后整个告警系统形同虚设。5.4 安全相关的注意事项密钥管理前面说过厂商密钥存配置中心加密存储业务侧密钥用哈希存储网关不需要知道明文。密钥要有有效期定期轮换。输入输出审计所有请求和响应都要记录但要注意脱敏。用户的敏感信息手机号、身份证号等在记录日志前要脱敏处理。我们用的是正则匹配加自定义规则的方式做脱敏。权限最小化每个业务线的 Key 只授予必要的模型权限不要图省事给全权限。这样即使某个 Key 泄露影响范围也可控。防重放攻击业务侧请求带时间戳和随机数网关校验时间戳在有效期内比如 5 分钟且随机数未被使用过。这样即使请求被截获也无法重放。6. 后续扩展方向与个人体会这套系统上线一年多中间迭代了十几个版本目前支撑着公司内部七八个业务线的模型调用日均调用量在百万级别。回过头看有几个方向是后续可以继续做的。智能路由。目前的路由策略还是基于规则的后续可以引入基于质量反馈的动态路由——根据历史调用的质量评分自动调整路由权重把更多流量导向质量更好的模型。成本优化。可以做一个成本预测模型根据历史调用模式预测未来一段时间的成本提前预警。还可以做自动的模型降级——当某个月成本接近预算时自动把部分非关键业务切到更便宜的模型。多模态支持。目前主要处理文本后续图片、音频、视频的调用也可以纳入统一管理。多模态的计量方式更复杂按图片张数、按音频时长等需要扩展计量模块。私有化模型的接入。公司内部也在部署一些开源模型这些模型的调用方式和云厂商不同但同样可以纳入统一管理层。好处是业务侧完全无感知私有模型和云模型可以互为备份。我个人在实际操作中的体会是统一管理层这件事早做比晚做好。一开始可能只有一两个模型觉得没必要但随着业务发展模型数量一定会增加到时候再重构成本更高。而且这套东西一旦建起来后面加模型、换模型、做成本优化都会变得非常轻松。前期投入大概两三个人月后面省下的维护成本和沟通成本远超这个投入。最后分享一个小技巧给每个厂商的适配器写一套完整的单元测试和集成测试。厂商接口变更时测试会第一时间告诉你哪里不兼容。我们有一次某厂商悄悄改了流式返回的结束标志就是因为有集成测试才发现否则业务侧要等到用户反馈才知道出问题了。测试用例要覆盖正常流程、异常流程、边界情况空响应、超长响应、特殊字符等这套测试是网关稳定性的基石。
返回列表