ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AI网关设计实战:多模型统一接入与Token计量治理

2026/10/5 9:16:45 拓冰建站 浏览量
AI网关设计实战:多模型统一接入与Token计量治理 1. 多模型时代应用架构里为什么突然多了一层过去两年我做后端架构评审被问得最多的问题从“你们用哪个大模型”变成了“你们怎么管这么多大模型”。这个变化本身就说明了一件事单一模型打天下的阶段已经过去了。现在一个稍微像样的AI应用背后往往挂着三到五个模型——便宜的用来做意图识别和分类贵的用来做复杂推理还有一个专门处理长文本或者多模态输入。问题随之而来每个模型的接口协议不一样计费方式不一样Token计量口径不一样连返回格式都各有各的脾气。你当然可以让业务代码直接去调各家SDK但那样做的代价我踩过。去年一个项目里前端要加一个“切换模型”的开关结果后端改了六个文件测试回归了两天上线后还因为某个模型的超时策略没对齐导致雪崩。那次之后我就认定应用和模型之间必须有一个中间层。这个中间层行业里现在普遍叫它AI网关英文常写作LLM Gateway。AI网关到底是什么用一句话说它是所有大模型调用的统一入口。你的应用只跟它对话它负责把请求翻译成各家模型能听懂的格式再把结果统一翻译回来。它管的不只是转发还包括Token计量、密钥管理、限流熔断、缓存、日志审计、成本核算以及最近越来越热的MCP协议对接。你可以把它理解成公司前台所有访客都找前台前台知道该把谁领到哪个部门也顺手记了一笔访客台账。这篇文章适合谁看如果你正在做AI应用的后端架构或者你是一个独立开发者手里已经接了不止一个模型被密钥管理和账单搞得头大那这篇就是写给你的。我会从设计思路讲到实操落地把选型逻辑、核心参数、踩坑经验都摊开说。哪怕你现在只用一个模型看完也会明白为什么早点加这层中间层后面会省很多事。2. 拆解AI网关的核心设计思路2.1 为什么不是“加个代理”那么简单很多人第一反应是不就是个反向代理吗Nginx配一下不就行了。我一开始也这么想直到发现事情没那么简单。普通反向代理处理的是无状态、同构的HTTP请求而大模型调用是有状态、异构、且带成本的。举几个具体的差异点你就明白了。第一请求体结构差异巨大。OpenAI风格的接口用messages数组某些模型用prompt字符串多模态模型还要塞图片的base64或者URL。代理层如果只是透传业务代码就得自己拼装每种格式那中间层就白加了。第二响应是流式的。大模型普遍用SSEServer-Sent Events流式返回代理层要能正确处理分块传输还要在流式过程中做Token计数这比普通代理复杂得多。第三成本是实时产生的。每一次调用都在烧钱网关必须能精确计量每个请求消耗了多少Token并且能按应用、按用户、按模型维度归集否则月底对账就是一笔糊涂账。所以AI网关的设计目标不是“转发”而是“抽象”和“治理”。抽象是指把多模型的差异屏蔽掉对上提供统一接口治理是指在这个统一入口上做限流、鉴权、缓存、监控、计费。这两件事决定了它必须是一个有业务逻辑的应用层组件而不是一个纯网络层的代理。2.2 统一接口的抽象层次怎么定设计网关时第一个要拍板的问题是抽象到什么程度。我见过三种做法各有取舍。最浅的一种是协议转换层只做格式映射。你传OpenAI格式进来它转成目标模型的格式发出去回来再转回OpenAI格式。好处是实现简单业务代码改动小坏处是它假设所有模型能力对等但实际上不同模型的参数支持度差别很大比如有的支持temperature有的支持top_p有的两个都支持但默认值不同。中间一种是能力抽象层把模型能力抽象成“对话”“补全”“嵌入”“多模态理解”几类每类定义一套标准参数网关负责把标准参数映射到具体模型。这种做法的好处是业务代码面向能力编程换模型时不用改代码代价是网关要维护一张能力映射表新模型接入时需要配置。最深的一种是路由决策层网关不仅做转换还根据请求内容自动选择模型。比如简单问题走便宜模型复杂问题走贵模型或者根据用户等级走不同模型。这种做法最智能但也最复杂需要一套可靠的分类或评分机制否则容易把重要请求路由到能力不足的模型上。我的建议是从协议转换层起步逐步演进到能力抽象层路由决策层作为可选增强。一上来就做智能路由大概率会因为分类不准而翻车。先把统一接口跑通把计量和限流做扎实等业务稳定了再考虑路由优化。2.3 密钥与凭证的管理策略这是最容易被低估、但出事最要命的部分。我见过把API Key硬编码在前端的也见过所有应用共用一个Key的。前者等于把钱包扔在大街上后者一旦某个应用被刷爆全部业务一起挂。网关在这件事上的价值是凭证收敛。所有模型的Key只存在网关里应用侧只持有网关自己签发的凭证。这样带来三个好处一是Key不落地到业务代码泄露面大幅缩小二是可以按应用分配不同的配额和权限A应用只能用便宜模型B应用才能调贵模型三是Key轮换时只改网关一处业务无感。具体实现上网关内部要维护一张凭证表记录每个模型供应商的Key、Base URL、可用模型列表、配额上限。对外签发凭证时可以用JWT把应用ID、允许的模型范围、过期时间编进去。这里有个细节要注意JWT的过期时间不要太长我一般设2小时配合刷新机制。太长了泄露后风险窗口大太短了刷新频繁增加开销。另外网关到模型供应商的那一跳Key要用环境变量或密钥管理服务注入绝对不能写在配置文件里提交到代码仓库。2.4 Token计量为什么是网关的必修课Token是大模型世界的计价单位也是网关最核心的计量对象。但Token计量有个坑不同模型的分词器不一样同一段文字在不同模型下Token数可能差20%以上。如果网关只是简单转发就没法给出准确的成本预估。我的做法是在网关里内置各模型的分词器或者至少内置一个估算规则。对于精确计量可以在请求发出前用对应模型的分词器算一遍输入Token响应回来后算输出Token。对于流式响应要在每个chunk到达时累加。这里有个实操技巧流式响应的Token计数不要等全部结束再算而是边收边算这样即使连接中断也能记录已消耗的部分。计量的粒度也很重要。我一般按“应用模型用户”三个维度记录每次调用写一条日志包含输入Token、输出Token、耗时、是否命中缓存、是否失败。这些日志汇总起来既能做成本分摊也能做容量规划。比如你发现某个应用的输入Token特别大可能是提示词写得太啰嗦优化一下就能省钱。3. 核心功能模块与实操要点3.1 请求路由与模型选择网关收到请求后第一件事是决定发给哪个模型。最简单的策略是显式指定请求里带model字段网关按字段路由。这种适合业务明确知道要用哪个模型的场景。稍微复杂一点的是别名映射业务传model: fast网关根据配置把fast映射到当前性价比最高的模型。这样换模型时只改网关配置业务无感。再往上就是条件路由。我做过一个场景用户提问长度小于200 Token走小模型大于200走大模型。实现方式是在网关里加一个前置判断根据输入长度选择目标模型。这个逻辑要小心因为长度不等于难度短问题也可能很复杂。更稳妥的做法是结合意图分类但这需要额外的小模型调用会增加延迟和成本。路由模块的实操要点有几个。第一要有降级链。主模型超时或报错时自动切到备用模型而不是直接给用户报错。降级链的配置要支持优先级和超时阈值。第二要记录路由决策。每次请求走了哪个模型、为什么走这个模型都要记下来方便排查“为什么这次回答质量差”这类问题。第三路由规则要可热更新。模型价格和可用性变化很快规则写死在代码里就得频繁发版最好做成配置中心下发。3.2 限流、熔断与重试的配合限流和熔断是保证网关自身不被拖垮的关键。限流我一般做两层全局层按模型供应商的配额限防止超额被供应商封禁应用层按应用限防止某个应用占用过多资源。限流算法用令牌桶比较合适因为大模型调用本身耗时较长固定窗口容易在窗口边界产生突刺。熔断的触发条件要仔细设计。我见过只按错误率熔断的结果供应商返回大量429限流时没触发因为429不算“错误”。实际上429、超时、5xx都应该计入熔断统计。熔断后的处理也有讲究可以返回缓存结果可以降级到备用模型也可以直接返回友好提示。我倾向于降级到备用模型用户体验最好但前提是备用模型确实可用。重试要特别小心。大模型调用不是幂等的重试可能导致重复计费。我的原则是只对明确的网络错误和5xx重试且最多重试一次。429不要立即重试要等退避时间。流式请求一旦开始返回内容就不要重试否则用户会看到重复内容。这些规则都要在网关里写清楚不能交给业务代码各自处理。3.3 缓存策略省钱的隐形冠军缓存是网关里投入产出比最高的功能之一。大模型调用又贵又慢如果相同或相似的请求能命中缓存直接省下真金白银。但缓存大模型响应有几个特殊之处。第一缓存键怎么定。不能只用请求体的哈希因为同样的提示词在不同模型、不同参数下结果不同。我一般用“模型参数提示词”的组合做键参数里temperature为0时才缓存大于0的结果有随机性缓存意义不大。第二语义缓存。有些请求字面不同但意思一样比如“今天天气怎么样”和“今天天气如何”。这种可以用嵌入向量做相似度匹配但阈值要调好太松会返回不相关结果太紧命中率低。第三缓存过期时间。事实类问题可以缓存久一点时效性问题要短。我一般默认1小时可配置。这里有个坑流式响应缓存。流式响应是一段段吐出来的缓存时要完整收集后再存命中时再模拟流式吐出。实现上要注意chunk的边界不能把两个chunk的内容粘在一起导致格式错乱。3.4 MCP协议对接的现状与实操MCPModel Context Protocol是最近热度很高的一个方向它试图标准化模型与外部工具、数据源的交互方式。简单说MCP让模型能“调用工具”比如查数据库、读文件、调API。网关在这个环节的角色是工具调用的统一代理。为什么网关要管MCP因为工具调用同样涉及鉴权、限流、审计。如果每个应用自己实现工具调用安全边界就散了。网关可以统一管理工具注册、权限校验、调用日志。实操上网关需要维护一个工具注册表记录每个工具的MCP服务地址、所需权限、超时设置。模型返回工具调用请求时网关先校验该应用是否有权限调这个工具再转发到对应的MCP服务拿到结果后回传给模型。目前MCP生态还在早期各家实现有差异。我的建议是先支持最基础的stdio和HTTP两种传输方式把工具注册和权限做扎实等协议稳定了再扩展。不要一上来就追求全功能容易陷入兼容性泥潭。4. 从零搭建一个最小可用网关4.1 技术选型与目录结构假设你现在要动手搭一个我推荐的技术栈是Python FastAPI因为异步支持好生态里大模型相关的库也全。如果你团队是Java背景Spring Boot WebFlux也可以但异步流式处理写起来会啰嗦一些。数据库用PostgreSQL存配置和日志Redis做缓存和限流计数。目录结构我一般这样组织gateway/ app/ main.py # 入口注册路由 config.py # 配置加载 models/ # 各模型适配器 base.py # 抽象基类 openai_adapter.py claude_adapter.py core/ router.py # 路由决策 limiter.py # 限流 cache.py # 缓存 tokenizer.py # Token计量 api/ chat.py # 对话接口 admin.py # 管理接口 db/ models.py # ORM模型 session.py tests/ requirements.txt这个结构的关键是适配器模式。每个模型一个适配器继承同一个基类实现chat和stream_chat两个方法。新增模型时只加一个文件不改核心逻辑。4.2 统一请求与响应格式定义统一格式我建议直接对齐OpenAI的Chat Completions格式因为它是事实标准大部分客户端库都支持。请求体核心字段{ model: fast, messages: [ {role: system, content: 你是一个助手}, {role: user, content: 你好} ], temperature: 0.7, stream: false, max_tokens: 1024 }响应体也对齐OpenAI格式包含choices、usage等字段。usage里要有prompt_tokens、completion_tokens、total_tokens这是计费依据。对于流式响应每个chunk的格式也要对齐最后一个chunk带上usage。这里有个细节不同模型的停止原因finish_reason要统一。OpenAI用stop、length、tool_calls有的模型用别的词。网关要做映射否则业务代码要处理多种枚举值。4.3 适配器实现的关键代码以OpenAI适配器为例核心是处理流式和非流式两种情况。非流式比较简单发请求、拿响应、转换格式、返回。流式要复杂一些我用伪代码说明关键逻辑async def stream_chat(self, request): async with httpx.AsyncClient() as client: async with client.stream(POST, url, jsonpayload, headersheaders) as resp: async for line in resp.aiter_lines(): if line.startswith(data: ): data line[6:] if data [DONE]: break chunk json.loads(data) # 累加Token计数 self.token_counter.add(chunk) # 转换格式后yield yield self.convert_chunk(chunk)关键点有三个一是用aiter_lines逐行读不要一次性读全部二是遇到[DONE]要正确终止三是每个chunk都要过一遍Token计数。Token计数在流式场景下不能依赖响应里的usage字段因为很多模型流式返回不带usage得自己用分词器算。4.4 限流与计量的落地配置限流用Redis的令牌桶实现核心是INCR加过期时间。伪代码def check_rate_limit(app_id, limit, window): key frate:{app_id}:{int(time.time() // window)} current redis.incr(key) if current 1: redis.expire(key, window) return current limit这个简单实现有个问题窗口边界突刺。比如限制每分钟100次第59秒来了100次第61秒又来100次实际两秒内200次。要精确控制得用滑动窗口或者漏桶但实现复杂。我的经验是对于大模型调用突刺影响不大因为单次调用耗时长天然平滑。所以简单实现够用别过度设计。计量落库我建议异步写不要阻塞请求。用消息队列或者后台任务把计量日志批量写入避免每次调用都同步写数据库。日志表按天分区方便清理和归档。5. 常见问题与排查技巧实录5.1 流式响应中断与Token计数不准这是最高频的问题。表现是用户看到一半内容停了或者账单上的Token数对不上。原因通常有三个一是网关到模型的连接超时设置太短模型还在生成就被掐断了二是网关到客户端的连接被中间层比如Nginx缓冲了导致流式变成一次性返回三是Token计数在异常路径上没走到。排查思路先看网关日志里这次请求的耗时和状态码如果网关侧正常但客户端断了多半是中间层缓冲问题。Nginx要配proxy_buffering off和proxy_cache off。如果是Token计数问题检查异常处理分支里有没有调用计数逻辑我一般把计数放在finally块里确保无论成功失败都记录。5.2 模型返回格式不一致导致的解析失败不同模型对同一语义的返回格式可能不同。比如工具调用OpenAI放在tool_calls字段有的模型放在function_call还有的放在content里用特定标记。网关如果只按一种格式解析遇到别的就崩。解决办法是在适配器里做格式归一化把所有变体都转成统一格式。同时加一层容错解析遇到不认识的字段不要直接抛异常而是记录原始响应并返回一个降级结果。我踩过的坑是某模型升级后改了返回结构网关没做容错导致整个应用不可用。后来加了原始响应日志排查起来快很多。5.3 密钥泄露与权限越界密钥泄露的常见路径日志里打印了完整请求头、错误信息里带了Key、配置文件提交到了仓库。防范措施日志脱敏Key只显示前4位和后4位错误信息统一处理不要把底层异常直接透出配置文件用.gitignore排除用环境变量注入。权限越界是指A应用调用了它不该调的模型。防范靠网关的凭证校验JWT里带上允许的模型列表路由前先校验。这里有个细节模型别名也要校验不能只校验实际模型名否则业务传个别名绕过检查。5.4 常见问题速查表问题现象可能原因排查方向解决措施流式响应一次性返回中间层缓冲检查Nginx配置关闭proxy_bufferingToken数对不上异常路径未计数检查finally块计数逻辑放finally模型切换后报错格式不兼容对比原始响应适配器做归一化429频繁触发限流阈值过低看配额配置调整令牌桶参数缓存命中率低缓存键太严格看键的构成引入语义缓存密钥疑似泄露日志或仓库搜代码和日志脱敏环境变量5.5 几个我踩过的坑第一个坑超时设置一刀切。不同模型响应速度差别很大小模型可能1秒返回大模型要30秒。如果网关统一设10秒超时大模型全挂。正确做法是按模型配置超时或者用动态超时。第二个坑重试导致重复计费。有次网络抖动网关重试了一次结果供应商那边两次都计费了。后来改成只对连接建立失败重试一旦请求发出去了就不重试。第三个坑缓存了带随机性的响应。temperature大于0时结果有随机性缓存后用户每次拿到一样的答案体验很怪。后来严格限制只有temperature0才缓存。第四个坑MCP工具调用没做超时。某个工具服务挂了网关一直等把线程池占满。后来给每个工具调用加了独立超时超时后返回错误让模型自己处理。6. 网关的扩展方向与个人体会网关搭起来之后能扩展的方向不少。往深了做可以加成本预算控制给每个应用设月度预算超了自动降级到便宜模型。往宽了做可以加多租户支持让不同团队各自管理自己的模型和配额。往智能了做可以加A/B测试同一请求按比例分流到不同模型对比效果和成本。我个人在实际操作中的体会是网关的价值不在于技术多复杂而在于把散落各处的治理逻辑收拢到一处。没网关的时候限流在业务代码里密钥在配置文件里计量在日志里出了问题到处找。有了网关这些都在一个地方改一处全生效。这个收敛带来的维护效率提升比任何单个功能都值钱。最后分享一个小技巧网关上线初期先跑影子模式。所有请求照常走原路径同时复制一份到网关对比两边结果和耗时。跑一周没问题再切流量。这样能把风险降到最低也能发现适配器里的隐藏bug。这个做法我在三个项目里用过每次都提前发现了问题值得一试。