鱼皮AI导航
鱼皮AI导航
  • 主页
  • 社区
  • AI工具
  • AI知识库
  • AI提示词
  • MCP
  • Skills
  • AI学习资源
  • AI资讯
  • GitHub开源
登录/注册
站长:程序员鱼皮 老鱼简历 商务合作 增值电信业务经营许可证
2026 鱼皮AI导航 | 沪ICP备19026706号-6
62
9
99+
精选

Spring AI 从入门到精通:构建你的 AI 开发知识体系

执笔经年
执笔经年
2026-06-30 17:09
阅读 8099
执笔经年
2026-06-30 17:09
阅读 8099

Spring AI 从入门到精通:构建你的 AI 开发知识体系

Spring AI 从入门到精通:构建你的 AI 开发知识体系.png

前言:为什么需要 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 提供了以下几个层面的能力:

  1. 可移植的 API 抽象层:你不需要关心底层是调用 OpenAI 的 GPT-4、Azure 的模型还是 Anthropic 的 Claude,统一的 ChatModel 接口可以让你的代码在任何模型提供商之间平滑切换。

  2. 企业数据与 AI 的桥梁:通过 Embeddings、VectorStore、ETL Pipeline、RAG 等组件,Spring AI 解决了"如何让 AI 模型访问企业私有数据"这一核心问题。

  3. AI 模型与外部工具的连接器:通过 Function Calling(Tool Calling)和 MCP 协议,Spring AI 让 AI 模型能够调用你的业务 API、数据库、外部服务。

  4. 生产级的工程实践:自动配置、可观测性(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:

▼
xml
复制代码
<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):

▼
xml
复制代码
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>

如果你使用的是 Gradle,相应的配置如下:

▼
groovy
复制代码
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:

▼
yaml
复制代码
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 风格的构建器,提供了链式调用的优雅体验。

▼
java
复制代码
@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 在内部完成了以下步骤:

  1. 将 user(message) 封装为一个 UserMessage 对象(Spring AI 的消息模型);
  2. 创建一个 Prompt 对象,包含这个消息;
  3. 通过 ChatModel(根据你的配置自动装配了 OpenAI 的实现)调用远程 API;
  4. 将 API 返回的 ChatResponse 转换为 Generation 对象;
  5. 最终通过 .content() 提取出纯文本响应。

这个过程中,你完全不需要关心 HTTP 请求的构造、JSON 的序列化和反序列化、错误处理、重试策略等底层细节 —— 这些都被 Spring AI 封装好了。


第三部分:核心入口 —— ChatClient 与 ChatModel 深度解析

3.1 ChatModel:模型抽象层

ChatModel 是 Spring AI 中最重要的抽象接口之一。它类似于 JDBC 中的 DataSource 或者 Spring Data 中的 Repository —— 它定义了一套统一的 API 规范,而具体的实现由各个模型提供商提供。目前 Spring AI 支持的 ChatModel 实现包括:

模型提供商Maven StarterChatModel 实现类
OpenAIspring-ai-starter-model-openaiOpenAiChatModel
Azure OpenAIspring-ai-starter-model-azure-openaiAzureOpenAiChatModel
Anthropic Claudespring-ai-starter-model-anthropicAnthropicChatModel
Google Geminispring-ai-starter-model-google-genaiGeminiChatModel
Amazon Bedrockspring-ai-starter-model-bedrock-converseBedrockProxyChatModel
Ollama (本地)spring-ai-starter-model-ollamaOllamaChatModel
智谱/DeepSeek 等spring-ai-starter-model-openai (兼容)OpenAiChatModel

ChatModel 接口中最核心的方法是 call(Prompt prompt),它接收一个 Prompt 对象,返回一个 ChatResponse 对象。此外,它还提供了 stream(Prompt prompt) 方法用于流式响应。值得强调的是,由于 ChatModel 是一个接口,你的业务代码完全不会绑定到任何具体的模型提供商 —— 这是 Spring 面向接口编程思想在 AI 领域的典型体现。

在底层使用 ChatModel 的例子:

▼
java
复制代码
@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:

▼
java
复制代码
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 工程中最重要的一环。它扮演着三个关键角色:

  1. 角色设定:告诉模型它是谁("你是一个资深 Java 架构师")、它的知识范围("你精通 Spring 全家桶")、它的行为准则("你总是给出可运行的完整代码")。

  2. 格式约束:要求模型以特定格式输出("返回合法 JSON"、"使用 Markdown 格式"、"代码注入使用 ```java 标记")。

  3. 边界设定:限制模型的行为范围("只回答 Java 相关的问题"、"如果不知道就如实说不知道")。

Spring AI 通过 SystemPromptTemplate 支持在 System Prompt 中使用占位符,这使得你可以动态注入参数:

▼
java
复制代码
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() 方法原生支持这一点。

▼
java
复制代码
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:

▼
java
复制代码
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(嵌入向量)的核心思想是将文本(无论是单词、句子、段落还是整篇文档)转换为一个高维空间中的数值向量。在这个向量空间中,语义相近的文本会被映射到彼此靠近的位置。比如"今天天气真好"和"阳光明媚,万里无云"这两个句子虽然用词不同但语义相似,它们的向量距离会很近;而"今天天气真好"和"如何配置数据库连接池"语义完全不相关,它们的向量距离就会很远。

这个特性有什么实际价值呢?当你有一个包含大量文档的知识库时,你想找到"和用户当前问题最相关的那些文档片段",你不需要做关键词匹配(那会漏掉同义词、近义词),也不需要全文检索(那太慢),你只需要:

  1. 将用户的查询文本转换为一个向量;
  2. 在向量数据库中搜索与之距离最近的 N 个向量;
  3. 返回这些向量对应的文档片段。

这就是向量相似度检索,它是 RAG 的检索部分的核心机制。

5.2 使用 Spring AI 生成 Embedding

Spring AI 提供了 EmbeddingModel 接口来统一抽象文本到向量的转换过程。和 ChatModel 一样,EmbeddingModel 也有面向不同提供商的实现(OpenAiEmbeddingModel、AzureOpenAiEmbeddingModel 等)。

▼
java
复制代码
@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:

▼
yaml
复制代码
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 管道由三个核心组件组成:

  1. DocumentReader(提取):从不同的数据源读取原始文档。
  2. DocumentTransformer(转换):对文档进行分片、清洗、元数据增强等处理。
  3. DocumentWriter(加载):将处理后的文档写入向量数据库。

这三个组件的串联形成了一个完整的数据流水线:

▼
text
复制代码
原始文档 → 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 的完整示例:

▼
java
复制代码
@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 文件的示例:

▼
java
复制代码
@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 数据是这样的:

▼
json
复制代码
[ { "id": 1, "title": "山地自行车选购指南", "description": "本指南覆盖入门级到专业级", "content": "在选择山地自行车时,你需要考虑..." } ]

你可以指定 content 字段作为向量化的正文内容,description 字段作为可检索的元数据。

6.3 DocumentTransformer:文本分片与清洗

DocumentTransformer 是将原始文档转换为适合向量检索的格式的关键步骤。它的核心任务是将过长的文档切分为适当大小的片段,这是一个非常微妙的工程问题,直接决定了你后续 RAG 的检索质量。

为什么需要分片?

  1. Token 限制:大多数 Embedding 模型都有输入长度限制(比如 OpenAI 的 text-embedding-3-small 最大支持 8191 个 Token),超长文本无法直接向量化。
  2. 检索精度:如果一个"Document"包含了 20 页的内容,即使它和用户的查询高度相关,也很难从 20 页中找到最相关的那一段。而分段后的每个片段粒度更细,检索结果更精准。
  3. 上下文窗口:当检索到的文档片段被填入 LLM 的 Prompt 时,你希望填入的是最相关的那一小段,而不是一大篇。

Spring AI 提供了 TokenTextSplitter 作为默认的文本分片器:

▼
java
复制代码
List<Document> documents = textReader.get(); List<Document> splitDocuments = new TokenTextSplitter().apply(documents);

DocumentTransformer 的接口定义为:

▼
java
复制代码
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:

▼
java
复制代码
DocumentTransformer pipeline = new KeywordMetadataEnricher(keywords) .andThen(new SummaryMetadataEnricher(summaryModel)) .andThen(new TokenTextSplitter()); List<Document> processed = pipeline.apply(rawDocuments);

6.4 从读取到写入:一个完整的 ETL 流程

将三个组件串联起来,一个完整的 ETL 流程如下:

▼
java
复制代码
// 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);

也可以使用更简洁的链式写法:

▼
java
复制代码
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:

▼
xml
复制代码
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-qdrant</artifactId> </dependency>
▼
yaml
复制代码
spring: ai: vectorstore: qdrant: host: localhost port: 6334 collection-name: my-knowledge-base

7.2 相似度检索

将文档存入向量数据库之后,最核心的操作就是相似度检索。VectorStore 接口提供了 similaritySearch 方法:

▼
java
复制代码
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 的完整流程可以分为两个阶段:

阶段一:数据准备(离线)

▼
text
复制代码
原始文档 → DocumentReader → DocumentTransformer → EmbeddingModel → VectorStore

阶段二:查询回答(在线)

▼
text
复制代码
用户提问 → EmbeddingModel → VectorStore.similaritySearch → 将检索结果注入 Prompt → ChatModel → 带上下文的回答

8.2 使用 Spring AI 实现 RAG

Spring AI 通过 RetrievalAugmentationAdvisor 将 RAG 能力封装为一个可插拔的 Advisor,可以轻松地挂载到 ChatClient 的调用链上:

▼
java
复制代码
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .similarityThreshold(0.50) .vectorStore(vectorStore) .build() ) .build(); String answer = chatClient.prompt() .advisors(retrievalAugmentationAdvisor) .user(question) .call() .content();

这段代码背后发生了什么:

  1. RetrievalAugmentationAdvisor 拦截了用户的问题。
  2. 它调用 VectorStoreDocumentRetriever,从 Vector Store 中检索与问题相关的文档片段(相似度阈值设为 0.50)。
  3. 检索到的文档片段被自动注入到 Prompt 的上下文中(通常放在 System Message 或作为额外消息插入)。
  4. 增强后的 Prompt 被发送给 ChatModel,模型基于这些参考资料生成回答。
  5. 返回给用户的回答中包含了来自知识库的准确信息。

这种设计的优雅之处在于: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(编程式定义)

▼
java
复制代码
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 可调用的工具:

▼
java
复制代码
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 的强大之处在于它的组合性。你可以在一个调用链中注册多个工具,模型会根据用户的意图智能选择:

▼
java
复制代码
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 类型来支持多模态数据:

▼
java
复制代码
// 从 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:

▼
java
复制代码
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 模型进行理解和总结:

▼
java
复制代码
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 是其默认实现(使用滑动窗口策略):

▼
java
复制代码
// 创建一个记忆实例 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 来实现,更加简洁:

▼
java
复制代码
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 链:

▼
java
复制代码
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 的真正威力在于它们可以链式组合:

▼
java
复制代码
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 同时使用了三个能力:

  1. Chat Memory(通过默认 Advisor):记住这个会话之前的对话内容。
  2. RAG(通过按需注册的 Advisor):从向量数据库检索相关文档。
  3. 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 连接:

▼
yaml
复制代码
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 注解来声明式定义暴露的工具和资源:

▼
java
复制代码
@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 的协议:

▼
yaml
复制代码
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 应用的可观测性比传统应用更加重要,原因有三:

  1. 成本敏感:每次调用 AI 模型都有实际的费用支出(Token 计费),你需要精确追踪每次调用的 Token 消耗量。
  2. 延迟不可控:AI 模型的响应时间波动很大(受负载、Prompt 复杂度、Function Calling 往返次数等因素影响),你需要监控 P50/P95/P99 延迟。
  3. 行为不可预测:模型的输出质量可能因为 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 的知识体系可以按照以下五个层次来理解:

▼
text
复制代码
┌─────────────────────────────────────────────────────────┐ │ 应用层 (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 变更和版本差异信息。

Java
技术
AI
62
161
分享
操作
评论
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片

目录

🔥 热门文章
1
手把手教你设计一个成熟的 Agent Harness
2500
2
我用 REA 逆向了付费颜色 APP,结果很意外!
570
3
离谱,DeepSeek 崩了 5 小时,大家却喊:梁圣恩情还不完??
420
4
DeepSeek 全新模型发布,性价比最高的一集!
380
5
B 站上 DeepSeek 闹鱼灾了,我发起《大肥鱼清除计划》!
370
6
自己学习ai,直接买cursor 的套餐,还是买gpt 或者claude 的套餐?
360
7
又一个国产版 Codex 神器,也来卷办公了!
330
8
day3-如何写出一个高质量的 Skill(组内分享) 高质量 Skill 不是“一个调得很顺的巨型 Prompt”,而是一个可验证、可回归、可维护的能力包。 先定义“什么算好”,再实现“怎么做好”。评估体系先于 Skill 本身成型。 下面用“故障诊断 Markdown 转 YAML”作为例子,但方法是通用的。 --- 一、先想清楚:Skill 到底是什么 一个 Skill 至少包含四部分: 部分 作用 接口 输入什么、输出什么、边界在哪 流程 分几步做,每步的产物是什么 规则 硬约束、经验规则、失败模式 降级策略 不确定时怎么办,而不是硬编一个看似合理的结果 高质量 Skill 的共同点:职责单一、接口清晰、规则可定位、输出可验证。 反例:把几十条经验全塞进一个主 Prompt,改一条规则要重调整个 Prompt,评估时也不知道是哪条规则退化。 --- 二、核心方法:先建评估,再写 Skill 不要先写转换器再补测试。正确顺序是: 1. 定义输出契约:什么算合法、什么算合格; 2. 建金标准集:一小批人工认可“可放行”的样本; 3. 先写评估 Skill:让它在金标准集上跑出基线; 4. 再迭代生成 Skill:每次修改跑同一套评估,无退化且目标维度提升才算正向。 这就是评估驱动开发:评估是 Skill 的单元测试,金标准集是回归锚点。 --- 三、评估 Skill:三层门禁,逐层递进 前一层不过,后一层不跑。每层留可追溯证据。 第 1 层:确定性硬门禁 用解析器、Schema、校验器完成,不消耗模型成本。 · 输出可解析; · 必填字段齐全,类型/枚举合法; · 引用目标存在,无悬空引用、无不可达节点; · 关键路径至少有一条能到达预期出口。 不通过直接打回。 第 2 层:语义断言 用脚本 + 结构遍历检查“逻辑对不对”,仍然保持确定性。 通用检查项: · 覆盖率:源材料里的关键步骤、分支、条件,输出里是否都有对应? · 完整性:每个判断节点是否都有明确出口?有没有死胡同? · 可达性:每条路径是否都能到达预期终点?有没有无出口循环? · 顺序与依赖:有先后依赖的操作,是否保留了顺序?有没有被拍平成无序列表? 例子:故障诊断转换中,检查“每个判断项是否有是/否/异常出口”“每条路径是否能到根因”。 第 3 层:LLM-as-Judge 只处理解析器表达不了的维度:忠实度、完整性、可执行性。 但 Judge 会漂移,不能单独放行,必须建立在第 1、2 层之上。Judge 的 Prompt、rubric、模型版本都要版本化,换模型必须用金标准集重新校准。 通用 Judge 维度: 维度 问什么 忠实度 输出是否忠实源材料意图?有没有自行发明内容? 完整性 关键步骤、条件、证据是否保留?隐式知识有没有被压成空话? 收敛性 输出是否仍指向原目标,而不是发散? 可执行性 下游不依赖源材料能否直接使用? --- 四、金标准集:回归测试的锚点 不需要一开始就标注几百份。用已认可的样本当种子,扩到 15~30 份,覆盖失败模式而非随机抽样: · 完整正例:典型流程; · 边界/异常分支:异常路径、循环、前置失败回退; · 已知易错模式:实际踩过的坑; · 留出集:不参与调优,只用于最终验收,防过拟合。 金标准不需要完美,只需要当前标准下认可可放行。 回归指标:关键路径用 pass^k(k 次全过),稳定性用 pass@k。 --- 五、工程约束:让批量产出可重放 · 幂等:同输入 + 同 Skill 版本 + 同规则 → 输出一致,否则无法判断变好是 Skill 改了还是随机波动; · 隔离不阻断:单份失败进隔离队列,记录失败层和原因,不阻塞其余; · 版本绑定:规则、rubric、金标准集进 Git;每份报告绑定“源版本 + 生成 Skill 版本 + 评估 Skill 版本 + Judge 模型版本”; · 抽样人工复核:Judge 临界样本 + 易错模式,人工抽检结果反过来校准 rubric。 --- 六、Skill 本身怎么写 · SKILL.md 控制篇幅,写清:输入约定、输出契约、分步流程、硬规则、降级策略; · 经验规则放 references/ 或 rules/,按失败模式分类,不要全塞进主 Prompt; · 不确定时标 needs_review: true,不要硬编看似合理的结果; · 迭代由评估报告驱动:报告告诉你哪层挂了、哪类失败模式,你改对应规则文件,而不是漫无目的调 Prompt。 --- 七、落地顺序(不要反过来) 1. 固化输出契约:Schema + 结构约束; 2. 建金标准集:从已认可的 + 最易出错的样本开始; 3. 先写评估 Skill:跑出基线报告; 4. 再迭代生成 Skill:每次修改对比逐项通过率,无退化且目标提升才算正向; 5. 批量运行 + 隔离回放:失败进队列,结束后人工抽检; 6. 评估 Skill 自身版本化:换 Judge 模型必须重新校准。 --- 八、给组员的三条硬要求 1. 不许凭感觉说“变好了”:每次修改必须附评估报告对比,逐项通过率说话。 2. 不许跳过确定性门禁直接看 Judge 分数:前两层挂了,Judge 分数没有意义。 3. 不许把规则堆进主 Prompt:规则按失败模式分文件,评估报告要能定位到具体文件。 --- 一句话收尾 生成 Skill 决定“能不能做”,评估 Skill 决定“敢不敢用”。 先让评估体系站稳,再让生成器迭代。这样交付的不是一堆“看起来还行”的产出,而是一套可证明、可回归、可交接的 Skill 质量体系。
310
9
AI 越强,被它拒绝的代价就越大 我有时候会想:AI 越来越厉害之后,有些事情,会不会反而越来越难做? 这个想法看起来有点矛盾。AI 明明在降低门槛,让不会写代码的人做出软件,让不会画画的人生成图片,让一个人完成过去需要整支团队才能完成的工作。但今天用 Codex 的时候,AI 拒绝帮我编写 SQL 注入的请求体,而那还是我有源码、在本地跑测试的情况。那么,往后是否只会更难? 我们假设某一天,AI 说:「这个请求违规,我不能帮助你。」到那个时候,你还有多少能力,能绕过它,把事情做完? 这里的「违规」需要打个引号。平台的规则、模型的判断、法律的要求,以及一件事实际有没有造成伤害,彼此之间并不总是画得上等号。一个请求可能确实危险,也可能只是缺少上下文被误判,或者恰好落在某家公司的限制范围里。 无论是哪种情况,随着我们越来越依赖 AI,这句拒绝都可能变得越来越贵。 今天,一个程序员让 AI 帮忙写代码,AI 拒绝了,他大概率还有别的办法:查文档、找开源项目、问同行,或者自己写。麻烦一点,慢一点,但路还在。 可如果十年之后,大部分程序都由 AI 完成,情况就不一样了。 很多人从接触编程的第一天起,就习惯了描述需求、等待生成、运行结果、继续迭代。他们能做出相当复杂的产品,却未必经历过独立处理底层问题的训练。平时,这种能力结构完全够用,效率甚至更高。只有 AI 拒绝的时候,他们才会发现:平常被省掉的那段学习过程,恰好是此刻最需要的东西。 到那时,一次拒绝的成本,可能就从「今晚多花两个小时」,变成「先花半年学会这门技术」。 拿 SQL 注入举个稍显极端的例子:往后,连真正理解这类攻击如何发生、能够独立构造测试的人,会不会也越来越少? 这里也包括那些有正当需求的人。一个开发者想检查自己的旧系统,一个经过授权的安全研究员想复现漏洞,假如 AI 识别到危险模式就拒绝,而使用者又只熟悉「让 AI 帮我检查」,这项原本可以独立完成的工作,就这样卡住了。 但是安全专家不会因此消失,但这类技能可能越来越集中在少数人手里。你仍然可以找专家,只是要付出更多的钱、更多的时间,以及更高的沟通成本。 类似的困境,未必只发生在网络安全领域。 你想从一个停止维护的软件里取回自己的数据,需要解析它的旧文件格式。AI 判断这涉及规避保护措施,拒绝继续。你拥有这些数据,却没有能力把它们读出来。 你买了一台设备,厂商停止提供服务。你想修改程序让它继续运转,过程中需要处理某种访问限制。AI 不愿协助,而你所掌握的维修方式,早已只剩下「把报错发给 AI」。 你想给常用的网站写一个辅助工具,减少重复操作。平台将此归入自动化限制,AI 也据此拒绝。过去会写脚本的人可以自己判断、自己处理;未来的使用者,可能连从哪里开始都不知道。 这些例子有一个共同点:当工具同时承担了「执行工作」和「传授知识」的角色,它的拒绝就不只是挡住了一次任务,也挡住了获得这种能力的机会。 更麻烦的是,这种依赖往往是我们主动选择的,而且每一步都很合理。 既然 AI 几秒钟就能做好,为什么还要花几个月学习?既然公司可以采购成熟的 AI 服务,为什么还要养一支负责基础工作的团队?既然市场上已经很少有人手写这些东西,学校为什么还要花大量时间教? 对个人、公司和学校来说,这些选择都有现实依据。但叠加起来,整个社会保留的独立能力就可能越来越少。等到某项能力突然受到限制,我们才发现,备用方案早已在追求效率的过程中被一点点削掉了。 所以,「AI 越强,做事越便宜」可能只描述了未来的一半。 另一半是:被允许的事情越来越便宜,被拒绝的事情依然需要人工完成,却要面对更少的人才、更高的报价,以及越来越难获得的知识。两边的成本差距,会越拉越大。 有人会说,可以换一个模型,或者用本地模型。这确实是重要的选项。只要工具之间还有充分竞争,开放模型还足够可用,限制就未必能形成真正的门槛。 但备用方案也需要维护。它需要硬件、知识、软件生态,以及一群仍然理解这些系统的人。如果大多数人已经习惯了最方便的商业服务,那么「理论上还有别的办法」和「普通人实际用得起、用得明白」,之间仍然可能隔着很远。 我担心的,正是这段距离。 我不指望 AI 对任何请求都照单全收。对明显会伤害他人的行为设置限制,有充分的理由。但我们也需要关心:限制由谁定义,误判有没有申诉渠道,用户能不能换工具,以及脱离这个工具之后,还剩下多少独立行动的能力。 保留这些能力,平时看起来甚至有点低效,明明可以自动生成,还要理解原理;明明可以一键完成,还要知道手动怎么做。 但这些看似多余的知识,会在工具拒绝、失效、涨价或改变规则的那一刻,成为真正的选择权。 AI 越强,我们可能越需要留意它说「不」的那一刻。 因为那句话的分量,最终取决于:它拒绝之后,我们还能做什么。
310
10
什么是LLM
300
🔥热门工具
朱雀AI检测朱雀AI检测
朱雀AI检测助手朱雀AI检测助手
LovartLovart
老鱼简历老鱼简历
WritePass官网WritePass官网
面多多面多多
CursorCursor
星月写作星月写作
零感AI零感AI
墨得问题墨得问题