大模型开发 - 08 ChatClient:构建智能对话应用的流畅 API
摘要: Spring AI框架中的ChatClient是与大语言模型交互的核心接口,提供统一API支持多种模型(如OpenAI、DeepSeek等)。它通过流畅的链式调用(如.prompt().call())简化开发,支持同步和流式响应。单模型场景下可自动配置ChatClient.Builder,多模型时需手动管理并通过@Qualifier注入。针对类似OpenAI的兼容端点(如Groq),可通过
文章目录
Pre
大模型开发 - 03 QuickStart_借助DeepSeekChatModel实现Spring AI 集成 DeepSeek
大模型开发 - 04 QuickStart_DeepSeek 模型调用流程源码解析:从 Prompt 到远程请求
大模型开发 - 06 QuickStart_本地大模型私有化部署实战:Ollama + Spring AI 全栈指南
大模型开发 - 07 ChatClient:构建统一、优雅的大模型交互接口
概述
在 Spring AI 框架中,ChatClient
是开发者与大语言模型(LLM)交互的核心入口。它提供了一套流畅(Fluent)的 API,支持同步调用与流式响应,并屏蔽了底层不同模型(如 OpenAI、Anthropic、DeepSeek、Groq 等)的差异,让你“一次编写,多模型运行”。
本文将全面解析 ChatClient
的设计理念、核心功能、高级用法及注意事项,高效构建企业级 AI 应用。
一、ChatClient 是什么?
ChatClient
是 Spring AI 提供的高层封装,用于与 AI 模型进行对话。它基于底层的 ChatModel
接口(如 OpenAiChatModel
、AnthropicChatModel
)构建,但提供了更易用、更强大的编程模型:
- 统一 API:无论底层是 GPT-4、Claude 还是 Llama3,代码写法一致
- 流畅链式调用:
.prompt().user("...").system("...").call()
- 支持同步与流式:
.call()
返回完整结果,.stream()
返回Flux
- 内置模板、工具调用、记忆、顾问(Advisors)等高级能力
📌 核心理念:面向 ChatClient 编程,而非具体模型实现。
二、快速入门:创建并使用 ChatClient
2.1 自动配置(单模型场景)
Spring Boot 会自动配置一个 ChatClient.Builder
原型 Bean:
@RestController
public class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/ai")
public String ask(@RequestParam String userInput) {
return chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
用户输入设置用户消息的内容。call()
方法向 AI 模型发送请求,content()
方法将 AI 模型的响应作为 String 返回。
⚠️ 注意:必须通过
ChatClient.Builder
构建,不能直接new
。
2.2 多模型场景:如何管理多个 ChatClient?
当应用需要同时使用多个模型(如 OpenAI + Anthropic),需禁用自动配置:
# application.properties
spring.ai.chat.client.enabled=false
设置属性
spring.ai.chat.client.enabled=false
来禁用 ChatClient.Builder 自动配置
然后手动创建多个 ChatClient
:
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient openAiClient(OpenAiChatModel model) {
return ChatClient.create(model);
}
@Bean
public ChatClient anthropicClient(AnthropicChatModel model) {
return ChatClient.create(model);
}
}
在使用时通过 @Qualifier
注入:
@Service
public class AiService {
@Qualifier("openAiClient")
private final ChatClient openAi;
@Qualifier("anthropicClient")
private final ChatClient anthropic;
// 根据业务逻辑选择模型
}
@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;
// Model selection
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");
}
// Use the selected chat client
System.out.print("\nEnter your question: ");
String input = scanner.nextLine();
String response = chat.prompt(input).call().content();
System.out.println("ASSISTANT: " + response);
scanner.close();
};
}
}
2.3 多个 OpenAI 兼容端点(如 Groq + OpenAI)
利用 mutate()
方法派生新实例:
// 基于同一个 OpenAiChatModel 派生不同配置
OpenAiApi groqApi = baseOpenAiApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();
OpenAiChatModel groqModel = baseChatModel.mutate()
.openAiApi(groqApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("llama3-70b-8192")
.temperature(0.5)
.build())
.build();
ChatClient groqClient = ChatClient.builder(groqModel).build();
@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 {
// Derive a new OpenAiApi for Groq (Llama3)
OpenAiApi groqApi = baseOpenAiApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();
// Derive a new OpenAiApi for OpenAI GPT-4
OpenAiApi gpt4Api = baseOpenAiApi.mutate()
.baseUrl("https://api.openai.com")
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Derive a new OpenAiChatModel for Groq
OpenAiChatModel groqModel = baseChatModel.mutate()
.openAiApi(groqApi)
.defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
.build();
// Derive a new OpenAiChatModel for GPT-4
OpenAiChatModel gpt4Model = baseChatModel.mutate()
.openAiApi(gpt4Api)
.defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build())
.build();
// Simple prompt for both models
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("Error in multi-client flow", e);
}
}
}
三、ChatClient 流畅 API 详解
3.1 构建 Prompt 的三种方式
// 1. 无参:链式构建
chatClient.prompt().user("Hello").system("You are helpful").call()
// 2. 传入 Prompt 对象
Prompt prompt = new Prompt("Hello");
chatClient.prompt(prompt).call()
// 3. 直接传用户消息(最简)
chatClient.prompt("Hello").call()
3.2 响应格式:多种返回类型
方法 | 返回类型 | 说明 |
---|---|---|
.content() |
String |
纯文本响应 |
.chatResponse() |
ChatResponse |
包含元数据(token 数、生成选项等) |
.entity(Actor.class) |
Actor |
自动反序列化为 Java 对象 |
.entity(new ParameterizedTypeReference<List<Actor>>(){}) |
List<Actor> |
支持泛型集合 |
.responseEntity() |
ResponseEntity<?> |
包含 HTTP 状态码、头等(底层调试用) |
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
record ActorFilms(String actor, List<String> movies) {}
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
💡 要求模型支持 JSON 输出,并在提示中明确要求返回 JSON 格式。
List<ActorFilms> actorFilms = chatClient.prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
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);
四、流式响应(Streaming)
适用于长文本生成、实时聊天等场景:
Flux<String> stream = chatClient.prompt()
.user("Write a poem about spring")
.stream()
.content();
// WebFlux 控制器中直接返回 Flux
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamPoem() {
return stream;
}
⚠️ 注意:流式必须依赖
spring-boot-starter-webflux
,即使你的应用是 Servlet 架构。
五、Prompt 模板与参数化
支持运行时动态替换变量:
String movies = chatClient.prompt()
.user(u -> u
.text("List 5 movies by composer {composer}")
.param("composer", "John Williams"))
.call()
.content();
默认使用 {}
作为占位符,但可自定义分隔符(避免与 JSON 冲突):
// 使用 < > 作为分隔符
chatClient.prompt()
.user(u -> u.text("Composer: <composer>").param("composer", "Hans Zimmer"))
.templateRenderer(
StTemplateRenderer.builder()
.startDelimiterToken('<')
.endDelimiterToken('>')
.build()
)
.call()
.content();
六、默认配置:简化运行时代码
在 @Configuration
中设置默认行为,避免重复代码。
6.1 默认系统提示
@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());
}
}
6.2 带参数的默认系统提示
@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());
}
}
6.3 其他默认项
defaultOptions()
:设置温度、模型名等defaultFunctions()
:注册工具函数defaultAdvisors()
:配置 RAG、记忆等顾问
七、高级特性:Advisors
Advisors 是 Spring AI 的核心扩展机制,用于拦截并增强 Prompt。
7.1 常见 Advisor
Advisor | 作用 |
---|---|
MessageChatMemoryAdvisor |
自动注入对话历史 |
QuestionAnswerAdvisor |
实现 RAG(检索增强生成) |
SimpleLoggerAdvisor |
记录请求/响应日志 |
7.2 使用示例:RAG + 记忆
ChatClient client = ChatClient.builder(chatModel)
.build();
String response = client.prompt()
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.user("What did I ask earlier about Spring AI?")
.call()
.content();
执行顺序:先加记忆,再做检索,最后生成回答。
7.3 日志记录
// 启用 DEBUG 日志
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
// 代码中添加
.prompt()
.advisors(new SimpleLoggerAdvisor())
.user("...")
八、聊天记忆(Chat Memory)
ChatMemory
用于存储对话历史,解决 LLM 无状态问题。
内置实现:MessageWindowChatMemory
- 默认保留最近 20 条消息
- 系统消息始终保留
- 支持多种存储后端:内存、JDBC、Cassandra、Neo4j
ChatMemory chatMemory = new MessageWindowChatMemory(
new InMemoryChatMemoryRepository()
);
配合 MessageChatMemoryAdvisor
自动注入上下文。
九、重要实现说明
Spring AI 的 ChatClient
同时支持命令式(Servlet)和响应式(Reactive) 编程模型,但需注意:
场景 | 要求 |
---|---|
流式响应 | 必须引入 spring-boot-starter-webflux |
非流式调用 | 需要 spring-boot-starter-web (Servlet) |
工具调用(Function Calling) | 是阻塞操作,会中断 Micrometer 链路追踪 |
HTTP 客户端 | 需同时配置 RestClient 和 WebClient |
Spring Boot 3.4 Bug | 需设置 spring.http.client.factory=jdk |
📌 建议:即使你只做同步调用,也建议引入 WebFlux 以支持未来流式需求。
十、总结
ChatClient
是 Spring AI 的最佳实践入口,它通过:
- 统一 API 屏蔽模型差异
- Fluent 链式调用提升开发体验
- 内置模板、记忆、RAG、工具调用等企业级能力
让你专注于业务逻辑,而非模型细节。
开发原则:
优先使用
ChatClient
,仅在需要深度定制时才使用ChatModel
。
通过合理配置默认项、Advisors 和记忆机制,你可以快速构建出功能强大、可维护、可扩展的 AI 应用。
延伸阅读:
更多推荐
所有评论(0)