跳到主要内容
版本:1.1.4

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() 即可获得实例。

ChatClientConfig.java
@Configuration
public class ChatClientConfig {

@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是一个乐于助人的AI助手")
.defaultOptions(chatOptions -> chatOptions.temperature(0.7))
.build();
}
}

2.1 基于 OpenAiApi 创建多个 ChatClient

自 1.0.3 起,OpenAiChatAutoConfigurationOpenAiApi 注册为独立的 Spring Bean,可直接注入并用于创建多个不同配置的 ChatClient,无需重复配置 API Key 和 Base URL。

MultiChatClientConfig.java
@Configuration
public class MultiChatClientConfig {

@Autowired
private OpenAiApi openAiApi; // 自动配置提供的 Bean

// 高温度客户端——适合创意写作
@Bean("creativeClient")
public ChatClient creativeClient() {
OpenAiChatModel creativeModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-5-chat-latest")
.temperature(0.9)
.build())
.build();
return ChatClient.builder(creativeModel).build();
}

// 低温度客户端——适合代码生成
@Bean("preciseClient")
public ChatClient preciseClient() {
OpenAiChatModel preciseModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-5-chat-latest")
.temperature(0.1)
.build())
.build();
return ChatClient.builder(preciseModel).build();
}
}

3. 同步调用

.call() 是最基础的调用方式,阻塞等待完整响应后返回 CallResponseSpec,支持四种结果提取方式。

3.1 获取文本内容

最常用的方式,直接返回模型输出的文本字符串。

完整示例:CallContentDemo.java
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
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
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
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
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
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 用户消息

设置用户输入,是最核心的配置方法。

String response = chatClient.prompt()
.user("解释一下 JVM 的内存模型")
.call()
.content();

5.2 系统消息

设置系统提示词,定义 AI 的角色和行为边界。

String response = chatClient.prompt()
.system("你是一个精通 Spring Boot 的资深架构师,回答应专业、准确、简洁")
.user("Spring Boot 自动配置的原理是什么?")
.call()
.content();

5.3 手动构造消息列表

当需要精确控制消息顺序或类型时,可直接传入 Message 列表。

完整示例:MessagesDemo.java
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 等)。

String response = chatClient.prompt()
.user("想一个创意项目名称")
.options(options -> options
.temperature(0.9)
.maxTokens(100)
)
.call()
.content();

6. 预设默认值

ChatClient.Builder 支持预设默认值,后续每次 .prompt() 调用自动继承,减少重复代码。

完整示例:DefaultValuesDemo.java
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 的对比

维度ChatClientChatModel
调用方式prompt().user().call().content()call(prompt).getResults().get(0)
Advisor 支持内置 Advisor 链不支持
结构化输出.entity(Class) 一行转换需手动使用 BeanOutputConverter
默认值Builder 预设 system/options/toolsdefaultOptions
模板参数Consumer 中 param(k, v)需手动创建 PromptTemplate
适用场景业务开发(99% 场景)底层扩展、自定义 Advisor 实现

ChatClient 内部依赖 ChatModel——Builder 需要传入 ChatModel 实例,所有调用最终通过 ChatModelCallAdvisor 委托给 ChatModel。



9. 完整综合示例

以下示例展示了一次请求中使用多个 ChatClient 特性:系统消息、用户消息、结构化输出、流式输出。

完整示例:ChatClientCompleteExample.java
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--- 流式输出完成 ---")
);
}
}