☰
RAG链路前架一层AI网关:MAI Gateway配置与调优实战
2026/10/3 15:45:50 网站建设 项目流程

1. 为什么要在RAG链路前面架一层AI网关

1.1 从一次检索抖动说起

去年下半年我接手了一个企业知识库项目,底层用LangChain4j做RAG检索增强,向量库选的是Milvus,嵌入模型跑在本地Ollama上,生成侧接的是公司统一的大模型服务。项目上线头两周一切正常,第三周开始陆续收到反馈:同一个问题,早上问和下午问,答案质量差得离谱,有时候引用的是三个月前的旧文档,有时候干脆答非所问。

排查过程很折磨人。日志里看不出明显异常,向量库的召回率指标也稳定,模型服务那边也没报错。后来我把每次请求的完整链路打点拉出来对比,才发现问题出在请求入口没有统一治理:前端A组传的query带了多余的空格和换行,B组传的query被截断到128字符,C组干脆把用户的历史对话拼进了检索query里。同一个知识库,三种输入形态,检索结果自然天差地别。

这件事让我意识到,RAG的效果瓶颈很多时候不在检索算法本身,而在进入检索之前的请求质量。而解决这个问题的位置,恰好就是AI网关该站的地方。

1.2 MAI Gateway在RAG架构中的定位

MAI Gateway这类AI网关,本质上是一个面向模型调用的流量治理层。它不负责检索,也不负责生成,它负责的是:请求进来之后,先做标准化、鉴权、限流、路由、缓存、日志,然后再把干净的请求转发给下游的RAG服务或模型服务。

放到RAG链路里,它的位置是这样的:

客户端 -> MAI Gateway -> [Query预处理] -> 检索服务(向量库) -> 重排 -> 生成模型 -> 返回

很多人会问:RAG框架本身不是已经有Chain和Router了吗,为什么还要在外面套一层网关?我的理解是,框架内的编排解决的是"逻辑怎么走",网关解决的是"流量怎么管"。前者关注业务逻辑,后者关注稳定性、可观测性和成本。两者不冲突,但职责必须分开。

1.3 不架网关会踩的三个坑

第一个坑是输入不可控。RAG对query质量极其敏感,一个多余的标点、一段无关的上下文,都可能让召回率掉十几个百分点。没有网关做统一预处理,每个调用方都按自己的理解传参,检索质量就是开盲盒。

第二个坑是成本不可见。RAG一次完整链路可能涉及嵌入模型调用、向量检索、重排模型调用、生成模型调用,每一环都在烧钱。没有网关做token统计和调用计数,你根本不知道钱花在哪了。我见过一个团队,重排模型用的是按次计费的API,结果因为没做缓存,同一个query被重复调用了上千次。

第三个坑是故障不可隔离。向量库挂了、嵌入服务超时、生成模型限流,这些故障如果没有网关做熔断和降级,会直接穿透到客户端。有了网关,你可以在入口层做超时控制、重试策略和兜底响应,把故障影响面收窄。

注意:网关不是银弹。它解决的是流量治理问题,不解决检索算法问题。如果你的RAG召回率本身就不行,加网关也救不回来。网关的价值在于让好的RAG更稳、更省、更可观测。

2. MAI Gateway接入RAG的核心配置拆解

2.1 路由规则:把不同请求分流到不同RAG链路

MAI Gateway的路由能力是它最实用的功能之一。在实际项目里,我通常会把RAG请求按业务域和query类型做两级分流。

第一级按业务域分。比如公司有产品文档库、客服话术库、内部制度库三个知识库,每个库对应一个独立的RAG服务实例。网关根据请求头里的X-Knowledge-Base字段,把请求路由到对应的后端。

第二级按query类型分。有些query是事实型检索("XX功能的参数是什么"),适合走向量检索;有些query是总结型("把XX文档的核心要点列出来"),适合走全文检索加长上下文生成。网关可以根据query长度、是否包含疑问词等特征做粗分类,路由到不同的处理链路。

配置示例(YAML格式,基于常见网关配置习惯):

routes: - name: product-rag match: headers: X-Knowledge-Base: "product" backend: rag-product-service timeout: 30s retry: 2 - name: policy-rag match: headers: X-Knowledge-Base: "policy" backend: rag-policy-service timeout: 45s retry: 1

这里有个细节:不同知识库的超时时间要区别设置。产品文档库文档短、检索快,30秒足够;制度库文档长、重排耗时,给到45秒。如果统一设成60秒,慢的那个会把快的拖死。

2.2 Query预处理:在网关层做还是在下游做

这是我在项目里纠结最久的一个问题。Query预处理包括去空格、去特殊字符、长度截断、敏感词过滤、同义词扩展等操作。放在网关层做的好处是统一,所有调用方都享受同样的预处理;放在下游做的好处是灵活,不同RAG链路可以有自己的预处理策略。

我最后的方案是分层处理:通用预处理(去空格、去控制字符、长度硬截断)放在网关层,业务相关预处理(同义词扩展、领域词典替换)放在下游RAG服务里。

理由是:通用预处理是"卫生问题",不该让每个下游服务重复实现;业务预处理是"策略问题",必须贴近业务逻辑。网关层做通用预处理,可以用Lua脚本或插件实现,性能损耗极小。

一个实测有效的通用预处理规则:

  • 去除首尾空白和连续空白符
  • 去除零宽字符和不可见控制字符
  • 将全角标点统一转为半角
  • query长度超过512字符时,保留前512字符并记录告警
  • 空query直接返回400,不往下游转发

提示:长度截断的阈值不要拍脑袋定。我的做法是统计线上query的长度分布,取P99值作为截断阈值。大部分场景下512字符已经覆盖99%以上的正常query。

2.3 缓存策略:RAG场景下什么该缓存什么不该缓存

RAG的缓存比普通API缓存复杂,因为一次请求涉及多个环节,每个环节的缓存策略都不一样。

嵌入向量可以缓存。同一个query的嵌入向量是确定的(假设模型不变),缓存起来能省掉重复的嵌入调用。我用Redis做嵌入缓存,key是query的MD5,value是向量数组,TTL设24小时。实测命中率在30%左右,嵌入调用成本直接降了三成。

检索结果可以短缓存。向量库的检索结果在知识库不变的情况下是稳定的,但知识库可能随时更新。我的做法是给检索结果设5分钟TTL,同时监听知识库更新事件,一旦有更新就主动清空相关缓存。

生成结果不建议缓存。生成模型的输出有随机性,同一个query两次调用可能得到不同答案。而且RAG的生成结果依赖检索到的上下文,上下文一变答案就变。缓存生成结果容易导致用户拿到过期答案。

重排结果可以缓存。重排模型的调用成本通常比生成模型低,但比嵌入模型高。如果检索结果缓存了,重排结果也可以跟着缓存,TTL和检索结果保持一致。

环节是否缓存TTL缓存key备注
Query预处理否--计算量极小,缓存无意义
嵌入向量是24hquery MD5模型变更时需清空
向量检索是5minquery MD5 + 知识库ID知识库更新时主动清空
重排是5min检索结果ID跟随检索缓存
生成否--输出有随机性,不建议缓存

2.4 限流与熔断:保护下游RAG服务

RAG链路的下游通常比较脆弱。向量库的并发能力有限,嵌入模型服务可能被多个业务共用,生成模型有严格的QPS限制。网关层的限流和熔断是保护这些下游服务的第一道防线。

限流我一般做两级:全局级和路由级。全局级限制整个网关的总QPS,防止突发流量打垮所有下游;路由级针对每个RAG服务单独限流,比如产品库RAG限制50 QPS,制度库RAG限制20 QPS。

熔断策略我参考的是错误率+慢调用比例双指标。当某个下游服务的错误率超过50%,或者慢调用(超过超时时间80%)比例超过30%,就触发熔断,后续请求直接返回兜底响应,不再转发。熔断后每隔30秒放一个探测请求,成功则恢复。

兜底响应很重要。RAG场景下,兜底不能只返回"服务繁忙",最好返回一个降级答案,比如"当前知识库服务繁忙,以下是与您问题最相关的文档标题列表,请稍后重试"。这样用户体验不会太差。

3. 真实案例:从零搭建一条带网关的RAG链路

3.1 环境准备与组件选型

这个案例是我去年给一个中型企业做的内部知识库项目,规模不大但链路完整,适合作为参考模板。

组件选型如下:

  • 网关:MAI Gateway(社区版即可满足需求)
  • RAG框架:LangChain4j(Java技术栈,团队熟悉)
  • 向量库:Milvus(单机版,数据量在百万级以内)
  • 嵌入模型:Ollama本地部署的bge-m3(中文效果好,本地调用无网络依赖)
  • 生成模型:公司统一的大模型服务(通过内网API调用)
  • 缓存:Redis 7.x
  • 监控:Prometheus + Grafana

选Ollama做嵌入的原因是数据不出内网,而且bge-m3在中文检索任务上的表现确实不错。生成模型走公司统一服务,是因为自部署大模型的成本太高,统一服务有专门的团队维护。

3.2 网关配置实操:从安装到跑通第一条请求

MAI Gateway的安装按官方文档走就行,我这里重点说配置。

第一步,定义上游服务。在网关配置里注册RAG后端服务:

upstreams: - name: rag-backend targets: - host: 10.0.1.20 port: 8080 health_check: path: /health interval: 10s

第二步,配置路由和插件。路由匹配所有/api/rag/**的请求,挂载预处理插件、缓存插件和限流插件:

routes: - name: rag-route match: path_prefix: /api/rag upstream: rag-backend plugins: - query-preprocess - redis-cache - rate-limit rate_limit: qps: 100 burst: 20

第三步,配置Query预处理插件。我用Lua脚本实现,核心逻辑就是前面说的通用预处理规则。脚本挂在access阶段,在转发之前执行。

第四步,验证。用curl发一条测试请求:

curl -X POST http://gateway:8000/api/rag/query \ -H "Content-Type: application/json" \ -H "X-Knowledge-Base: product" \ -d '{"query": " 产品A的 最大并发数是多少 "}'

观察网关日志,确认query被预处理成了产品A的最大并发数是多少,然后正常转发到了下游。

3.3 检索链路的关键参数调优

RAG检索效果的好坏,很大程度上取决于几个关键参数。我在这个项目里反复调了三轮,最终确定的参数如下。

TopK:向量检索返回的候选文档数。设太小会漏掉相关文档,设太大会引入噪声。我的经验值是先设20,再根据重排后的效果调整。如果重排后前3篇的相关性都不错,说明TopK够了;如果前3篇里只有1篇相关,说明TopK可能偏小。

相似度阈值:低于这个阈值的文档直接丢弃。bge-m3的余弦相似度通常在0.3到0.9之间,我设的阈值是0.45。低于0.45的基本是无关文档,留着只会干扰生成。

重排TopN:重排后保留的文档数。这个直接决定塞进生成模型的上下文长度。我设的是5,因为生成模型的上下文窗口有限,塞太多文档反而会稀释关键信息。

分块大小:文档切分的粒度。我试过256、512、1024三种,最终选了512。256太碎,一个完整段落被切成好几块,检索出来上下文不完整;1024太大,一块里混了多个主题,检索精度下降。512是一个比较平衡的值。

参数初始值调优后调整依据
TopK1020初始值漏召回明显
相似度阈值0.30.45低分文档干扰生成
重排TopN105上下文过长导致答案发散
分块大小1024512大块检索精度不足

3.4 上线后的效果对比与数据

上线运行一个月后,我拉了几个核心指标做对比。

检索命中率(人工标注的相关文档是否出现在Top5中):从上线前的62%提升到81%。提升主要来自Query预处理和参数调优,网关本身不直接提升命中率,但它让预处理和参数配置变得可管理。

平均响应时间:从2.3秒降到1.4秒。降幅主要来自缓存命中,嵌入缓存和检索缓存的综合命中率在35%左右。

生成模型调用成本:下降约28%。原因是缓存减少了重复调用,同时重排TopN从10降到5,每次生成的输入token数减少了一半。

故障恢复时间:从平均15分钟降到3分钟以内。网关的熔断和健康检查让故障发现和隔离变得自动化。

实操心得:不要指望一次调优就到位。我的做法是每周拉一次线上query样本,人工标注100条,看检索和生成的效果,然后微调参数。RAG调优是一个持续迭代的过程,没有一劳永逸的配置。

4. RAG落地中最容易踩的五个坑

4.1 坑一:把网关当RAG框架用

我见过有团队试图在网关层做检索逻辑,比如在网关插件里直接查向量库。这是典型的职责错位。网关的优势是轻量、高并发、低延迟,它的插件机制适合做无状态的预处理和后处理,不适合做有状态的检索和生成。

检索逻辑应该放在专门的RAG服务里,网关只负责转发和治理。如果你发现网关配置越来越复杂,插件里塞了几百行业务逻辑,那说明架构设计出了问题。

4.2 坑二:忽略query的语义完整性

Query预处理最容易犯的错误是过度清洗。比如把query里的标点全部去掉,结果"产品A和产品B的区别"变成了"产品A和产品B的区别",语义没变;但"产品A的价格是多少?"去掉问号后变成陈述句,检索时的意图识别可能出错。

我的原则是:只清洗无意义的字符,不动有语义的标点。问号、感叹号、引号这些有语义的标点要保留。只去除零宽字符、控制字符和连续空白。

4.3 坑三:缓存key设计不合理

缓存key如果只用一个query字符串,会出大问题。同一个query在不同知识库下的检索结果完全不同,如果key里不带知识库ID,就会串库。同理,如果嵌入模型换了版本,旧缓存就失效了,key里要带模型版本号。

我的缓存key设计是:{知识库ID}:{模型版本}:{query的MD5}。这样既避免了串库,也避免了模型升级后的脏缓存。

4.4 坑四:限流阈值拍脑袋定

限流阈值定太高等于没限,定太低会误伤正常流量。我的做法是先观察再限制:上线初期不开启限流,只做统计,观察一周的QPS分布,取P99值上浮20%作为限流阈值。

另外,限流要区分正常流量和异常流量。如果某个IP在短时间内发起大量相同query,那大概率是爬虫或重试风暴,应该单独处理,而不是简单限流。

4.5 坑五:日志打点不完整

RAG链路的日志如果只记录最终结果,排查问题时就是睁眼瞎。我要求日志必须记录:原始query、预处理后的query、检索到的文档ID列表、重排后的文档ID列表、生成模型的输入token数和输出token数、各环节耗时。

这些日志通过网关统一收集,打到Prometheus和ELK里。有了这些数据,任何效果波动都能快速定位到具体环节。

常见问题排查思路解决方法
检索结果不稳定对比不同时间的query日志检查预处理是否一致,缓存是否串库
响应时间突增看各环节耗时打点定位慢环节,检查下游服务状态
生成答案发散看重排后的文档列表降低重排TopN,提高相似度阈值
缓存命中率低看缓存key分布检查key设计,调整TTL
限流误伤看限流触发日志调整阈值,区分正常和异常流量

5. 关于RAG和网关配合的一些个人体会

这个项目做完之后,我又陆续参与了几个RAG相关的需求,有问答机器人、有文档助手、有智能客服。踩的坑多了,慢慢形成了一些自己的判断。

网关的价值在规模上来之后才明显。如果你只是做一个demo,或者日请求量不到一千,那网关带来的复杂度可能大于收益。但一旦请求量上来了,调用方多了,知识库多了,没有网关就是灾难。

RAG的效果上限由检索决定,下限由生成决定。检索决定了你能不能找到正确的文档,生成决定了你能不能把文档里的信息准确表达出来。网关能帮的是让检索和生成都稳定运行,但它改变不了两者的能力上限。

不要追求一步到位的完美架构。我的习惯是先跑通最小链路,然后根据实际遇到的问题逐步加组件。网关是这样,缓存是这样,限流也是这样。一开始就设计一个面面俱到的架构,大概率会过度设计,而且很多设计在实际场景下根本用不上。

最后分享一个我常用的调试技巧:在网关层加一个debug模式,当请求头里带X-Debug: true时,网关会把预处理后的query、检索到的文档、重排结果、生成输入全部返回给调用方。这个功能在排查问题时极其好用,比翻日志快得多。当然,debug模式要加权限控制,不能对所有人开放。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询