spring-projects/spring-ai:Spring 官方的 AI 应用开发框架¶
定位说明:本文技术栈是 Java/Spring,与”Go 后端”岗位的源码关联度较低。收录它主要是为了借鉴 Spring AI 用 AOP/Advisor 模式解耦横切关注点(RAG 检索、会话历史、审计)的设计思想——这套思想在 Go 侧可以类比为中间件链或装饰器模式。非 Go 岗面试的必读源码,跨语言的架构思路可以选读。
先用大白话说清楚它是什么:Spring AI 是 Spring 官方出的 AI 应用开发框架。如果你的团队本来就是 Java/Spring 技术栈,这是目前最顺手的接入大模型的方式——不用另起一套 Python 服务,也不用自己重新搭一层跟主体系统脱节的可观测性、权限和限流体系。它的核心思路很朴素:用 AOP(面向切面编程)把 RAG 检索、对话历史拼接、审计日志这些逻辑从业务代码里摘出去,变成可插拔的拦截器(Spring AI 里叫 Advisor),再配合 Spring Boot 一贯的自动装配风格,让你少写很多胶水代码。
0. 最小示例:先看最简单的调用方式¶
在讲企业级特性之前,先看 Spring AI 最基础的用法——不涉及 RAG、不涉及历史记录,纯粹是“发一个 Prompt,拿到回复”:
@Autowired
private ChatClient.Builder chatClientBuilder;
public String askSimple(String question) {
ChatClient chatClient = chatClientBuilder.build();
// 最基础的调用:一行 Prompt,一行返回
return chatClient.prompt()
.user(question)
.call()
.content();
}
这几行代码背后,ChatClient.Builder 已经是 Spring Boot 自动装配好的 Bean——接哪个厂商的模型、用什么 API Key,都在 application.yml 里配置,业务代码完全不用关心这些细节。
但企业级场景通常不会只是“问一句答一句”:还得把用户问题和向量库里的资料拼起来做 RAG、记住多轮对话的历史、审计每一次调用。如果这些逻辑都直接堆进业务代码,askSimple 这样的方法很快会膨胀成几十行夹杂着检索、拼接、日志的意大利面代码。Spring AI 解决这个问题的方式,是引入了 Advisor(顾问链)架构。
1. 核心设计思路:用 AOP 把 RAG、历史拼接、审计这些逻辑从主线摘出去¶
Spring AI 的核心架构精髓在于 Advisor(顾问链)。说人话就是:它借用了 Spring 开发者已经很熟悉的 AOP(面向切面编程,Aspect-Oriented Programming)思路——业务代码里只写“发什么 Prompt、要什么结果”,至于要不要去向量库检索资料、要不要把历史对话拼进去、要不要记审计日志,这些横切关注点(Cross-cutting Concerns)由一条 Advisor 链在背后自动完成,业务代码对此完全无感知。
客户端请求 ────────────────────────────────────────────────────────┐
│ │
v v
+────────────────────────────────────────────────────────────────────────+
| ChatClient.call() 执行管道 |
| |
| [Advisor 1: SafeGuardAdvisor] --> 拦截 Prompt 执行输入风控审计 |
| │ |
| v |
| [Advisor 2: QuestionAnswerAdvisor (RAG)] |
| ├──> 自动调用 VectorStore.similaritySearch(Query) |
| └──> 将召回的 Document 列表注入 Prompt 上下文 |
| │ |
| v |
| [Advisor 3: MessageChatMemoryAdvisor] --> 自动从 ChatMemory 读取历史并拼装 |
| │ |
| v |
| [LLM Client (OpenAI/Azure)] ──> 执行物理网络 HTTP 调用 ───────> 返回结果 |
+────────────────────────────────────────────────────────────────────────+
版本提示:早期里程碑版本中,
QuestionAnswerAdvisor、MessageChatMemoryAdvisor等 Advisor 支持直接new构造实例;当前 GA 版本统一改为静态 Builder 模式(如QuestionAnswerAdvisor.builder(vectorStore).build()),且会话历史 Advisor 的正式类名是MessageChatMemoryAdvisor(而非MessageChatHistoryAdvisor)。下文代码按当前 GA 版本 API 书写。
具体这条链是怎么运作的?以最常用的 RAG Advisor 为例:
1.1 QuestionAnswerAdvisor (标准 RAG Advisor) 运行原理¶
当在 ChatClient 中挂载了 QuestionAnswerAdvisor 后,每次发起对话:
1. 切面拦截:在发送请求前,Advisor 拦截原始的 Prompt。
2. 语义检索:自动调用关联的 VectorStore 对用户 Question 进行语义距离检索。
3. 上下文重构:将检索出来的 List<Document> 按指定的模板格式化,动态替换掉 Prompt 中的 {documents} 占位符,完成上下文增强。
4. 下发通信:重构完成后,才将请求交还给物理 LLM Client 发出,对上层业务代码实现了完全的无感知检索注入。
2. 自动装配与监控:让大模型调用接入已有的运维体系¶
光有 Advisor 链解决的是业务逻辑解耦的问题,工程上还有两个现实问题:密钥和连接怎么配置管理、调用产生的延迟和 Token 消耗怎么监控。Spring 一如既往地用它标志性的两件套来解决——自动装配(Auto-configuration)和基于 Micrometer 的可观测性体系。
- 声明式环境配置 (Properties & Secrets):
利用 Spring Boot 的
AutoConfiguration,只需在application.yml中声明配置,框架会自动装配 Connection Pool、SSL 双向证书并实例化OpenAiChatModelBean,杜绝了凭证硬编码风险。 - 企业级可观测性 (Micrometer & OpenTelemetry):
Spring AI 默认深度集成
Micrometer。每次模型调用都会自动输出吞吐率、Token 消耗率、TTFT 延迟指标,并配合 Spring Cloud Sleuth/Zipkin 自动生成带 Trace ID 的分布式调用链,这在企业微服务调试中是无可替代的优势。
3. 基于 Spring AI 的生产级 RAG 控制层(Java 实践)¶
把前面两节的 Advisor 链、自动装配和可观测性放在一起,实际项目里长什么样?以下展示了在 Spring Boot 中构建具备 Advisor 链与 PGVector 检索的强集成 Java 代码范式:
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class EnterpriseRAGController {
private final ChatClient chatClient;
// 1. 通过构造器注入自动配置的 ChatClient.Builder、pgvector VectorStore 与 ChatMemory 实例
@Autowired
public EnterpriseRAGController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore, ChatMemory chatMemory) {
// 2. 声明式配置 ChatClient 链条,注入 AOP Advisors(当前 GA 版本统一使用静态 Builder 构造)
this.chatClient = chatClientBuilder
.defaultSystem("You are a professional SecOps assistant. Answer based strictly on context.")
.defaultAdvisors(
// 挂载会话历史 Advisor:自动维护会话历史,先于 RAG 检索执行
MessageChatMemoryAdvisor.builder(chatMemory).build(),
// 挂载 RAG Advisor:自动执行向量相似度检索并重写 Prompt
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.build();
}
/**
* 流式响应企业审计问答接口,具备自动 AOP 检索与历史追溯
*/
@GetMapping(value = "/ask", produces = "text/event-stream")
public Flux<String> askStream(@RequestParam("query") String query, @RequestParam("sessionId") String sessionId) {
// 3. 业务代码极其干净,不显式编写任何检索或历史拼接逻辑
return this.chatClient.prompt()
.user(query)
.advisors(a -> a
.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "tenant_id == 't_10086'") // 动态过滤
.param(ChatMemory.CONVERSATION_ID, sessionId) // 会话 ID 为必传参数,缺省会抛出 IllegalArgumentException
)
.stream()
.content(); // 流式返回 Token 片段
}
}
4. 生产级故障演进与系统调优¶
| 故障现象 | 底层诱因 | 系统级表现 | 预防与排查手段 |
|---|---|---|---|
| AOP 链路循环引用 / 堆溢出 (OOM) | 会话历史 Advisor 疏于管理,导致无限追加历史消息,超出大模型上下文硬边界。 | Java Heap 内存爆满,频繁发生 FGC(Full GC)引发 P99 时延失控,最后抛出 OOM。 | 1. 对 MessageChatMemoryAdvisor 所用的 ChatMemory 实现配置窗口/淘汰策略,避免历史无限增长。2. 及时执行垃圾回收。 |
| 异步取消断开信号断裂 | 客户端主动关闭了 HTTP 连接,但底层 Reactor 流没有接收到 Cancel 信号。 | 后台依然持续向大模型发起流式计费请求,白白损耗高昂的 Token 费用。 | 1. 核心控制器必须采用响应式生态(Spring WebFlux)。 2. 确保 Reactor 管道与物理 HTTP Client(如 Netty/WebClient)生命周期完全对齐。 |
| HTTP 连接池干涸 | 自动配置的 WebClient 连接数较小,无法应对高并发大模型调用。 |
请求挂起,并抛出 HttpClientErrorException: ... 或连接获取超时错误。 |
1. 调大 spring.ai.openai.client.connection-pool 最大限制。2. 实施超时与断路器机制(如 Resilience4j)。 |
5. 资深系统架构师面试表达方案¶
面试提问:在企业级后端架构中,为什么要选用 Spring AI 而不是 Python 的 LangChain?它的底层 Advisor 切面架构解决了哪些痛点?
回答模版: 我们是一个纯 Spring 技术栈的团队,一开始也评估过用 Python 的 LangChain 搭一层单独的 AI 网关服务,但很快发现这条路要重新搭一套跟主体系统割裂的可观测性、限流和权限体系,维护成本比想象中高。Spring AI 吸引我们的地方,其实就是它没有发明新的一套东西,而是把 RAG 检索、历史拼接这些逻辑套进了 Spring 熟悉的 AOP Advisor 模式里——业务代码里只看得到 Prompt 和返回值,检索和历史维护是 Advisor 链自己在背后做的。
不过 Advisor 这套东西也不是没有代价。我们上线不久就遇到过一次 Full GC 频繁的问题,查下来是 MessageChatMemoryAdvisor 绑定的会话历史没做淘汰策略,长会话的用户历史一直在攒,Heap 被慢慢吃满。后来给 ChatMemory 加了窗口大小限制才算解决。Micrometer 和 Trace ID 这套集成倒是省心,大模型调用的延迟和 Token 消耗能直接接进我们原来的监控大盘,不用另外搭一套指标体系——这大概是 Spring AI 相比 Python 方案对我们团队最实际的加分项。