聊天客户端API
ChatClient 提供流式风格API,用于与AI模型进行通信。它同时支持同步编程模型和流式编程模型。
请参阅本文档末尾的实现说明,了解在 ChatClient 中命令式编程模型与响应式编程模型的组合使用方式。
该流式API提供了构建提示词(Prompt)各组成部分的方法,提示词将作为输入传递给AI模型。提示词包含引导AI模型输出内容与行为的指令文本。从API视角来看,提示词由一组消息集合构成。
AI模型主要处理两类消息:用户消息(来自用户的直接输入)和系统消息(由系统生成,用于引导对话流程)。
这些消息通常包含占位符,程序运行时会根据用户输入替换占位符内容,从而定制AI模型对用户输入的响应结果。
同时还可配置提示词相关选项,例如指定要使用的AI模型名称、温度参数(用于控制生成内容的随机性与创意程度)。
创建聊天客户端
通过 ChatClient.Builder 对象创建 ChatClient。你可以借助Spring Boot自动配置获取已自动装配的 ChatClient.Builder 实例并注入业务类,也可以通过编码方式手动创建实例。
使用自动配置的ChatClient.Builder
在最简单的使用场景中,Spring AI 提供Spring Boot自动配置,自动创建原型类型的 ChatClient.Builder Bean,可直接注入到自定义类中。以下是一个简单示例,演示如何获取用户请求对应的字符串响应结果。
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}在该示例中,用户输入赋值给用户消息内容。call() 方法向AI模型发起请求,content() 方法将AI模型的响应以字符串形式返回。
多聊天模型适配
在单个应用中,常会遇到需要集成多个聊天模型的场景:
为不同类型任务分配不同模型(如高性能模型处理复杂推理,轻量低成本模型处理简单任务)
当某个模型服务不可用时,实现故障降级兜底机制
对不同模型或配置进行A/B测试
允许用户根据个人偏好自主选择模型
组合专用领域模型(代码生成模型、创意内容生成模型等)
默认情况下,Spring AI 仅自动配置单个 ChatClient.Builder Bean。若应用需要集成多个聊天模型,可按以下方式处理:
首先需关闭 ChatClient.Builder 自动配置,配置参数:spring.ai.chat.client.enabled=false,之后即可手动创建多个 ChatClient 实例。
同模型类型创建多个ChatClient
适用于底层使用同一模型、但配置参数不同的多实例创建场景。
// 编码方式创建ChatClient实例
ChatModel myChatModel = ... // 已由Spring Boot自动装配
ChatClient chatClient = ChatClient.create(myChatModel);
// 借助构建器实现更精细化配置
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
.defaultSystemPrompt("You are a helpful assistant.")
.build();不同模型类型创建独立ChatClient
接入多个AI模型时,可为每个模型单独定义 ChatClient Bean:
import org.springframework.ai.chat.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
return ChatClient.create(chatModel);
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {
return ChatClient.create(chatModel);
}
}随后可通过 @Qualifier 注解将指定Bean注入业务组件:
@Configuration
public class ChatClientExample {
@Bean
CommandLineRunner cli(
@Qualifier("openAiChatClient") ChatClient openAiChatClient,
@Qualifier("anthropicChatClient") ChatClient anthropicChatClient) {
return args -> {
var scanner = new Scanner(System.in);
ChatClient chat;
// 模型选择
System.out.println("\nSelect your AI model:");
System.out.println("1. OpenAI");
System.out.println("2. Anthropic");
System.out.print("Enter your choice (1 or 2): ");
String choice = scanner.nextLine().trim();
if (choice.equals("1")) {
chat = openAiChatClient;
System.out.println("Using OpenAI model");
} else {
chat = anthropicChatClient;
System.out.println("Using Anthropic model");
}
// 使用选中的聊天客户端
System.out.print("\nEnter your question: ");
String input = scanner.nextLine();
String response = chat.prompt(input).call().content();
System.out.println("ASSISTANT: " + response);
scanner.close();
};
}
}多兼容OpenAI接口端点适配
OpenAiApi 和 OpenAiChatModel 提供 mutate() 方法,可基于现有实例修改属性生成新实例,非常适合对接多个兼容OpenAI协议的API服务。
@Service
public class MultiModelService {
private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);
@Autowired
private OpenAiChatModel baseChatModel;
@Autowired
private OpenAiApi baseOpenAiApi;
public void multiClientFlow() {
try {
// 为Groq(Llama3)生成新的OpenAiApi实例
OpenAiApi groqApi = baseOpenAiApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();
// 为OpenAI GPT-4生成新的OpenAiApi实例
OpenAiApi gpt4Api = baseOpenAiApi.mutate()
.baseUrl("https://api.openai.com")
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// 基于Groq API创建专用ChatModel
OpenAiChatModel groqModel = baseChatModel.mutate()
.openAiApi(groqApi)
.defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
.build();
// 基于GPT-4 API创建专用ChatModel
OpenAiChatModel gpt4Model = baseChatModel.mutate()
.openAiApi(gpt4Api)
.defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build())
.build();
// 通用测试提示词
String prompt = "What is the capital of France?";
String groqResponse = ChatClient.builder(groqModel).build().prompt(prompt).call().content();
String gpt4Response = ChatClient.builder(gpt4Model).build().prompt(prompt).call().content();
logger.info("Groq (Llama3) response: {}", groqResponse);
logger.info("OpenAI GPT-4 response: {}", gpt4Response);
}
catch (Exception e) {
logger.error("多客户端调用异常", e);
}
}
}ChatClient流式风格API
ChatClient流式API通过重载的 prompt 方法提供三种创建提示词的方式,以此开启流式调用链路:
prompt():无参方法,从零开始链式构建用户消息、系统消息等完整提示词结构prompt(Prompt prompt):接收Prompt对象,可传入通过非流式API手动创建的Prompt实例prompt(String content):便捷重载方法,直接传入用户文本内容
ChatClient响应处理
ChatClient API通过流式调用提供多种AI模型响应格式化方式。
返回ChatResponse对象
AI模型的原始响应为 ChatResponse 复杂结构,包含生成元数据、多组生成结果(Generations)及令牌消耗信息(单个令牌约等效3/4个英文单词)。大模型服务商通常按单次请求消耗令牌数计费。
调用 call() 后链式调用 chatResponse() 可获取带元数据的完整响应对象:
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();映射为实体类
可将模型返回文本直接映射为Java实体类,通过 entity() 方法实现。
定义Java记录类示例:
record ActorFilms(String actor, List<String> movies) {}快速映射模型输出到实体:
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);支持泛型集合类型重载方法:
List<ActorFilms> actorFilms = chatClient.prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});原生结构化输出
当下多数AI模型原生支持结构化输出,可在调用ChatClient时通过 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 顾问参数开启该能力。可通过 ChatClient.Builder 的 defaultAdvisors() 全局配置,也可单次调用单独配置:
ActorFilms actorFilms = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);部分模型(如OpenAI)不原生支持对象数组格式,此类场景可使用Spring AI默认结构化输出转换能力。
流式响应
通过 stream() 方法获取异步流式响应:
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();也可通过 chatResponse() 获取流式 ChatResponse 对象。
后续版本将支持响应式流式调用直接返回Java实体类,现阶段需手动通过结构化输出转换器拼接并解析流式结果:
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});
Flux<String> flux = this.chatClient.prompt()
.user(u -> u.text("""
Generate the filmography for a random actor.
{format}
""")
.param("format", this.converter.getFormat()))
.stream()
.content();
String content = this.flux.collectList().block().stream().collect(Collectors.joining());
List<ActorsFilms> actorFilms = this.converter.convert(this.content);提示词模板
ChatClient流式API支持将用户文本、系统文本定义为带变量的模板,运行时动态替换变量值。
String answer = ChatClient.create(chatModel).prompt()
.user(u -> u
.text("Tell me the names of 5 movies whose soundtrack was composed by {composer}")
.param("composer", "John Williams"))
.call()
.content();ChatClient 内部通过 PromptTemplate 处理模板文本,依托 TemplateRenderer 实现变量替换。Spring AI 默认采用基于StringTemplate引擎的 StTemplateRenderer,同时提供无模板处理的 NoOpTemplateRenderer。
通过 ChatClient 配置的 TemplateRenderer 仅作用于链式构建的提示词内容,不影响顾问(Advisor)内部自定义模板。开发者可自定义 TemplateRenderer 实现,也可修改默认模板语法分隔符。
自定义模板分隔符示例:
String answer = ChatClient.create(chatModel).prompt()
.user(u -> u
.text("Tell me the names of 5 movies whose soundtrack was composed by <composer>")
.param("composer", "John Williams"))
.templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.call()
.content();call() 方法返回值
链式调用 call() 后,支持多种响应类型获取方式:
String content():返回模型响应字符串内容ChatResponse chatResponse():返回包含多组生成结果与令牌元数据的完整响应对象ChatClientResponse chatClientResponse():包含ChatResponse与ChatClient执行上下文,可获取顾问执行过程中的附加数据(如RAG检索文档)entity系列方法:映射为Java实体类型
responseEntity系列方法:同时返回完整ChatResponse与结构化实体对象
注:call() 仅指定同步/调用模式,并不会立即触发模型调用;真正发起请求执行模型调用,是在调用 content()、chatResponse() 等取值方法时触发。
stream() 方法返回值
链式调用 stream() 后,流式响应支持以下返回类型:
Flux<String> content():返回模型逐段生成的字符串流Flux<ChatResponse> chatResponse():返回带元数据的流式ChatResponse对象Flux<ChatClientResponse> chatClientResponse():返回包含执行上下文的流式响应对象
消息元数据
ChatClient 支持为用户消息和系统消息附加自定义元数据,用于传递上下文信息,供模型推理或后续业务处理使用。
为用户消息添加元数据
// 逐个添加元数据键值对
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata("messageId", "msg-123")
.metadata("userId", "user-456")
.metadata("priority", "high"))
.call()
.content();
// 批量添加元数据
Map<String, Object> userMetadata = Map.of(
"messageId", "msg-123",
"userId", "user-456",
"timestamp", System.currentTimeMillis()
);
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata(userMetadata))
.call()
.content();为系统消息添加元数据
String response = chatClient.prompt()
.system(s -> s.text("You are a helpful assistant.")
.metadata("version", "1.0")
.metadata("model", "gpt-4"))
.user("Tell me a joke")
.call()
.content();默认元数据配置
可在ChatClient构建器层面配置全局默认元数据:
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem(s -> s.text("You are a helpful assistant")
.metadata("assistantType", "general")
.metadata("version", "1.0"))
.defaultUser(u -> u.text("Default user context")
.metadata("sessionId", "default-session"))
.build();
}
}元数据校验规则
元数据键不能为空或空字符串
元数据值不能为null
批量传入Map时,键和值均不能包含null元素
非法示例(会抛出参数异常):
// 非法:键为null
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata(null, "value"))
.call()
.content();
// 非法:值为null
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata("key", null))
.call()
.content();元数据获取
元数据会封装在 UserMessage 和 SystemMessage 对象中,通过消息的 getMetadata() 方法获取,适用于顾问处理、对话历史解析等场景。
默认配置使用
在配置类中为ChatClient设置默认系统提示词,可简化业务代码,无需每次请求重复指定系统文本。
固定默认系统文本
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
.build();
}
}控制器调用示例:
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/simple")
public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return Map.of("completion", this.chatClient.prompt().user(message).call().content());
}
}带参数的默认系统文本
支持在默认系统文本中使用占位符,运行时动态传入参数替换:
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
.build();
}
}@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai")
Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
return Map.of("completion",
this.chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
}其他全局默认配置
在 ChatClient.Builder 中可配置多项全局默认项:默认模型参数、默认工具函数、默认用户文本、默认顾问链路等,运行时可通过同名非default前缀方法覆盖全局配置。
顾问机制(Advisors)
Advisors API 提供灵活强大的能力,可拦截、修改、增强Spring应用中的AI交互流程,常用于为提示词追加上下文数据。
常用上下文数据类型:私有业务数据、对话历史记录。
顾问执行顺序至关重要,前一个顾问的修改结果会传递给下一个顾问。
ChatClient.builder(chatModel) .build() .prompt() .advisors( MessageChatMemoryAdvisor.builder(chatMemory).build(), QuestionAnswerAdvisor.builder(vectorStore).build() ) .user(userText) .call() .content();
上述配置中,先通过会话内存顾问追加对话历史,再通过问答检索顾问基于历史和用户问题检索关联上下文,提升回答精准度。
日志记录
SimpleLoggerAdvisor 是内置顾问,用于记录ChatClient的请求与响应数据,便于调试和监控AI交互。
启用方式:构建ChatClient时加入日志顾问链路,建议放在链路末尾;配置日志级别为DEBUG即可打印日志。
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();日志配置:logging.level.org.springframework.ai.chat.client.advisor=DEBUG
支持自定义日志打印格式与顺序,生产环境需谨慎避免打印敏感数据。
聊天记忆(Chat Memory)
ChatMemory 接口定义对话内存存储规范,提供消息新增、查询、清空等方法。内置默认实现为 MessageWindowChatMemory。
MessageWindowChatMemory 维护固定数量的消息窗口(默认20条),超出上限自动淘汰旧消息,保留系统消息;新增系统消息时会清空历史系统消息,兼顾上下文完整性与内存占用可控性。
底层通过 ChatMemoryRepository 实现存储,提供内存、JDBC、Cassandra、Neo4j 等多种存储实现。
实现说明
ChatClient 支持命令式与响应式编程模型混用,是该API的特色设计
自定义模型HTTP客户端时,需同时配置 RestClient 和 WebClient
Spring Boot 3.4 存在已知Bug,需配置 spring.http.client.factory=jdk 避免部分AI功能异常
流式响应仅支持响应式技术栈,命令式应用需引入webflux依赖
非流式调用仅适配Servlet技术栈,响应式应用需引入web依赖并兼容阻塞调用
工具函数调用为阻塞式,会导致Micrometer链路追踪不完整
内置顾问普通调用为阻塞式,流式调用为非阻塞式,可单独配置顾问调度器