做了两年多RAG落地项目,我有个特别深的感受:很多团队把注意力全放在向量检索、rerank、chunk切分上,结果一上生产就发现,真正让系统变慢、变贵、变难维护的,往往是模型调用这层。AI网关就是专门解决这一层问题的。我们内部沉淀的一套方案叫MAI Gateway,定位是面向AI应用、尤其是RAG场景的模型流量治理网关。这篇文章会把这套方案的落地思路、核心模块、配置过程和排查经验完整写出来。如果你正在做RAG知识库、Agent应用,或者已经发现多个模型API混用之后调用链越来越乱,可以参考这里的做法。基础读者也不用担心,我会从问题讲起。
1. 为什么RAG落地需要AI网关:先看清问题
1.1 RAG架构里最容易被忽略的“接入层”
RAG的标准流程大家都很熟:用户提问,检索知识库,拿到相关内容,拼进Prompt,交给大模型生成回答。看起来只有四步,但真实生产环境里,第四步“交给大模型”远没有想象中简单。你至少要面对这几个问题:多个模型厂商的API不能混用;不同知识域对模型能力要求不同;线上流量有高峰,模型服务商容易被限流;相同或相似的问题反复请求,成本飞速上涨;出了问题需要排查,却很难追踪是检索失败还是模型回答故障。这些问题全都不在RAG算法层,而在模型接入层。
我习惯把这一层叫作“模型接入层”或者“AI网关层”。可以用一个生活类比来理解:如果RAG系统是一栋办公楼,向量库是仓库,Prompt工程是加工车间,那么大模型API就好比水电煤气——每间办公室都要用,但又不能每家都自己去拉管线,必须有一个集中管理的总闸。MAI Gateway在这里做的事,就是把这套总闸做成可配置、可观测、可灰度、可回滚。它不是帮你做检索,也不是替代模型,而是让“调模型”这件事变成一种基础设施能力。
1.2 没有网关时的四种典型混乱场景
先说第一种:代码里到处硬编码模型SDK。今天业务A直接调通义千问,业务B直接调DeepSeek,业务C又调了别的模型。看上去每个人都有自由度,实际上每个模型接口参数、鉴权方式、返回格式都可能有差异。等到模型厂商出问题或者要换更优模型时,你就要一个服务一个服务去改代码、发版,非常痛苦。
第二种是没有缓存。用户问“报销流程是什么”,上午问一遍,下午又问一遍,系统每次都把同样的问题、同样的知识片段发给大模型,每次都秒回,但每次都扣钱。我把这类流量叫做“重复生成流量”,它既不产生增量价值,还把成本白白推高。如果每天有一万次请求,重复率30%,每次生成2000 token,折合下来一年可能要多烧好几万。
第三种是一个模型provider抖动,整个RAG应用跟着崩溃。比如某个模型服务在高峰期超时,如果你没有隔离和降级,所有请求都卡在那里,线程池被占满,最后连健康检查都挂了。这时候就算检索质量再好,用户感知到的只是“系统又崩了”。
第四种是审计缺失。企业内部的知识库往往涉及合规问题:谁问了什么、系统给了什么回答、用了哪个模型、花了多少钱,都需要有据可查。没有网关层的话,日志散落在各个业务服务里,出问题只能靠猜。这四种场景一旦同时出现,RAG项目就很难真正生产可用。
2. MAI Gateway核心设计思路与选型
2.1 网关定位:不是反向代理,而是模型流量治理层
有人可能会问:Nginx、Kong这些API网关已经很成熟了,为什么还要单独做一个AI网关?我的回答是:它们关注的东西不一样。普通API网关按URL、Service、IP来做路由和限流,但它读不懂Prompt,算不了Token,也做不了语义缓存。AI网关更像一个专门为大模型流量设计的“语义层网关”,需要在协议层理解模型调用,而不是只做四层或七层转发。
MAI这个缩写,我们内部是这么解释的:Model Access & Integration Gateway,即“模型访问与集成网关”。它要解决的是一组更贴近业务的问题:选哪个模型、要不要命中缓存、每分钟消耗多少Token、这次的调用链路是否完整。它和传统网关不是替代关系,而是配合关系。外部流量可以先经过Nginx/Kong做基础接入,再由RAG编排服务调用内部的MAI Gateway去访问模型。这样普通网关负责南北向安全,AI网关负责模型流量的精细化治理。
2.2 关键模块拆解:路由、认证、限流、缓存、审计
MAI Gateway的核心由五个模块组成:Router、Authenticator、RateLimiter、Semantic Cache、Auditor。每个模块都对应一个实际痛点。
Router是模型路由,但它不是靠请求路径来做路由的。我们要求上游RAG服务在请求头里带上类似x-rag-domain这样的标签,比如legal、medical、general,网关根据标签选择不同的模型。这样做的好处是,模型选择与业务代码解耦,路由规则变更不需要重新发布应用。
Authenticator负责统一鉴权。上游业务不再直接持有模型厂商的API Key,而是使用网关分配的AK/SK。模型厂商的主Key只存放在网关环境变量或密钥管理服务里。这样即使某个业务服务被攻破,泄露的也只是网关子Key,可以单独回收,不影响全局。
RateLimiter是双层限流:一层限QPS,一层限Token。大模型厂商的计费和容量都跟Token有关,只看QPS远远不够。比如一个请求虽然只有10次每秒,但每次输出可能上万Token,照样能把上游容量打满。MAI Gateway会在Redis里维护滑动窗口,同时统计输入和输出Token。
Semantic Cache是语义缓存。RAG场景里用户问法变化很多,精确哈希缓存几乎没有意义。MAI Gateway会把用户问题和检索到的知识片段做向量化,用相似度判断是否命中缓存。命中之后直接返回历史生成结果,可以省掉一次大模型调用。具体策略后面我单独讲。
Auditor是全量审计模块。每次请求都会记录traceId、模型名称、Prompt大小、生成Token数、耗时、成本和返回码。这些日志不只在网关侧保存,还会通过消息队列送到审计平台,供合规和成本分析使用。
2.3 为什么选择自研而不是直接套用API网关
我不是说自研比开源好,而是想说明边界。通用API网关确实可以挂载插件完成一部分功能,但做久了你会发现,解析Prompt、计算Token、识别模型流式协议、做向量化缓存这些逻辑,塞进通用网关的插件体系里会很别扭。升级网关版本、调试插件、压测性能,全都会被绑在一起,边界很模糊。
我们选择独立做MAI Gateway,核心原因是希望有一条清晰的“模型治理边界”。模型厂商有变更,只改网关;新模型上线,只加Provider配置;想调整成本策略,只动路由规则。业务侧统一走OpenAI兼容接口,前面已经接好的代码几乎不用动。如果团队没有精力自研,也可以使用现成的开源AI网关项目,但架构上仍然建议把网关独立成一个服务,而不是塞进业务代码里。
3. RAG场景下的MAI Gateway行业落地方案
3.1 串联知识库检索与大模型调用的标准路径
在实际的RAG工程中,调用链路通常是这样:用户请求先进到RAG编排服务,编排服务先做向量检索和混合检索,拿到top-k知识片段,再组装Prompt,然后调用MAI Gateway,由网关转发给真正的大模型。网关并不取代检索服务,但它在两者之间承担了“模型调用总闸”的角色。这样设计的好处是,检索优化和模型策略优化互不影响,各司其职。
我们给业务侧的SDK大概长这样:
from mai_gateway.client import MAIGatewayClient client = MAIGatewayClient( base_url="http://mai-gateway:8080/v1", api_key="app_ak_xxx" ) resp = client.chat.completions.create( model="route:rag", # 网关内路由 messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query} ], headers={ "x-rag-domain": "legal", "x-rag-tenant": "tenant_01" } )关键在于model="route:rag",业务侧不关心实际用哪个模型,只告诉网关“这是RAG场景”。真正选哪个模型,由网关根据请求头里的domain、scene等标签动态决定。响应里我们会额外返回一个X-MAI-Trace-Id,方便后续把检索链路和生成链路串起来。
3.2 路由策略:按知识领域、成本、时延分发到不同模型
一个常见的RAG知识库可能同时包含法律条款、产品FAQ、技术文档和闲聊类问题。让所有问题都走同一个大参数模型,成本高又没必要。我们的做法是在网关里配置多条路由规则,按照知识领域分发。
下面是一个简化的配置示例:
routes: - name: legal-high-accuracy match: header: x-rag-domain value: legal provider: qwen-max priority: 10 - name: general-cheap match: header: x-rag-domain value: general provider: deepseek-chat priority: 5 providers: - name: qwen-max base_url: ${QWEN_BASE_URL} api_key_env: QWEN_API_KEY timeout_ms: 30000 max_retries: 1 - name: deepseek-chat base_url: ${DEEPSEEK_BASE_URL} api_key_env: DEEPSEEK_API_KEY timeout_ms: 15000 max_retries: 0这里有两个细节值得展开。第一,为什么用请求头而不是在网关里做语义分类?因为让网关再跑一次意图识别会带来额外的时延和成本,而且上游RAG服务在检索时通常已经知道知识领域了,把这个信息透传过来更准确。第二,priority字段表示匹配优先级,当同一个请求同时满足多条规则时,优先走法律类高精度模型。类似的需求还有按租户分流、按对话场景分流,本质上都是用元信息代替硬编码。
在Agentic RAG场景里,链路往往是多跳的:先判断问题属于哪个子知识库,再决定要不要调用工具,最后再汇总生成。这类请求通常需要模型具备较强的function calling和长上下文能力。我们会在请求头里增加x-rag-scene: agent,把这类流量单独路由到支持工具调用的模型上,避免被通用路由误伤。
3.3 缓存策略:让重复检索和相似问题不再打模型
语义缓存是RAG落地里被低估的模块。多数知识库产品有两类问题特别适合缓存:一是高频重复问题,比如“密码重置流程”“如何提请假”;二是语义相同但表达不同的变体问题,比如“报销流程是什么”和“我想知道怎么报销”。精确缓存对后者无效,但语义缓存可以。
MAI Gateway实现语义缓存时,会把请求中的用户问题做向量化,和缓存库里的历史问题比对,当相似度超过阈值时直接返回缓存内容。我们内部通常把cosine阈值设置为0.92,命中后响应头会带上X-MAI-Cache: HIT。缓存里不只有生成结果,还会带上知识来源引用,这样用户看到答案时仍然能知道出自哪个文档。
配置项大致如下:
cache: semantic: enabled: true threshold: 0.92 ttl_seconds: 600 include_search_refs: true需要注意两个坑。第一,如果知识库本身更新频繁,必须把知识版本号或者批次ID纳入缓存判断,否则用户会拿到旧答案。第二,涉及用户隐私或强动态数据的请求不要开启缓存,比如“我的订单到哪里了”,这类请求天然不该缓存。在我们的FAQ类知识库里,语义缓存命中率保持在25%到35%之间,整体响应时间能从3秒降到0.5秒以内,成本下降非常明显。
3.4 可观测性:从“检索命中率”到“端到端链路”
RAG社区常聊Hit Rate、MRR这类检索评估指标,但生产环境里光看这些是不够的。检索质量好不代表系统快、便宜、稳定。我们更需要一套能回答“模型调用慢在哪、钱花在哪、哪个Provider在抖动”的观测体系。MAI Gateway在这块会输出一组核心指标,再和RAG检索指标合并看。
| 指标名 | 来源 | 说明 |
|---|---|---|
| RAG Hit Rate | RAG检索服务 | 知识片段是否被正确召回 |
| LLM调用耗时 | MAI Gateway | 模型生成环节耗时 |
| 网关缓存命中率 | MAI Gateway | 语义缓存拦截掉的重复请求比例 |
| Provider错误率 | MAI Gateway | 上游模型服务稳定性 |
| 单次请求成本 | MAI Gateway | Token消耗与单价折算 |
| 端到端满意度 | 应用层 | 最终用户任务完成率 |
实现上,我们用OpenTelemetry为每条请求生成统一的traceId,网关响应头里返回这个id。RAG编排服务如果做了埋点,那么一次完整的“问题进来-检索-生成-返回”过程,就能在链路追踪系统里看到每一段的耗时。当用户投诉回答错误时,我先看trace,再查审计日志,很快就能定位问题到底出在检索环节,还是模型选择环节,而不是凭感觉排查。
4. 实操过程:从0到1部署MAI Gateway
4.1 环境准备与基础配置
这里给出一套最小可运行的部署方式,不依赖K8s,Docker Compose就能跑起来。需要的东西包括:一台Linux服务器或者本地Docker环境、Redis用于语义缓存和限流、一个数据库用于存审计日志(流量不大时PostgreSQL就够了)、以及模型厂商的API Key。如果你已经通过vLLM或Ollama本地部署了模型,也可以把它当作一个OpenAI兼容服务配置进网关。
启动配置大致如下:
services: mai-gateway: image: mai-gateway:1.0.0 ports: - "8080:8080" environment: REDIS_ADDR: redis:6379 AUDIT_DSN: postgres://user:pass@audit-db:5432/mai_gateway volumes: - ./config:/etc/mai-gateway depends_on: - redis - audit-db redis: image: redis:7 ports: - "6379:6379" audit-db: image: postgres:15 environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mai_gateway之所以把Redis和审计数据库单独拆出来,是因为网关本身要保持无状态,这样才能在流量增加时水平扩容。Redis一挂,缓存和限流会受影响,但网关仍然可以转发请求,只是成本控制和缓存能力会降级。审计数据库也不应该跟网关同一套存储,否则排查问题的时候会互相干扰。
4.2 配置一个RAG专用的透明接入层
下面是一份更完整的网关配置,它把一个RAG场景拆分成了两条路由:通用问答走便宜模型,Agent场景走支持工具调用的模型。你可以直接抄这份配置改一改:
server: listen: ":8080" providers: - name: qwen-turbo type: openai_compatible base_url: ${QWEN_BASE_URL} api_key_env: QWEN_API_KEY timeout_ms: 15000 max_retries: 1 - name: deepseek-chat type: openai_compatible base_url: ${DEEPSEEK_BASE_URL} api_key_env: DEEPSEEK_API_KEY timeout_ms: 20000 max_retries: 0 routes: - name: rag-general match: header: x-rag-domain value: general provider: qwen-turbo - name: rag-agent match: header: x-rag-scene value: agent provider: deepseek-chat cache: semantic: enabled: true threshold: 0.92 ttl_seconds: 600 limit: qps: 100 tokens_per_minute: 100000 audit: enabled: true driver: postgres这份配置读起来很直白:定义了两个Provider,定义了两条路由,开启了语义缓存、限流和审计。上游RAG服务只需要把domain或scene标签放到请求头里,网关就能自动选择合适的模型。后面如果需要接入新模型,比如本地私有大模型,只需在providers里增加一项,再在routes里把某个domain指过去,业务代码完全不用动。
4.3 灰度与回滚方案
RAG项目有三个极其容易打架的变量:知识库内容、检索参数、模型版本。如果同时改,出问题时很难归因。我们习惯用网关先隔离模型变量,具体方法是权重灰度。比如新模型上线时,先让10%的流量走新模型,90%继续走老模型,观察答案质量和耗时。
配置示例:
routes: - name: rag-general match: header: x-rag-domain value: general providers: - name: qwen-turbo weight: 90 - name: qwen-plus weight: 10如果新模型表现不好,把它的weight改成0,流量立刻全部回老模型。如果新模型只是某些知识域表现不好,也可以按路由规则只让特定domain走新模型,实现更精细的灰度。
另一个实用操作是给Provider增加status: drain。当某个Provider进入drain状态时,网关不会给它分配新流量,但已建立的流式请求会继续跑完。这比直接拔Key优雅得多,适合在模型服务升级或运维窗口期使用。从我们的实操经验来看,把灰度逻辑放在网关层,比放在业务代码里要省事很多,因为业务方不需要跟着发版。
5. 常见问题与排查实录
5.1 问题速查表
以下是我们在生产环境里遇到过的高频问题,整理成一张速查表,方便先定位再动手。
| 症状 | 可能原因 | 排查路径 |
|---|---|---|
| 所有请求变慢或大量超时 | Provider容量不足,或者模型服务超时配置太短 | 看upstream_latency和provider_error_rate,把异常Provider临时置为drain |
| 语义缓存命中率极低 | 相似度阈值太高,或embedding模型不一致 | 查缓存命中样本,统一向量模型与维度,把阈值降到0.88做对照 |
| 成本没有下降 | 网关只缓存了最终回答,但检索阶段仍然在重复执行 | 启用“检索引用+生成结果”组合缓存,再看审计日志里的prompt重复度 |
| 回答质量突然下降 | 灰度权重切到了参数更小的模型 | 查看审计日志里的model字段,对比路由权重,快速回滚 |
| 某些用户被限流误杀 | Token预估不准,或全局限流共享 | 按租户拆分限流配额,换用模型厂商官方Tokenizer |
这张表看起来简单,但每一条背后都有实际案例。比如限流误杀,在一次大促场景里,某个租户的调用量突然上涨,把全局Token配额打满,结果其它租户全被误伤。后来我们把limit改成按x-rag-tenant维度配置,问题才彻底解决。所以部署网关时,建议一开始就规划好租户维度,而不是等到出事故再改。
5.2 避坑经验:超时、重试、上下文截断
先说超时。大模型生成时间本来就不稳定,简单FAQ可能1秒返回,复杂推理可能20秒甚至更久。RAG场景里还要叠加检索耗时,如果网关默认超时设成5秒,你会发现大量正常请求被误杀。我们的经验是按路由设置超时:通用问答15秒,Agent多跳场景30到40秒,本地私有模型因为算力有限,超时要放得更宽。流式场景还要区分“首字延迟”和“总完成时间”,不要用总超时去卡流式连接,否则用户刚看到第一个字就被断连。
再说重试。面对Provider抖动,很多人会不假思索地加重试,实际上这是最危险的操作。一个Provider故障时,如果每个请求都重试2次,网关透出的流量会变成原来的3倍,反而把故障扩大。我们只对幂等请求做一次重试,而且必须等待至少1秒的退避时间。生成类请求本身就很难保证幂等,所以重试策略一定要保守。
最后是上下文截断。RAG系统经常把多个知识片段塞进Prompt,再加上对话历史,很容易超出模型上下文窗口。如果网关不去干预,模型可能会把排在后面的关键资料直接忽略,导致“一本正经胡说八道”。我们在网关层做了一道保护:转发前估算Prompt的Token数,如果超过模型max_tokens的一定比例,直接返回告警给RAG编排服务,让上游压缩历史或裁剪知识片段,而不是把截断风险丢给模型。这样才能保证用户看到的答案是基于完整资料生成的。
5.3 别把RAG瓶颈全归到检索上
最近社区里聊RAG瓶颈聊得很多,知识割裂、GraphRAG、Ontology RAG、Agentic RAG这些概念都很热。它们解决的是“怎么让模型在更复杂的知识结构里找到信息”,这确实是检索侧的重要升级。但我们也观察到,一旦上了GraphRAG或Agentic RAG,模型调用次数会明显增加。比如一次多跳检索可能要连续调用模型做判断,再汇总生成,调用量是普通RAG的好几倍。
这时真正的瓶颈很可能从“检索质量”转移到了“模型调用治理”。没有统一的网关,多跳调用的耗时、成本、错误处理都会变成新一轮混乱。所以我认为,把知识割裂问题交给检索层去解决,把模型流量治理问题交给AI网关层去解决,是一个比较清晰的分工。这并不冲突,而是递进关系。MAI Gateway就是我们在这一递进关系里补上的重要一环。
最后说一点个人体会。我见过不少团队一上来就调embedding模型、换GraphRAG、上rerank,把检索指标刷得很漂亮,但上线后用户体感还是差。后来排查发现,大部分超时和费用问题都出在模型调用层。RAG项目越往后做,越会意识到“模型流量不可控”才是最大的隐性瓶颈。MAI Gateway这套方案不一定非得自研,你可以先用一个轻量的网关把语义缓存和路由做起来,再逐步加审计和灰度。顺序很重要:先让模型调用可管,再做检索优化,效果才会被真正放大。希望这篇文章对正在做RAG落地的朋友有参考价值。