
1. 为什么我要把 AI 网关塞进 RAG 链路里第一次听到“AI 网关用于 RAG”这个组合我脑子里冒出来的第一个念头是这不是多此一举吗RAG 的核心不就是检索加生成检索用向量库生成用大模型中间加一层网关图什么直到去年底我接手一个企业知识库项目才彻底改变了这个看法。那个项目的背景很典型客户内部有十几个业务系统每个系统都沉淀了大量文档、工单、FAQ他们想做一个统一的智能问答入口让员工用自然语言就能查到跨系统的信息。听起来就是个标准的 RAG 场景但真正动手之后才发现问题根本不在检索和生成本身而在“怎么把十几个来源、格式各异、权限不同的数据源统一管起来并且让多个上层应用都能安全、稳定地调用这套能力”。这就是 AI 网关真正发挥作用的地方。MAI Gateway在这个项目里扮演的角色不是简单的反向代理而是整个 RAG 链路的统一入口、流量调度器、权限校验点和可观测性采集点。它解决的核心问题是当你的 RAG 系统从“一个 demo”变成“一个被多个业务方依赖的服务”时你需要一个中间层来隔离复杂度。这篇文章我会把整个落地过程拆开讲包括为什么选网关而不是直连、网关层具体做了哪些事、RAG 检索和生成环节怎么和网关配合、踩过哪些坑、以及如果你也想在自己的项目里引入 AI 网关可以直接抄的配置和思路。适合正在做 RAG 项目、或者 RAG 已经跑起来但开始遇到“多应用接入”“权限混乱”“调用不可观测”这类问题的朋友。2. 先搞清楚AI 网关在 RAG 里到底管什么2.1 网关不是“加一层”而是“收口”很多人对网关的理解停留在“转发请求”这个层面觉得加个网关就是多一跳网络开销。但在 RAG 场景里网关的价值恰恰在于它把原本散落在各处的横切关注点收拢到了一起。我画个简单的对比你就明白了。没有网关的时候你的 RAG 系统大概是这样应用 A 直接调向量库检索然后自己拼 prompt 调大模型应用 B 也这么干但用的 embedding 模型版本不一样应用 C 想加个敏感词过滤又在自己的代码里写了一套。结果就是同一个知识库三个应用三种行为出了问题根本不知道是哪一层的事。有了 AI 网关之后所有应用都只跟网关打交道。网关负责统一鉴权、统一路由到正确的检索服务和模型服务、统一做请求和响应的日志记录、统一做限流和降级。应用层只需要关心“我要问什么问题”不需要关心“背后用的是哪个向量库、哪个模型、有没有做重排”。这里有个关键认知网关管的是“能力的分发”不是“能力的实现”。检索逻辑、生成逻辑还是各自的服务在做网关只负责让这些能力被安全、可控、可观测地消费。2.2 RAG 链路的三个关键卡点网关正好能接住在实际项目里我总结出 RAG 系统最容易出问题的三个地方而这三个地方恰好都是网关的强项。第一个卡点是模型调用的成本和稳定性。RAG 每次问答至少调一次 embedding 加一次生成如果知识库大、并发高token 消耗非常可观。网关层可以做 token 计数、按应用配额、失败自动切换备用模型。我们项目里就配了主备两个生成模型主模型超时或报错时网关自动切到备用业务方完全无感知。第二个卡点是检索结果的权限过滤。企业知识库最怕的就是 A 部门的人查到了 B 部门的机密文档。如果在检索服务里做权限每个检索请求都要带上用户身份检索服务要理解权限模型耦合太重。放到网关层做就清爽很多网关在转发检索请求前根据用户身份注入权限过滤条件检索服务只管按条件查不关心权限逻辑。第三个卡点是多应用接入时的可观测性。当五个应用同时调你的 RAG 服务某个应用突然变慢你怎么知道是它自己的问题还是 RAG 服务的问题网关层的全链路日志和指标采集能直接告诉你是检索耗时涨了还是生成耗时涨了还是某个应用自己发请求太频繁把配额打满了。2.3 为什么选 MAI Gateway 而不是自己写中间件市面上网关产品不少自己用 Spring Cloud Gateway 或者 Nginx 加 Lua 也能攒一个。我最终选 MAI Gateway 主要看中三点。一是它对 AI 场景的原生支持。普通网关处理的是 HTTP 请求转发但 AI 网关需要理解 token、理解流式响应、理解模型调用的重试语义。MAI Gateway 内置了这些能力不用自己造轮子。比如流式生成的时候如果上游模型断了普通网关直接把断流透传给客户端但 MAI Gateway 可以配置重试策略在 token 还没开始输出时自动重试对用户来说就是多等了几百毫秒而不是看到一个断掉的回答。二是它的插件机制足够灵活。RAG 链路里每个环节的需求都不一样检索前要注入权限、生成前要拼 prompt 模板、响应后要记录问答对。MAI Gateway 的插件可以在请求的不同阶段插入自定义逻辑而且插件之间可以共享上下文这点比在 Nginx 里写 Lua 舒服太多。三是可观测性开箱即用。它自带 Prometheus 指标暴露和结构化日志检索命中率、生成延迟、token 消耗这些 RAG 核心指标不用自己埋点配置一下就能采集。3. 落地架构网关如何串起检索与生成3.1 整体链路设计我们的最终架构是这样的客户端请求先到 MAI Gateway网关做鉴权和限流后把请求拆成两路。一路是检索请求网关注入用户权限过滤条件后转发给检索服务检索服务查向量库返回候选文档。另一路是生成请求网关拿到候选文档后用配置好的 prompt 模板拼装上下文再转发给模型服务生成回答。最后网关把生成结果和引用来源一起返回给客户端。这个设计里有个细节值得说检索和生成是串行的但网关层做了异步编排。检索请求发出后网关不会阻塞等待而是拿到检索结果后立即触发生成请求两个阶段之间的上下文传递在网关内部完成客户端只感知到一次请求。提示如果你的 RAG 链路里还有重排环节也可以放在网关层编排。重排服务作为一个独立的上游网关在检索完成后调用重排再把重排结果传给生成。这样整个 RAG 流程对客户端就是一个黑盒。3.2 检索环节的网关配置要点检索环节网关主要做三件事权限注入、查询改写、结果缓存。权限注入前面提过了具体实现是在网关插件里根据 JWT 里的用户部门信息往检索请求里加一个filter字段。检索服务收到后直接把这个 filter 拼到向量库的查询条件里。我们用的向量库支持元数据过滤所以这个方案很自然。查询改写是后来加的。用户问“上个月的销售数据”直接拿这句话去检索效果很差因为文档里写的是“2024年11月销售报表”。网关层加了一个轻量的查询改写插件用一个小模型把用户口语化的 query 改写成更适合检索的形式。这个插件只在检索前触发不影响生成。结果缓存是成本优化的关键。很多问题是重复的比如“年假怎么申请”这种网关层做语义缓存相似问题直接返回缓存结果不走检索和生成。我们实测下来缓存命中率大概在 18% 左右相当于省了将近五分之一的 token 消耗。3.3 生成环节的网关配置要点生成环节网关的核心工作是 prompt 编排和模型路由。Prompt 编排是在网关层用模板引擎做的。检索返回的文档列表会被填充到一个预设的模板里模板里包含系统指令、上下文文档、用户问题三部分。这样做的好处是 prompt 模板可以热更新不用改代码就能调整生成行为。我们项目里就根据业务方反馈迭代了好几版模板每次都是改配置直接生效。模型路由是根据请求特征动态选模型。简单问题走小模型复杂问题走大模型。判断逻辑放在网关插件里根据 query 长度、检索到的文档数量、是否有历史对话等特征来决定。这个策略让我们的平均生成成本降了大概 30%而回答质量没有明显下降。# MAI Gateway 模型路由配置示例 routes: - name: simple-query condition: query.length 50 context.docs 3 target: gpt-3.5-turbo - name: complex-query condition: query.length 50 || context.docs 3 target: gpt-4 - name: fallback condition: default target: gpt-3.5-turbo3.4 网关层的数据流转细节请求进来的时候网关先解析 JWT 拿到用户身份然后生成一个 trace_id 贯穿整个链路。这个 trace_id 会透传到检索服务和模型服务方便出问题时串联日志。检索请求发出前网关插件往请求体里注入user_filter和trace_id。检索服务返回后网关把候选文档暂存在请求上下文里同时记录检索耗时和命中数量。生成请求发出前网关从上下文里取出候选文档填充 prompt 模板再注入模型路由决策。生成过程中如果是流式响应网关会边收边转发同时统计 token 数量。生成完成后网关把回答、引用来源、trace_id 一起返回给客户端。整个过程中网关采集的指标包括请求总数、检索延迟、生成延迟、token 消耗、缓存命中率、模型切换次数、错误率。这些指标按应用维度和用户维度分别聚合方便定位问题。4. 实操过程从零把网关接进现有 RAG 系统4.1 环境准备与网关部署我们是在 Kubernetes 环境里部署的MAI Gateway 官方提供了 Helm Chart安装过程比较顺。但有几个配置项需要根据 RAG 场景调整。首先是超时时间。普通 API 网关默认超时可能就 30 秒但 RAG 的生成环节如果回答较长很容易超过这个时间。我们把生成路由的超时设成了 120 秒检索路由设成 10 秒。流式响应的话超时时间可以设更长因为只要在持续输出 token 就不算超时。其次是连接池配置。RAG 场景下检索和生成的并发特征不一样检索是短平快生成是长连接。我们给检索上游配了较大的连接池但较短的空闲回收时间给生成上游配了较小的连接池但较长的保持时间。# 上游连接池配置 upstreams: - name: retrieval-service max_connections: 200 idle_timeout: 30s - name: llm-service max_connections: 50 idle_timeout: 300s4.2 鉴权与权限过滤的落地鉴权我们用的是 JWT网关层校验签名和过期时间解析出用户 ID 和部门 ID。权限过滤的粒度我们做到了文档级别每个文档在入库的时候会打上部门标签检索时网关根据用户部门注入过滤条件。这里有个坑要注意如果用户属于多个部门过滤条件要用 OR 关系而不是 AND。我们一开始没注意导致跨部门用户什么都查不到。后来改成department IN (user_departments)才正常。另一个坑是权限过滤要在检索前做不能在检索后做。因为向量检索返回的是 top-k 结果如果先检索再过滤很可能过滤完就没剩几条了。必须在查询条件里就带上过滤让向量库在过滤后的集合里做相似度搜索。4.3 Prompt 模板的配置与热更新Prompt 模板我们放在网关的配置中心里支持热更新。模板用 Jinja2 语法可以引用检索结果、用户信息、历史对话等变量。你是一个企业知识库助手请根据以下文档回答用户问题。 如果文档中没有相关信息请如实告知不要编造。 相关文档 {% for doc in documents %} [{{ loop.index }}] {{ doc.content }} 来源{{ doc.source }} {% endfor %} 用户问题{{ query }} 请用简洁的中文回答并在末尾标注引用的文档编号。这个模板我们迭代了五版。第一版没有“不要编造”的指令模型经常在文档没相关内容时硬编。第二版加了引用编号要求方便用户溯源。第三版调整了文档展示格式把来源放在内容后面。第四版加了历史对话变量支持多轮。第五版针对不同业务线做了模板分支。实操心得Prompt 模板的版本管理很重要。我们每次修改都会记录版本号和修改原因出问题时可以快速回滚。网关层可以配置按应用灰度不同的模板版本方便 A/B 测试。4.4 流式响应的处理与 token 统计流式响应是 RAG 体验的关键用户不用等整个回答生成完就能看到内容。但流式响应给网关层带来了两个挑战一是超时判断二是 token 统计。超时判断我们用的是“空闲超时”而不是“总超时”。只要上游还在持续输出 token连接就不算超时。只有超过一定时间没有新 token 产出才判定为超时并触发重试或降级。Token 统计在流式场景下比较麻烦因为 token 是逐个吐出来的。我们的做法是在网关层对每个 chunk 做累加计数生成结束后汇总。这个计数不需要特别精确用于成本核算和配额管理足够了。# 网关层流式 token 统计的简化逻辑 class StreamTokenCounter: def __init__(self): self.prompt_tokens 0 self.completion_tokens 0 def on_request(self, prompt): # 请求发出时估算 prompt token self.prompt_tokens estimate_tokens(prompt) def on_chunk(self, chunk): # 每个响应 chunk 累加 self.completion_tokens estimate_tokens(chunk) def on_complete(self): return { prompt_tokens: self.prompt_tokens, completion_tokens: self.completion_tokens, total_tokens: self.prompt_tokens self.completion_tokens }4.5 缓存策略的配置与效果语义缓存我们用的是向量相似度匹配。用户 query 进来后先跟缓存库里的历史 query 做相似度比较超过阈值就返回缓存结果。阈值设置很关键。设太高缓存命中率低设太低会返回不相关的答案。我们实测下来 0.92 是个比较平衡的值。另外缓存要设置过期时间知识库更新后旧缓存要能失效。我们配的是 24 小时过期同时知识库更新时会主动清除相关缓存。缓存 key 的设计也有讲究。同样的 query不同部门的人问答案可能不一样因为权限过滤后的文档不同。所以缓存 key 要包含用户部门信息。我们用的是hash(query department)作为 key。5. 踩坑记录与常见问题排查5.1 检索命中率突然下降怎么查上线两周后有一天业务方反馈问答质量明显变差。排查发现是检索命中率从 85% 掉到了 60%。按链路一层层查网关层看检索请求正常发出检索服务看查询正常执行向量库看返回结果数量正常。最后发现问题出在网关的查询改写插件上——那天更新了一个改写模型的版本新版本把一些专业术语改写得过于口语化导致检索不到。排查这类问题的思路是先在网关层看请求和响应的原始内容确认网关没有篡改再看检索服务的日志确认查询条件符合预期最后看向量库的返回确认相似度分数分布。如果网关层做了查询改写一定要把改写前后的 query 都记录下来否则根本不知道检索服务收到的是什么。5.2 生成结果不稳定的排查思路生成不稳定通常表现为同样的问题有时候回答很好有时候答非所问。可能的原因有三个。一是检索结果不稳定。如果向量库的索引在更新或者 top-k 参数有变化每次检索到的文档可能不同。排查方法是固定 query 多次调用看检索结果是否一致。二是模型路由不稳定。如果路由条件设置得过于敏感同样的 query 可能被路由到不同模型。排查方法是看网关日志里的路由决策记录。三是 prompt 模板渲染不稳定。如果模板里有随机因素或者变量缺失渲染出的 prompt 会不同。排查方法是把每次实际发送给模型的 prompt 完整记录下来。我们项目里遇到过一次原因是 prompt 模板里引用了一个可选的用户信息字段有些请求有这个字段有些没有导致模板渲染结果不一致。后来把可选字段都加了默认值才解决。5.3 常见问题速查表问题现象可能原因排查位置解决方法检索结果为空权限过滤条件过严网关插件日志检查用户部门与文档标签是否匹配生成超时模型响应慢或网络抖动网关上游延迟指标配置重试和降级策略Token 消耗异常高缓存失效或 prompt 过长网关 token 统计检查缓存命中率和模板长度流式响应中断上游连接断开网关连接日志配置空闲超时和自动重试多应用互相影响未做应用级隔离网关配额配置按应用设置独立限流和配额回答引用错误检索文档与生成不匹配网关上下文传递日志检查文档传递顺序和编号5.4 几个容易忽略的细节第一个细节是 trace_id 的透传。网关生成的 trace_id 要透传到检索服务和模型服务并且这些服务在日志里要带上这个 ID。否则出了问题你只能看到网关的日志看不到下游的细节。第二个细节是错误响应的标准化。检索失败、生成失败、超时这些错误返回给客户端的格式要统一并且要包含足够的排查信息。我们定义了一套错误码比如RETRIEVAL_TIMEOUT、GENERATION_FAILED客户端可以根据错误码做不同的处理。第三个细节是配置变更的灰度。网关的配置变更影响面很大prompt 模板、路由规则、限流阈值这些改动一定要支持灰度。我们用的是按应用灰度先在一个应用上生效观察一段时间没问题再全量。第四个细节是成本监控。RAG 的 token 消耗是持续成本网关层要能按应用、按用户、按天统计 token 消耗并且设置预算告警。我们有一次发现某个应用的 token 消耗突然涨了十倍查下来是那个应用的调用方写了个死循环在疯狂重试。6. 这套方案适合什么场景不适合什么场景6.1 适合引入 AI 网关的情况如果你的 RAG 系统满足以下任意一条引入 AI 网关的收益会比较明显。多个应用需要接入同一套 RAG 能力。网关的统一鉴权和配额管理能省掉每个应用各自对接的重复工作。对成本敏感需要精细化的 token 管理。网关层的 token 统计、缓存、模型路由能直接降低账单。有权限隔离需求。网关层做权限注入比在检索服务里做更干净也更容易审计。需要可观测性来定位问题。RAG 链路长没有网关层的统一日志和指标排查问题基本靠猜。6.2 不建议引入网关的情况如果你的 RAG 系统还在原型阶段只有一个应用在用日调用量不到一千次那加网关就是给自己找麻烦。这个阶段最重要的是快速验证效果直接调检索和生成服务更简单。另外如果团队里没有人熟悉网关运维引入网关会增加一个需要维护的组件。网关本身的高可用、配置管理、版本升级都需要投入精力。小团队要权衡这个投入是否值得。6.3 从简单到复杂的演进路径我的建议是分三步走。第一步RAG 直接跑通检索和生成直连先验证效果。第二步当第二个应用要接入时引入网关做统一入口和鉴权。第三步当调用量上来、成本压力出现时再逐步加上缓存、模型路由、精细配额这些高级能力。不要一上来就把所有功能都配上那样配置复杂度太高出了问题也不好定位。我们项目也是迭代了三个月才到现在的状态中间好几次因为配置太复杂把自己坑了。7. 关于 RAG 和网关配合的一些个人体会这个项目做下来我最大的体会是RAG 的瓶颈往往不在检索算法或模型能力上而在工程化的细节里。检索命中率差几个百分点可能只是查询改写没做好生成成本高可能只是缓存没配好多应用接入混乱可能只是缺一个统一的入口层。MAI Gateway 在这套方案里不是主角RAG 的检索和生成才是。但网关让检索和生成的能力变得可管理、可观测、可控制。就像一家餐厅厨师做菜是核心但如果没有前台接待、点单系统、传菜流程再好的厨师也服务不了多少客人。如果你正在做 RAG 项目我的建议是先把检索和生成跑通然后尽早考虑接入层的事情。不要等到五个应用都直连你的检索服务、出了问题互相甩锅的时候才想起来加网关那时候改造的成本会高很多。最后分享一个我们项目里的小技巧网关层的日志一定要把完整的 prompt 和检索结果记录下来但要注意脱敏。我们有一次排查一个生成质量问题就是因为日志里记录了完整的 prompt才发现是模板里一个变量渲染错了。如果没有这个日志这个问题可能永远找不到原因。当然脱敏也很重要用户隐私数据不能明文落盘我们是在网关层做了字段级的脱敏后再写日志。