![Spring AI[02]:核心API入门-使用ChatClient与大模型对话](https://img.dunling.com/blogimg/20260106/913ed454d1c9484cb56f09a37754cd1d.jpg)
本章目标:深入掌握 Spring AI 最核心的 ChatClient API,学会发送结构化消息、使用提示模板、处理流式响应,为构建复杂 AI 应用打下坚实基础。
2.1 本章核心概念预览
|
概念 |
说明 |
|
ChatClient |
Spring AI 提供的统一聊天接口,类似 WebClient,用于与大模型对话 |
|
Model |
大语言模型(如 GPT-4、qwen-plus),负责生成文本 |
|
Prompt |
发送给模型的输入指令,决定模型的行为 |
|
Response |
模型返回的结果,可以是文本、结构化数据等 |
|
Starter |
Spring Boot 自动配置模块,简化集成(如 |
2.2 ChatClient API 设计理念
Spring AI 中的 ChatClient API 的设计核心,是致力于将 AI 能力无缝、优雅地集成到成熟的 Spring 生态中,为 Java 开发者提供一套符合 Spring 哲学(如”约定优于配置”、依赖注入)的统一编程模型.
ChatClient 作为核心接口,定义了与AI模型交互的统一方式。无论后端是 OpenAI、Azure AI、Anthropic,还是国内的通义千问,开发者面对的都是同一套简洁的 API(如 call() 和 stream() 方法)。这意味着,当需要更换模型时,你一般只需修改配置文件中的 API 密钥和模型名称即可,业务代码无需任何改动。这种设计极大地降低了模型锁定的风险和技术切换的成本。
API 的设计深受现代 Java 和 Spring 风格的影响,提供了流畅的(Fluent)DSL 来构建请求,使得代码超级清晰易读:
String response = chatClient.prompt()
.user("请将'Hello World'翻译成法语。") // 设置用户消息
.call() // 执行调用
.content();
- org.springframework.ai.chat.client.ChatClient 被定义为了一个接口SpringAI 提供了默认的实现org.springframework.ai.chat.client.DefaultChatClient,实则例对象由org.springframework.ai.chat.client.ChatClient.Builder来构建
- 得益于SpringBoot 的自动配置, 在spring-ai-autoconfigure-model-chat-client-1.0.2.jar中有org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration自动配置类,它配置了org.springframework.ai.chat.client.ChatClient.Builder这个Bean对象,它会读取application.yml中的配置,设置模型参数,列如 key和所使用的模型。所以在使用的时候,可以直接注入后使用。
实战 1:最小使用样例
新建一个子模块 chapter02, 在application.yml中配置大模型参数:
# chapter02/src/main/resources/application.yml
spring:
ai:
dashscope:
# 必填,在操作系统环境变量中设置这个变量后,重启IDEA才能生效。由于IDEA启动的时候会缓存这个变量
api-key: ${AI_BAI_LIAN_API_KEY}
chat:
options:
model: qwen-plus
# 这个值0~1,值越大代表生成的结果随机性越强。如果是一个聊天,这个值可以大一点。如果是一些严谨的规划,则这个值可以设置小一些
temperature: 0.7
maven依赖:
<!-- chapter02/pom.xml -->
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 模型依赖:根据选型替换(如openai/deepseek/qwq) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
...
目前在chapter02 项目中增加一个Spring Controller:
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
// http://localhost:8080/chat/sync?question=什么是SpringAI
@GetMapping("/chat/sync")
public String syncChat(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
String question) {
return chatClient.prompt().user(question).call().content();
}
}
- 自动配置中已经可以直接使用ChatClient.Builder这个bean了,所以在构造方法中直接注入这个bean,上面代码为了简洁,直接调用了 build()方法来构建 chatClient对象,并没有对chatClient做过多的配置。更多的配置项,后面会讲到。
- Controller中提供了一个 GET请求,参数是 question为了测试方便,提供了默认值
- 启动应用测试: 打开浏览器,地址栏中输入[http://localhost:8080/chat/sync?question=](http://localhost:8080/chat/sync?question=)什么是SpringAI
当输入完回车后,会发现要等一段时间才看到有结果出现,而不是平时看到了马上就会有文字输出。这是由于我们使用的是同步调用的方式,即 chatClient.prompt().user(question).call().content(), call()方法的调用就是同步调用。
2.3 同步 vs 流式响应(Streaming)
Spring AI 支持两种调用模式:
|
模式 |
特点 |
适用场景 |
|
同步调用 |
等待模型生成完整响应后返回 |
简单问答、结构化输出 |
|
流式调用 |
逐字/逐 token 返回,实现“打字机效果” |
聊天界面、长文本生成 |
实战 2:流式调用
在Controller中增加一个流式调用的方法:
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
...
// http://localhost:8080/chat/stream?question=什么是SpringAI
@GetMapping(value = "/chat/stream", produces ="text/html;charset=UTF-8")
public Flux<String> streamChat(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
String question) {
return chatClient.prompt().user(question).stream().content();
}
...
}
- produces=text/html;charset=UTF-8 定义了输出的是文本,并指定字符集为 UTF-8
- Flux<String>:响应式流,逐段返回内容
- 前端可用 EventSource 接收并实时显示
流式响应超级适合 Web 聊天界面,用户体验更自然。
重新启动项目后,访问
http://localhost:8080/chat/stream?question=什么是SpringAI 会发现,页面上显示的内容就类似于打字机的效果
2.4 消息类型:prompt,user,assistant,system
在 Spring AI 中,prompt、system、user 和 assistant 是与大语言模型(LLM)交互的核心概念,它们共同构成了一次完整的对话请求。下面这个表格能帮你快速把握它们的核心区别。
|
概念 |
角色与功能 |
类比与特点 |
|
Prompt (提示词) |
一次完整请求的容器,包含所有消息和配置选项 |
类似于一次 HTTP 请求,包含了请求头(选项)和请求体(消息列表)。 |
|
System (系统角色) |
在后台设定 AI 的角色、行为和规则,不直接参与对话 |
类似于操作系统的内核,隐藏在幕后却决定了系统的整体行为风格。 |
|
User(用户角色) |
提问者,代表最终用户向 AI 提出的具体问题或指令 |
类似于向搜索引擎输入的关键词,是触发AI响应的直接缘由。 |
|
Assistant(助手角色) |
回答者,代表 AI 给出的回复,也用于在连续对话中提供历史上下文 |
类似于对话中的记忆单元,保证了多轮对话的连贯性。 |
实战 3:Prompt1
下面通过代码来理解它们。 在ChatController类中再添加一个方法:
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
...
// http://localhost:8080/chat/prompt
@GetMapping(value = "/chat/prompt1", produces = "text/html;charset=UTF-8")
public Flux<String> prompt1(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
String question) {
SystemMessage systemMessage = new SystemMessage("请按照以下格式输出:
" +
"1. 输出格式为:序号. 标题
" +
"2. 输出的序号为1-10,从1开始
" +
"3. 输出的标题为10个字以内的中文标题");
UserMessage userMessage = new UserMessage(question);
// 将消息封装成Prompt
Prompt prompt = new Prompt(systemMessage, userMessage);
return chatClient.prompt(prompt)
.stream().content();
}
...
}
GET http://localhost:8080/chat/prompt
1. Java核心技术 2. 深入理解Java 3. Java编程思想 4. Effective Java 5. Java并发编程 6. 重构改善程序 7. 设计模式之禅 8. JVM虚拟机详解 9. Spring实战指南 10. 算法(第4版)
2.5 核心概念详解
1. Prompt:请求的容器
你可以把 Prompt 理解为一个信封。它主要包含两部分:
- 消息序列(List<Message>):信封里按顺序排列的信纸,即 system、user、assistant 等不同角色的消息。
- 请求选项(ChatOptions):信封上的一些附加说明,如设置本次请求的模型参数(温度值 temperature、最大生成长度 max_tokens 等
Prompt 类将所有这些信息打包,然后通过模型的 call或者stream 方法发送给 AI 模型进行处理
2. Message 的角色分工
不同的 Message 角色在对话中扮演着截然不同的角色,理解它们是如何协作的至关重大。
- System:设定舞台背景
System 消息用于在对话开始前对 AI 进行“背景设定”和“角色扮演”。它一般是不可见的,但会深刻影响 AI 后续所有的回复风格和内容边界,例如:
// 设定一个专业的翻译官角色
SystemMessage systemMsg = new SystemMessage("你是一名专业的翻译官,负责将中文翻译成英文。翻译时应准确、流畅,符合英文表达习惯。");
- User & Assistant:驱动对话进行
User 和 Assistant 消息构成了对话的可见部分,它们一问一答,推动对话前进,
- UserMessage 是用户当前的问题或指令。
- AssistantMessage 一般是 AI 对上一个 UserMessage 的回复。但在构造请求时,它更重大的用途是提供对话历史,这对于实现连续对话(多轮对话)至关重大。AI 模型本身是无状态的,它需要依靠我们提供的完整历史记录来理解上下文。
官方文档(
https://docs.spring.io/spring-ai/reference/api/prompt.html)中提供了 Spring AI 的Message继承模式:
![Spring AI[02]:核心API入门-使用ChatClient与大模型对话](https://img.dunling.com/blogimg/20260106/af3f9926af134697ab3b1ebcc01633a9.jpg)
Spring AI基于Message接口类,最终衍生出SystemMessage、UserMessage、AssistantMessage和ToolResponseMessage。
实战 4:Prompt2
模拟多轮对话,体验 AssistantMessage, 在Controller中再添加一个方法:
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
...
// http://localhost:8080/chat/prompt2
@GetMapping(value = "/chat/prompt2", produces = "text/html;charset=UTF-8")
public Flux<String> prompt2(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
String question) {
List<Message> messages = new ArrayList<>();
//1. 第一设置系统指令,定义AI的行为(例如:作为一个友善的助手)
SystemMessage systemMessage = new SystemMessage("你是一个友善的助手,熟悉Java相关的书记,乐于用中文协助用户回答Java书籍相关问题。" +
"以列表的形式输出");
messages.add(systemMessage);
//2. 第一轮对话:用户提问
UserMessage userMessage1 = new UserMessage(question);
messages.add(userMessage1);
//3. 构建Prompt并发送请求
Prompt prompt = new Prompt(messages);
// 获取AI的回复
String responseText = chatClient.prompt(prompt).call().content();
log.info("第一轮对话输出:{}", responseText);
// 将AI的回复(AssistantMessage)加入历史记录,以备下一轮使用
messages.add(new AssistantMessage(responseText));
// 4. 第二轮对话:用户基于上一轮的内容继续提问
messages.add(new UserMessage("列表中第一个书名是什么?它主要讲解了哪些Java知识?"));
//再次构建Prompt,此时messages中包含了完整的对话上下文
prompt = new Prompt(messages);
return chatClient.prompt(prompt)
.stream().content();
}
...
}
GET http://localhost:8080/chat/prompt2
控制台输出第一轮对话(...省略其它输出):
1. 《Java核心技术 卷I:基础知识》(原书第11版)—— Cay S. Horstmann
Java经典教材,系统讲解Java基础与核心概念,适合初学者和中级开发者。
2. 《Effective Java》(第3版)—— Joshua Bloch
Java领域必读之作,深入讲解最佳实践和设计原则,适合中高级开发者。
3. 《Java编程思想》(Thinking in Java,第4版)—— Bruce Eckel
深入剖析Java语言的设计理念与面向对象思想,内容详实,适合进阶学习。
...
这些书籍覆盖了Java语言基础、高级特性、并发、JVM、设计模式和工程实践,是Java开发者成长路上的重大资源。
浏览器上输出第二轮对话结果(...省略其它输出):
列表中第一本书是: 《Java核心技术 卷I:基础知识》(原书第11版)—— Cay S. Horstmann ....
3.ChatOptions
ChatOptions表明Prompt的选项,它可以准确控制模型行为, Prompt构造方法最后一个参数可以配置ChatOptions
|
参数 |
说明 |
推荐值 |
|
temperature |
创造性 vs 确定性 |
0.0(确定)~ 1.0(值越大,随机性越强) |
|
maxTokens |
最大输出 token 数 |
512 ~ 2048 |
|
topP |
核采样概率 |
0.9 |
|
frequencyPenalty |
重复惩罚 |
0.5 |
|
presencePenalty |
新话题鼓励 |
0.5 |
ChatOptions options = ChatOptions.builder()
.withModel("gpt-3.5-turbo") // 指定模型
.withTemperature(0.3f) // 低温度:更确定
.withMaxTokens(200) // 限制长度
.withTopP(0.9f)
.build();
Prompt prompt = new Prompt(
new UserMessage("用三句话总结微服务架构的优点"),
options
);
2.6 Prompt 模板
硬编码提示词(prompt)不利于维护。Spring AI 提供 PromptTemplate 支持动态填充。实则现类包括:SystemPromptTemplate、AssistantPromptTemplate和FunctionPromptTemplate可以生成对应的Message。
![Spring AI[02]:核心API入门-使用ChatClient与大模型对话](https://img.dunling.com/blogimg/20260106/054f8768f58b45ac82b69a458ecbb939.jpg)
实战 5:使用 PromptTemplate 生成周报
创建模板文件
chapter02/src/main/resources/prompt/weekly-report.tpl:
你是一个专业的技术团队负责人。
请根据以下信息生成本周工作周报:
项目名称:{projectName}
完成事项:
{accomplishments}
遇到问题:
{challenges}
要求:
- 语言正式,结构清晰
- 分“项目进展”、“问题与风险”、“下周计划”三部分
- 不超过 300 字
在Controller中再添加一个方法:
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
...
// http://localhost:8080/chat/promptTpl
@GetMapping(value = "/chat/promptTpl", produces = "text/html;charset=UTF-8")
public Flux<String> promptTpl(@RequestParam(defaultValue = "请生成一个周报")
String question) {
// 创建一个PromptTemplate
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("classpath:/prompt/weekly-report.tpl");
List<String> acc = Arrays.asList("完成用户模块开发", "修复登录 Bug");
List<String> cha = Arrays.asList("第三方接口响应慢");
// 设置占位符实际内容并返回system角色的message
Message systemMessage = systemPromptTemplate.createMessage(Map.of("projectName", "喵星球知识库",
"accomplishments", String.join("
", acc),
"challenges", String.join("
", cha)));
// 设置用户的问题message
Message userMessage = new UserMessage(question);
// 组装Prompt
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
return chatClient.prompt(prompt).stream().content();
}
...
}
GET http://localhost:8080/chat/promptTpl
当然可以!以下是一份通用的周报模板,您可以根据具体岗位(如:技术、运营、市场、行政等)进行调整。如果您提供更详细的信息(如部门、工作内容、本周完成事项、下周计划等),我可以为您定制更具体的周报。
...
2.7 默认角色
上面可以看到Prompt中可以设置多个角色Message,但是不是必须的。如果在单次聊天会话中没有指定角色,可以在构建chatClient对象的时候,指定默认的角色Message。
// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
...
public ChatController(ChatClient.Builder builder) {
// this.chatClient = builder.build();
this.chatClient = builder
.defaultSystem("你是一个精通Java的工程师,专门解决Java遇到的问题。")// 设定默认角色
.defaultUser("你是谁?")//设置默认问题
.build();
}
...
这样在chatClient送出 Prompt 的时候,如果没有指定角色Message,就会使用默认的角色Message。
2.7 本章小结
- ChatClient 是 Spring AI 的核心入口,API 设计简洁且强劲。
- 支持同步和流式调用,满足不同场景需求。
- 通过 UserMessage、AssistantMessage、SystemMessage 构建结构化对话。
- 使用 PromptOptions 精细控制模型生成行为。
- PromptTemplate 实现提示词与代码分离,提升可维护性。
下一章预告:第3章《数据结构化输出-让 AI 返回 Java 对象》
在下一章中,我们将学习如何让 AI 返回结构化的 Java 对象,而不是原始字符串。例如:
- 让 AI 解析用户输入并返回 UserRegistration 对象
- 提取会议纪要中的时间、地点、参会人等字段
我们将使用 @Schema 注解和 JSON Mode 实现这一目标,彻底告别字符串解析!
继续前进吧!
源代码地址:
https://github.com/kaiwill/kaifamiao
