跳到主要内容

ChatClient API 使用指南

什么是 ChatClient

ChatClient 是 Spring AI 提供的与 AI 模型交互的流式 API。它的设计灵感来自 WebClient 和 RestClient,支持同步和流式两种调用方式。

核心特点:

  • 流式 API 设计,链式调用更优雅
  • 支持 Prompt 模板和参数替换
  • 自动将响应映射为 Java 对象
  • 支持流式输出(像 ChatGPT 那样逐字显示)

快速开始

最简单的例子

Spring Boot 会自动配置好 ChatClient.Builder,直接注入就能用:

@RestController
class MyController {

private final ChatClient chatClient;

public MyController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}

@GetMapping("/ai")
String chat(String userInput) {
return chatClient.prompt()
.user(userInput)
.call()
.content();
}
}

这个例子就实现了:

  1. 用户输入作为 Prompt
  2. 调用 AI 模型
  3. 返回结果字符串

使用多个 AI 模型

实际项目中常需要同时使用多个模型:

  • 任务分类:简单问题用便宜模型,复杂推理用强大模型
  • 容灾备份:一个模型挂了切换到另一个
  • AB 测试:对比不同模型的效果
  • 用户选择:让用户自己选模型

配置多个 ChatClient

首先关闭自动配置:

spring.ai.chat.client.enabled=false

场景 1:同一模型不同配置

ChatModel chatModel = ...; // Spring Boot 已经配置好的

// 方式一:简单创建
ChatClient client = ChatClient.create(chatModel);

// 方式二:自定义配置
ChatClient client = ChatClient.builder(chatModel)
.defaultSystem("你是一个 Java 专家")
.build();

场景 2:不同类型的模型

@Configuration
public class ChatClientConfig {

@Bean
public ChatClient openAiClient(OpenAiChatModel model) {
return ChatClient.create(model);
}

@Bean
public ChatClient claudeClient(AnthropicChatModel model) {
return ChatClient.create(model);
}
}

使用时用 @Qualifier 区分:

@Service
public class AIChatService {

@Autowired
@Qualifier("openAiClient")
private ChatClient openAi;

@Autowired
@Qualifier("claudeClient")
private ChatClient claude;

public String chat(String input, String modelType) {
ChatClient client = "openai".equals(modelType) ? openAi : claude;
return client.prompt(input).call().content();
}
}

场景 3:多个 OpenAI 兼容接口

有些国产模型兼容 OpenAI 接口,可以用 mutate() 方法切换:

@Service
public class MultiModelService {

@Autowired
private OpenAiChatModel baseModel;

@Autowired
private OpenAiApi baseApi;

public void useMultipleModels() {
// 创建 Groq API 客户端
OpenAiApi groqApi = baseApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();

// 创建阿里云 API 客户端
OpenAiApi aliApi = baseApi.mutate()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build();

// 创建不同的模型
OpenAiChatModel groqModel = baseModel.mutate()
.openAiApi(groqApi)
.defaultOptions(
OpenAiChatOptions.builder()
.model("llama3-70b-8192")
.temperature(0.5)
.build()
)
.build();

OpenAiChatModel aliModel = baseModel.mutate()
.openAiApi(aliApi)
.defaultOptions(
OpenAiChatOptions.builder()
.model("qwen-turbo")
.temperature(0.7)
.build()
)
.build();

// 分别调用
String prompt = "介绍一下 Spring AI";

String groqResp = ChatClient.builder(groqModel)
.build()
.prompt(prompt)
.call()
.content();

String aliResp = ChatClient.builder(aliModel)
.build()
.prompt(prompt)
.call()
.content();

System.out.println("Groq: " + groqResp);
System.out.println("阿里云: " + aliResp);
}
}

流式 API 详解

三种初始化方式

ChatClient 提供了三种 prompt() 重载方法:

1. 无参数:使用流式 API 构建

chatClient.prompt()
.system("你是一个 Java 专家")
.user("介绍一下 Spring Boot")
.call()
.content();

2. 传入 Prompt 对象

Prompt prompt = new Prompt("Hello AI");
chatClient.prompt(prompt)
.call()
.content();

3. 直接传入文本(最简单)

chatClient.prompt("介绍一下 Spring AI")
.call()
.content();

提示模板与变量替换

ChatClient 支持在提示中使用变量,运行时自动替换。比如让 AI 列出某个作曲家配乐的电影:

String answer = chatClient.prompt()
.user(u -> u
.text("请列出由 {composer} 创作配乐的五部电影")
.param("composer", "约翰·威廉姆斯"))
.call()
.content();

内部使用 PromptTemplate 处理模板,默认基于 StringTemplate 引擎,变量用 {} 包裹。

自定义模板分隔符

如果提示中包含 JSON,可能会和 {} 冲突,可以改用其他分隔符:

String answer = chatClient.prompt()
.user(u -> u
.text("请列出由 <composer> 创作配乐的五部电影")
.param("composer", "约翰·威廉姆斯"))
.templateRenderer(StTemplateRenderer.builder()
.startDelimiterToken('<')
.endDelimiterToken('>')
.build())
.call()
.content();

这样就能避免 JSON 语法冲突了。

响应类型

1. 返回字符串

最常用的方式,直接拿到 AI 的回复:

String response = chatClient.prompt()
.user("告诉我一个笑话")
.call()
.content();

2. 返回 ChatResponse(带元数据)

如果需要知道 token 消耗等信息:

ChatResponse response = chatClient.prompt()
.user("告诉我一个笑话")
.call()
.chatResponse();

// 获取元数据
int tokenUsed = response.getMetadata().getUsage().getTotalTokens();
String content = response.getResult().getOutput().getContent();

System.out.println("回答:" + content);
System.out.println("消耗 token:" + tokenUsed);

3. 返回 ChatClientResponse(包含执行上下文)

ChatClientResponse 不仅包含 ChatResponse,还包含执行上下文,可以访问 advisors 执行过程中的额外数据(比如 RAG 流程中检索到的文档):

ChatClientResponse clientResponse = chatClient.prompt()
.user("介绍一下 Spring AI")
.call()
.chatClientResponse();

ChatResponse chatResponse = clientResponse.getChatResponse();
String content = chatResponse.getResult().getOutput().getContent();

// 访问执行上下文中的额外数据
Map<String, Object> context = clientResponse.getContext();
List<Document> retrievedDocs = (List<Document>) context.get("retrievedDocuments");

4. 返回 Java 对象(结构化输出)

这是 ChatClient 最强大的功能,自动将 AI 响应转成 Java 对象。

例子 1:返回单个对象

record ActorFilms(String actor, List<String> movies) {}

ActorFilms result = chatClient.prompt()
.user("随机生成一个演员的电影作品列表")
.call()
.entity(ActorFilms.class);

System.out.println("演员:" + result.actor());
result.movies().forEach(System.out::println);

例子 2:返回集合类型

List<ActorFilms> result = chatClient.prompt()
.user("生成汤姆汉克斯和比尔穆瑞的电影作品列表,每人5部")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});

result.forEach(actorFilm -> {
System.out.println("演员:" + actorFilm.actor());
actorFilm.movies().forEach(movie -> System.out.println(" - " + movie));
});

使用 StructuredOutputConverter

如果默认的 JSON 解析不够用,可以自定义转换器:

StructuredOutputConverter<ActorFilms> converter = new JsonStructuredOutputConverter<>(ActorFilms.class);

ActorFilms result = chatClient.prompt()
.user("生成演员作品列表")
.call()
.entity(converter);

原生结构化输出

新的 AI 模型(如 GPT-4、Claude 3.5)原生支持结构化输出,准确率更高:

// 全局开启
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.defaultAdvisorParams(Map.of(
AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT, true
))
.build();

// 或者单次调用开启
ActorFilms result = chatClient.prompt()
.user("生成演员作品列表")
.advisors(a -> a.param(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT, true))
.call()
.entity(ActorFilms.class);

流式响应

有时候需要像 ChatGPT 那样逐字显示回复,可以用 stream() 方法:

流式返回字符串

Flux<String> contentStream = chatClient.prompt()
.user("讲一个故事")
.stream()
.content();

contentStream.subscribe(
chunk -> System.out.print(chunk), // 每收到一块就打印
error -> System.err.println("错误:" + error),
() -> System.out.println("\n完成")
);

流式返回 ChatResponse

如果需要同时获取元数据:

Flux<ChatResponse> responseStream = chatClient.prompt()
.user("讲一个故事")
.stream()
.chatResponse();

responseStream.subscribe(response -> {
String chunk = response.getResult().getOutput().getContent();
System.out.print(chunk);

// 可以访问每个 chunk 的元数据
if (response.getMetadata() != null) {
// 处理元数据
}
});

流式返回 ChatClientResponse

如果需要访问执行上下文:

Flux<ChatClientResponse> clientResponseStream = chatClient.prompt()
.user("介绍一下 Spring AI")
.stream()
.chatClientResponse();

clientResponseStream.subscribe(clientResponse -> {
String chunk = clientResponse.getChatResponse()
.getResult().getOutput().getContent();
System.out.print(chunk);

// 访问执行上下文
Map<String, Object> context = clientResponse.getContext();
});

配置默认值

在创建 ChatClient 时设置默认值,可以简化运行时代码,避免重复配置。

默认系统消息

如果每次都要设置相同的系统提示,可以在构建时设置:

@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一位海盗,回答问题时请使用海盗的语气。")
.build();
}

运行时只需要提供用户消息:

// 系统消息会自动添加
String response = chatClient.prompt()
.user("介绍一下 Spring Boot")
.call()
.content();

默认选项

可以设置默认的模型选项:

ChatClient client = ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder()
.temperature(0.7)
.maxTokens(1000)
.build())
.build();

默认函数

如果经常使用相同的函数,可以设置为默认:

ChatClient client = ChatClient.builder(chatModel)
.defaultFunctions("getWeather", "getTime")
.build();

运行时覆盖默认值

即使设置了默认值,运行时也可以覆盖:

// 覆盖默认系统消息
String response = chatClient.prompt()
.system("你是一个 Java 专家") // 覆盖默认的海盗语气
.user("介绍一下 Spring Boot")
.call()
.content();

// 覆盖默认选项
ChatResponse response = chatClient.prompt()
.user("讲一个故事")
.options(ChatOptions.builder()
.temperature(0.9) // 覆盖默认的 0.7
.build())
.call()
.chatResponse();

Advisor

Advisor 是 ChatClient 最强大的扩展机制。它可以在调用 AI 模型前后插入自定义逻辑,比如添加记忆功能、记录日志、增强问答能力等。

AdvisorSpec 接口

ChatClient 的流式 API 提供了 AdvisorSpec 接口来配置 advisors。这个接口提供了一些方法:

interface AdvisorSpec {
AdvisorSpec param(String k, Object v); // 添加单个参数
AdvisorSpec params(Map<String, Object> p); // 批量添加参数
AdvisorSpec advisors(Advisor… advisors); // 添加一个或多个顾问
AdvisorSpec advisors(List<Advisor> advisors); // 添加顾问列表
}

重要提醒:添加 advisors 的顺序很重要,因为这决定了它们的执行顺序。每个 advisor 都会以某种方式修改提示或上下文,这些修改会传递给链中的下一个 advisor。

ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 先执行,添加对话历史
QuestionAnswerAdvisor.builder(vectorStore).build() // 后执行,基于问题和历史进行搜索
)
.user(userText)
.call()
.content();

在这个配置中,MessageChatMemoryAdvisor 先执行,把对话历史添加到提示中。然后 QuestionAnswerAdvisor 会基于用户问题和添加的对话历史进行搜索,可能提供更相关的结果。

RAG(检索增强生成)

QuestionAnswerAdvisor 支持检索增强生成(RAG),通过在提示中追加基于用户文本的相关上下文信息。

请参考检索增强生成指南了解更多信息。

运行时覆盖默认设置

虽然可以设置默认值,但运行时也可以用对应的非 default 前缀方法来覆盖:

  • options(ChatOptions chatOptions) - 覆盖默认选项
  • function(String name, String description, java.util.function.Function<I, O> function) - 添加函数
  • functions(String… functionNames) - 添加函数名列表
  • user(String text), user(Resource text), user(Consumer<UserSpec> userSpecConsumer) - 设置用户消息
  • advisors(Advisor… advisor) - 添加顾问
  • advisors(Consumer<AdvisorSpec> advisorSpecConsumer) - 配置顾问

内置 Advisor

1. MessageChatMemoryAdvisor(消息记忆)

这个顾问可以让对话有记忆,AI 会记住之前的聊天内容:

@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor())
.build();
}

配置后,每次对话都会自动带上历史消息。

自定义配置:

MessageChatMemoryAdvisor memoryAdvisor = MessageChatMemoryAdvisor.builder()
.chatMemory(ChatMemory.builder()
.id("user-session") // 会话标识
.maxSize(100) // 最多记住多少条消息
.build())
.build();

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(memoryAdvisor)
.build();

2. PromptChatMemoryAdvisor(提示记忆)

专门处理提示词记忆的顾问:

PromptChatMemoryAdvisor promptMemory = PromptChatMemoryAdvisor.builder()
.systemTextProvider(() -> "你是 Java 专家")
.userTextProvider(() -> "请详细解释")
.build();

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(promptMemory)
.build();

3. VectorStoreChatMemoryAdvisor(向量存储记忆)

用向量数据库来存储和查找记忆内容:

VectorStoreChatMemoryAdvisor vectorMemory = VectorStoreChatMemoryAdvisor.builder()
.vectorStore(vectorStore)
.searchRequest(SearchRequest.builder()
.topK(5) // 找5条最相关的记忆
.build())
.build();

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(vectorMemory)
.build();

4. QuestionAnswerAdvisor(问答顾问)

问答场景专用,会自动从文档库中找相关内容加到提示里:

QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.searchRequest(SearchRequest.builder()
.topK(3) // 找3篇最相关的文档
.build())
.build();

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(qaAdvisor)
.build();

5. LoggingAdvisor 和 SimpleLoggerAdvisor(日志顾问)

记录对话过程,方便调试和监控:

// 简单版日志顾问
SimpleLoggerAdvisor logger = new SimpleLoggerAdvisor();

// 完整版日志顾问
LoggingAdvisor loggingAdvisor = LoggingAdvisor.builder()
.loggerName("ai.chat")
.logLevel(LogLevel.INFO)
.build();

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(logger, loggingAdvisor)
.build();

SimpleLoggerAdvisor 会记录 ChatClient 的请求和响应数据,对调试和监控 AI 交互很有用。

重要提醒:Spring AI 支持对 LLM 和向量存储交互的可观测性。更多信息请参考 Observability 指南。

要启用日志,需要把 SimpleLoggerAdvisor 添加到顾问链中,建议添加到链的末尾:

ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("讲个笑话")
.call()
.chatResponse();

要看到日志,需要把顾问包的日志级别设置为 DEBUG:

logging.level.org.springframework.ai.chat.client.advisor=DEBUG

你可以自定义从 AdvisedRequest 和 ChatResponse 记录哪些数据,使用以下构造函数:

SimpleLoggerAdvisor(
Function<ChatClientRequest, String> requestToString,
Function<ChatResponse, String> responseToString,
int order
)

使用示例:

SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "自定义请求:" + request.prompt().getUserMessage(),
response -> "自定义响应:" + response.getResult(),
0
);

这样你可以根据需要定制记录的信息。

注意:生产环境中要小心记录敏感信息。

临时使用 Advisor

有些 Advisor 你可能不想全局开启,只在特定场景使用:

String response = chatClient.prompt()
.user("介绍一下 Spring Boot")
.advisors(new SimpleLoggerAdvisor()) // 这次对话加个日志
.call()
.content();

// 也可以传一些参数给顾问
response = chatClient.prompt()
.user("生成一个随机数")
.advisors(a -> a
.param("temperature", 0.9) // 这次用高一点的随机性
.param("max_tokens", 100) // 限制回答长度
)
.call()
.content();

自定义 Advisor

如果你有特殊需求,可以自己写一个 Advisor:

public class CustomLoggingAdvisor implements ChatClientAdvisor {

@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
// 发送请求前可以在这里做点什么
System.out.println("用户问:" + request.userText());
return request;
}

@Override
public ChatClientResponse adviseResponse(ChatClientResponse response, Map<String, Object> context) {
// 收到回答后可以在这里处理
System.out.println("AI答:" + response.getChatResponse().getResult().getOutput().getContent());
return response;
}
}

// 然后像用内置顾问一样使用
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(new CustomLoggingAdvisor())
.build();

可观测性

Spring AI 接入了 Micrometer 和 OpenTelemetry,可以监控对话的各种指标:

开启监控

management:
metrics:
export:
otlp:
enabled: true
tracing:
sampling:
probability: 1.0 # 记录所有请求

自定义指标

@Autowired
private MeterRegistry meterRegistry;

public void chatWithMetrics() {
String response = chatClient.prompt()
.user("你好")
.call()
.content();

// 统计调用次数
meterRegistry.counter("ai.chat.requests").increment();
}

错误处理

调用 AI 难免会遇到各种异常,需要做好处理:

try {
String response = chatClient.prompt()
.user("写个死循环代码")
.call()
.content();
} catch (AiResponseException e) {
// AI 模型本身报错,比如内容违规
System.err.println("AI 模型报错:" + e.getMessage());
} catch (Exception e) {
// 网络问题、配置错误等其他异常
System.err.println("系统异常:" + e.getMessage());
}

高级配置

超时控制

ChatClient client = ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder()
.timeout(Duration.ofSeconds(30)) // 30秒超时,避免等太久
.build())
.build();

自动重试

ChatClient client = ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder()
.retry(RetryTemplate.builder()
.maxAttempts(3) // 失败了最多重试3次
.backoff(Backoff.fixed(Duration.ofSeconds(1))) // 每次重试间隔1秒
.build()))
.build();

并发和资源控制

// 如果需要处理大文件或大量数据
WebClient webClient = WebClient.builder()
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10MB
.build())
.build();

// 具体怎么配置到 ChatClient 看你用的什么 AI 服务商

最佳实践

1. 合理组合 Advisor

ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
new SimpleLoggerAdvisor(), // 记录日志,方便调试
new MessageChatMemoryAdvisor(), // 让对话有上下文
new QuestionAnswerAdvisor(vectorStore) // 基于文档库回答问题
)
.defaultOptions(ChatOptions.builder()
.temperature(0.7) // 回答稳定一些
.maxTokens(1000) // 别让回答太长
.build())
.build();

2. 性能调优

// 高并发场景下可以加缓存
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(new CachingAdvisor()) // 缓存重复问题
.build();

// 连接池配置
@Configuration
public class AiConfig {
@Bean
public OpenAiApi openAiApi() {
return OpenAiApi.builder()
.withApiKey(System.getenv("OPENAI_API_KEY"))
.withMaxRetries(3) // 失败自动重试
.build();
}
}

3. 安全第一

// 检查用户输入
public String safeChat(String userInput) {
if (userInput.length() > 1000) {
throw new IllegalArgumentException("内容太长了");
}

// 过滤敏感词
userInput = filterSensitiveWords(userInput);

return chatClient.prompt()
.user(userInput)
.call()
.content();
}

// 检查 AI 输出
public String filteredResponse(String userInput) {
String response = chatClient.prompt()
.user(userInput)
.call()
.content();

// 确保输出安全
return filterUnsafeContent(response);
}

4. 资源清理

@Service
public class ChatService implements DisposableBean {

private final ChatClient chatClient;

public ChatService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}

@Override
public void destroy() throws Exception {
// 应用关闭时清理资源
if (chatClient instanceof AutoCloseable) {
((AutoCloseable) chatClient).close();
}
}
}

Chat Memory(对话记忆)

ChatMemory 接口代表对话记忆的存储。它提供添加消息到对话、从对话检索消息以及清除对话历史的方法。

目前有一个内置实现:MessageWindowChatMemory。

MessageWindowChatMemory 维护一个消息窗口,最大大小可指定(默认20条消息)。当消息数量超过限制时,旧消息会被移除,但系统消息会被保留。如果添加了新的系统消息,所有之前的系统消息都会从记忆中移除。这样确保了对话的最新上下文始终可用,同时保持内存使用受限。

MessageWindowChatMemory 基于 ChatMemoryRepository 抽象,它为对话记忆提供了存储实现。有多个实现可用,包括 InMemoryChatMemoryRepository、JdbcChatMemoryRepository、CassandraChatMemoryRepository 和 Neo4jChatMemoryRepository。

更多详情和使用示例,请参见 Chat Memory 文档。

实现要点

Spring AI ChatClient 在设计上有一些特别的地方,主要是因为它同时支持命令式和响应式编程模型。通常应用要么用命令式的,要么用响应式的,不会两种都用。但 ChatClient 两者都支持。

HTTP 客户端配置

如果你要自定义模型的 HTTP 客户端交互,需要同时配置 RestClient 和 WebClient 两个。

重要提醒:Spring Boot 3.4 有一个 bug,如果你用了 ImageModel 之类的,需要设置这个属性:

spring.http.client.factory=jdk

不然默认用 reactor,会导致某些 AI 工作流出问题。

编程模型选择

流式处理只支持响应式栈

如果你想用流式响应(像 ChatGPT 那样逐字显示),必须包含响应式栈:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

非流式处理只支持 Servlet 栈

如果你用的是响应式应用,但又需要非流式处理,必须包含 Servlet 栈:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

这种情况下有些调用会是阻塞的。

函数调用是阻塞的

工具调用(函数调用)是命令式的,会导致阻塞工作流。这也会影响 Micrometer 观测,导致部分观测中断(比如 ChatClient 的 span 和工具调用的 span 不会连接,第一个会保持不完整状态)。

Advisor 的执行方式

内置的 advisors 在标准调用时执行阻塞操作,在流式调用时执行非阻塞操作。用于流式调用的 Reactor Scheduler 可以通过每个 Advisor 类的 Builder 来配置。

小结

ChatClient 是 Spring AI 最核心的组件,那个 Advisor 系统让它变得特别灵活。你可以像搭积木一样组合各种功能,做出很强大的 AI 应用。

关键是要处理好错误情况,优化性能,还有安全问题,这些都是上线前必须考虑的。