大部分团队做RAG知识库,都是从“文档切块+向量库+大模型”这种最小闭环起步的。我接手公司知识库项目时也是这个状态,最开始只有一两个业务方在用,接口随便串、模型随便配,倒也没什么大问题。但等到知识库从几万篇文档涨到几十万篇,接入方从1个变到6个,模型从1套变成Embedding、重排、Chat各两三套的时候,问题就开始集中爆发了:检索服务偶尔超时、模型服务限流、同一批问题反复消耗Token、各个业务方都觉得自己应该改上游参数。后来我把AI网关引入RAG链路,用MAI Gateway统一收口了全部模型出口,才真正把这些问题压住。这篇文章就围绕这个落地方案,把我做过的事、踩过的坑、以及最关键的设计思路完整梳理一遍,给正在做RAG或者正打算给RAG加网关的团队一个参考。
1. RAG项目跑到一定规模,瓶颈往往不是模型而是链路
很多人以为RAG的瓶颈就是“检索命中率低”“生成质量差”,但等你真正把系统放到生产环境跑起来,会发现事情没那么简单。命中率低是算法问题,但链路拥堵是架构问题,这两个问题经常被混为一谈。实际上,当你的RAG服务开始被多个团队、多个场景共用时,最先挂掉的往往不是模型,而是整条调用链上那些没人愿意负责的中间环节。
1.1 一条RAG请求背后挂着的服务数,远超你的想象
我最初画RAG架构图时,脑子里只有三件事:用户问题进来、向量库召回片段、大模型生成答案。但等到系统上线后,我把一次完整请求的调用链拉出来,才发现实际链路比这个复杂得多:
- 鉴权服务:确认调用方是谁、有没有权限、走哪个配额;
- 意图识别/问题改写:部分场景会先做问题分类或query改写,再决定走哪套检索策略;
- 检索服务:向量检索、倒排检索、混合检索,有时还要带上过滤条件;
- 粗排/重排:召回Top-K不够,还得交给重排模型再精排一轮;
- Prompt组装:按模板拼上下文,可能还要动态插入业务提示词;
- 大模型生成:主模型生成答案,有的场景后面还挂着内容审核模型;
- 后处理:格式化、溯源信息拼接、敏感信息过滤。
这里面的任何一个环节超时,整个请求就会卡住。最难受的是,这些服务往往由不同团队维护,有的在Kubernetes集群里,有的在裸金属服务器上,有的甚至还是某个同事笔记本上临时跑的本地服务。每个服务都有自己的超时时间、重试策略和容量上限,你根本没法在一个请求里统一控制它们的行为。
我当时第一个想法是:能不能在业务代码里做统一超时控制?试了之后发现很难,因为所有子调用都散落在不同模块里,改一遍的工程量巨大,而且业务代码里塞太多故障处理逻辑,后续根本没法维护。真正合理的做法是把这些控制能力从业务代码里抽出来,放到请求入口那一层,这就是我决定引入AI网关的起点。
1.2 多个业务方直连上游模型服务,是一场事故的温床
知识库从单一团队使用变成多业务方共用后,第一个失控的就是账号和密钥。每个业务方都来找你要上游模型服务的API Key,你给了,就得接受以下后果:
- 有人拿Key偷偷调非RAG场景的接口,月底账单吓人;
- 多个业务方共享一个Key,触发上游限流后互相踩踏,A团队在跑批量任务,B团队的正常问答也跟着超时;
- 有人出于“好心”修改了上游模型的温度、Top-P参数,结果所有调用方的生成效果全变了。
最典型的例子是我们当时的一个合作方,为了调低单次请求延迟,直接把重排模型的最大Token数改小了,结果精排环节截断了长文档的语义信息,整体命中率掉了好几个点。事后排查花了两天,最后发现是有人在配置中心里偷偷改的。
网关解决的就是这种“裸连”问题。把上游模型服务全部隐藏到网关后面,调用方统一访问网关暴露的OpenAI兼容接口,密钥由网关统一托管。业务方只能通过路由名称来使用模型,修改参数需要走网关策略,而不是直连上游想改就改。
1.3 “Hit Rate低”和“链路堵”是两件事,别把架构问题当算法问题处理
RAG社区里讨论最多的指标是Hit Rate,也就是检索召回内容对答案生成的命中率。很多人用RAG-based框架或者LangChain4j、Spring AI这类方案做检索优化,把精力全部花在调整分段策略、Embedding模型、重排序算法上。但在我接触的不少生产项目里,Hit Rate低只是表面现象,底层原因是链路太慢、模型经常超时,导致检索请求根本没走到生成环节就失败了。
我当时做过一次统计:某业务方反馈“答案质量变差”,我们查日志发现,他们40%的请求在大模型生成前就已经超时被业务方主动放弃。也就是说,压根没生成答案,用户看到的自然是“效果变差”。这种情况下,你去优化Embedding模型、调整chunk_size,纯属白费力气。必须先解决链路拥堵,让请求稳定地走完整个流程,才有资格谈算法层面的命中率优化。
这也让我意识到,RAG项目发展到一定阶段,必须引入一个专门的“链路治理层”,也就是AI网关。它不负责检索,也不负责生成,但它决定了检索和生成能不能稳定地串起来。
2. MAI Gateway在RAG场景里到底管哪些事
很多团队一听到“AI网关”,第一反应是“这不就是个反向代理服务吗”?这个理解有对的部分,但不够完整。普通反向代理只处理流量转发和负载均衡,而MAI Gateway这类AI网关的核心价值在于:它理解模型调用的语义,能针对不同模型接口做差异化治理。在RAG场景里,它的价值体现在五个方面。
2.1 用一套OpenAI兼容接口,收口Embedding、重排、Chat三路模型调用
RAG链路里至少有三种模型接口:Embedding接口用于把文本转成向量,重排接口用于精排检索结果,Chat接口用于最终生成答案。这三路接口的协议各不相同,有的来自云端API,有的来自本地Ollama,有的是内部自研模型服务。
如果让业务代码直接对接这三路接口,每个调用方都要写好几套适配代码,而且各自维护连接池、超时配置和错误重试逻辑,重复代码满天飞。MAI Gateway的做法是把三路接口统一成一个入口域名,对外暴露OpenAI兼容的API形态。业务方不需要关心上游是谁,只要按OpenAI的请求格式发给网关就行,网关内部再根据路由配置把请求转发到对应的真实服务上。
这样做的最大好处是:上层代码几乎不用动。我们当时有套基于LangChain4j的Java工程,原本直接调用OpenAI的Chat接口,接入MAI Gateway后只需要改一下BaseURL,框架层的Retriever配置、LLM配置全部原封不动。Spring AI的项目更简单,把api-key换成网关下发的客户端Key,base-url指到网关地址就完事。
2.2 路由与降级:主模型故障时自动切到备选模型,调用方无感
RAG项目跑在生产环境后,最怕的就是模型服务不可用。云端大模型API偶尔会抖动,返回5xx错误;本地部署的开源模型服务也可能因为资源耗尽而假死。如果没有网关层,每次故障都要靠业务方自己改配置切模型,等运维发现并处理完,业务早就挂了。
MAI Gateway允许你在路由配置里为同一个逻辑模型指定多个上游。比如逻辑模型名rag-chat,主上游指向云端大模型,备选上游指向本地部署的小模型。当主上游连续返回5xx或触发超时阈值时,网关自动把流量切到备选上游,整个过程对调用方完全透明。
有一点需要特别注意:自动降级不能做得太激进。我自己实践下来的经验是,要设置一个“故障观察窗口”。比如主上游在10秒内出现5次5xx,才触发降级;触发后至少保持30秒不切回,避免模型服务刚恢复就被流量重新打挂。这个参数不能拍脑袋定,要看上游服务实际稳定性来调整。我把这个经验写在部署手册里,后面接手的同事不至于踩同样的坑。
2.3 语义缓存:让相似问题不再反复调用大模型,成本和延迟同时下降
RAG知识库有个典型特征:用户问题高度重复,但表述各不相同。比如“报销流程是什么”“我要报销怎么做”“报销的步骤”这种,语义几乎一样,每次却都要走一遍完整的检索+生成链路,非常浪费。
传统缓存没法解决这类问题,因为请求文本不一样,缓存Key就对不上。MAI Gateway提供的是语义缓存,它会先把用户的问题转成一个向量(复用你注册的Embedding路由),然后用向量相似度去匹配缓存里已有的请求,相似度超过阈值就之间返回缓存答案,不再触发重排和Chat调用。
我配置语义缓存时踩过一个细节问题:命中阈值的设置需要根据业务场景调整。设得太低,比如0.88,会把“报销流程”和“报税流程”这种表述相近但内容完全不同的请求误判为同一个问题,返回错误答案;设得太高,比如0.99,缓存命中率极低,基本起不到降本效果。我们后来根据线上日志调了几轮,最终稳定在0.93左右,既能覆盖同义改写的情况,又不会误杀相近但不相同的问题。
2.4 限流与配额:按业务方、按接口粒度分别管理,而不是一刀切
RAG服务一旦被多个业务方共用,流量治理就成了刚需。有的业务方在做数据分析,需要跑大批量离线任务,对延迟不敏感;有的业务方在做在线客服问答,对延迟特别敏感,不能容忍被排队。如果你把两类业务放在同一个限流维度上,要么离线任务拖垮在线服务,要么在线服务把离线任务全部饿死。
MAI Gateway的限流规则支持按客户端维度、按路由维度、按时间窗口分别配置。我当时的做法是:
- 在线问答业务:QPS限流稍微放宽,但单请求超时时间收紧,保证用户体验;
- 离线批量业务:QPS限流严格控制,允许排队,单请求超时可适当放大;
- 全局限流:以模型服务实际容量为上限,设置一个“硬顶”,避免任何情况下把上游打挂。
这套规则上线后,最明显的改观是“一家拖死全家”的情况再也没有出现过。即使某个业务方的调用量突然涨了10倍,其他业务方的请求依然稳稳跑在自己的配额内。
2.5 链路可观测:每一条请求的路由决策、Token消耗、失败原因全部留痕
RAG链路出问题时,最头疼的是不知道该查哪个环节。网关层能统一记录所有模型调用的日志,包括请求走了哪条路由、上游是哪个服务、耗时多久、Token消耗多少、失败原因是什么。有了这些数据,排查问题的时间能从小时级缩短到分钟级。
我当时还把网关的指标接到了监控面板上,重点盯四个维度:请求成功率、P95延迟、Token消耗量、语义缓存命中率。这几个指标一挂,整个RAG系统的健康状态一目了然。哪条链路开始劣化,哪个模型开始变慢,成本有没有失控,全部都能在趋势图上提前看到,不至于等到用户投诉了才开始排查。
3. 行业落地拓扑:网关应该放在RAG链路的哪个位置
MAI Gateway的作用机制清楚了,接下来最关键的问题是:部署在哪里?这不是一个简单的网络问题,它直接决定了你的RAG系统是“多了一个转发组件”,还是“多了一层真正的治理能力”。
3.1 推荐架构:网关收口所有模型出口,业务侧只面对同一个域名
我推荐的拓扑结构是:网关放在业务代码和上游模型服务之间,所有模型调用都通过网关完成。业务侧只需要知道一个网关域名,不需要知道上游的地址、密钥、协议细节。
RAG服务对外暴露的仍是原有接口——用户提问、返回答案和溯源信息。RAG服务内部再通过网关调用Embedding、重排、Chat三路模型。这里有个容易混淆的点:网关不是给最终用户用的,而是给RAG服务自身用的。最终用户仍然访问你的RAG应用,RAG应用内部统一走网关。
这样做的好处是,上游模型服务的地址、密钥、路由变化,都被限制在网关这一层,业务代码完全感知不到。比如我们后来把Embedding模型从云端API换成本地自建的BGE-M3,只改了网关路由配置,业务代码一行没动。
3.2 本地模型和云端模型混部部署时,网关就是“交通警察”
很多RAG项目的落地形态是混合部署:Embedding用本地开源模型(比如通过Ollama跑bge-m3或者Qwen的Embedding模型),因为数据要进内网不能出域;Chat用云端大模型,因为效果确实更好;重排模型可能又是另一个来源。
这种混部形态下,网关的价值特别明显。本地Ollama服务有没有挂,云端API有没有限流,重排模型响应快不快,网关都能统一感知和调度。没有网关的话,你等于在业务代码里维护一张“模型路由表”,每次某个模型服务出问题,都要改代码重新上线,这在生产环境里是完全不可接受的。
3.3 快速部署:Docker Compose就能把MAI Gateway拉起来
MAI Gateway本身是无状态服务,部署成本很低。开发环境和中小规模生产环境用Docker Compose完全够用。我当时的部署结构是这样:
services: mai-gateway: image: mai/gateway:0.5.2 ports: - "8080:8080" environment: MAI_ADMIN_TOKEN: "${MAI_ADMIN_TOKEN}" MAI_LOG_LEVEL: "info" volumes: - ./config:/etc/mai-gateway - ./data:/var/lib/mai-gateway restart: unless-stopped这里有个容易被忽略的点:网关的管理端口和业务端口最好分开。管理端口用于配置下发、指标查询,绝对不能暴露到公网;业务端口才是对外提供模型调用的入口。分开之后,即便业务端口被攻击者扫到,也没法通过管理API修改网关配置。
3.4 框架接入细节:LangChain4j、Spring AI、LlamaIndex改BaseURL即可
RAG工程项目常用的框架无外乎LangChain4j、Spring AI、LangChain(Python)、LlamaIndex这几种。这些框架都兼容OpenAI的API协议,所以接入MAI Gateway极其简单,核心操作就是改BaseURL和API Key。
以Spring AI为例:
spring: ai: openai: base-url: http://mai-gateway.example.com:8080/v1 api-key: ${RAG_CLIENT_KEY}调用方要清楚一点:这里的api-key不是上游模型厂商的密钥,而是网关下发的客户端隔离Key。每个业务方一个Key,网关按Key识别调用方身份,再套用对应的限流、配额策略。这样就解决了前面说的“密钥一把梭”的问题。
LangChain4j更直接,构造OpenAiChatModel的时候把baseUrl指到网关就行,上游模型名称用网关路由名称代替。整个RAG工程的检索链路、Prompt模板、向量存储逻辑都不需要改动。
4. RAG链路里的典型故障场景,网关是怎么兜底的
理论说完了,讲点实战。RAG系统跑在生产环境后,故障并不罕见,关键在于响应速度和处理方式。我把这几类最常见的问题和网关的兜底策略整理出来,可以当做一个故障预案参考。
4.1 检索服务超时:统一超时预算,超时后自动降级
RAG链路里最脆弱的环节往往不是模型服务,而是检索服务。向量库在数据量涨到一定程度后,查询性能会明显下降;混合检索涉及多个索引的合并,更加容易超时。
我在网关里为检索相关的调用设置了严格超时:Embedding接口单次允许3秒,重排接口单次允许2秒。一旦上游检索服务超时,网关直接返回降级响应,同时RAG应用层收到降级信号后,会走“无检索回答”模式——也就是告诉用户“当前知识库检索暂时超时,以下内容基于模型自身知识生成,仅供参考”。
这个策略在一开始受到了业务方的质疑:“检索挂了你还强行生成答案,那不是误导用户吗?”我的观点是:如果你的知识库本身是辅助参考型的,有答案比没答案好;如果是医疗、法律这种高精度场景,那就应该直接报错,不要强行降级。降级策略必须按业务场景区分,不能一刀切。
4.2 大模型5xx/429:故障转移与退避重试
大模型API服务出现5xx错误或者429限流,是RAG项目里最高频的故障类型。云端API再稳定,也扛不住突发流量。网关层能做的是组合拳:重试、退避、降级。
我的配置策略是:
- 5xx错误最多重试2次,第一次间隔1秒,第二次间隔3秒;
- 429错误不盲目重试,而是等待更长时间,比如5秒后再试一次,不行就触发降级;
- 同一路由配置备选模型,主模型连续失败后自动切换;
- 重试只对幂等请求生效,非幂等请求直接失败,不让调用方重复提交。
这里有个运维细节:重试次数和超时时间必须算好总预算。比如单次Chat请求你设了60秒超时,又允许了2次重试,那理论上一个请求最长要等180秒,这对在线业务是不可接受的。我通常把单次超时控制在20秒以内,重试最多1次,整体用30秒做兜底。
4.3 Embedding并发过高:排队、批量、限流三管齐下
RAG知识库在导入文档或者做全量更新时,会产生大量的Embedding调用,瞬间把Embedding服务打满。如果在线检索也走同一套Embedding服务,就会互相干扰。
网关的处理办法是给Embedding路由单独设置限流,并且支持请求排队。离线导入场景可以容忍排队,在线检索场景不能排队,所以我设了两条规则:在线检索的Embedding请求QPS限流但优先级高,离线导入的QPS严格控制且允许排队。这样既不影响在线体验,又能慢慢把文档向量化任务跑完。
如果你用的是本地Ollama跑Embedding,建议提前测试一下它的并发上限。我之前用一台普通GPU机器跑Embedding,并发一高就持续超时,后来在网关侧把QPS降到实际承载能力的70%,立刻稳定了。
4.4 长上下文成本失控:Token预算和上下文截断
RAG请求的一个特点是上下文很长:检索回来的相关片段动辄几千字,加上系统提示词和历史对话,很容易突破单次请求的Token上限,而且成本飙升。
网关可以在进入Chat模型之前对请求做预处理:统计上下文总Token数,超出预算时自动做截断。比如每个请求固定分配“上下文Token预算”,其中检索片段的总量不能超过预算的一半,历史对话按时间倒序保留最新的,超出的部分直接丢弃。
这个策略帮我省下了一大笔成本。之前有些同事做数据分析,一个问题带了几十轮历史对话,每次请求都在烧钱;网关层加上上下文截断后,单次请求的平均Token消耗下降了约35%,对回答质量几乎没有影响,因为长对话里真正有用的其实也就最近几轮。
5. 从0接入MAI Gateway的核心流程:配置、调试、验证
工具选好了,架构定好了,接下来就是实打实的落地。我把接入的完整流程写下来,按这个步骤走,基本不会出大问题。
5.1 注册三路模型路由
进入MAI Gateway管理端,新增三条核心路由。
第一条是Embedding路由,逻辑名称设为rag-embedding,上游指向本地Embedding服务:
{ "name": "rag-embedding", "match": { "path": "/v1/embeddings", "model": "rag-embedding" }, "upstream": { "url": "http://embedding-service:8000/v1/embeddings", "timeout": 3000 }, "limit": { "qps": 100, "burst": 20 } }第二条是重排路由rag-rerank,指向重排模型服务。第三条是Chat路由rag-chat,指向云端大模型,并配置备用模型。
路由配置时有一个关键点:路由名称与上游模型名称的映射要统一。调用方在请求里只写“rag-chat”,不用关心上游到底是什么模型,未来更换模型时,调用方代码完全不需要改动,这就是网关解耦的体现。
5.2 配置语义缓存和限流规则
语义缓存配置的关键参数有三个:缓存嵌入模型、相似度阈值、过期时间。缓存嵌入模型直接复用前面注册的rag-embedding路由,相似度阈值按我前面的建议先设0.93,过期时间根据知识库更新频率来定。我们的文档每周更新一次,TTL设为7天;如果业务文档时效性要求高,TTL要缩短,或者干脆关闭语义缓存,改成精确缓存。
限流规则按调用方维度配置,我当时的配置大概是这样的:
- 业务方A(在线问答):QPS 50,超时15秒;
- 业务方B(批量分析):QPS 10,超时30秒;
- 业务方C(测试联调):QPS 5,超时10秒;
- 全局硬顶:QPS 200。
另外,每个调用方的月度Token预算也通过网关配额管理,超了就直接拒绝调用,避免月底账单爆表。
5.3 验证效果:延迟、成功率、成本三条曲线
网关配置完成后,不是直接切全量流量,而是先让一个业务方灰度跑一周,拉出三条曲线验证效果。
第一条是请求成功率曲线。网关上线后,最直观的变化是阶段性的超时失败明显减少,尤其是之前动辄4xx、5xx的调用大幅下降,成功率从95%左右稳定到99%以上。
第二条是P95延迟曲线。语义缓存命中率升上来后,部分高频问题的响应时间从3秒以上降到300毫秒以内,整体P95降低约40%。这里要强调:不是所有请求都变快,但用户体验确实上了一个台阶。
第三条是Token消耗曲线。缓存命中后,大模型调用次数明显下降,月底看账单最直观,同样的业务量,成本比上月省了20%左右。
有的团队比较激进,第一天就想全量切换。我建议别这么做。网关接入初期必然会有配置不完全匹配的场景,灰度一周可以让问题暴露在可控范围内,也方便你观察配置参数是否需要调整。
6. 网关接入RAG链路这一年,我踩过的那些坑
最后把实战中踩过的坑整理一下。这些问题在官方文档里基本不会写,但实际接入时遇到的概率极高,提前说明白能帮你省下不少排查时间。
6.1 SSE流式转发的坑:不透传就等着前端一直转圈
RAG应用常做流式输出,模型一个字一个字蹦出来,用户看起来很酷。但如果你的网关开启了缓存或者做了响应体缓冲,流式输出就会被破坏,前端会一直等完整响应,表现为“转圈半天不出字”。
解决方案是网关在处理Chat流式请求时,必须开启“流式透传”模式。如果不是流式接口,千万不要把缓存和缓冲逻辑套在流式请求上。我当时花了大半天排查这个问题,最后发现是默认缓存策略把SSE响应体整个吞了,关掉缓存的流式透传开关后立刻恢复。
6.2 鉴权透传:别把上游API Key暴露给调用方
业务方调用网关时使用客户端Key,网关转发给上游时使用上游厂商的API Key。这里要严格做到“两边隔离”:调用方永远接触不到上游密钥。
有过一个案例:某同事为了调试方便,把上游密钥配进了业务方环境变量,结果调试日志里把完整请求头打了出来,密钥直接泄漏。网关侧一定要做好日志脱敏,Authorization头里的敏感内容不能落到日志里,这是硬性要求。
6.3 语义缓存的边界:命中率上去了,答案却过期了
语义缓存不是“越多越好”。我们曾把语义缓存的相似度阈值调到0.90,缓存命中率直接冲到60%,表面数据很好看,但业务方很快投诉答案陈旧——因为知识库刚更新过,但高频问题仍命中了旧缓存。
当时调整方案是:时效性要求高的场景,给路由单独设置短TTL,比如10分钟;时效性低的场景(如制度问答、历史问题汇总)才用长TTL。另外,在缓存Key中引入“知识库版本号”,知识库更新后自动清空缓存,这个方案最彻底。
6.4 重试不是免费午餐:必须考虑幂等性
网关层配置重试时要特别注意:如果上游模型已经成功处理了请求,但响应在传输途中超时,此时重试会导致同一个请求被处理两次。放在RAG场景里,最直接的后果是:生成侧如果做了日志记录或者数据入库,就会产生重复数据。
我的处理原则是:只有幂等请求才允许自动重试。比如检索类请求天然幂等,可以放心重试;Chat类请求需要看是否有副作用,有副作用的场景不重试,直接返回失败让调用方决定怎么处理。
6.5 限流维度别按IP一刀切
刚开始配置限流时,我图省事直接按来源IP做了限流,结果出了两件事:一是同一个NAT出口下所有用户共用一个IP,正常用户访问稍微集中一点就触发限流;二是有人用代理IP轮询绕过限制。
后来调整为按客户端Key识别调用方,限流效果才真正准确。网关下发的Key每个业务方一个,业务方内部有多个NAT出口也没关系,反正限流对象是“这个业务方”,不是“某个IP”。如果确有更细粒度的隔离需求,可以让业务方申请多个Key,按模块或按功能分开使用。
以上这些坑,本质上都是同一个主题:网关不是加一个反向代理完事,而是要围绕RAG链路的实际流量特征做精细治理。如果团队正在搭建RAG知识库,或者现有RAG项目已经出现链路不稳、成本失控、多团队共用混乱这些问题,我的建议是尽早把AI网关纳入架构,不要等踩完所有坑再回头补课。工具只是手段,真正让系统稳定的是那句老话——入口统一、策略集中、可观测、可降级。