Spring AI Chat Client
目录
一、介绍
在前面“Chat Models”小节内容中,我们在与各个模型进行对话时使用的是对应模型的ChatModel对象,例如:DeepSeekChatModel、OllamaChatModel。然后通过chatModel.call(...)、chatModel.stream(...)直接调用模型,这其中我们需要手动构造Prompt、处理流响应(例如将返回封装成对象、从模型响应中获取回复内容)、拼装调用链(RAG需要自己构建流程)等操作。
以上使用ChatModel与模型进行对话方式中,如果在项目中使用大模型涉及到记忆上下文、Prompt模版化、RAG开发、返回内容映射为实体等操作时,单纯的ChatModel代码量很多,维护成本高,为了简化这个流程,Spring AI 中提供了ChatClient对象,该对象可以看做一个更高级的“客户端 API”,建立在ChatModel之上,可以用链式的方式快速搭配 Prompt、系统设定、变量替换、上下文记忆等,并支持文本/JSON/实体对象等多种形式的输出。
ChatModel和Chat Client对象对比如下:
| 方面 | ChatModel | ChatClient |
|---|---|---|
| Prompt构建 | 需手动new Prompt(...) | 链式.prompt().user(...)使用 |
| 流式处理 | 手动处理流对象 | .stream().content()简洁获取流内容 |
| 上下文/记忆 | 手动拼接历史上下文 | 支持内置ChatMemory和Advisor |
| RAG架构 | 需自行编写检索、检索结果推进流程 | 有内置Advisor支持RAG简化 |
| 输出映射 | .chatResponse()后自己解析JSON映射到实体 | .entity(...)可直接映射实体类 |
特别注意:ChatClient 目前只支持 Chat(对话)模型,不包括 Embedding、Image、Audio 等多模态模型要使用 Embedding、Image 或 Audio 等模型,需要直接使用 Spring AI 提供的对应 API,比如 EmbeddingModel、ImageModel、AudioModel。
二、使用ChatClient
下面以使用DeepSeek为例来演示Spring AI中如何使用ChatClient。涉及ChatClient创建、设置提示词、流式回复、回复映射到对象操作。
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.15-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringAIChatClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringAIChatClient</name>
<description>SpringAIChatClient</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本,
引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
</dependencies>
<!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本-->
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
2、application.properties
spring.application.name=SpringAIChatClient
server.port=8080
#配置 Deepseek的基础URL、密钥和使用模型
spring.ai.deepseek.base-url=https://api.deepseek.com
spring.ai.deepseek.api-key=${deepseek_api_key}
spring.ai.deepseek.chat.options.model=deepseek-chat
3、创建pojo包并创建Student.java类
package com.example.springaichatclient.pojo;
public class Student {
private Long id;
private String name;
private Integer age;
public Student() {}
public Student(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
// Getter 和 Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "', age=" + age + "}";
}
}
4、ChatController.java
package com.example.springaichatclient.controller;
import com.example.springaichatclient.pojo.Student;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient client;
public ChatController(ChatClient.Builder builder) {
//在构造函数中创建 ChatClient
//并设置默认 system prompt
this.client = builder
.defaultSystem("你是一个聊天助手,名字叫小智。")
.build();
}
//使用文本响应用户问题、设置Prompt提示词
@GetMapping("/chat")
public Map<String, String> chatText(@RequestParam("message") String message) {
String reply = client.prompt("如果用户让你讲故事,只能讲解神话故事,不能讲其他的。")
.user(message)
.call()
.content();
return Map.of("reply", reply);
}
@GetMapping("/response")
public ChatResponse chatResponse(@RequestParam("message") String message) {
return client.prompt()
.user(message)
.call()
.chatResponse();
}
@GetMapping("/getOneStudent")
public Student getOneEntity() {
return client.prompt()
.user("生成1个Student对象,输出单个JSON对象,字段:id(Long),name(String),age(Integer)")
.call()
.entity(Student.class);
}
@GetMapping("/getStudentList")
public List<Student> getStudentList() {
return client.prompt()
.user("生成3个Student对象,JSON数组格式,字段:id(Long),name(String),age(Integer)")
.call()
.entity(new ParameterizedTypeReference<List<Student>>() {});
}
@GetMapping(path = "/chatStream")
public Flux<String> chatStream(@RequestParam("message") String message, HttpServletResponse response) {
// 避免返回乱码
response.setCharacterEncoding("UTF-8");
return client.prompt()
.user(message)
.stream()// 开启流式响应
.content();
}
}
以上代码中需要注意:defaultSystem(...)是设置全局系统提示词;prompt(...)是设置当前对话提示词;user(...)为用户输入消息内容。
5、测试
# http://localhost:8080/ai/chat?message=给我讲个故事
{
"reply": "好的,我很高兴为您讲一个神话故事。今天我要讲的是中国上古神话《夸父追日》:\n\n很久很久以前,在北方的大荒之中,住着一个名叫夸父的巨人。他是后土神的孙子,信神的儿子。夸父身高如山,力大无穷,耳朵上挂着两条黄蛇,手里也握着两条黄蛇。\n\n那时候,太阳每天从东边升起,西边落下,夸父觉得太阳跑得太快了,大地上的时间太短暂。于是他决定要追上太阳,让它慢下来。\n\n夸父迈开巨大的步伐,开始追逐太阳。他跑啊跑啊,跨过一座座高山,越过一条条大河。太阳在天上跑,夸父在地上追。眼看就要在禺谷追上太阳了,夸父却感到口渴难忍。\n\n他俯下身来,一口气喝干了黄河的水,又喝干了渭河的水,还是觉得口渴。于是他向北跑去,想要喝大泽的水。可是还没跑到大泽,夸父就因为极度干渴而倒下了。\n\n临死前,夸父扔出了他的手杖。那手杖化作了一片桃林,结满了鲜美的桃子,为后来路过的人解渴。\n\n这个故事展现了古人征服自然的雄心壮志,也告诉我们做事要量力而行。您觉得这个神话故事怎么样?"
}
# http://localhost:8080/ai/response?message=你是谁
{
... ...
"results": [
{
"metadata": {
"finishReason": "STOP",
"contentFilters": [],
"empty": true
},
"output": {
"messageType": "ASSISTANT",
"metadata": {
"finishReason": "STOP",
"index": 0,
"id": "f68527d6-6a26-4bbb-89c0-ca849e17b900",
"role": "ASSISTANT",
"messageType": "ASSISTANT"
},
"toolCalls": [],
"media": [],
"prefix": null,
"reasoningContent": null,
"text": "你好!我是小智,一个智能聊天助手,随时为你提供帮助和解答问题。无论是日常疑问、学习辅导,还是闲聊放松,我都可以陪你聊聊!有什么我可以帮你的吗?😊"
}
}
]
}
# http://localhost:8080/ai/getOneStudent
{
"id": 1,
"name": "张三",
"age": 20
}
# http://localhost:8080/ai/getStudentList
[
{
"id": 1,
"name": "张三",
"age": 20
},
{
"id": 2,
"name": "李四",
"age": 21
},
{
"id": 3,
"name": "王五",
"age": 22
}
]
更多推荐


所有评论(0)