Spring AI 从入门到精通:构建你的 AI 开发知识体系
前言:为什么需要 Spring AI?
在过去的两年里,大语言模型(LLM)以惊人的速度渗透到了软件开发的每一个角落。从 ChatGPT 的横空出世,到如今各类 AI 原生应用的遍地开花,开发者面临的核心挑战已经悄然发生了变化 ------ 不再是"能不能调用一个 AI 模型",而是"如何将 AI 模型与企业的现有数据、业务系统、API 体系深度融合,构建出真正有价值的生产级应用"。
Java 生态在这股浪潮中曾经一度显得有些落后。Python 凭借 LangChain、LlamaIndex 等框架抢占了 AI 应用开发的先机,而 Java 开发者要么被迫切换到 Python 技术栈,要么只能通过简陋的 HTTP 客户端封装来调用大模型接口 ------ 这两种方案都不够理想。
Spring AI 的出现彻底改变了这一局面。它将 Spring 生态二十年来积累的设计哲学 ------ 依赖注入、面向接口编程、约定优于配置、自动装配、可移植性抽象 ------ 完整地注入了 AI 应用开发领域。如果你熟悉 Spring Boot,你会发现 Spring AI 的使用体验几乎是"零学习成本"的:你不需要学习新的编程范式,不需要理解复杂的 Pipeline 概念,只需要像使用 Spring Data 或 Spring Security 一样,引入 Starter 依赖、配置几行属性、注入一个 Bean,就能开始构建强大的 AI 应用。
截至本文编写时,Spring AI 已经演进到了 1.1.x 和 2.0.0 里程碑版本,支持包括 OpenAI、Azure OpenAI、Anthropic Claude、Google Gemini、Amazon Bedrock、Ollama 在内的几乎所有主流 AI 服务提供商,并且提供了对数十种向量数据库的统一抽象。更重要的是,它不仅仅是一个"API 封装层",而是一个完整的 AI 应用开发框架 ------ 它内置了 RAG(检索增强生成)、Function Calling(函数调用)、多模态支持、对话记忆管理、ETL 数据管道、MCP(模型上下文协议)等几乎所有现代 AI 应用开发所需的关键能力。
本文将按照"从浅到深,逐层拆解"的方式,带你全面理解 Spring AI 的每一个核心组件。我们会从最基础的 ChatClient 开始,逐步深入到 Prompt 工程、Embeddings 与向量存储、ETL 数据管道、RAG、Function Calling、多模态、对话记忆、MCP 协议以及可观测性。每一个组件我都会用代码示例配合讲解,让你不仅"知道是什么",更能"看懂怎么做"。
第一部分:概念铺垫 ------ Spring AI 是什么,它的设计哲学是什么
1.1 Spring AI 的核心定位
在开始写代码之前,我们有必要先花一些篇幅理解 Spring AI 在整个 AI 技术栈中的位置。这个问题很重要,因为很多开发者第一次接触 Spring AI 时,会把它和 LangChain、LlamaIndex 等框架直接对标,而这种对标其实是不完全准确的。
Spring AI 的核心定位可以概括为:一个连接企业数据与 API 到 AI 模型的、符合 Spring 设计哲学的应用框架。注意这里的关键词是"连接"------ Spring AI 的目标不是重新发明一套 AI 的开发范式,而是让已经精通 Spring 生态的 Java 开发者,能够用最自然、最符合 Spring 习惯的方式来构建 AI 应用。
具体来说,Spring AI 提供了以下几个层面的能力:
-
可移植的 API 抽象层 :你不需要关心底层是调用 OpenAI 的 GPT-4、Azure 的模型还是 Anthropic 的 Claude,统一的
ChatModel接口可以让你的代码在任何模型提供商之间平滑切换。 -
企业数据与 AI 的桥梁:通过 Embeddings、VectorStore、ETL Pipeline、RAG 等组件,Spring AI 解决了"如何让 AI 模型访问企业私有数据"这一核心问题。
-
AI 模型与外部工具的连接器:通过 Function Calling(Tool Calling)和 MCP 协议,Spring AI 让 AI 模型能够调用你的业务 API、数据库、外部服务。
-
生产级的工程实践:自动配置、可观测性(Metrics/Tracing)、对话记忆管理、流式响应等,这些是"能跑"和"能上生产"之间的差距。
1.2 Spring AI 的设计哲学
理解 Spring AI 的设计哲学至关重要,因为它决定了整个框架的使用体感。如果你是一个有 Spring 开发经验的工程师,以下三点会让你的学习曲线变得非常平坦:
第一,坚持 Spring 的"可移植性抽象"传统。 这和 Spring Data 的设计思路完全一致 ------ 你操作的是 JpaRepository 接口,底层的具体实现可以是 MySQL、PostgreSQL、MongoDB,你切换数据库时业务代码完全不需要改动。Spring AI 也是如此:你操作的是 ChatModel 接口,底层可以是 OpenAI、Anthropic、Ollama 或者任何其他实现。
第二,拥抱 Spring Boot 的自动配置体系。 引入一个 Starter 依赖,配置 application.yml 中的几行属性,然后直接 @Autowired 注入你需要的 Bean,一切就绪。这是 Spring Boot 开发者最熟悉的体验,Spring AI 忠实地继承了下来。
第三,提供"简单场景简单做,复杂场景可以深度控制"的分层 API。 对于最常见的场景(比如一次性问答),ChatClient 可以让你在一行调用链中完成所有操作;而对于复杂的场景(比如多轮对话、RAG、Function Calling),你可以通过 Advisors 链逐步叠加能力,每一步都是可插拔的、可定制的。
第二部分:起步 ------ 配置环境与第一个 AI 调用
2.1 添加依赖
Spring AI 使用独立的 BOM(Bill of Materials)来管理所有依赖版本。首先在你的 pom.xml 中添加 Spring AI 的 BOM:
bash
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后添加 OpenAI 的 Starter(你可以替换为其他模型提供商的 Starter):
bash
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
如果你使用的是 Gradle,相应的配置如下:
bash
dependencyManagement {
imports {
mavenBom("org.springframework.ai:spring-ai-bom:1.1.2")
}
}
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
}
2.2 配置 API Key
在 application.yml 中配置你的 OpenAI API Key:
bash
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini
temperature: 0.7
Spring AI 支持通过环境变量、配置文件、命令行参数等多种方式注入 API Key,这种灵活性让你在不同环境(开发、测试、生产)之间切换变得非常容易。你还可以为不同的模型提供商配置各自的参数,所有配置项都有合理的默认值。
2.3 第一个 AI 调用:使用 ChatClient
现在我们来写第一个真正意义上的 AI 调用。Spring AI 提供了两个层面的 API:底层的 ChatModel 和更高层的 ChatClient。对于绝大多数场景,我推荐你直接从 ChatClient 开始。它是一个 Fluent API 风格的构建器,提供了链式调用的优雅体验。
bash
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String chat(@RequestParam(defaultValue = "用三句话介绍 Java 语言的特点") String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content();
}
}
启动 Spring Boot 应用,访问 http://localhost:8080/ai?message=什么是面向对象编程,你就会收到 AI 模型的回复。整个调用过程不到 20 行代码。
我来解释一下这段代码背后发生了什么:当你调用 chatClient.prompt().user(message).call().content() 时,Spring AI 在内部完成了以下步骤:
- 将
user(message)封装为一个UserMessage对象(Spring AI 的消息模型); - 创建一个
Prompt对象,包含这个消息; - 通过
ChatModel(根据你的配置自动装配了 OpenAI 的实现)调用远程 API; - 将 API 返回的
ChatResponse转换为Generation对象; - 最终通过
.content()提取出纯文本响应。
这个过程中,你完全不需要关心 HTTP 请求的构造、JSON 的序列化和反序列化、错误处理、重试策略等底层细节 ------ 这些都被 Spring AI 封装好了。
第三部分:核心入口 ------ ChatClient 与 ChatModel 深度解析
3.1 ChatModel:模型抽象层
ChatModel 是 Spring AI 中最重要的抽象接口之一。它类似于 JDBC 中的 DataSource 或者 Spring Data 中的 Repository ------ 它定义了一套统一的 API 规范,而具体的实现由各个模型提供商提供。目前 Spring AI 支持的 ChatModel 实现包括:
| 模型提供商 | Maven Starter | ChatModel 实现类 |
|---|---|---|
| OpenAI | spring-ai-starter-model-openai |
OpenAiChatModel |
| Azure OpenAI | spring-ai-starter-model-azure-openai |
AzureOpenAiChatModel |
| Anthropic Claude | spring-ai-starter-model-anthropic |
AnthropicChatModel |
| Google Gemini | spring-ai-starter-model-google-genai |
GeminiChatModel |
| Amazon Bedrock | spring-ai-starter-model-bedrock-converse |
BedrockProxyChatModel |
| Ollama (本地) | spring-ai-starter-model-ollama |
OllamaChatModel |
| 智谱/DeepSeek 等 | spring-ai-starter-model-openai (兼容) |
OpenAiChatModel |
ChatModel 接口中最核心的方法是 call(Prompt prompt),它接收一个 Prompt 对象,返回一个 ChatResponse 对象。此外,它还提供了 stream(Prompt prompt) 方法用于流式响应。值得强调的是,由于 ChatModel 是一个接口,你的业务代码完全不会绑定到任何具体的模型提供商 ------ 这是 Spring 面向接口编程思想在 AI 领域的典型体现。
在底层使用 ChatModel 的例子:
bash
@RestController
public class LowLevelController {
private final ChatModel chatModel;
public LowLevelController(ChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/chat/low-level")
public String chat(@RequestParam String message) {
Prompt prompt = new Prompt(new UserMessage(message));
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getText();
}
}
你可以看到,使用 ChatModel 需要你手动构造 Prompt 和 UserMessage 对象,并且需要自己处理 ChatResponse 中的 Generation。这个 API 层级提供了最大的控制力,但也带来了更多的样板代码。
3.2 ChatClient:Fluent API 的优雅封装
ChatClient 是 Spring AI 推荐的默认入口。它在 ChatModel 之上提供了更加符合开发者直觉的 Fluent API,让你可以用链式调用的方式完成从 Prompt 构建到响应提取的整个过程。
ChatClient 的核心方法是 .prompt(),它返回一个 ChatClient.PromptSpec 对象,这个对象提供了一系列用于构建 Prompt 的方法:
.system(String text)/.system(SystemMessage message)------ 设置系统消息.user(String text)/.user(UserMessage message)------ 设置用户消息.messages(Message... messages)------ 设置多个消息.options(ChatOptions options)------ 覆盖默认的模型参数(温度、Top-P 等).advisors(Advisor... advisors)------ 注册 Advisor 链.tools(ToolCallback... tools)------ 注册可调用的工具.call()------ 执行同步调用,返回ChatClient.CallSpec.stream()------ 执行流式调用,返回Flux<String>
.call() 之后的 CallSpec 提供了几个提取响应的方法:
.content()------ 直接返回纯文本内容.chatResponse()------ 返回完整的ChatResponse对象.entity(Class<T> type)------ 将响应映射为指定类型的 Java 对象.entities(ParameterizedTypeReference<T> type)------ 映射为泛型集合类型
这种设计的美妙之处在于,你可以根据场景的复杂程度,自由选择在调用链的哪个层次"停下来"。如果只需要一段文本,.content() 就够了;如果需要完整的元数据(Token 用量、Finish Reason 等),再用 .chatResponse();如果需要结构化的 JSON 输出,则使用 .entity()。
第四部分:Prompt 工程 ------ 与 AI 模型高效沟通的艺术
4.1 理解 Spring AI 的消息模型
在深入 Prompt 工程之前,我们必须先理解 Spring AI 的消息模型。所有的 Prompt 最终都是由一个或多个 Message 对象组成的,而 Message 接口有两个核心子类:
UserMessage:代表用户的输入,这是对话的主体内容。SystemMessage:代表系统级的指令,用于设定 AI 的角色、行为约束和输出格式。System Message 不会直接显示给用户,但它对模型的行为有着至关重要的影响。AssistantMessage:代表 AI 模型之前的回复,主要用于维护多轮对话的上下文。
这三类消息共同构成了 Prompt。一个典型的 Prompt 通常至少包含一个 SystemMessage 和一个 UserMessage:
bash
String userText = """
请介绍三位黄金海盗时代中最著名的海盗,并说明他们的特点。
至少为每位海盗写一句话。
""";
Message userMessage = new UserMessage(userText);
String systemText = """
你是一个乐于助人的 AI 助手,帮助人们查找信息。
你的名字是 {name}
请以 {voice} 的风格回复用户的问题,并在回复中提及你的名字。
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemPromptTemplate.createMessage(
Map.of("name", "Jack", "voice", "海盗的口吻"));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
4.2 System Prompt 的角色与最佳实践
System Prompt 是整个 Prompt 工程中最重要的一环。它扮演着三个关键角色:
-
角色设定:告诉模型它是谁("你是一个资深 Java 架构师")、它的知识范围("你精通 Spring 全家桶")、它的行为准则("你总是给出可运行的完整代码")。
-
格式约束:要求模型以特定格式输出("返回合法 JSON"、"使用 Markdown 格式"、"代码注入使用 ```java 标记")。
-
边界设定:限制模型的行为范围("只回答 Java 相关的问题"、"如果不知道就如实说不知道")。
Spring AI 通过 SystemPromptTemplate 支持在 System Prompt 中使用占位符,这使得你可以动态注入参数:
bash
SystemPromptTemplate template = new SystemPromptTemplate("""
你是一个 {role},精通 {expertise}。
回答问题时请遵循以下准则:
- {guideline_1}
- {guideline_2}
当前对话的上下文是:{context}
""");
Message systemMessage = template.createMessage(Map.of(
"role", "Java 全栈架构师",
"expertise", "Spring AI、Spring Boot、微服务架构",
"guideline_1", "始终提供完整的、可直接运行的代码示例",
"guideline_2", "在解释概念时,优先使用类比和实际场景",
"context", "为一个 5 人团队设计 AI 聊天机器人后端"
));
4.3 结构化输出(Structured Output)
在实际的生产应用中,我们通常不希望 AI 返回一段自由格式的文本,而是希望它返回结构化的 JSON 数据,这样我们的程序才能可靠地解析和处理。Spring AI 通过 ChatClient 的 .entity() 方法原生支持这一点。
bash
record MovieReviews(Movie[] movie_reviews) {
enum Sentiment {
POSITIVE, NEUTRAL, NEGATIVE
}
record Movie(Sentiment sentiment, String name) {}
}
MovieReviews reviews = chatClient.prompt()
.system("""
Classify movie reviews as positive, neutral or negative.
Return valid JSON.
""")
.user("""
Review: "Her" is a disturbing study revealing the direction
humanity is headed if AI is allowed to keep evolving, unchecked.
It's so disturbing I couldn't watch it.
JSON Response:
""")
.call()
.entity(MovieReviews.class);
这段代码的工作流程非常清晰:首先通过 System Prompt 告诉模型"你的输出必须是合法的 JSON",然后在 User Prompt 中提供待分类的文本并提示"JSON Response:",最后通过 .entity(MovieReviews.class) 告诉 Spring AI 要将模型返回的 JSON 自动反序列化为指定的 Java 记录类。
当你需要将格式化指令动态注入到 Prompt 中时(这在某些需要结构化的 Prompt 模板中非常有用),可以使用 StructuredOutputConverter:
bash
StructuredOutputConverter outputConverter = ...;
String userInputTemplate = """
... user text input ....
{format}
""";
Prompt prompt = new Prompt(
PromptTemplate.builder()
.template(userInputTemplate)
.variables(Map.of("format", outputConverter.getFormat()))
.build()
.createMessage()
);
这种做法的底层原理是:StructuredOutputConverter.getFormat() 会返回具体的格式化指令(比如"返回一个 JSON 对象,包含字段 X、Y、Z"),然后将这段指令嵌入到 Prompt 的 {format} 占位符中,使得模型能够明确理解输出格式的要求。
第五部分:Embeddings ------ 让 AI 理解你的数据
5.1 什么是 Embedding,为什么它至关重要?
如果说 ChatClient 解决的是"让 AI 和我们对话"的问题,那么 Embeddings 解决的就是"让 AI 理解我们的数据"的问题。这是整个 RAG(检索增强生成)体系的基石。
Embedding(嵌入向量)的核心思想是将文本(无论是单词、句子、段落还是整篇文档)转换为一个高维空间中的数值向量。在这个向量空间中,语义相近的文本会被映射到彼此靠近的位置。比如"今天天气真好"和"阳光明媚,万里无云"这两个句子虽然用词不同但语义相似,它们的向量距离会很近;而"今天天气真好"和"如何配置数据库连接池"语义完全不相关,它们的向量距离就会很远。
这个特性有什么实际价值呢?当你有一个包含大量文档的知识库时,你想找到"和用户当前问题最相关的那些文档片段",你不需要做关键词匹配(那会漏掉同义词、近义词),也不需要全文检索(那太慢),你只需要:
- 将用户的查询文本转换为一个向量;
- 在向量数据库中搜索与之距离最近的 N 个向量;
- 返回这些向量对应的文档片段。
这就是向量相似度检索,它是 RAG 的检索部分的核心机制。
5.2 使用 Spring AI 生成 Embedding
Spring AI 提供了 EmbeddingModel 接口来统一抽象文本到向量的转换过程。和 ChatModel 一样,EmbeddingModel 也有面向不同提供商的实现(OpenAiEmbeddingModel、AzureOpenAiEmbeddingModel 等)。
bash
@RestController
public class EmbeddingController {
private final EmbeddingModel embeddingModel;
public EmbeddingController(EmbeddingModel embeddingModel) {
this.embeddingModel = embeddingModel;
}
@GetMapping("/ai/embedding")
public Map<String, Object> embed(@RequestParam String message) {
EmbeddingResponse embeddingResponse =
this.embeddingModel.embedForResponse(List.of(message));
return Map.of("embedding", embeddingResponse);
}
}
embedForResponse(List.of(message)) 方法接收一个文本列表,返回一个 EmbeddingResponse 对象,其中包含了每条文本对应的浮点数向量。你可以根据自己的需要调整输入文本的批处理大小,Spring AI 会负责与模型提供商的高效通信。
这里需要特别注意一点:用于生成 Embedding 的模型和用于对话的模型通常是不同的 。比如 OpenAI 的 text-embedding-3-small 和 text-embedding-3-large 是专门优化的 Embedding 模型,它们的输出向量比 GPT 对话模型更适合做相似度计算。在 Spring AI 的配置中,你需要分别配置 Chat Model 和 Embedding Model:
bash
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini # 对话模型
embedding:
options:
model: text-embedding-3-small # Embedding 模型
第六部分:ETL 数据管道 ------ 把原始文档变成 AI 可用的知识
6.1 ETL 管道的整体架构
ETL(Extract, Transform, Load)管道是 RAG 的数据准备阶段。它的任务是将各种格式的原始文档(PDF、Word、Markdown、JSON、网页等)转化为向量数据库中的、可以被高效检索的结构化数据。Spring AI 的 ETL 管道由三个核心组件组成:
- DocumentReader(提取):从不同的数据源读取原始文档。
- DocumentTransformer(转换):对文档进行分片、清洗、元数据增强等处理。
- DocumentWriter(加载):将处理后的文档写入向量数据库。
这三个组件的串联形成了一个完整的数据流水线:
bash
原始文档 → DocumentReader → List<Document> → DocumentTransformer → List<Document> → DocumentWriter → 向量数据库
在 Spring AI 中,这三个组件被设计为函数式接口,既可以用最简洁的代码串联使用,也可以在每个阶段单独定制。
6.2 DocumentReader:从各种来源读取文档
Spring AI 提供了多种开箱即用的 DocumentReader 实现:
| Reader | 描述 |
|---|---|
TextReader |
读取纯文本文件 |
JsonReader |
读取 JSON 文件,可按字段提取内容 |
PagePdfDocumentReader |
按页读取 PDF 文件 |
ParagraphPdfDocumentReader |
按段落读取 PDF 文件 |
TikaDocumentReader |
通过 Apache Tika 支持几乎所有文档格式 |
以下是使用 ParagraphPdfDocumentReader 读取 PDF 的完整示例:
bash
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdfWithCatalog() {
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(
"classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(
ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build()
);
return pdfReader.read();
}
}
PdfDocumentReaderConfig 提供了丰富的配置选项:你可以设置页边距、删除顶部行(比如页眉)、指定每份 Document 包含多少页。这种细粒度的控制对于处理格式复杂的 PDF 非常有价值。
下面是使用 JsonReader 读取 JSON 文件的示例:
bash
@Component
class MyJsonReader {
private final Resource resource;
MyJsonReader(@Value("classpath:bikes.json") Resource resource) {
this.resource = resource;
}
List<Document> loadJsonAsDocuments() {
JsonReader jsonReader = new JsonReader(
this.resource, "description", "content");
return jsonReader.get();
}
}
JsonReader 的参数允许你指定 JSON 中哪些字段作为文档的内容、哪些作为元数据。比如你的 JSON 数据是这样的:
bash
[
{
"id": 1,
"title": "山地自行车选购指南",
"description": "本指南覆盖入门级到专业级",
"content": "在选择山地自行车时,你需要考虑..."
}
]
你可以指定 content 字段作为向量化的正文内容,description 字段作为可检索的元数据。
6.3 DocumentTransformer:文本分片与清洗
DocumentTransformer 是将原始文档转换为适合向量检索的格式的关键步骤。它的核心任务是将过长的文档切分为适当大小的片段,这是一个非常微妙的工程问题,直接决定了你后续 RAG 的检索质量。
为什么需要分片?
- Token 限制 :大多数 Embedding 模型都有输入长度限制(比如 OpenAI 的
text-embedding-3-small最大支持 8191 个 Token),超长文本无法直接向量化。 - 检索精度:如果一个"Document"包含了 20 页的内容,即使它和用户的查询高度相关,也很难从 20 页中找到最相关的那一段。而分段后的每个片段粒度更细,检索结果更精准。
- 上下文窗口:当检索到的文档片段被填入 LLM 的 Prompt 时,你希望填入的是最相关的那一小段,而不是一大篇。
Spring AI 提供了 TokenTextSplitter 作为默认的文本分片器:
bash
List<Document> documents = textReader.get();
List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
DocumentTransformer 的接口定义为:
bash
public interface DocumentTransformer
extends Function<List<Document>, List<Document>> {
default List<Document> transform(List<Document> transform) {
return apply(transform);
}
}
它是一个标准的 Function<List<Document>, List<Document>>,这意味着你可以用 Java 的 andThen() 方法轻松组合多个 Transformer:
bash
DocumentTransformer pipeline =
new KeywordMetadataEnricher(keywords)
.andThen(new SummaryMetadataEnricher(summaryModel))
.andThen(new TokenTextSplitter());
List<Document> processed = pipeline.apply(rawDocuments);
6.4 从读取到写入:一个完整的 ETL 流程
将三个组件串联起来,一个完整的 ETL 流程如下:
bash
// Step 1: 读取 PDF
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(
"classpath:/knowledge-base.pdf",
PdfDocumentReaderConfig.builder()
.withPagesPerDocument(1)
.build()
);
List<Document> documents = pdfReader.read();
// Step 2: 分片
TokenTextSplitter splitter = new TokenTextSplitter();
List<Document> chunks = splitter.apply(documents);
// Step 3: 写入向量数据库
vectorStore.accept(chunks);
也可以使用更简洁的链式写法:
bash
vectorStore.accept(
new TokenTextSplitter().apply(
new ParagraphPdfDocumentReader("classpath:/knowledge-base.pdf").read()
)
);
这里 vectorStore.accept(chunks) 的工作是:对每一个 Document 分片调用 Embedding 模型生成向量,然后将文本内容和对应的向量一起存入向量数据库。这一切都在 Spring AI 内部自动完成。
第七部分:Vector Store ------ 向量数据库的抽象层
7.1 Spring AI 支持的向量数据库
如果说 Embeddings 是 RAG 的"引擎",那么 Vector Store 就是 RAG 的"仓储"。Spring AI 通过 VectorStore 接口提供了对数十种向量数据库的统一抽象,这其中包括:
- Milvus ------ 高性能开源向量数据库,适合大规模场景
- Pinecone ------ 全托管向量数据库服务
- Weaviate ------ 自带向量化和模式管理的开源方案
- Qdrant ------ Rust 编写的高性能向量搜索引擎
- Chroma ------ 轻量级、适合开发和原型
- PGVector ------ PostgreSQL 扩展,在关系数据库中实现向量搜索
- Redis Stack ------ 基于 Redis 的向量搜索
- Elasticsearch ------ 全文检索与向量搜索的融合
- MongoDB Atlas ------ 文档数据库的向量搜索扩展
- Oracle、Cassandra、Neo4j 等
无论你选择哪个数据库,只需要引入对应的 Starter 依赖并配置连接信息,你的业务代码完全不需要改动。例如使用 Qdrant:
bash
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
</dependency>
bash
spring:
ai:
vectorstore:
qdrant:
host: localhost
port: 6334
collection-name: my-knowledge-base
7.2 相似度检索
将文档存入向量数据库之后,最核心的操作就是相似度检索。VectorStore 接口提供了 similaritySearch 方法:
bash
List<Document> similarDocuments = vectorStore.similaritySearch(
SearchRequest.builder()
.query("如何配置 Spring Boot 的数据源?")
.topK(5)
.similarityThreshold(0.7)
.build()
);
三个关键参数:
query:用户的查询文本。Spring AI 会自动将其转换为 Embedding 向量,然后在数据库中搜索。topK:返回最相似的 K 个文档片段。这个值需要根据你的上下文窗口大小来设置,通常 3-5 个是最平衡的选择。similarityThreshold:相似度阈值(0.0-1.0)。只有相似度高于此阈值的文档才会被返回。这个参数非常重要 ------ 如果没有阈值限制,向量数据库会返回"看起来最像但可能完全不相关"的结果,而 0.7 是一个经过大量实践验证的合理默认值。
第八部分:RAG ------ 检索增强生成
8.1 RAG 的工作原理
RAG(Retrieval Augmented Generation,检索增强生成)是 Spring AI 中最引人注目的能力之一。它解决了一个 AI 应用中的根本性矛盾:大语言模型的知识截止于训练数据,它们不知道你企业内部的文档、最新的业务数据、今天刚发布的产品规格。
RAG 的核心思想非常优雅:在把用户的问题发给 AI 模型之前,先去你的知识库(向量数据库)中检索与问题最相关的文档片段,然后把"用户的问题"和"检索到的相关资料"一起打包放到 Prompt 里发给模型。这样,模型在生成回答时就有了"参考材料",可以基于你的数据给出准确的、有时效性的回答。
RAG 的完整流程可以分为两个阶段:
阶段一:数据准备(离线)
bash
原始文档 → DocumentReader → DocumentTransformer → EmbeddingModel → VectorStore
阶段二:查询回答(在线)
bash
用户提问 → EmbeddingModel → VectorStore.similaritySearch →
将检索结果注入 Prompt → ChatModel → 带上下文的回答
8.2 使用 Spring AI 实现 RAG
Spring AI 通过 RetrievalAugmentationAdvisor 将 RAG 能力封装为一个可插拔的 Advisor,可以轻松地挂载到 ChatClient 的调用链上:
bash
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build()
)
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();
这段代码背后发生了什么:
RetrievalAugmentationAdvisor拦截了用户的问题。- 它调用
VectorStoreDocumentRetriever,从 Vector Store 中检索与问题相关的文档片段(相似度阈值设为 0.50)。 - 检索到的文档片段被自动注入到 Prompt 的上下文中(通常放在 System Message 或作为额外消息插入)。
- 增强后的 Prompt 被发送给 ChatModel,模型基于这些参考资料生成回答。
- 返回给用户的回答中包含了来自知识库的准确信息。
这种设计的优雅之处在于:RAG 逻辑完全被隔离在 Advisor 中,你的业务代码不需要任何修改。你可以随时切换到不同的检索策略、调整相似度阈值、替换底层的向量数据库,而业务调用代码保持完全不变。
第九部分:Function Calling ------ 让 AI 调用你的代码
9.1 什么场景需要 Function Calling?
Function Calling(在 Spring AI 中也称为 Tool Calling)是实现 AI Agent 的关键技术。它让 AI 模型能够"意识到"外部工具的存在,并在合适的时机选择调用这些工具来获取信息或执行操作。
考虑以下典型场景:
- 查询天气 :"明天的北京天气怎么样?"------ 模型自身没有实时天气数据,但它可以调用你的
getCurrentWeather函数。 - 发送邮件 :"帮我把会议纪要通过邮件发给张三"------ 模型可以调用你的
sendEmail函数。 - 数据库查询 :"上个月销售额最高的产品是什么?"------ 模型可以调用你的
querySalesData函数。 - 调用企业内部 API :"帮我把这个订单的状态改为已发货"------ 模型可以调用你的
updateOrderStatus函数。
在这些场景中,AI 模型不需要自己"知道"天气、销售额、订单状态,它只需要知道有哪些工具可用、每个工具需要什么参数,然后像一个"调度中心"一样决定调用哪个工具、传什么参数。实际的执行由你的 Java 代码完成,AI 模型只是"决策者"。
9.2 使用 Spring AI 实现 Function Calling
Spring AI 提供了两种方式来定义工具函数,这里分别介绍。
方式一:使用 FunctionToolCallback(编程式定义)
bash
ToolCallback weatherCallback = FunctionToolCallback
.builder("getCurrentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherService.Request.class)
.build();
String response = ChatClient.create(chatModel)
.prompt()
.user("What's the weather in Paris, Tokyo, and New York?")
.tools(weatherCallback)
.call()
.content();
当用户问"What's the weather in Paris, Tokyo, and New York?"时,模型会识别出需要调用 getCurrentWeather 函数,然后自动发起工具调用、获取天气数据,并基于这些数据生成最终的回复。注意,模型可能会对一个请求并发调用多个工具 ------ 比如这个例子中,模型可能会同时查询三个城市的天气。
方式二:使用 @Tool 注解(声明式定义)
如果你的类已经封装了业务逻辑,可以使用 Spring AI 的注解方式让它变成 AI 可调用的工具:
bash
public class WeatherService {
@Tool(description = "Get the weather in location")
public String weatherByLocation(
@ToolParam(description = "City or state name") String location) {
// 实际的天气查询逻辑
return "The weather in " + location + " is sunny, 25°C";
}
}
// 使用时直接传入实例,Spring AI 会自动解析 @Tool 注解
String response = ChatClient.create(chatModel)
.prompt()
.user("What's the weather like in Boston?")
.tools(new WeatherService())
.call()
.content();
当一个 ChatClient 调用链中注册了多个工具时,模型会根据用户的提问自动判断是否需要调用工具、调用哪个工具、传什么参数。这个过程被称为"工具调用循环"(Tool Call Loop),Spring AI 的 ToolCallingAdvisor 会自动管理这个循环 ------ 你不需要写任何循环或判断逻辑。
Tool Calling 的强大之处在于它的组合性。你可以在一个调用链中注册多个工具,模型会根据用户的意图智能选择:
bash
String response = ChatClient.create(chatModel)
.prompt()
.user("帮我查一下北京明天的天气,然后给张三发邮件告诉他明天要不要带伞")
.tools(
new WeatherService(), // 天气查询工具
new EmailService(), // 邮件发送工具
new CalendarService() // 日历查询工具
)
.call()
.content();
模型会自动先调用天气工具获取北京的天气预报,然后分析结果判断是否需要带伞,最后调用邮件工具向张三发送通知 ------ 整个过程在 ToolCallingAdvisor 的管理下自动完成,开发者只需要定义工具的能力。
第十部分:多模态支持 ------ 不止于文本
10.1 Spring AI 的多模态能力概述
多模态(Multimodal)是指 AI 模型理解和处理多种信息形式(文本、图像、音频、视频等)的能力。Spring AI 的 Message 接口通过 Media 类型来支持多模态数据:
bash
// 从 Classpath 加载图片
var imageResource = new ClassPathResource("/multimodal.test.png");
var userMessage = new UserMessage(
"Explain what do you see on this picture?",
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource))
);
ChatResponse response = chatModel.call(
new Prompt(userMessage,
OpenAiChatOptions.builder()
.model("gpt-4o") // 必须使用支持视觉的模型
.build()
)
);
或者使用图片 URL:
bash
var userMessage = new UserMessage(
"Explain what do you see on this picture?",
List.of(new Media(MimeTypeUtils.IMAGE_PNG,
URI.create("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png")))
);
ChatResponse response = chatModel.call(new Prompt(userMessage));
对于 PDF 文件,同样可以作为多模态输入发送给 AI 模型进行理解和总结:
bash
var pdfResource = new ClassPathResource("/document.pdf");
var userMessage = UserMessage.builder()
.text("Please summarize this document.")
.media(List.of(new Media(new MimeType("application", "pdf"), pdfResource)))
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage)));
10.2 多模态支持的模型
目前支持视觉多模态的主流模型包括:
| 模型 | 支持的模态 |
|---|---|
| GPT-4o / GPT-4o-mini | 文本、图像 |
| GPT-4 | 文本、图像 |
| Anthropic Claude 3 / 3.5 | 文本、图像、PDF |
| Google Gemini 系列 | 文本、图像、音频、视频 |
使用多模态功能时务必注意:你必须选择支持对应模态的模型 ,比如使用 gpt-4o 而不是 gpt-3.5-turbo,否则会收到错误。
第十一部分:对话记忆(Chat Memory)------ 让 AI 记住上下文
11.1 对话记忆的必要性
默认情况下,每一次 chatClient.prompt().user(message).call() 都是独立的、无状态的。模型不知道你上一轮说了什么,也不记得你之前让它扮演了什么角色。这对于"一次性问答"的场景没问题,但对于任何需要多轮交互的场景 ------ 聊天助手、客服系统、代码助手 ------ 都会造成体验上的断裂。
对话记忆(Chat Memory)就是来解决这个问题的。它的核心原理非常简单:将每一轮对话的消息(用户的输入和模型的回复)追加到一个消息列表中,在下一次对话时把这个列表作为历史消息传给模型。 这样一来,模型就有了"记忆"。
11.2 使用 Spring AI 的 Chat Memory
Spring AI 提供了 ChatMemory 接口来实现对话记忆,MessageWindowChatMemory 是其默认实现(使用滑动窗口策略):
bash
// 创建一个记忆实例
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = "007";
// 第一轮对话
UserMessage userMessage1 = new UserMessage("My name is James Bond");
chatMemory.add(conversationId, userMessage1);
ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
chatMemory.add(conversationId, response1.getResult().getOutput());
// 第二轮对话(不需要再次告知名字)
UserMessage userMessage2 = new UserMessage("What is my name?");
chatMemory.add(conversationId, userMessage2);
ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
// 模型会回答 "James Bond"
如果你使用的是 ChatClient,记忆管理通过 MessageChatMemoryAdvisor 来实现,更加简洁:
bash
chatClient.prompt()
.user("Do I have license to code?")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
ChatMemory.CONVERSATION_ID 是一个关键参数,每次调用都必须提供 ,否则会抛出 IllegalArgumentException。这个 ID 用于区分不同的会话 ------ 每个用户、每个对话窗口都应该有一个唯一的会话 ID。
11.3 多个 Advisor 的组合使用
MessageChatMemoryAdvisor 可以和 RetrievalAugmentationAdvisor、QuestionAnswerAdvisor 等其他 Advisor 组合使用,形成一个 Advisor 链:
bash
chatClient.prompt()
.advisors(a -> a
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.param(ChatMemory.CONVERSATION_ID, conversationId))
.user("根据我们之前的讨论,帮我找到相关的技术文档")
.call()
.content();
在这个链中,MessageChatMemoryAdvisor 负责注入历史对话上下文,QuestionAnswerAdvisor 负责从向量数据库检索相关文档。两者协作,模型在生成回答时既有对话历史又有知识库的支撑。
第十二部分:Advisors ------ Spring AI 的插件化架构
12.1 Advisors 的设计理念
Advisors 是 Spring AI 中最重要的架构概念之一,它是整个框架"可插拔、可组合"设计哲学的集中体现。你可以把 Advisor 理解为一个拦截器链(Interceptor Chain):每个 Advisor 在 Prompt 被发送到模型之前、模型返回响应之后,都可以对数据进行拦截和处理。
Spring AI 内置了多个 Advisors:
| Advisor | 功能 |
|---|---|
MessageChatMemoryAdvisor |
管理对话历史记忆 |
RetrievalAugmentationAdvisor |
从向量数据库检索相关文档注入 Prompt |
QuestionAnswerAdvisor |
基于知识库的问答 |
ToolCallingAdvisor |
管理 Function Calling 的调用循环 |
DynamicToolSearchAdvisor |
动态搜索并注册工具 |
12.2 构建 Advisor 链
Advisors 的真正威力在于它们可以链式组合:
bash
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
String answer = chatClient.prompt()
.advisors(a -> a
.advisors(
RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.50)
.build())
.build()
)
.param(ChatMemory.CONVERSATION_ID, sessionId))
.user("Spring AI 中如何配置向量数据库?")
.tools(new DocumentationSearchTool())
.call()
.content();
在这个示例中,ChatClient 同时使用了三个能力:
- Chat Memory(通过默认 Advisor):记住这个会话之前的对话内容。
- RAG(通过按需注册的 Advisor):从向量数据库检索相关文档。
- Tool Calling (通过
.tools()注册):提供文档搜索工具给模型调用。
这三个能力完全独立、互不干扰,但它们可以被无缝组合到一个调用链中。这就是 Spring AI Advisors 架构的魅力 ------ 你可以像搭积木一样逐步叠加 AI 应用的能力,每个"积木"都独立可测试、独立可替换。
第十三部分:MCP ------ 模型上下文协议
13.1 什么是 MCP?
MCP(Model Context Protocol)是由 Anthropic 提出的一种开放协议,旨在标准化 AI 模型与外部工具、数据源之间的通信方式。Spring AI 完整实现了 MCP 协议,支持同时作为 MCP Client 和 MCP Server。
MCP 的核心价值在于标准化。在没有 MCP 之前,每个 AI 框架、每个工具都有自己的一套工具注册和调用机制,导致生态割裂。MCP 定义了一套统一的协议,使得:
- 任何支持 MCP 的工具都可以被任何支持 MCP 的 AI 应用调用;
- AI 应用可以将自己的内部能力通过 MCP 暴露给其他 AI 应用;
- 工具的发现、注册、调用、安全控制都有了统一的标准。
13.2 配置 MCP Client
Spring AI 通过 Spring Boot 的自动配置体系来管理 MCP 连接:
bash
spring:
ai:
mcp:
client:
enabled: true
name: my-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC # 或 ASYNC 用于响应式应用
streamable-http:
connections:
server1:
url: http://localhost:8083
endpoint: /mcp
stdio:
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
Spring AI 支持三种 MCP 传输方式:
- SSE(Server-Sent Events):适用于服务端推送事件。
- Streamable HTTP:基于 HTTP 的流式传输,最常用的方式。
- Stdio:通过标准输入输出进行进程间通信,适用于本地工具。
13.3 构建 MCP Server
如果你想让自己的应用能够被其他 AI 应用通过 MCP 协议调用,你可以构建一个 MCP Server。Spring AI 通过 @McpTool 和 @McpResource 注解来声明式定义暴露的工具和资源:
bash
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
@Component
public class MyMcpTools {
@McpTool(description = "查询指定用户的完整订单历史")
public List<Order> getUserOrders(
@McpToolParam(description = "用户唯一标识符") String userId,
@McpToolParam(description = "查询最近 N 天的订单") int days) {
// 实际的数据库查询逻辑
return orderRepository.findByUserIdAndDateRange(userId, days);
}
@McpTool(description = "生成指定时间范围内的销售报表")
public SalesReport generateSalesReport(
@McpToolParam(description = "报表开始日期(ISO 格式)") String startDate,
@McpToolParam(description = "报表结束日期(ISO 格式)") String endDate) {
// 实际的报表生成逻辑
return reportService.generate(startDate, endDate);
}
}
@Component
public class MyMcpResources {
@McpResource(description = "公司的产品目录信息")
public String getProductCatalog() {
return productService.getCatalog();
}
}
配置 MCP Server 的协议:
bash
spring:
ai:
mcp:
server:
name: my-cool-mcp-server
protocol: STREAMABLE # 或 STATELESS
Spring Boot 的自动配置会自动扫描带有 @McpTool 和 @McpResource 注解的 Bean,将它们注册为 MCP Server 的工具和资源。任何支持 MCP 协议的 AI 客户端都可以发现并调用这些能力。
第十四部分:可观测性 ------ 生产环境中的 AI 监控
14.1 为什么 AI 应用需要可观测性?
AI 应用的可观测性比传统应用更加重要,原因有三:
- 成本敏感:每次调用 AI 模型都有实际的费用支出(Token 计费),你需要精确追踪每次调用的 Token 消耗量。
- 延迟不可控:AI 模型的响应时间波动很大(受负载、Prompt 复杂度、Function Calling 往返次数等因素影响),你需要监控 P50/P95/P99 延迟。
- 行为不可预测:模型的输出质量可能因为 Prompt 的微小变化而产生显著差异,你需要追踪调用的成功率和错误类型。
14.2 Spring AI 的观测能力
Spring AI 无缝集成了 Spring 生态的可观测性体系(Micrometer + OpenTelemetry),提供了以下观测维度:
核心组件的指标和追踪覆盖:
- ChatClient:每次调用的延迟、Token 消耗(Input/Output)、成功率
- ChatModel:底层模型调用的延迟、Token 使用量
- EmbeddingModel:Embedding 生成的延迟和维度
- ImageModel:图片生成的延迟和尺寸
- VectorStore:向量检索的延迟和返回结果数量
数据的分类策略:
- 低基数键(Low-cardinality keys):如模型名称、提供商名称等,同时添加到 Metrics 和 Traces 中。
- 高基数键(High-cardinality keys):如具体的 Prompt 内容、会话 ID 等,仅添加到 Traces 中。
你只需要引入对应的 Spring Boot Actuator 和 Micrometer 依赖,Metrics 和 Tracing 就会自动启用。这种"零配置自动观测"的设计,让 AI 应用的运维也变得和传统 Spring Boot 应用一样简单。
第十五部分:总结 ------ Spring AI 的学习路径与知识体系
15.1 知识体系全景图
经过了从"第一个 AI 调用"到"MCP 协议"的完整旅程,现在让我们把这些组件串联成一个有机的整体。Spring AI 的知识体系可以按照以下五个层次来理解:
bash
┌─────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ REST Controller / GraphQL / 消息队列 / 定时任务 │
├─────────────────────────────────────────────────────────┤
│ 编排层 (Orchestration) │
│ ChatClient · Advisors · Chat Memory · Tool Calling │
├─────────────────────────────────────────────────────────┤
│ 核心层 (Core) │
│ ChatModel · EmbeddingModel · Prompt · Messages │
│ Structured Output · Streaming · Multimodal │
├─────────────────────────────────────────────────────────┤
│ 数据层 (Data) │
│ ETL Pipeline · VectorStore · DocumentReader │
│ DocumentTransformer · DocumentWriter │
├─────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │
│ MCP Protocol · Observability · Auto-configuration │
└─────────────────────────────────────────────────────────┘
15.2 推荐学习路径
根据你的背景和需求,我推荐以下学习路径:
第一阶段:入门(1-2 天)
- 搭建 Spring Boot + Spring AI 项目
- 理解 ChatClient 的基本用法
- 完成第一个"发送消息、接收回复"的完整流程
- 理解
application.yml中模型参数的配置
第二阶段:对话与 Prompt(2-3 天)
- 深入理解 Message 模型(UserMessage、SystemMessage、AssistantMessage)
- 掌握 System Prompt 的编写技巧
- 学习结构化输出(
.entity()映射 Java 对象) - 理解 PromptTemplate 的占位符机制
第三阶段:RAG 体系(3-5 天)
- 理解 Embedding 的概念和 EmbeddingModel 的使用
- 学习 ETL Pipeline(DocumentReader → DocumentTransformer → DocumentWriter)
- 选一个向量数据库(推荐从 Chroma 或 PGVector 开始),理解 VectorStore 的基本操作
- 使用 RetrievalAugmentationAdvisor 实现端到端的 RAG
第四阶段:AI Agent 能力(3-5 天)
- 掌握 Function Calling / Tool Calling
- 使用 @Tool 和 @ToolParam 注解构建工具
- 理解 Chat Memory 和 MessageChatMemoryAdvisor
- 学习 Advisors 链的组合使用
第五阶段:生产级实践(持续)
- 配置可观测性(Metrics + Tracing)
- 理解 MCP 协议、搭建 MCP Server
- 流式响应的实现与优化
- 多模态支持的集成
15.3 最后的建议
学习 Spring AI 的过程,本质上是在学习"如何用工程化的方式构建 AI 应用"。Spring AI 的设计让这个学习过程变得非常平滑 ------ 你不需要在一开始就理解 RAG、Function Calling 这些高级概念,你只需要从 ChatClient 开始,写几个简单的调用,逐渐积累体感,然后根据实际需求逐步向调用链上叠加能力。
记住 Spring AI 的核心设计哲学:简单场景简单做,复杂场景可以深度控制 。当你只需要一段 AI 回复时,.call().content() 就够了;当你需要让 AI 访问企业内部数据时,加上 RetrievalAugmentationAdvisor;当你需要 AI 执行操作时,注册 ToolCallback。每一次能力的叠加都是可插拔的,每一个组件都是可替换的。
这就是 Spring 生态二十年来一直坚持的工程哲学 ------ 当你掌握了核心抽象,你就掌握了整个生态。Spring AI 将这个哲学完整地带入了 AI 时代。
参考资料 :本文内容基于 Spring AI 官方参考文档(
docs.spring.io/spring-ai/reference),代码示例来自官方文档中的实际使用场景,经过重新组织和补充说明。建议配合官方文档阅读以获取最新的 API 变更和版本差异信息。