企业级 AI 网关的职责是把多家大模型接入统一收敛,整体架构可以划分为模型抽象层、模型适配层、路由治理层、会话管理层与可观测层五个核心模块。Spring AI 的 ChatModel 接口把 OpenAI、DeepSeek、通义千问等厂商差异屏蔽在适配器里,业务服务只依赖统一接口;网关在此基础上叠加模型路由、降级熔断、流式转发与用量计量,才能稳定扛住生产流量。
一、五个核心模块的职责划分
| 模块 | 职责 | 关键实现 |
|---|---|---|
| 接入层 | 对外暴露统一 HTTP / SSE 接口 | Spring MVC + SseEmitter |
| 模型抽象层 | 屏蔽厂商差异,定义统一调用契约 | Spring AI ChatModel、EmbeddingModel |
| 模型适配层 | 把统一请求翻译成各厂商私有协议 | OpenAiChatModel、QwenChatModel 等 |
| 路由治理层 | 模型路由、降级、重试、熔断 | RoutingChatModel + Resilience4j |
| 会话管理层 | 多轮对话上下文维护 | ChatMemory |
| 可观测层 | 用量计量、链路追踪、调用日志 | 结构化日志 + 指标采集 |
六个模块可以归成一条主线:接入层是入口,抽象层和适配层解决「怎么调」,路由治理层解决「调哪个」,会话层解决「带什么上下文」,可观测层解决「调得怎么样」。
二、模块之间的交互链路
2.1 一次流式对话的完整流程
- 客户端请求到达接入层,先过鉴权与限流,校验通过后建立 SSE 连接;
- 路由治理层按策略解析目标模型,策略包含优先级、成本、可用性与数据合规要求;
- 模型抽象层调用对应适配器发起流式请求,拿到响应数据流;
- 适配器把厂商的 token 增量翻译成统一结构,接入层逐段转发给前端渲染;
- 对话结束写入用量记录,包括 token 数、耗时、模型标识与结果状态;
- 触发降级条件时,路由层自动切换到备选模型重发,前端无感知。
2.2 模型路由如何落地
@Component
public class RoutingChatModel implements ChatModel {
private final Map<String, ChatModel> models; // modelId -> 厂商适配器
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
String modelId = resolveModel(prompt); // 按策略解析目标模型
return models.get(modelId).stream(prompt)
.timeout(Duration.ofSeconds(30))
.onErrorResume(e -> models.get("fallback").stream(prompt));
}
}
路由层把「选哪个模型」从业务代码里抽出来,优先级、成本、可用性都在这里集中决策。默认请求走便宜模型,敏感查询走强模型,供应商故障时沿降级链切换,业务服务完全不感知。
2.3 会话上下文如何传递
多轮对话的上下文不是存一个无限增长的数组。会话管理层按用户维度维护窗口,超出长度限制时做截断或摘要压缩,再拼进下一轮请求。上下文与模型绑定同一把会话 ID,路由切换时带上同一份历史,用户感知不到换模型,只会在回答风格上有所察觉。压缩策略按对话类型区分,代码问答保留报错栈,闲聊场景直接丢弃早期轮次,避免上下文被无效内容挤占。
2.4 容错与错误归一化
降级链按 OpenAI → DeepSeek → 通义千问的顺序排列,配合指数退避重试和熔断器,避免一家供应商故障拖垮整条链路。厂商错误码先归一化成统一异常,再映射成标准 HTTP 状态返回,前端不必为每家厂商维护一套错误表。归一化的粒度也要控制:超时、限流、内容审查是三类高频错误,分别映射不同状态码和提示文案;厂商侧的 5xx 与 4xx 要分开处理,4xx 说明请求本身有问题,重试没有意义,直接返回调用方。
三、为什么用网关收口而不是各服务直连
| 维度 | 各服务直连模型 | 网关统一收口 |
|---|---|---|
| 模型切换 | 改 N 个服务的代码 | 网关配置一处改 |
| 密钥管理 | 散落在各服务配置 | 集中在网关保管 |
| 用量计量 | 各服务口径不一 | 统一计费与审计 |
| 容错 | 每个服务各写一套 | 网关统一降级熔断 |
| 供应商锁定 | 业务代码耦合厂商 SDK | 只依赖统一接口 |
模型抽象层的价值在切换场景里最明显:新增一家厂商只写一个适配器并注册路由,业务代码零改动。代价是每次调用都要在统一模型和厂商私有格式之间做一次转换,对毫秒级延迟敏感的场景能感知到,通常被网络延迟覆盖。网关收口还带来一个运营侧收益:供应商账单可以直接对照网关里的用量记录逐笔核对,不用猜某个服务把流量打到了哪家。企业同时采购多家模型时,同一份调用量按可用性、成本、质量三个口径拆表,为后续配额调整和商务谈判提供依据。
四、流式对话的工程细节
SSE 的单向通道足够覆盖对话场景,浏览器原生支持自动重连,比 WebSocket 少一层协议维护成本。数据流必须加超时保护,模型响应延迟波动大,不加超时一个慢请求就可能拖住连接池。接入层要按用户维度维护连接池,心跳用注释行维持,避免网关在空闲窗口掐断连接;超时设置过短会误伤慢模型,过长又占住连接池,项目里按模型分别配置。日志只记录首包耗时与总 token 数,逐 token 打印会淹没日志系统。每次调用落一条可观测记录,包含模型、token、耗时与状态码,成本和质量的核算都从这条记录出发。
常见问题(FAQ)
Q1:新增一家模型厂商要改业务代码吗?
不用,实现适配器并注册到路由即可。
Q2:模型降级后对话上下文还一致吗?
降级请求携带同一对话历史,结果可能不同但链路完整。
Q3:网关会成为性能瓶颈吗?
转发链路很轻,瓶颈在模型本身,配合异步即可。