RAG系统接入AI网关实战:解决多模型调用稳定性问题
去年下半年我接手团队内部的知识库问答系统时第一眼看到的是这样一幅画面系统跑了大半年技术栈是LangChain加自建的向量库模型调用直连各家供应商的官方接口。表面上看一切正常但下午访问高峰一来问题就陆续冒出来——有用户反馈知识库突然变笨了运维日志里全是限流报错开发同学为了查一次回答变慢的原因要在三四个供应商控制台之间来回切换。真正让我下定决心改造的是一次线上事故某家头部大模型供应商的服务宕机了将近二十分钟整个知识库问答直接瘫痪所有请求都堆积在重试队列里。那时候我才意识到RAG链路比普通Web服务更依赖模型调用层而这一层一直处于裸奔状态。后来我在所有模型调用前面加了一层AI网关——MAI Gateway把路由、缓存、重试、故障转移全部收口到网关层问题才算真正被改彻底。这篇博文就围绕这次落地来写为什么RAG场景尤其需要网关MAI Gateway的接入架构怎么设计代码改造怎么做到最小侵入以及跑了一周之后拿到的真实延迟、命中率、成本数据。如果你正在做RAG知识库或者被多模型调用搞得焦头烂额这篇经验应该对你有参考价值。1. RAG链路天然是多模型调用的重灾区先复习一下一条RAG请求背后到底发生了几次模型调用这是理解后续所有问题的基础。文档入库时切块切出来的每一段文本都要过一遍embedding模型做向量化。我这边一个批次入库的文档可能有几十份PDF切出来的块数以万计这意味着embedding接口要被高并发调用很久。文档问答时用户的query也要先转成向量才能去向量库做相似度检索检索到上下文之后拼好prompt再调用一次大模型生成如果还做了重排rerank中间又会多一次模型调用。所以一个用户看起来平淡无奇的问答背后至少要打三四个模型接口。这和普通API服务的单点依赖完全不同每个环节都是不同供应商、不同协议、不同限流策略。没有网关的情况下我遇到的主要问题可以归纳成四类。第一密钥和配置散落。每个供应商的API Key散落在不同配置中心和代码仓库里而且开发、测试、生产三套环境的key还不一样。有一次新同事入职光是把这些key和环境变量理清楚就花了半天。第二限流没有兜底。embedding这种高频调用很容易触发供应商的rate limit一旦触发业务代码里如果没有完善的退避重试用户端感知就是知识库坏掉了。第三供应商锁定。代码里到处是某个供应商SDK特有的调用方式今天想换一家模型改动面非常大。第四故障没有转移。某一家供应商不可用时整个系统跟着不可用哪怕其它供应商的模型完全能顶上。这四个问题本质上是同一个问题模型调用层缺少一个统一代理。AI网关恰好就是干这个的。它不是RAG流程里的检索或排序环节而是给所有模型调用装了一个总机让业务代码只需要面对一个入口。2. MAI Gateway核心能力拆解它到底解决了什么MAI Gateway是一款基于OpenAI兼容协议设计的AI网关核心思路是把所有上游模型供应商抽象成统一的provider对外暴露一个标准入口路由、缓存、重试、限流、可观测都在这一层完成。下面是我在实际落地中真正用上的核心能力。2.1 OpenAI兼容协议的统一抽象MAI Gateway对外提供的是标准OpenAI协议接口也就是/v1/chat/completions和/v1/embeddings。这意味着你手上任何支持OpenAI协议的SDK只要把base_url指向网关地址就能无缝接入业务代码里不用引入各家SDK。这一点非常重要。我这边生成模型用的是主流大模型供应商embedding又来自另一家重排模型是第三家。如果没有统一协议RAG代码里至少要维护三套SDK调用方式。接网关之后三套调用统一收敛成一套OpenAI风格调用。2.2 多Provider路由与故障转移MAI Gateway允许你为同一个逻辑模型定义多个provider并为每条路由配置优先级和故障转移策略。比如embedding请求主用A供应商当A供应商返回5xx或触发限流时网关自动把请求转发到B供应商整个过程对上层透明。RAG场景下embedding的高频特性让限流几乎每天都会出现故障转移不是可选项是刚需。以前A供应商限流我只能在业务代码里写退避重试眼睁睁看着延迟飙升。现在网关层直接把流量切到备用通道用户几乎无感知。2.3 请求级缓存MAI Gateway支持对请求做响应缓存命中后直接返回结果不再打到上游。对于embedding这类高频且结果确定的接口缓存收益非常明显对于生成类接口相同问题的相似请求也能命中缓存。我一开始对缓存是有顾虑的担心生成类模型的结果被缓存会牺牲多样性。实际操作下来发现知识库问答中大量用户问题高度重复比如内部政策类问题就那几十个问法缓存反而让响应变快、成本下降。关键在于缓存key的设计要合理这点后面踩坑部分细说。2.4 可观测性和链路追踪网关层可以导出Prometheus指标也支持把每次调用的模型、供应商、耗时、token数记录到日志里。以前排查一次回答变慢要翻三四个控制台现在在网关的一次日志里就能看到完整链路信息——用了哪个模型、走了哪个供应商、哪个环节最慢。为了让你对能力边界有个清晰认识我用表格整理一下MAI Gateway在RAG场景里能做什么和不能做什么。能力解决的问题RAG场景中的价值OpenAI统一协议多SDK并存业务代码只维护一套调用方式多Provider路由供应商锁定灵活切换模型故障转移单点故障供应商宕机时自动切换请求缓存重复请求穿透上游降低embedding/问答延迟与成本可观测性排查困难一次日志看到完整调用链路需要强调网关不解决检索质量问题。向量库召回不准、重排效果不好、chunk切得有问题这些是RAG引擎本身的事网关管不了。但网关能把调用层的稳定性兜住让RAG系统的下限显著提高。3. 我落地时的MAI Gateway接入架构设计这一步是整个落地最关键的环节。如果架构设计不到位网关不但解决不了问题还会成为新的瓶颈。3.1 网关卡在所有模型调用的前方我的整体架构是这样设计的所有RAG组件——包括文档入库脚本、检索服务、问答服务——都不再直连供应商而是统一请求MAI Gateway。网关部署在Kubernetes集群内部通过Service暴露一个内部域名给业务调用。业务服务 - MAI Gateway - 各家模型供应商网关不在业务链路的旁边而是横插在所有模型调用前面。这样做的好处是无论哪个RAG组件需要调用模型都必须经过统一入口策略不会被绕过。入库脚本的embedding请求、问答服务的query embedding、生成请求、重排请求全部走网关。3.2 用三条路由覆盖完整RAG链路我在MAI Gateway里配置了三条核心路由分别对应RAG链路中的三个模型调用点。第一条是embedding路由对应/v1/embeddings端点。主provider配置为线上效果更好的商业embedding模型备用provider配置为成本更低的同类模型。当主用供应商限流或不可用时自动切换到备用模型。这里有个细节值得注意备用embedding模型的向量维度如果不同检索阶段会出大问题。所以我在网关层做了一个向量维度校验策略发现备用模型返回的维度与主模型不一致时直接告警。第二条是chat路由对应/v1/chat/completions端点。生成模型我配置了两个provider一个偏重质量一个偏重速度和成本。正常流量走质量型网关支持按请求头或请求体标记分流把部分内部测试流量引导到速度型模型上跑对比评测。第三条是rerank路由我把它映射到网关的/v1/rerank自定义端点。重排模型单独走一条路由好处是重排请求有自己的重试、超时和限流参数不会和生成模型互相挤占。3.3 环境隔离和密钥管理开发环境、测试环境、生产环境各部署一套MAI Gateway实例配置模板统一通过环境变量覆盖供应商key和模型版本。业务侧不再持有任何供应商密钥全部密钥集中在网关侧管理。这个改动对安全审计帮助很大之前密钥散落在代码仓库的问题彻底解决了。网关实例之间不共享缓存因为开发和测试环境如果共用同一份缓存会很影响联调。生产环境单独部署后我还专门确认了网关节点与模型供应商之间的网络连通性避免因为出口IP变化触发供应商的风控策略。4. 从直连供应商改成网关代理代码改造怎么做改造的侵入性比我预想的小很多因为MAI Gateway对外是OpenAI兼容协议而现有RAG代码用的LangChain本身就支持通过环境变量切换OpenAI兼容服务的地址。4.1 Embedding和生成的统一改造改造前我的embedding初始化是这样的from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings( modeltext-embedding-3-small, api_keyos.environ[EMBEDDING_PROVIDER_KEY], base_urlhttps://embedding-provider.example.com/v1, )生成模型初始化是这样的from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, api_keyos.environ[LLM_PROVIDER_KEY], base_urlhttps://llm-provider.example.com/v1, )改造后两者都指向同一个网关地址from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings( modelembedding-model-name, api_keyos.environ[MAI_GATEWAY_KEY], base_urlhttps://mai-gateway.internal.example.com/v1, )from langchain_openai import ChatOpenAI llm ChatOpenAI( modelchat-model-name, api_keyos.environ[MAI_GATEWAY_KEY], base_urlhttps://mai-gateway.internal.example.com/v1, )改动只有三行key换成了网关keybase_url换成了网关地址model名换成了在网关路由里定义的逻辑模型名。LangChain内部通过这个base_url调用OpenAI兼容端点完全不需要改其它业务逻辑。4.2 重排模型的自定义接入重排模型当时没有走LangChain的标准轮子是团队自己封装的一个请求服务。原代码直接调用了重排供应商的SDK。改造后这段代码简化为对网关/v1/rerank端点的HTTP请求把供应商SDK部分删掉。网关端负责把rerank请求翻译成目标供应商协议再把打分结果转回统一格式。这个翻译能力是AI网关比较容易被忽略的价值。RAG链路中不止有LLM和embedding还有rerank如果每个模型类型都引入一套SDKRAG代码会变得越来越臃肿。网关统一收敛之后即使以后换了重排供应商业务代码也不需要再动。4.3 重试策略需要重新划分边界这是改造中唯一需要动脑的地方。原先业务代码里针对每家供应商都写了重试逻辑比如超时3秒就重试最多重试3次。接入网关后网关注入到链路中如果网关也配置重试业务层和网关层两边叠加最坏情况会变成某个请求整体耗时翻了九倍这就是典型的重试风暴。我的处理方式是明确重试责任方在网关。业务侧把SDK重试次数设为0或1只保留连接级别的快速失败网关层配置了按HTTP状态码区分的重试策略——5xx重试、429退避重试、4xx不重试。改造后重试行为变得可预期不会再出现业务层和网关层互相放大延时的现象。这里也踩过一个细节坑SDK自带的重试和网关重试是两套独立机制即使业务侧设了0次某些SDK对连接错误还会自动重连一次。所以改造后我专门在测试环境模拟了上游故障验证实际重试行为是否符合预期而不是只看配置。5. 线上跑了一周的真实数据延迟、失败率、成本变化改造完成后的第一个星期我每天都盯着网关的监控面板。这里梳理几组有代表性的数据都是生产环境真实跑出来的没有经过任何美化。具体数值和你的模型选择、流量模型强相关但趋势值得参考。5.1 嵌入与问答的失败率变化改造前一周embedding请求的失败率约为4.8%失败原因几乎全是供应商限流问答请求的失败率在2.1%左右一部分是限流导致一部分是偶发的网络超时。接入网关后embedding失败率降到了0.6%左右剩余失败基本是备用供应商也同时抖动导致问答失败率降到了0.3%以下。这个提升的来源主要是两点故障转移让瞬时限流不再表现为业务报错网关层的连接池复用降低了一部分TCP握手带来的超时概率。5.2 延迟的变化P50与P95问答请求的P50延迟从改造前的1.4秒降到了1.1秒P95从1.8秒降到了1.3秒。延迟下降最直接的原因是缓存。知识库问答场景里用户问题重复度比我预想高特别是关于制度、流程类的问题一周内被问到几十次很常见。网关层面的请求缓存命中率跑到了34%这部分请求直接省掉了模型生成时间。embedding请求的P95延迟变化不大大概从420毫秒降到380毫秒。原因很容易解释——embedding请求大部分是首次查询缓存帮不上忙延迟改善主要来自连接复用和更合理的并发控制。5.3 成本变化成本方面的数据更有说服力。由于缓存命中和故障转移时自动切换到低成本备用模型一周下来模型总调用成本比改造前一周下降了约15%。这里需要说明缓存节省的主要是重复生成类请求的token成本embedding请求虽然量大但单价低节省比例有限。指标改造前改造后变化embedding失败率4.8%0.6%明显下降问答失败率2.1%0.3%明显下降问答P50延迟1.4秒1.1秒下降问答P95延迟1.8秒1.3秒下降缓存命中率无34%新增模型调用成本基准下降约15%下降5.4 一次没有通知的故障演练最让我觉得这个网关没白接的是上线第四天发生的一次真实故障。某家供应商的模型服务在下午出现了持续五分钟的5xx报错。放在改造前这个故障意味着整个知识库问答至少不可用五分钟。但当时网关检测到连续失败自动把生成请求切换到了备用provider监控面板里能看到失败率有一个小小的尖峰随即恢复正常。用户侧没有任何感知也没有一条投诉。6. 落地过程中躲不过去的几个坑接入架构和代码改造只是开头真正耗费精力的是调优阶段。这一节我把四个印象最深的坑和排查过程完整记录一下每个都付出了真实的生产事故代价。6.1 重试风暴业务层和网关层双重放大上线第一天就遇到了诡异现象明明上游供应商一切正常部分请求的响应时间却高达十几秒。查了网关日志和业务日志发现同一个请求被重试了七八次。根因是我忽略了业务代码里遗留的重试逻辑。虽然我把SDK的配置重试次数改成0了但业务层还有一层业务超时兜底如果3秒没拿到结果就重新发起请求。网关层的重试配置是3秒超时后重试2次两边叠加后一个请求最多会被发送六次延迟被成倍放大。排查过程就是用请求ID串联两个日志系统看到同一个request_id在五分钟内反复出现问题一目了然。最终方案还是回到责任边界业务侧彻底关掉与模型调用相关的重试只保留网关出口的重试。所有重试状态码、超时时间都在网关层统一配置。6.2 流式输出在网关层被缓冲第二个坑和流式响应有关。问答服务为了体验用的是流式输出。接入网关后我观察到一个问题用户看到的首字响应时间从原来的600毫秒左右涨到了1.2秒。最初怀疑是网关所在网络节点慢查了半天发现是网关默认把流式响应缓存住了吃完整个响应才往外转发。我知道流式场景下网关不能这么干于是翻了网关配置文档把流式转发模式切换成边收边发的透传模式。切换后首字响应时间恢复到700毫秒左右。这个坑对RAG场景尤其重要因为知识库问答的prompt很长如果网关要等完整响应才转发用户感知到的卡顿会非常明显。任何要在RAG链路里接入网关的团队务必先验证流式转发是否真的支持逐字透传。6.3 Embedding缓存Key的一个低级失误上线第二天我发现一个诡异现象某些中文文档的向量化结果似乎串了。同一段文本请求embedding有时候返回的向量是对的有时候返回的是另一段文本的向量。定位到缓存层后我发现问题出在embedding请求体里。LangChain在向量化时默认会把文本拼成一个大的请求体数组而MAI Gateway的缓存key默认是对整个请求体计算哈希。这就带来一个致命问题请求体里如果有一个空字符串占位或者文本顺序微调缓存key完全不匹配本来可以命中的缓存持续失效更严重的是如果请求体里包含的是不同文本的拼接数组一个key对应的是一个向量数组这本身没有问题。真正的问题是我在调试时手动改了文本内容但由于请求体带了时间戳之类的动态字段导致同一个语义的文本生成了不同的缓存key于是缓存疯狂写、又疯狂不中。排查这事的教训是网关的缓存key必须基于业务真正关心的内容来设计而不是简单对整个请求体取哈希。尤其对embedding接口需要把请求体里的文本内容做规范化处理——去掉不可见字符、统一大小写、按固定顺序排序再计算缓存key。同时要排除掉请求体里所有动态字段比如timestamp、request_id这类运行时才会生成的参数。6.4 连接池与DNS解析的偶发超时上线第四天后监控里出现少量偶发超时告警频率不高一天几次集中在网关容器重启后的前十分钟。排查到容器内连接池与DNS解析的问题网关节点长时间空闲后到供应商的连接被回收但客户端连接池不知道继续复用旧连接发送请求触发了偶发的RST或SYN重传。解决方法是给网关配置了更短的空闲连接回收时间和更激进的keepalive探活参数。这个配置在负载均衡和高并发场景下基本不会遇到但知识库问答有明显的忙闲潮汐夜间几乎空闲早上上班高峰期骤然来量连接池冷启动问题就被放大了。这四个坑给我的整体感受是AI网关落地不是把流量接过来就完事了它是一层需要持续调优的基础设施。网关层和业务层、网关层和上游供应商之间都有大量隐性的边界需要厘清很多问题只有在生产流量的真实形态下才会暴露。7. 一些个人体会和下一步打算如果要用一句话概括这次实践我会说RAG系统的上限取决于检索和生成的质量下限取决于调用层的稳定性。MAI Gateway并不能帮你提高检索精度也不能帮你优化chunk切分但它能帮你把三四个供应商的接口收敛成一个入口把限流、重试、故障转移这些脏活从业务代码里剥离出去。我更深的体会是接入网关之后团队对整个RAG系统的掌控感完全不一样了。以前模型调用链路是一团迷雾出了问题要靠猜现在网关日志把每次调用的模型、供应商、耗时、token消耗全部摊开哪个环节慢、哪家供应商又开始抖动了一目了然。这种可观测性带来的改进能力是那些花在模型调参上的时间换不来的。后续我计划做两件事一件是把网关的缓存策略从请求级升级成语义级通过向量相似度判断用户query是否与已缓存的query同义进一步提升知识库问答的缓存命中率另一件是尝试部署本地开源模型作为最终的兜底provider让网关故障转移的最后一棒落在本地环境彻底摆脱对外部供应商的依赖。最后分享一个实用的收尾建议如果你也在考虑给RAG项目接入网关不要一上来就追求路由、缓存、限流、重试全量配置。先做一个最小闭环——只统一入口和故障转移跑通一周拿到线上数据后再按实际需求逐步加缓存和限流。这样既不会一上来就淹没在配置项里也能每一步都看到明确收益。