ChatClient
ChatClient 是 Spring AI 最核心的用户入口,提供 Builder 模式的流式 API,将 ChatModel 的底层调用封装为直观的 prompt().user().call().content() 调用链。
1. 架构定位
ChatClient 是 Spring AI 的核心入口,通过 Builder 模式构建,提供 prompt().user().call().content() 式的流式 API。
2. 创建 ChatClient
ChatClient 通过 Builder 模式创建。Builder 由 Spring AI 自动配置提供,注入后调用 .build() 即可获得实例。
- 自动注入
- 手动创建
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是一个乐于助人的AI助手")
.defaultOptions(chatOptions -> chatOptions.temperature(0.7))
.build();
}
}
public class ManualChatClientExample {
public static void main(String[] args) {
OllamaApi ollamaApi = OllamaApi.builder()
.baseUrl("http://localhost:11434")
.build();
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaOptions.builder().model("qwen3:8b").build())
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个乐于助人的AI助手")
.build();
String response = chatClient.prompt()
.user("你好,请用一句话介绍自己")
.call()
.content();
System.out.println(response);
}
}
3. 同步调用
.call() 是最基础的调用方式,阻塞等待完整响应后返回 CallResponseSpec,支持四种结果提取方式。
3.1 获取文本内容
最常用的方式,直接返回模型输出的文本字符串。
完整示例:CallContentDemo.java
@Component
public class CallContentDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CallContentDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
String response = chatClient.prompt()
.user("用一句话解释什么是 Spring AI")
.call()
.content();
System.out.println(response);
}
}
3.2 结构化输出
将模型输出自动映射为 Java POJO、List 或 Map。
完整示例:CallEntityDemo.java
@Component
public class CallEntityDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CallEntityDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
record Actor(String name, List<String> films) {}
@Override
public void run(String... args) {
Actor actor = chatClient.prompt()
.user("列出演员梁朝伟和他最著名的三部电影。输出格式: {\"name\": \"...\", \"films\": [\"...\"]}")
.call()
.entity(Actor.class);
System.out.println("演员: " + actor.name());
System.out.println("代表作: " + actor.films());
}
}
3.3 获取完整响应
返回 ChatResponse 对象,包含所有 Generation 结果、Token 用量、元数据等完整信息。
完整示例:CallChatResponseDemo.java
@Component
public class CallChatResponseDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CallChatResponseDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
ChatResponse response = chatClient.prompt()
.user("用一句话介绍杭州")
.call()
.chatResponse();
for (Generation generation : response.getResults()) {
System.out.println("输出: " + generation.getOutput().getText());
System.out.println("元数据: " + generation.getMetadata());
}
System.out.println("Token 用量: " + response.getMetadata().getUsage());
}
}
3.4 获取原始响应与实体
适用于需要在业务实体之外访问 Token 用量或元数据的场景。
完整示例:CallResponseEntityDemo.java
@Component
public class CallResponseEntityDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CallResponseEntityDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
record Translation(String original, String translated) {}
@Override
public void run(String... args) {
ResponseEntity<ChatResponse, Translation> result = chatClient.prompt()
.user("将 'Hello World' 翻译成中文。输出格式: {\"original\": \"...\", \"translated\": \"...\"}")
.call()
.responseEntity(Translation.class);
System.out.println("翻译结果: " + result.getEntity().translated());
System.out.println("消耗 Token: " + result.getResponse().getMetadata().getUsage().getTotalTokens());
}
}
4. 流式调用
.stream() 返回 StreamResponseSpec,通过 Reactor Flux 实现逐字输出,适合打字机效果展示。
4.1 流式文本
完整示例:StreamContentDemo.java
@Component
public class StreamContentDemo implements CommandLineRunner {
private final ChatClient chatClient;
public StreamContentDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
Flux<String> stream = chatClient.prompt()
.user("写一首关于春天的五言绝句")
.stream()
.content();
stream.subscribe(
chunk -> System.out.print(chunk),
error -> System.err.println("错误: " + error.getMessage()),
() -> System.out.println("\n[流式输出完成]")
);
}
}
4.2 流式原始响应
获取每个 chunk 的完整 ChatResponse,可逐块检查元数据。
完整示例:StreamChatResponseDemo.java
@Component
public class StreamChatResponseDemo implements CommandLineRunner {
private final ChatClient chatClient;
public StreamChatResponseDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
Flux<ChatResponse> stream = chatClient.prompt()
.user("用三句话介绍 Java 21 的新特性")
.stream()
.chatResponse();
stream.subscribe(chunk -> {
String text = chunk.getResults().get(0).getOutput().getText();
if (text != null) {
System.out.print(text);
}
});
}
}
5. 请求配置
ChatClientRequestSpec 提供了丰富的请求配置方法,在 .prompt() 之后、.call() / .stream() 之前调用。
5.1 用户消息
设置用户输入,是最核心的配置方法。
- 字符串
- 从资源文件加载
- Consumer 方式(模板参数)
String response = chatClient.prompt()
.user("解释一下 JVM 的内存模型")
.call()
.content();
完整示例:UserFromResourceDemo.java
@Component
public class UserFromResourceDemo implements CommandLineRunner {
private final ChatClient chatClient;
public UserFromResourceDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
String response = chatClient.prompt()
.user(new ClassPathResource("prompts/summary-prompt.txt"))
.call()
.content();
System.out.println(response);
}
}
完整示例:UserConsumerDemo.java
@Component
public class UserConsumerDemo implements CommandLineRunner {
private final ChatClient chatClient;
public UserConsumerDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
String response = chatClient.prompt()
.user(userSpec -> userSpec
.text("请用{language}解释{docType}文档的结构")
.param("language", "中文")
.param("docType", "pom.xml")
)
.call()
.content();
System.out.println(response);
}
}
5.2 系统消息
设置系统提示词,定义 AI 的角色和行为边界。
- 字符串
- Consumer 方式(带参数)
String response = chatClient.prompt()
.system("你是一个精通 Spring Boot 的资深架构师,回答应专业、准确、简洁")
.user("Spring Boot 自动配置的原理是什么?")
.call()
.content();
String response = chatClient.prompt()
.system(systemSpec -> systemSpec
.text("你是一个{role},只回答与{domain}相关的问题")
.param("role", "Java 面试官")
.param("domain", "Java 核心技术")
)
.user("什么是 happens-before 原则?")
.call()
.content();
5.3 手动构造消息列表
当需要精确控制消息顺序或类型时,可直接传入 Message 列表。
完整示例:MessagesDemo.java
@Component
public class MessagesDemo implements CommandLineRunner {
private final ChatClient chatClient;
public MessagesDemo(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public void run(String... args) {
String response = chatClient.prompt()
.messages(List.of(
new SystemMessage("你是一个代码审查专家"),
new UserMessage("请审查这段代码的安全性: public class UserService { ... }")
))
.call()
.content();
System.out.println(response);
}
}
5.4 模型参数
针对单次请求覆盖默认的模型参数(temperature、topP、model 等)。
- Lambda 方式
- 实例方式
String response = chatClient.prompt()
.user("想一个创意项目名称")
.options(options -> options
.temperature(0.9)
.maxTokens(100)
)
.call()
.content();
String response = chatClient.prompt()
.user("用一句话总结微服务架构的优势")
.options(OllamaOptions.builder()
.model("qwen3:4b")
.temperature(0.3)
.build()
)
.call()
.content();
6. 预设默认值
ChatClient.Builder 支持预设默认值,后续每次 .prompt() 调用自动继承,减少重复代码。
完整示例:DefaultValuesDemo.java
@Component
public class DefaultValuesDemo implements CommandLineRunner {
private final ChatClient chatClient;
public DefaultValuesDemo(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个精通{language}的编程助手")
.defaultSystem(system -> system.param("language", "Java"))
.defaultOptions(options -> options.temperature(0.7))
.build();
}
@Override
public void run(String... args) {
// 每次调用自动继承 defaultSystem 和 defaultOptions
String r1 = chatClient.prompt()
.user("什么是面向接口编程?")
.call()
.content();
System.out.println("第一次: " + r1);
// 可以在单次请求中覆盖默认值
String r2 = chatClient.prompt()
.system("你是一个精通 Python 的编程助手")
.user("什么是装饰器?")
.call()
.content();
System.out.println("第二次(覆盖): " + r2);
}
}
Builder 支持的默认值方法:
| 方法 | 说明 |
|---|---|
defaultSystem(String) / defaultSystem(Consumer) | 默认系统消息 |
defaultUser(String) / defaultUser(Consumer) | 默认用户消息 |
defaultOptions(ChatOptions) | 默认模型参数 |
defaultTools(Object...) / defaultToolNames(String...) | 默认工具 |
defaultAdvisors(Advisor...) / defaultAdvisors(Consumer) | 默认 Advisor |
defaultToolContext(Map) | 默认工具上下文 |
7. 从请求创建新 ChatClient
ChatClientRequestSpec.mutate() 方法基于当前请求的配置快照创建一个新的 ChatClient.Builder,便于复用请求上下文快速派生不同配置的 ChatClient 实例。
// 发起第一次请求,获得请求配置快照
ChatClient.ChatClientRequestSpec requestSpec = chatClient.prompt()
.user("介绍一下 Java 21")
.system("你是一个 Java 技术专家");
// 从当前请求配置创建一个新的 ChatClient
ChatClient derivedClient = requestSpec.mutate()
.defaultOptions(options -> options.temperature(0.8))
.build();
// 新 ChatClient 继承了原请求的 user 和 system 消息
String result = derivedClient.prompt()
.call()
.content();
mutate() 在 1.0.1 中增强了 advisors 和 advisorParams 的深拷贝,确保新 Builder 与原始请求之间不会共享可变状态。
注意:
mutate()复制的是当前请求级别的配置,而非 Builder 级别的默认值。
8. 与 ChatModel 的对比
| 维度 | ChatClient | ChatModel |
|---|---|---|
| 调用方式 | prompt().user().call().content() | call(prompt).getResults().get(0) |
| Advisor 支持 | 内置 Advisor 链 | 不支持 |
| 结构化输出 | .entity(Class) 一行转换 | 需手动使用 BeanOutputConverter |
| 默认值 | Builder 预设 system/options/tools | 仅 defaultOptions |
| 模板参数 | Consumer 中 param(k, v) | 需手动创建 PromptTemplate |
| 适用场景 | 业务开发(99% 场景) | 底层扩展、自定义 Advisor 实现 |
ChatClient 内部依赖 ChatModel——Builder 需要传入 ChatModel 实例,所有调用最终通过 ChatModelCallAdvisor 委托给 ChatModel。
9. 完整综合示例
以下示例展示了一次请求中使用多个 ChatClient 特性:系统消息、用户消息、结构化输出、流式输出。
完整示例:ChatClientCompleteExample.java
@Component
public class ChatClientCompleteExample implements CommandLineRunner {
private final ChatClient chatClient;
public ChatClientCompleteExample(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个精通软件开发的技术专家")
.defaultOptions(options -> options.temperature(0.7))
.build();
}
@Override
public void run(String... args) {
// 同步调用:结构化输出
record Summary(String title, String content) {}
Summary summary = chatClient.prompt()
.user("总结 Spring AI ChatClient 的核心特性。输出格式: {\"title\": \"...\", \"content\": \"...\"}")
.call()
.entity(Summary.class);
System.out.println("标题: " + summary.title());
System.out.println("内容: " + summary.content());
// 流式调用:打字机效果
Flux<String> stream = chatClient.prompt()
.user("用 100 字左右介绍 Spring AI")
.options(options -> options.temperature(0.5))
.stream()
.content();
stream.subscribe(
System.out::print,
Throwable::printStackTrace,
() -> System.out.println("\n--- 流式输出完成 ---")
);
}
}