跳到主要内容 Spring AI 框架快速入门与核心接口详解 | 极客日志
Java AI java
Spring AI 框架快速入门与核心接口详解 Spring AI 是简化 Java 应用集成 AI 功能的开源框架。 AI 基本概念(模型、LLM、Prompt、Token),演示了基于 Spring Boot 和 DeepSeek API 的快速入门配置。重点讲解了核心接口 ChatModel 与 ChatClient 的区别及用法,涵盖消息类型(System/User/Assistant)、结构化输出与流式输出(SSE/Flux)实现,以及 Advisors 拦截器机制的应用。
链路追踪 发布于 2026/4/6 更新于 2026/4/13 4 浏览基本概念
什么是 AI
AI :也就是 人工智能(Artificial Intelligence) ,顾名思义,就是 让机器模拟人类智能的科学与技术 。
我们通过一个示例来对比理解:
**普通计算机程序:**像一台自动售货机。你按下特定的按钮(输入),它就给你一瓶特定的饮料(输出)。它的 所有行为都是程序员预先设定好的规则 。
人工智能程序 :更像是一个正在学习的孩子。你给它看很多猫和狗的图片(数据),并告诉它哪个是猫,哪个是狗。经过学习后,当你给它一张它从未见过的猫咪图片时,它也能识别出来。它 ,而不是依赖硬编码的规则。
自己从数据中学会了规律
因此,AI 的核心是 从经验中学习,并根据所学做出决策或预测 。
而目前最主流、最引人注目的 AI 分支是 生成式人工智能 ,也就是现在常说的 AIGC(Artificial Intelligence Generated Content,人工智能生成内容) 。它与传统 AI(主要用于分析数据,比如识别人脸)不同,它的目标是 利用人工智能技术自动生成或创造出各类数字内容 ,比如写文章、报告、翻译、编程。
为了更好的理解 AI,我们先来理解其中的一些 常见术语 。
模型(Model) **模型(Model)**是 AI 系统的核心 ,它是 通过算法在数据上训练后得到的结果 。模型本质上是一个 数学函数 ,它接收输入数据,并进行计算,然后产生输出。
我们常说的'调用一个 AI',实际上就是在使用这个'模型'。模型文件大小不一,可以从几 MB 到几十 GB。
可以把 AI 模型想象成一个 '虚拟大脑' 。这个大脑通过在大量数据上进行'训练'或'学习',掌握了一些技能和知识。而当被提问时,就需要运用这个大脑掌握的知识来解决问题。
大语言模型(LLM) LLM(Large Language Model,大语言模型) :一种基于 深度学习 的、使用海量文本数据训练的 模型 。它的主要任务是理解和生成人类语言。LLM 是当前生成式 AI 热潮的代表。它们的特点是'大 ',体现在 训练数据量大、模型参数数量巨大 。
可以将其看做一个进行了超大规模训练的 '专家大脑' 。它通过学习互联网上几乎所有的文本,掌握了语言的语法、句法、事实知识以及上下文逻辑,拥有数百亿甚至数千亿个参数,并且因为它什么都学过,所以能应对各种各样的话题和任务。
提示词(Prompt) **提示词(Prompt):**用户提供给 AI 模型的 指令、问题或上下文信息 。模型根据提示词来生成相应的回复。提示词的质量直接决定了 AI 回答的质量。
而设计和优化提示词的过程被称为'提示词工程 ',是一门新兴的技能。
提示词就像是给 AI 这位'天才'下达的'工作订单 '。订单越清晰、越具体,完成的工作质量就越高。
例如:
简单提示词 :'法国的首都是哪里?' -> 模型回答:'巴黎。'
复杂提示词(角色扮演) :'假设你是一位资深营养师,请为我(一位办公室久坐的上班族)设计一份为期一周的健康午餐食谱。' -> 模型会以营养师的口吻提供一份详细的食谱。
词元(Token) 词元(Token) :是模型处理和理解的 基本文本单位 。它不是完全等同于一个英文单词或一个汉字。模型在处理前,会先将文本拆分成词元,同时,词元也是计费和衡量模型处理长度的基本单位。
英文中,单词 unbelievable 可能会被拆分成三个词元 ["un", "believe", "able"]
中文中,'我喜欢编程'这句话,很可能会被拆分成四个词元 ["我", "喜", "欢", "编程"]
不同模型的分词规则不同,同一个词在不同模型中可能被拆分成不同词元 。
了解了 AI 的基本概念,接下来,我们来看 Spring AI 相关内容。
Spring AI 是什么 Spring AI 是一个 基于 Spring 生态系统 的 开源人工智能应用框架 ,它的核心目标是 简化 AI 功能在 Java 应用程序中的集成过程 ,让 Java 开发者也能高效地构建生成式 AI 应用。
Spring AI 提供了作为 开发 AI 应用基础的抽象 。这些抽象具有多种实现,可以通过 最少的代码更改轻松实现组件切换 。
Spring AI 提供了一系列强大而实用的功能,使其成为一个功能完备的 AI 应用开发框架:
1. 统一的多模型支持 :支持与众多主流的 AI 模型提供商进行交互,包括 OpenAI 、Microsoft 、Amazon 、Google 和 Anthropic 等,无论是云端模型还是本地部署的模型(如通过 Ollama),都能通过一致的接口进行调用 。
2. 强大的数据集成能力 :这是 Spring AI 的一大亮点。它内置了对 向量数据库 (如 Chroma、Pinecone、Redis 等)的支持。
3. 与 Spring 生态无缝集成 :作为 Spring 大家庭的一员,它能自然地与 Spring Boot 、Spring Data 等其他知名项目协同工作。
4. 简化的开发模式 :允许 AI 模型根据需要请求执行客户端定义的函数,从而接入实时信息或触发具体动作。
了解了相关概念后,我们就来上手体验一下 Spring AI 。
快速入门
环境要求
JDK 版本:JDK 17 或以上 (推荐 JDK 21),这是强制要求,因为 Spring Boot 3.x 本身就需要 JDK 17+。
Spring Boot 版本:Spring Boot 3.2 或以上 ,具体版本可以是 3.3.3、3.4.3 或 3.5.0,选择一个稳定的 3.x 最新版本即可。
**AI 服务凭证:**有效的 API Key ,需要一个来自 AI 服务提供商 (如 OpenAI、DeepSeek、阿里百炼等)的账户和 API Key。
在本篇文章中,我们以 DeepSeek 作为示例来进行学习。
申请 API Key 点击创建之后输入名称即可完成创建,但需要注意的是 API key 仅在创建时可见可复制 。
项目创建 Spring AI 专门为 OpenAI 及兼容 API 服务设计了 spring-ai-openai-spring-boot-starter ,用于快速集成大模型语言能力到 Spring Boot 应用中:
正常创建 Maven 项目(注意 JDK 和 Spring Boot 版本),并 添加 Spring AI 依赖 :
<dependency >
<groupId > org.springframework.ai</groupId >
<artifactId > spring-ai-openai-spring-boot-starter</artifactId >
<version > 1.0.0-M6</version >
</dependency >
在 application.yml 中配置 API 密钥:
spring:
ai:
openai:
api-key: 申请的 API Key
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.7
spring.ai.openai.base-url :要连接的 URL
spring.ai.openai.api-key :申请的 DeepSeek API 密钥
spring.ai.openai.chat.options.model :要使用的 DeepSeek LLM 模型
spring.ai.openai.chat.options.temperature :用于 控制模型生成文本的随机性和创造性 :低温 (接近 0.0) → 保守、确定、可预测;高温 (接近 2.0) → 冒险、多样、富有想象力。也就是说,temperature 值越低,相同的提问得到的结果越类似。**此外,**不建议在同一个补全请求中同时修改 temperature 和 top_p ,因为这两个设置的交互作用难以预测。
此时,就已经完成了 项目的创建 和 DeepSeek 的接入 ,接下来,我们通过编写接口来调用模型。
接口编写 @RestController
@RequestMapping("/deepseek")
public class DeepSeekChatController {
@Autowired
private OpenAiChatModel deepSeekChatModel;
@GetMapping("/chat")
public String generate (String message) {
return deepSeekChatModel.call(message);
}
}
运行,并访问 http://127.0.0.1:8080/deepseek/chat?message=你是谁 进行测试。
上述,我们通过 ChatModel 完成了与模型的交互。
而在 Spring AI 框架中,ChatModel 和 ChatClient 是构建 对话式 AI 应用的两大核心接口,接下来,我们分别来看这两个接口。
核心接口
ChatModel ChatModel 直接与底层 AI 模型(如 GPT-4、Claude 等)通信,处理原始的请求和响应。
@Service
public class ChatService {
@Autowired
private ChatModel chatModel;
public String askQuestion (String question) {
UserMessage userMessage = new UserMessage (question);
Prompt prompt = new Prompt (List.of(userMessage));
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getContent();
}
}
ChatClient ChatClient 在 ChatModel 之上提供了一层 流畅的 API ,简化了常见的使用模式。
即 ChatClient 是对 ChatModel 的一层包装。
@Service
public class ChatService {
@Autowired
private ChatClient chatClient;
public String askQuestion (String question) {
return chatClient.call(question);
}
}
可以看到,ChatClient 的使用更加简洁直观。
ChatModel 与 ChatClient 对比:
维度 ChatModel ChatClient 抽象层级 底层,接近原始模型 高层,面向业务使用 返回值 ChatResponse(包含丰富元数据的完整响应对象) ChatResponse(直接获得内容的纯文本)或流式响应 使用方法 需要手动构造 Prompt 对象 提供流式的 builder 模式 控制粒度 精细控制 快捷简便
消息类型 在 Spring AI 中,所有消息类型都实现了 org.springframework.ai.chat.messages.Message 接口,系统中的消息被设计用来 模拟一个多轮对话中的不同参与者 。
消息类型 对应角色 核心作用 SystemMessage 系统 / 导演 设定 AI 的背景、角色、行为和回复风格。通常在对话开始时提供,为整个会话定下基调。 UserMessage 用户 / 提问者 代表人机交互中的人类一方,是驱动对话前进的源泉。 AssistantMessage 助理 / AI 本身 代表 AI 在之前轮次中做出的回复。是多轮对话连贯性的保障。 FunctionMessage 函数 / 工具 代表 AI 通过函数调用获得的额外信息或操作结果。 ToolMessage 工具 功能与 ToolMessage 完全相同,是 ToolMessage 的别名。 MediaMessage 多媒体 表示除文本外的其他类型消息数据,例如图像。
其中,最常使用的是 SystemMessage、UserMessage 和 AssistantMessage 。
SystemMessage SystemMessage 通常用于设定 AI 助手的 身份、性格、行为准则和对话规则 ,一般位于对话的开头,为整个对话设定基调。
@RequestMapping("/chat")
@RestController
public class ChatClientController {
private ChatClient chatClient;
public ChatClientController (ChatClient.Builder chatClientBuilder) {
this .chatClient = chatClientBuilder
.defaultSystem("你叫小小鱼,是一款专业的智能答疑 AI 助手,擅长 Java 和 Python,以友好的态度来回答问题" )
.build();
}
@GetMapping("/call")
public String generation (String userInput) {
return this .chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
在 ChatClient 中,通过 defaultSystem 来设置 AI 模型的 默认系统消息 ,通过 ChatClient.Builder 链式调用设置的系统消息会作为对话的 "初始指令",注入到每次对话的上下文中,引导 AI 的回复风格或身份设定。
UserMessage UserMessage 表示我们提出的具体问题或指令,上述输入的 "你是谁",就是 UserMessage 。
AssistantMessage AssistantMessage :是 AI 模型给出的回复。
AssistantMessage 是实现 连贯多轮对话 的关键。每次 AI 回复后,可以将这个回复作为 AssistantMessage 保存下来,并在下一次请求时将其作为历史上下文的一部分发送给 AI。
输出格式
结构化输出 若想要从 LLM 接收 结构化输出 ,Spring AI 支持将 ChatModel/ChatClient 方法的返回类型从 Spring 更改为其他类型。
通过 entity() 方法将模型输出转化为 自定义实体 。
@RequestMapping("/chat")
@RestController
public class ChatClientController {
private ChatClient chatClient;
public ChatClientController (ChatClient.Builder chatClientBuilder) {
this .chatClient = chatClientBuilder
.build();
}
@GetMapping("/entity")
public String entity (String userInput) {
Recipe entity = this .chatClient.prompt()
.user(String.format("请帮我生成%s的菜谱" , userInput))
.call()
.entity(Recipe.class);
return entity.toString();
}
record Recipe (String dis, List<String> ingredients) {}
}
流式输出 **传统输出(非流式):**等待全部生成完成后才一次性返回,用户长时间等待 → 突然显示完整答案。像 寄送一封平信 ,写完所有内容才寄出,对方一次性收到整封信。
**流式输出:**边生成边返回,立即推送部分结果,几乎立即开始显示 → 逐字逐句增长。像 打电话 一样,对方一边说话,你一边就能听到。
用户提问:"请写一篇关于春天的短文"
AI 模型生成过程:
"春天"... (立即返回)
"春天来了"... (继续返回)
"春天来了,万物复苏"... (持续返回)
直到生成完整回答
Spring AI 主要通过 响应式编程 来实现流式输出,使用 stream() 方法生成 Flux 流。
@RequestMapping("/chat")
@RestController
public class ChatClientController {
private ChatClient chatClient;
public ChatClientController (ChatClient.Builder chatClientBuilder) {
this .chatClient = chatClientBuilder
.build();
}
@GetMapping(value = "/stream", produces = "text/html;charset=utf-8")
public Flux<String> stream (String userInput) {
return this .chatClient.prompt()
.user(userInput)
.stream()
.content();
}
record Recipe (String dis, List<String> ingredients) {}
}
但是,我们思考这样一个问题:由于 HTTP 协议本身设计为无状态的请求 - 响应模式,也就是严格来说,无法做到服务器主动推送消息到客户端,那么我们要如何实现服务器的流式响应呢?
我们可以通过 **SSE(Server-Sent Events,服务器发送事件)**来实现流式传输,允许服务器 主动向浏览器推送数据流 。
SSE 协议介绍 SSE 是一种 基于 HTTP 的 轻量级实时通信协议 ,浏览器通过 内置的 EventSource API 接收并处理这些实时事件。
服务器向客户端声明:接下来发送的是 流消息(streaming) 。
此时客户端不会关闭连接,会一直等待服务器发送过来新的数据流。
1. 单向通信 :数据流 只能从服务器推送到客户端 。客户端不能通过这个连接向服务器发送数据(除了最初的建立连接请求)。
2. 基于 HTTP/HTTPS :SSE 使用标准的 HTTP 协议,这意味着它可以轻松地穿越大多数防火墙和代理服务器,无需特殊的配置。
3. 长连接 :客户端发起一个普通的 HTTP 请求,但服务器会保持这个连接处于打开状态,而不是在发送一次响应后就关闭它。
4. 文本数据流 :服务器通过这个持久的连接,持续地向客户端发送遵循特定格式的文本数据流。
5. 自动重连 :SSE 协议内建了重连机制。如果连接意外断开,浏览器会自动尝试重新连接到服务器。
SSE 数据格式 服务器向浏览器发送 SSE 数据,需要设置必须的 HTTP 头信息 。
Content-Type: text/event-stream;charset=utf-8
Connection: keep-alive
整个数据流由 一系列消息 组成,**每条消息(message)**由 一行或多行文本 构成,每行文本以一个 字段名 开头,后跟一个冒号和一个空格,然后是字段的值,每条消息以一个 空行 (即两个连续的换行符 \n\n)结束。
field 的常见取值有:data、event、id、retry 。
data data :消息主体 ,是最重要的字段,用于 承载消息的实际内容 ,如果一个消息包含多个 data 行,客户端会将它们用换行符 (\n) 连接起来,形成一个完整的数据字符串。可用于传递 JSON 字符串、纯文本、XML 等任何文本数据。
data: 这是一条简单的消息\n\n
data: Hello\n
data: World\n
data: !\n\n
event event :事件类型,用于指定消息的自定义类型,若提供了此字段,客户端将触发对该特定事件名的监听器;否则,将触发通用的 **onmessage**事件,可用于 对不同类型的消息进行分类处理 。
event: userJoined
data: Alice
id id :事件 id,用于为消息设置一个唯一的 ID(字符串),如果连接中断,当客户端重新连接时,会在 HTTP 请求头 **Last-Event-ID**中自动发送最后一个接收到的 ID。可以用于实现 消息的幂等性和断点续传 。
id: msg-123
data: 这是一条重要消息
retry retry :重连时间,表示 建议浏览器在连接断开后再次尝试连接之前应等待的毫秒数 ,由于这 不是一个强制命令 ,浏览器 **可能会忽略它。**用于避免在服务器出现故障时,客户端过于频繁地重试。
示例:告诉浏览器,如果连接失败,请等待 10 秒后再尝试重连。
SSE 使用示例 @Slf4j
@RequestMapping("/sse")
@RestController
public class SseController {
@RequestMapping("/end")
public void end (HttpServletResponse response) throws IOException, InterruptedException {
log.info("发起请求:event" );
response.setContentType("text/event-stream;charset=utf-8" );
PrintWriter writer = response.getWriter();
for (int i = 0 ; i < 10 ; i++) {
String s = "event: foo\n" ;
s += "data: " + new Date () + "\n\n" ;
writer.write(s);
writer.flush();
Thread.sleep(1000L );
}
writer.write("event: end\ndata: EOF\n\n" );
writer.flush();
}
}
<!DOCTYPE html >
<html lang ="en" >
<head >
<meta charset ="UTF-8" >
<meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
<title > SSE</title >
</head >
<body >
<div > </div >
<script >
let eventSource = new EventSource ("/sse/end" );
eventSource.addEventListener ("foo" , function (event ) {
console .log (event);
document .getElementById ("sse" ).innerHTML = event.data ;
});
eventSource.addEventListener ("end" , function (event ) {
console .log ("连接关闭" );
eventSource.close ();
});
</script >
</body >
</html >
可以看到成功传输消息,并在消息传输完毕后关闭连接。
而在 Spring 中,可以通过 WebFlux 优雅地实现 SSE 协议,也就是我们之前使用的 Flux ,它是 WebFlux 中的核心组件,我们来看 Flux 的使用和常见操作 。
Flux Flux 的使用流程:创建 → 转换 → 过滤 → 消费 。
import reactor.core.publisher.Flux;
Flux<String> fixedFlux = Flux.just("Hello" , "World" , "!" );
List<String> list = Arrays.asList("A" , "B" , "C" );
Flux<String> fromCollection = Flux.fromIterable(list);
Flux<Integer> rangeFlux = Flux.range(1 , 5 );
Flux<Long> intervalFlux = Flux.interval(Duration.ofSeconds(1 )).take(5 );
Flux<String> arrayFlux = Flux.fromArray(new String []{"X" , "Y" , "Z" });
Flux<String> emptyFlux = Flux.empty();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Result" );
Flux<String> futureFlux = Flux.fromFuture(future);
Flux<String> original = Flux.just("apple" , "banana" , "cherry" );
Flux<String> uppercased = original.map(String::toUpperCase);
Flux<String> words = Flux.just("hello world" , "spring ai" );
Flux<String> splitWords = words.flatMap(word -> Flux.fromArray(word.split(" " )));
Flux<Object> objects = Flux.just("text1" , "text2" );
Flux<String> strings = objects.cast(String.class);
Flux<Integer> numbers = Flux.range(1 , 4 );
Flux<Integer> cumulativeSum = numbers.scan((acc, current) -> acc + current);
Flux<Integer> allNumbers = Flux.range(1 , 10 );
Flux<Integer> evenNumbers = allNumbers.filter(n -> n % 2 == 0 );
Flux<String> withDuplicates = Flux.just("A" , "B" , "A" , "C" );
Flux<String> uniqueItems = withDuplicates.distinct();
Flux<String> limited = original.take(2 );
Flux<String> skipped = original.skip(1 );
Flux<Integer> sequence = Flux.range(1 , 100 );
Flux<Integer> firstPart = sequence.takeWhile(n -> n < 10 );
Flux<Long> sampled = Flux.interval(Duration.ofMillis(100 ))
.sample(Duration.ofSeconds(1 ))
.take(3 );
Flux<String> data = Flux.just("one" , "two" , "three" );
data.subscribe(
item -> System.out.println("Received: " + item),
error -> System.err.println("Error: " + error),
() -> System.out.println("Completed!" )
);
Mono<List<String>> listMono = data.collectList();
Mono<Integer> sum = numbers.reduce(0 , Integer::sum);
Mono<Long> count = data.count();
Mono<Boolean> hasData = data.hasElements();
Mono<Void> completionSignal = data.then();
Advisors Advisors 是 Spring AI 中的一种拦截器机制 ,允许我们在 AI 调用链的特定节点注入自定义逻辑。
Before Call(调用前) :在请求发送到 AI 模型 之前 执行,主要用于 修改提示词 。
After Call(调用后) :在收到 AI 响应后、返回给客户端 之前 执行。
用户输入 → Advisor1.before() → Advisor2.before() → AI 模型调用 → Advisor2.after() → Advisor1.after() → 最终响应
在 Spring AI 中 内置了一些 Advisor ,如 SimpleLoggerAdvisor ,其主要功能是进行日志记录,只需要将其添加到 Advisor 链中,就可以自动记录 Advisor 的聊天请求和响应:
@RequestMapping("/chat")
@RestController
public class ChatClientController {
private ChatClient chatClient;
public ChatClientController (ChatClient.Builder chatClientBuilder) {
this .chatClient = chatClientBuilder
.defaultSystem("你叫小小鱼,是一款专业的智能答疑 AI 助手,擅长 Java 和 Python,以友好的态度来回答问题" )
.build();
}
@GetMapping("/advisor")
public String advisor (String userInput) {
return this .chatClient.prompt()
.advisors(new SimpleLoggerAdvisor ())
.user(userInput)
.call()
.content();
}
record Recipe (String dis, List<String> ingredients) {}
}
logging:
level:
org.springframework.ai.chat.client.advisor: debug
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online