Spring AI 是 Java 生态里把大模型接进 Spring Boot 应用的标准答案。这篇核心知识总结以 2024 年的 1.0 里程碑版本为基准,把模型接入、Prompt 管理、RAG、Function Calling、Agent 化改造这些绕不开的组件一次讲清楚,避免新手在堆满 OpenAI SDK、LangChain 和自研胶水代码之间来回折腾。适合刚接触 AI 应用开发的后端工程师、想把已有 Spring Boot 服务快速接入大模型的技术负责人,也适合从 Python 的 LangChain 生态想回迁到 Java 的技术人员。
先说结论:Spring AI 不是单纯给 Java 开发者再加一层“调 API 的封装”,而是把 model、prompt、memory、vector store、tool 这些 AI 应用里的常用零件,统一成一套 Spring 风格的编程模型。你不需要今天对接 OpenAI、明天对接 Ollama、后天对接通义千问,就各写一套 HTTP 调用逻辑。Spring AI 替你封装好接口差异,留下的是你熟悉的 ChatClient、Advisor、与 Spring Boot 自动配置。我用 2024 年大半年时间在几个真实项目里把这套东西跑顺之后,发现它最值钱的地方不是“省代码”,而是让 AI 逻辑变成了可以单元测试、可替换、可监控的后端资源。
1. 先搞清楚 Spring AI 到底解决了什么问题
1.1 从“手动调模型”到“模型抽象层”
Java 接入大模型,最早的做法非常原始:写一个 OpenAI SDK 调用,在业务代码里拼 Prompt、解析 JSON、把历史消息塞进数组,再靠 HttpClient 或者 RestTemplate 发请求。这套东西能用,但一旦项目里有多个模型、多个业务场景,问题立刻暴露。
首先是模型切换成本高。你用了 OpenAI 的 ChatCompletion 结构,下次想换成通义千问或者本地 Ollama,并不是改一个 base-url 就行,连请求体格式、流式返回方式、参数命名都要跟着改。其次是 Prompt 和上下文管理完全失控。每次都要手动维护 System Prompt、历史消息、工具返回结果,稍微复杂一点的业务很容易在 Controller 里堆进两百行 prompt 拼接代码。
Spring AI 做的事情,是把这些统一成一层“模型抽象”。它借鉴了 Spring 生态一贯的思路,用 ChatModel 作为统一入口,底层各厂商的差异在自动配置里消化掉。对你来说,只面对一个 ChatClient,配置项也是同一套。这有点像 JDBC 的意义:你只知道 Connection、PreparedStatement,至于连的是 MySQL 还是 PostgreSQL,由驱动和连接串决定。
这套设计带来的直接好处,是业务代码不用绑死某个模型厂商。2024 年大模型价格和效果变化极快,今天用 A 模型跑得挺好,明天 B 模型上线效果更好、价格更低,切换就是改配置和重新评估 prompt,不用推倒重写代码。
1.2 核心模块一眼看全
Spring AI 的模块划分,在 2024 年已经基本定型。最核心的是 spring-ai-core,它定义 ChatModel、EmbeddingModel、Prompt、Message、Document、Advisor 这些抽象。上层按模型厂商拆分,比如 spring-ai-openai、spring-ai-ollama、spring-ai-azure-openai、spring-ai-anthropic,国内用得多的还有 spring-ai-alibaba 对接阿里云百炼。
除了模型接入,还有一个容易忽视的模块是 spring-ai-document-reader,专门读取 PDF、Markdown、Word、TXT 等文件为文档对象,是 RAG 数据管道的起点。再往下是 spring-ai-vector-store 的适配层,支持 PGVector、Redis、Elasticsearch、Milvus、Chroma 等常见向量库。功能上最强的部分是 spring-ai-function-calling 和 spring-ai-agent 相关扩展,可以把 Java 方法暴露给模型自动调用。
我的建议是,第一次接触不要试图把所有模块都学一遍。先抓住一条主线:ChatClient + Prompt + ChatModel 打通对话,VectorStore + QuestionAnswerAdvisor 打通 RAG,@Tool 打通工具调用。这条线走通之后,其他的都是锦上添花。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念拆解:ChatClient、Advisor 与 Tool Calling
2.1 ChatClient 是新的编程入口
Spring AI 1.0 时代最值得关注的 API 是 ChatClient。它是类似 RestClient、WebClient 一样的 Fluent 接口,用来代替早期直接注入 ChatModel 的方式。
一个最简单的调用是这样:
java复制@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String ask(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
如果你用过 Spring 生态的 RestTemplate 或者 WebClient,这个风格几乎零学习成本。prompt() 负责构建一个 Prompt,里面可以带 System 消息、User 消息、历史消息和各类参数;call() 是同步调用,stream() 则是流式返回 Flux<String>,适合 SSE 场景输出打字机效果。
要注意的是,ChatClient.Builder 建议通过构造器注入,容器会自动帮你配置。别在代码里手动 new,否则你没法享受 Spring AI 的自动装配,Advisor、默认记忆、默认参数都会失效。我在项目里见过有人为了图省事,每个方法里都 ChatClient.builder(openAiChatModel).build(),结果不仅浪费连接,还绕过了全局配置。
2.2 Advisor 链与 ChatMemory
Advisor 是 Spring AI 里最有“Spring 味”的设计。你可以把它理解成 Spring MVC 里的拦截器或者 AOP 通知,它会在模型调用前后插入逻辑。
最常见的使用场景是对话记忆。如果不做任何处理,每次调用 ChatClient 都是无状态的,模型完全不记得上一轮说了什么。传统做法是把历史消息手动塞进 Prompt,代码很啰嗦。用 MessageChatMemoryAdvisor 可以自动完成这件事:
java复制ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(messageMemory))
.build();
这里 messageMemory 是一个 ChatMemory 实现,Spring AI 默认提供了基于内存的 InMemoryChatMemory,也可以接入 Redis 做持久化。Advisor 会在调用模型前从 memory 里取出最近 N 条消息加入到 Prompt,调用后再把本次对话写回 memory。
我实际用下来觉得,Advisor 机制最大的价值是“可组合”。你可以把 RAG 检索、内容审核、敏感词过滤、多轮记忆分别做成不同的 Advisor,然后按照业务需要叠加。比如一个客服系统,就是 QuestionAnswerAdvisor 加 MessageChatMemoryAdvisor 加自定义的 SafeGuardAdvisor,顺序影响结果,这个顺序是可以调整的。
2.3 Function Calling 与结构化输出
Function Calling 是让模型具备“行动能力”的关键。不是让模型输出一段 JSON 让你自己解析执行,而是让模型在需要时直接调用 Java 方法,拿到结果后再生成回答。
在 Spring AI 里,定义一个工具非常简单:
java复制@Component
public class OrderTools {
@Tool("根据订单号查询订单状态")
public String getOrderStatus(String orderId) {
// 查数据库 / 调接口
return orderService.findStatusById(orderId);
}
}
然后在 ChatClient 里开启工具注册:
java复制ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(OrderTools.class)
.build();
String answer = chatClient.prompt()
.user("订单 20240112 现在什么状态?")
.call()
.content();
模型会根据用户问题判断需要调用哪个 tool、需要传什么参数,Spring AI 负责把调用请求转换成对应 Java 方法调用。这个过程对业务代码是透明的。
再配合结构化输出,可以让模型按要求返回对象而不是纯文本:
java复制ChatClient chatClient = ChatClient.builder(chatModel).build();
OrderInfo orderInfo = chatClient.prompt()
.user("把这段话里的订单号、金额、状态提取出来")
.call()
.entity(OrderInfo.class);
实现原理是 Spring AI 自动为 OrderInfo 生成一个 JSON Schema,要求模型按这个结构返回,再通过 BeanOutputConverter 反序列化。这个功能非常实用,尤其做信息抽取、表单自动填充、数据清洗时,能省掉大量解析 Prompt 输出字符串的工作。
3. 手把手实现一个带记忆和 RAG 的问答接口
3.1 项目搭建与依赖配置
假设你有一个 Spring Boot 3.2 以上的项目,接入 Spring AI 的第一步是引入对应 BOM 和模型 starter。以 OpenAI 为例,2024 年的坐标大致如下:
xml复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
再引入模型 starter:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
配置在 application.yml 里:
yaml复制spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: ${OPENAI_BASE_URL}
chat:
options:
model: gpt-4o-mini
temperature: 0.7
要提醒三点。第一,api-key 千万别写死在 yml 里,用环境变量注入。第二,如果你在本地用 Ollama 跑模型,就换成 spring-ai-starter-model-ollama,然后设置 spring.ai.ollama.base-url=http://localhost:11434。第三,模型名称要确认,默认值不一定是你能调的模型,写一个不存在的模型名会直接报 404 或者 400。我踩过这个坑,厂商平台对模型名的校验比想象中严格。
3.2 数据准备与 VectorStore 接入
RAG 的完整链路是:读取文档 → 切分 → 向量化 → 存入向量库 → 检索 → 拼进 Prompt。Spring AI 里每一步都有对应组件。
先把文档读进来并切分:
java复制var reader = new PagePdfDocumentReader("classpath:/docs/product-manual.pdf");
var documents = reader.get();
var splitter = TokenTextSplitter.builder()
.withChunkSize(400)
.withChunkOverlap(50)
.build();
var chunks = splitter.apply(documents);
PagePdfDocumentReader 是按页读取 PDF,TokenTextSplitter 按 token 数量切分,chunkOverlap 用来保留上下文衔接。中文场景我建议 chunkSize 不要太大,300~500 token 之间比较稳,太大容易把不相关内容混在一起,检索精度会下降。
向量库这里我用 SimpleVectorStore 做示例,它不需要额外部署,适合本地测试:
java复制@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
SimpleVectorStore store = SimpleVectorStore.builder(embeddingModel).build();
return store;
}
}
生产环境不要用 SimpleVectorStore,它默认不持久化,重启数据就丢了。推荐换 PGVector 或者 Redis,配置好数据库连接和索引,默认的 API 是兼容的,代码不需要大改。
3.3 代码实现与调用效果
把向量库和 ChatClient 组合起来,就是一个最简 RAG 问答接口:
java复制@Service
public class QaService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public QaService(ChatClient.Builder builder, VectorStore vectorStore) {
this.vectorStore = vectorStore;
this.chatClient = builder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
}
public String answer(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
QuestionAnswerAdvisor 会自动做一件事:在真正调用模型前,先把用户问题拿去做向量检索,从 vector store 里找出最相关的文档片段,然后把片段作为上下文附加到 Prompt 里。这样模型回答时,就能参考文档内容而不是凭空编造。
在 Controller 里暴露接口:
java复制@RestController
@RequestMapping("/qa")
public class QaController {
private final QaService qaService;
public QaController(QaService qaService) {
this.qaService = qaService;
}
@GetMapping("/ask")
public String ask(@RequestParam String question) {
return qaService.answer(question);
}
}
我实际跑通后第一感觉是:代码量确实少,但陷阱也不少。比如向量检索默认的 topK 是 4,意味着只取最相似的 4 段文档。如果你的文档本身切割得不好,正确答案恰好没进 topK,模型就答不出来。这时候不要急着调 Prompt,先看看检索结果里到底有没有相关片段。
4. RAG、Agent 与 Alibaba 生态的落地经验
4.1 RAG 的完整链路与实战调优
RAG 看着很简单,真正落地时细节非常多。从文档解析开始就有坑:PDF 里如果是扫描件,不经过 OCR 直接读取,出来的全是乱码;Word 文档带分页符和页眉页脚,不清理会污染切分结果。Spring AI 提供了多种 Reader,但你不要指望一个 Reader 处理所有格式。
分块策略是另一个重点。TokenTextSplitter 只是按固定大小切,不会理解语义。对于结构清晰的文档,更推荐先按章节标题切,再用 TokenTextSplitter 做二次切分。我在项目里对内部知识库的做法是:先把 Markdown 按 #、## 拆成段落,再对超过 800 token 的段落继续切,overlap 控制在 50 token 左右。这样既能保留语义边界,又能保证片段长度合适。
检索环节有两个参数需要调:topK 和相似度阈值。topK 决定取多少候选片段,阈值决定哪些片段算“足够相似”。在 Spring AI 里可以通过检索请求参数设置:
java复制SearchRequest request = SearchRequest.builder()
.query(question)
.topK(6)
.similarityThreshold(0.5)
.build();
QuestionAnswerAdvisor 也支持传入 SearchRequest 作为构造参数。这组值不要盲抄,一定要拿一批真实问题去做回归测试。我见过有人把阈值设到 0.9,结果大部分问题都检索不到内容,模型就只能说“不知道”;阈值太低又会导致上下文里塞满无关片段,回答跑偏。
最后是重排。如果文档量大、候选片段多,可以在检索之后加一个 rerank 步骤,用更强大的模型对候选片段重新排序。Spring AI 本身不直接提供 rerank 器,但它有足够灵活的接口让你在 Advisor 里插入这一步。对于严肃的业务场景,我建议直接上 rerank,别省。
4.2 Agent 化改造:从 Dify 工作流到 Spring AI 代码
2024 年很多人是先用了 Dify、Coze 这类低代码平台拖了几个 AI 工作流,然后发现业务要整合进现有 Java 系统时很别扭。社区里“把 Dify 工作流转成 Spring AI Java 代码”的讨论热度一直不低,GitHub 上也有不少人在做类似脚手架。
我的观点是:Dify 工作流本质上描述的是“状态、步骤、工具调用”的组合,这些东西用 Spring AI 的 @Tool 加 Advisor 天然就能对应上。比如你在 Dify 里做了一个“查订单 → 判断是否可退款 → 执行退款”的工作流,翻译成 Spring AI 就是三个 Java 方法:
java复制@Component
public class AfterSaleTools {
@Tool("根据订单号查询订单信息")
public String getOrder(String orderId) { ... }
@Tool("判断订单是否满足退款条件")
public boolean checkRefundable(String orderId) { ... }
@Tool("执行退款操作")
public String refund(String orderId) { ... }
}
然后让模型作为“流程编排者”,根据用户意图自行决定调用哪些工具、按什么顺序调用。这比硬编码工作流要灵活,也比低代码平台更容易测试和维护。
要注意的是,Agent 化不是银弹。模型做工具调度的成功率不是 100%,尤其是多步骤、强状态的流程,模型可能跳步或顺序混乱。我的经验是:关键业务步骤不要让模型自由发挥,可以用 Spring AI 的 AbstractChatService 或自定义 Advisor 来约束流程。Agent 适合开放式、步骤不固定的场景,固定业务流程老老实实用 BPM 或者状态机。
4.3 Spring AI Alibaba 与百炼模型的接入问题
国内项目里,大量团队是把 Spring AI 用在阿里云百炼上,因此 spring-ai-alibaba 的关注度一路走高。你会发现这个项目是 Spring AI 官方仓库之外,国内社区最活跃的一个适配层,对接的是 DashScope 和百炼平台的通义系列模型。
接入方式和标准 OpenAI 几乎没有区别。引入对应的 starter,配置好 api-key 和模型名,其余代码不变。2024 年到 2025 年这段时间,社区里出现过“Spring AI Alibaba 是不是停更了”的疑问。我观察下来,它并不是停更,而是很多能力在往 Spring AI 主线合并,同时阿里云更多在维护自己的 Alibaba Cloud AI 生态,节奏看起来慢了。如果你担心依赖风险,建议关注两个点:一是官方仓库的 commit 活跃度,二是自己项目的核心代码是否高度依赖某个厂商专属特性。只要你的代码只依赖 ChatModel、ChatClient 这些抽象,底层换个适配层成本很低。
如果你要同时跑国内百炼和国外模型,也完全可以。Spring AI 是按 ChatModel Bean 来区分模型的,你可以注册两个不同的 ChatModel,然后用不同的 ChatClient 指向各自场景。不需要一个应用只有一个模型入口。
5. 常见问题与排查技巧实录
5.1 问题速查表
整理了一个我在社区答疑和被内部分享时反复讲到的速查表,方便你直接对照:
| 现象 | 常见原因 | 处理建议 |
|---|---|---|
| 调用报 401 | API Key 错误、环境变量没注入 | 检查配置来源,不要在代码里硬编码密钥 |
| 调用报 404 | 模型名称不存在或未开通权限 | 确认模型 ID,先在平台控制台手动跑通 |
| 请求超时 | 网络问题、模型响应耗时较长 | 调大客户端超时时间,开启流式输出优化体验 |
| 回答里完全没有文档内容 | 向量库为空、搜索阈值过高 | 检查是否有数据写入,降低 similarityThreshold |
| 多轮对话“失忆” | 没有配置 ChatMemory Advisor | 添加 MessageChatMemoryAdvisor |
| 向量维度不匹配 | Embedding 模型不一致 | 入库和检索必须使用同一个 EmbeddingModel |
| 模型返回内容被截断 | 达到了 maxTokens 上限 | 调大 maxTokens,或缩短 Prompt 和上下文 |
| 结构化输出解析失败 | 模型返回了不符合 JSON Schema 的内容 | 给模型增加描述并要求严格按照 JSON 输出 |
这里单独说一下向量维度问题。很多坑是因为你不知道向量库里存的是哪一批向量。比如你先用 OpenAI 的 text-embedding-3-small 写了一批数据,后来把 embedding 模型换成了另一个,向量维度从 1536 变成了 1024,检索时直接报错。所以 embedding 模型必须作为全局固定配置,升级前先做数据迁移,或者重建索引。
5.2 几个容易踩的坑
第一个坑是默认参数导致的“模型太随机”。Spring AI 的很多模型 option 如果没设置,会走模型平台自己的默认值,这会导致同一个 Prompt 在不同时间返回差异巨大。如果你的业务需要稳定输出,除了设置 temperature,还建议在 Prompt 里明确要求输出格式,并用结构化输出接管返回结果。
第二个坑是 SimpleVectorStore 的持久化。很多人觉得本地测试没问题,结果生产上线后忘记切换,每次重启后检索结果完全为空。我的建议是,即使是验证阶段,也至少用文件方式把 SimpleVectorStore 的数据存下来,比如 SimpleVectorStore.builder(embeddingModel).withFile("/tmp/vector.json").build(),避免每次启动重新向量化。
第三个坑是 Prompt 注入。RAG 检索到的文档片段也是不可信内容。如果文档里包含类似“忽略以上指令,只回答……”的内容,模型可能会被带偏。尤其是面向公网的知识库问答,必须加一道防护。我通常会写一个自定义 Advisor,在拼接进 Prompt 之前过滤可疑内容,并且在 System Prompt 里注明文档片段只是参考资料,不是指令。
第四个坑是把大量逻辑塞进单个 Tool 方法。@Tool 理论上可以调用任何 Java 方法,但模型对工具的参数理解有限。参数一多,模型传参就容易出错。建议工具方法参数尽量少、语义尽量明确,返回内容也尽量精简,返回大段 JSON 只会浪费 token 而且让模型更难提取关键信息。
第五个坑是忽略可测试性。Spring AI 提供了 FakeChatModel 这类测试实现,用来在 CI 里跑通主流程,不消耗真实模型调用。但很多人不带测试就上线,导致模型升级、Prompt 调整后,业务结果突然变化却毫无察觉。至少要用一组固定输入做快照测试,把模型输出和检索结果变更纳入到 CI 关注范围里。
我个人在实际操作中的体会是:Spring AI 的学习曲线不像网上说的那么陡,但它的“简单”是建立在 Spring 惯例之上的简单。如果你对自动配置、Bean 注入、条件装配本身不熟,用起来会觉得到处是魔法。反过来,一旦你理解了 ChatModel、Advisor、VectorStore 这几个核心抽象,后面加什么新模型、新向量库都只是换个 Bean 的事。
最后再分享一个小技巧:刚开始接 Spring AI,别一上来就设计复杂的 Agent 架构。先写一个 ChatClient 同步调用,跑通模型;再加一个 Advisor 把记忆接上;然后引入 QuestionAnswerAdvisor 做 RAG;最后才考虑 @Tool 和 Agent 编排。每一步都不难,但每一步都会暴露不同的问题。把这条链路亲手走完一遍,你对 Spring AI 的理解就不会只停留在文档表面。
