跳到主要内容
Spring AI 实战指南:从概念到流式输出 | 极客日志
Java AI java
Spring AI 实战指南:从概念到流式输出 综述由AI生成 Spring AI 是简化 Java 应用集成 AI 功能的开源框架,提供统一的多模型支持和数据集成能力。涵盖 AI 基本概念如 LLM、Prompt 和 Token,演示了环境搭建与 API Key 申请流程。重点解析 ChatModel 与 ChatClient 两大核心接口的差异与应用场景,介绍 SystemMessage、UserMessage 等消息类型管理。此外还深入讲解了结构化输出、基于 SSE 和 Flux 的流式输出实现,以及 Advisors 拦截器机制的使用,适合希望快速上手 Spring AI 的开发者参考。
城市逃兵 发布于 2026/4/7 更新于 2026/5/23 16 浏览Spring AI 实战指南
基本概念
什么是 AI
AI(Artificial Intelligence)即人工智能,核心是让机器模拟人类智能。我们可以这样理解差异:
普通计算机程序像自动售货机,输入特定按钮就输出特定饮料,行为完全由预设规则决定。而 AI 程序更像在学习的孩子,给它看大量猫狗图片并标注,它就能自己学会规律,识别从未见过的猫咪。
目前最主流的是生成式人工智能(AIGC),目标是利用 AI 自动生成内容,比如写文章、翻译或编程。
模型(Model)
模型是 AI 系统的核心,本质是通过算法在数据上训练后得到的数学函数。我们常说的'调用 AI',实际上就是在使用这个模型。它可以想象成一个'虚拟大脑',通过训练掌握技能,回答问题时运用这些知识。
大语言模型(LLM)
LLM(Large Language Model)是基于深度学习、使用海量文本训练的模型。特点是'大',体现在训练数据量和参数数量巨大。它像一个经过超大规模训练的专家大脑,掌握了语法、事实和逻辑,能应对各种话题。
提示词(Prompt)
提示词是用户提供给 AI 的指令或上下文。提示词的质量直接决定回答质量。设计优化提示词的过程叫'提示词工程'。
简单提示词:'法国的首都是哪里?' -> '巴黎。'
复杂提示词(角色扮演):'假设你是资深营养师,为我设计一周健康午餐食谱。' -> 模型会以营养师口吻提供详细方案。
词元(Token)
Token 是模型处理文本的基本单位,不完全等同于单词或汉字。它是计费和衡量长度的基本单位。
英文 unbelievable 可能拆分为 ["un", "believe", "able"]
中文'我喜欢编程'可能拆分为 ['我', '喜', '欢', '编程']
不同模型分词规则不同,同一个词在不同模型中拆分结果也可能不同。
Spring AI 是什么
Spring AI 是一个基于 Spring 生态的开源 AI 应用框架,旨在简化 Java 应用中集成 AI 功能的过程。它提供了开发 AI 应用的基础抽象,支持多种实现,可以通过最少代码更改切换组件。
主要特性包括:
统一的多模型支持 :兼容 OpenAI、Microsoft、Amazon、Google、Anthropic 等主流提供商,云端或本地部署(如 Ollama)均可通过一致接口调用。
强大的数据集成能力 :内置对向量数据库(Chroma、Pinecone、Redis 等)的支持。
与 Spring 生态无缝集成 :自然协同 Spring Boot、Spring Data 等项目。
简化的开发模式 :允许 AI 模型请求执行客户端定义的函数,接入实时信息或触发动作。
快速入门
环境要求
JDK 版本 :JDK 17 或以上(推荐 JDK 21),Spring Boot 3.x 强制要求。
Spring Boot 版本 :3.2 或以上,选择稳定的 3.x 最新版本即可。
AI 服务凭证 :有效的 API Key,需来自 AI 服务提供商(如 OpenAI、DeepSeek、阿里百炼等)。
本文以 DeepSeek 为例进行演示。
申请 API Key
访问 DeepSeek 官网进入 API 开放平台,创建 API Key。注意 Key 仅在创建时可见可复制。学习使用充值少量金额即可。
项目创建
Spring AI 为 OpenAI 及兼容 API 服务设计了 spring-ai-openai-spring-boot-starter。
创建 Maven 项目并添加依赖:
< >
org.springframework.ai
spring-ai-openai-spring-boot-starter
1.0.0-M6
dependency
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</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
base-url:连接地址
api-key:API 密钥
model:使用的 LLM 模型
temperature:控制随机性。低温(接近 0.0)保守确定,高温(接近 2.0)多样富有想象力。不建议同时修改 temperature 和 top_p。
接口编写 @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=你是谁 测试。
核心接口 Spring AI 构建对话式 AI 应用主要依赖两个核心接口:ChatModel 和 ChatClient。
ChatModel ChatModel 直接与底层 AI 模型通信,处理原始请求和响应,更底层且灵活。
@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,简化常见使用模式,更高级简洁。
@Service
public class ChatService {
@Autowired
private ChatClient chatClient;
public String askQuestion (String question) {
return chatClient.call(question);
}
}
维度 ChatModel ChatClient 抽象层级 底层,接近原始模型 高层,面向业务 返回值 ChatResponse(含丰富元数据) ChatResponse(纯文本)或流式响应 使用方法 手动构造 Prompt 对象 流式 builder 模式 控制粒度 精细控制 快捷简便
消息类型 所有消息类型都实现了 Message 接口,模拟多轮对话中的不同参与者。
消息类型 对应角色 核心作用 SystemMessage 系统 / 导演 设定背景、角色、风格,定基调 UserMessage 用户 / 提问者 驱动对话前进 AssistantMessage 助理 / AI 代表 AI 回复,保障连贯性 FunctionMessage 函数 / 工具 代表函数调用结果 MediaMessage 多媒体 图像等非文本数据
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();
}
}
通过 defaultSystem 设置默认系统消息,作为对话的初始指令注入上下文。
UserMessage & AssistantMessage UserMessage 代表具体问题,AssistantMessage 是 AI 回复。实现连贯多轮对话的关键是将 AI 回复保存为 AssistantMessage,并在下次请求时作为历史上下文发送。
输出格式
结构化输出 Spring AI 支持将模型输出转化为自定义实体。通过 entity() 方法指定返回类型。
@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) {}
流式输出 传统输出等待全部生成完成后一次性返回,流式输出则边生成边返回,类似打电话体验。
Spring AI 通过响应式编程实现流式输出,使用 stream() 方法生成 Flux<String>。
@GetMapping(value = "/stream", produces = "text/html;charset=utf-8")
public Flux<String> stream (String userInput) {
return this .chatClient.prompt()
.user(userInput)
.stream()
.content();
}
由于 HTTP 协议无状态,无法主动推送,我们通过 SSE(Server-Sent Events)实现服务器向浏览器推送数据流。
SSE 协议介绍 SSE 是一种基于 HTTP 的轻量级实时通信协议,浏览器通过 EventSource API 接收事件。
单向通信 :仅服务器推送到客户端。
基于 HTTP/HTTPS :穿越防火墙无需特殊配置。
长连接 :保持连接打开,持续发送数据。
文本数据流 :遵循特定格式的文本流。
自动重连 :断开后浏览器自动尝试重连。
SSE 数据格式 需设置 HTTP 头:Content-Type: text/event-stream;charset=utf-8。
每条消息由字段名、冒号、空格和值组成,以空行结束。常见字段:
data :消息主体,承载实际内容。
event :事件类型,用于分类处理。
id :唯一 ID,用于断点续传。
retry :建议重连等待时间(毫秒)。
data: 这是一条简单的消息
event: foo
data: Hello World
id: msg-123
data: 重要消息
retry: 10000
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();
}
}
<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 >
在 Spring 中,可通过 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();
String first = data.blockFirst();
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 :请求发送到模型之前,常用于修改提示词。
After Call :收到响应后、返回给客户端之前。
执行流程:用户输入 → Advisor1.before() → ... → AI 模型调用 → ... → Advisor1.after() → 最终响应。
Spring AI 内置了 SimpleLoggerAdvisor,用于记录聊天请求和响应。
@RequestMapping("/chat")
@RestController
public class ChatClientController {
private ChatClient chatClient;
public ChatClientController (ChatClient.Builder chatClientBuilder) {
this .chatClient = chatClientBuilder
.defaultSystem("你叫小小鱼,是一款专业的智能答疑 AI 助手" )
.build();
}
@GetMapping("/advisor")
public String advisor (String userInput) {
return this .chatClient.prompt()
.advisors(new SimpleLoggerAdvisor ())
.user(userInput)
.call()
.content();
}
}
logging:
level:
org.springframework.ai.chat.client.advisor: debug
相关免费在线工具 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