Spring AI ChatClient 深度解析:优雅构建大模型应用的利器
Posted on 2026-05-30 22:07 work hard work smart 阅读(23) 评论(0) 收藏 举报摘要:ChatClient 是 Spring AI 提供的高级 API,通过链式调用和 Builder 模式,让大模型调用变得更加优雅和灵活。本文结合实际项目代码,全面讲解 ChatClient 的使用技巧和最佳实践。
一、为什么需要 ChatClient?
在使用 Spring AI 开发大模型应用时,我们有两种主要方式:
方式 1:直接使用 ChatModel
@Autowired
private ChatModel chatModel;
public String call(String message) {
return chatModel.call(message);
}
缺点:
- 功能相对底层,需要手动构建 Prompt
- 缺少统一的链式调用 API
- Advisor(拦截器)机制使用不便
方式 2:使用 ChatClient(推荐)
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个专业助手")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
return chatClient.prompt(message).call().content();
优势:
- ✅ 链式调用,代码更优雅
- ✅ Builder 模式,配置更灵活
- ✅ 内置 Advisor 机制,支持日志、缓存等切面功能
- ✅ 统一的 API 风格,降低学习成本
二、ChatClient 核心概念
1. ChatClient 的三层结构
ChatClient(客户端)
├── Builder(构建器)
│ ├── defaultSystem() - 默认系统提示词
│ ├── defaultAdvisors() - 默认拦截器
│ └── defaultOptions() - 默认模型参数
│
└── PromptSpec(提示词规格)
├── prompt() - 构建提示词
├── system() - 覆盖系统提示词
├── user() - 设置用户消息
├── call() - 同步调用
└── stream() - 流式调用
2. 生命周期管理
ChatClient 应该在应用启动时创建,整个生命周期复用:
@RestController
public class ChatController implements InitializingBean {
@Autowired
private ChatModel chatModel;
private ChatClient chatClient;
@Override
public void afterPropertiesSet() {
// 在 Bean 初始化后创建 ChatClient
chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个 AI 助手")
.build();
}
}
三、ChatClient 五种调用方式
方式 1:最简单调用
直接传入字符串,使用默认配置:
@GetMapping("/simple")
public String simpleCall(String message) {
return chatClient.prompt(message).call().content();
}
测试:
curl "http://localhost:8000/client/simpleCall?message=介绍一下Spring AI"
执行流程:
prompt(message)
→ 构建 Prompt 对象
→ 添加 defaultSystem 提示词
→ 调用 ChatModel
→ 返回内容
方式 2:动态覆盖 System 提示词
在运行时动态修改系统提示词:
@GetMapping("/translator")
public String translateCall(String message) {
return chatClient.prompt(message)
.system("你是一个专业的翻译助手,请把中文翻译成英文")
.call()
.content();
}
场景:同一个 ChatClient,不同接口需要不同的角色设定。
方式 3:使用 user() 方法
分离用户消息的设置:
@GetMapping("/chat")
public String chatCall(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
与方式 1 的区别:
prompt(message):一步到位prompt().user(message):分步构建,更灵活
方式 4:使用 Prompt 对象
完全手动控制,最大灵活性:
@GetMapping("/advanced")
public String advancedCall(String message) {
Prompt prompt = new Prompt(
new SystemMessage("请详细回答我的问题"),
new UserMessage(message)
);
return chatClient.prompt(prompt)
.call()
.content();
}
适用场景:
- 复杂的多消息对话
- 需要精确控制消息顺序
- 历史对话记录复用
方式 5:流式输出
实时返回结果,适合长文本:
@GetMapping("/stream")
public Flux<String> streamCall(String message) {
return chatClient.prompt(message)
.stream()
.content();
}
前端调用:
const response = await fetch('/client/stream?message=写一首诗');
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
四、ChatClient.Builder 深度配置
完整配置示例
@Override
public void afterPropertiesSet() throws Exception {
chatClient = ChatClient.builder(dashScopeChatModel)
// 1. 默认系统提示词
.defaultSystem("请用英文回答问题")
// 2. 默认拦截器(Advisor)
.defaultAdvisors(
new SimpleLoggerAdvisor() // 日志记录
)
// 3. 默认模型参数
.defaultOptions(
DashScopeChatOptions.builder()
.model("qwen-plus")
.temperature(0.7)
.maxTokens(2000)
.build()
)
.build();
}
配置项详解
1. defaultSystem() - 默认系统提示词
.defaultSystem("""
你是一个专业的 Java 开发专家,擅长 Spring Boot 和微服务架构。
请用简洁的代码示例回答问题,并添加详细注释。
""")
作用:所有调用都会自动带上这个系统提示词,无需重复设置。
2. defaultAdvisors() - 默认拦截器
Advisor 是 ChatClient 的核心扩展机制:
.defaultAdvisors(
new SimpleLoggerAdvisor(), // 记录日志
new MessageChatMemoryAdvisor(), // 对话记忆
new VectorStoreAdvisor() // 向量数据库检索
)
常用 Advisor:
| Advisor | 功能 | 场景 |
|---|---|---|
SimpleLoggerAdvisor |
打印请求和响应日志 | 开发调试 |
MessageChatMemoryAdvisor |
自动管理对话历史 | 多轮对话 |
VectorStoreAdvisor |
RAG 向量检索 | 知识库问答 |
3. defaultOptions() - 默认模型参数
.defaultOptions(
DashScopeChatOptions.builder()
.model("qwen-plus") // 模型名称
.temperature(0.7) // 创造性 (0-1)
.maxTokens(2000) // 最大输出长度
.topP(0.9) // 核采样参数
.build()
)
支持的参数(以 DashScope 为例):
model:指定模型(qwen-turbo、qwen-plus、qwen-max)temperature:创造性(0 严谨,1 创意)maxTokens:最大 token 数topP:核采样参数stop:停止词列表
五、实战场景
场景 1:多角色 ChatClient
不同业务场景使用不同的 ChatClient:
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient translatorClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个专业翻译,请把中文翻译成英文")
.defaultOptions(DashScopeChatOptions.builder()
.temperature(0.3) // 翻译需要严谨
.build())
.build();
}
@Bean
public ChatClient writerClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个创意作家,擅长写出优美的文字")
.defaultOptions(DashScopeChatOptions.builder()
.temperature(0.9) // 写作需要创意
.build())
.build();
}
}
使用:
@RestController
public class MultiRoleController {
@Autowired
@Qualifier("translatorClient")
private ChatClient translatorClient;
@Autowired
@Qualifier("writerClient")
private ChatClient writerClient;
@GetMapping("/translate")
public String translate(String message) {
return translatorClient.prompt(message).call().content();
}
@GetMapping("/write")
public String write(String message) {
return writerClient.prompt(message).call().content();
}
}
场景 2:动态模型切换
运行时动态选择模型:
@GetMapping("/chat")
public String chat(String message, String model) {
return chatClient.prompt(message)
.options(DashScopeChatOptions.builder()
.model(model) // 动态指定模型
.build())
.call()
.content();
}
测试:
# 使用 qwen-plus
curl "http://localhost:8000/client/chat?message=你好&model=qwen-plus"
# 使用 deepseek-v3
curl "http://localhost:8000/client/chat?message=你好&model=deepseek-v3"
场景 3:对话记忆(多轮对话)
结合 ChatMemory 实现上下文对话:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(
new InMemoryChatMemory(), // 内存存储
"user-123", // 用户 ID
10 // 保留 10 条历史消息
)
)
.build();
@GetMapping("/conversation")
public String conversation(String message) {
return chatClient.prompt(message).call().content();
}
六、完整 Controller 示例
package cn.hollis.llm.llmentor.controller;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/client")
public class ChatClientDemoController implements InitializingBean {
@Autowired
private ChatModel dashScopeChatModel;
private ChatClient chatClient;
/**
* 简单调用
*/
@GetMapping("/simple")
public String simpleCall(String message) {
return chatClient.prompt(message).call().content();
}
/**
* 覆盖默认 System 提示词
*/
@GetMapping("/translator")
public String translateCall(String message) {
return chatClient.prompt(message)
.system("你是一个翻译助手,请把中文翻译成英文")
.call()
.content();
}
/**
* 使用 user() 方法
*/
@GetMapping("/chat")
public String chatCall(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
/**
* 使用 Prompt 对象
*/
@GetMapping("/advanced")
public String advancedCall(String message) {
Prompt prompt = new Prompt(
new SystemMessage("请详细回答问题"),
new UserMessage(message)
);
return chatClient.prompt(prompt).call().content();
}
/**
* 流式输出
*/
@GetMapping("/stream")
public Flux<String> streamCall(String message) {
return chatClient.prompt(message).stream().content();
}
@Override
public void afterPropertiesSet() throws Exception {
chatClient = ChatClient.builder(dashScopeChatModel)
// 默认系统提示词
.defaultSystem("请用英文回答问题")
// 默认拦截器
.defaultAdvisors(new SimpleLoggerAdvisor())
// 默认模型参数
.defaultOptions(
DashScopeChatOptions.builder()
.temperature(0.7)
.build()
)
.build();
}
}
七、最佳实践
1. ChatClient 应该复用
❌ 错误做法(每次请求都创建):
@GetMapping("/bad")
public String badPractice(String message) {
ChatClient client = ChatClient.builder(chatModel).build(); // 每次都创建!
return client.prompt(message).call().content();
}
✅ 正确做法(启动时创建,全局复用):
private ChatClient chatClient;
@Override
public void afterPropertiesSet() {
chatClient = ChatClient.builder(chatModel).build();
}
2. 合理使用 Advisor
开发环境开启日志,生产环境关闭:
@Bean
public ChatClient chatClient(ChatModel chatModel, Environment env) {
ChatClient.Builder builder = ChatClient.builder(chatModel);
// 只在开发环境添加日志 Advisor
if (env.acceptsProfiles(Profiles.of("dev"))) {
builder.defaultAdvisors(new SimpleLoggerAdvisor());
}
return builder.build();
}
3. 分离配置和调用
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个 AI 助手")
.defaultOptions(DashScopeChatOptions.builder()
.temperature(0.7)
.build())
.build();
}
}
@RestController
public class ChatController {
@Autowired
private ChatClient chatClient; // 直接注入使用
@GetMapping("/chat")
public String chat(String message) {
return chatClient.prompt(message).call().content();
}
}
4. 错误处理
@GetMapping("/safe")
public String safeCall(String message) {
try {
return chatClient.prompt(message).call().content();
} catch (Exception e) {
log.error("调用大模型失败", e);
return "抱歉,服务暂时不可用,请稍后重试";
}
}
八、ChatClient vs ChatModel 对比
| 特性 | ChatModel | ChatClient |
|---|---|---|
| API 风格 | 底层 API | 链式调用 |
| 配置方式 | 每次调用设置 | Builder 预设 |
| Advisor 支持 | 手动管理 | 内置支持 |
| 适用场景 | 简单调用、底层控制 | 企业级应用、复杂场景 |
| 代码优雅度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 学习成本 | 低 | 中 |
九、总结
ChatClient 是 Spring AI 提供的高级 API,通过以下特性让大模型调用更优雅:
- 链式调用:
prompt().system().user().call().content() - Builder 模式:一次性配置,全局复用
- Advisor 机制:日志、缓存、RAG 等切面功能
- 灵活覆盖:默认配置 + 运行时覆盖
推荐做法:
- 企业级项目优先使用 ChatClient
- 简单测试可以使用 ChatModel
- 合理配置 Advisor,提升开发效率
通过 ChatClient,我们可以用更少的代码、更清晰的结构,构建强大的大模型应用!
参考资源:
作者:Work Hard Work Smart
出处:http://www.cnblogs.com/linlf03/
欢迎任何形式的转载,未经作者同意,请保留此段声明!
浙公网安备 33010602011771号