
3. ChatClient:你与 AI 对话的终极武器
3.1 为什么需要 ChatClient?
直接调 ChatModel 也可以,但很啰嗦。看看对比:
只用 ChatModel(啰嗦版):
java
@Autowired
private ChatModel chatModel;
// 构建一个 Prompt,手动处理各种细节
Prompt prompt = new Prompt(
List.of(new UserMessage("讲个笑话")),
OpenAiChatOptions.builder().temperature(0.7).build()
);
ChatResponse response = chatModel.call(prompt);
String text = response.getResult().getOutput().getText();
用 ChatClient(简洁版):
java
String text = chatClient.prompt()
.user("讲个笑话")
.call()
.content();
ChatClient 用流式 API 把常见的操作都串联起来了,读起来像自然语言。
3.2 创建 ChatClient
有三种方式:
方式一:自动注入(最常用)
java
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/ai")
String chat(@RequestParam String input) {
return chatClient.prompt()
.user(input)
.call()
.content();
}
}
Spring Boot 自动配置了一个 ChatClient.Builder 的 prototype Bean。注入它,.build() 一下就完事了。
方式二:手动创建
java
ChatClient chatClient = ChatClient.create(chatModel);
方式三:带默认配置的 Builder
java
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是一个有帮助的助手,回答简洁明了。")
.defaultOptions(OpenAiChatOptions.builder().temperature(0.3).build())
.build();
}
3.3 三种 prompt() 方式
java
// 方式 1:无参,后面慢慢拼
chatClient.prompt()
.user("你好")
.system("你是一个助手")
.call()
.content();
// 方式 2:直接传字符串(最简)
chatClient.prompt("你好").call().content();
// 方式 3:传一个完整的 Prompt 对象
Prompt prompt = new Prompt(new UserMessage("你好"));
chatClient.prompt(prompt).call().content();
3.4 call() 的返回值
call() 之后,你有四种选择:
java
// 1. 只要文本
String content = chatClient.prompt().user("讲个笑话").call().content();
// 2. 要完整的 ChatResponse(含元数据)
ChatResponse chatResponse = chatClient.prompt()
.user("讲个笑话")
.call()
.chatResponse();
// 可以拿到 token 用量、模型名称等
System.out.println("用了 " + chatResponse.getMetadata().getUsage().getTotalTokens() + " 个 token");
// 3. 要 ChatClientResponse(含执行上下文,RAG 场景有用)
ChatClientResponse clientResponse = chatClient.prompt()
.user("讲个笑话")
.call()
.chatClientResponse();
// 4. 映射成 Java 对象
ActorFilms result = chatClient.prompt()
.user("生成一个随机演员的电影作品列表")
.call()
.entity(ActorFilms.class);
3.5 流式响应(Streaming)
不想等 AI 一句话说完才显示?用 stream():
java
Flux<String> flux = chatClient.prompt()
.user("用中文讲一个关于程序员的长笑话")
.stream()
.content();
flux.subscribe(
chunk -> System.out.print(chunk), // 一个字一个字出来
error -> System.err.println("出错了: " + error),
() -> System.out.println("\n--- 讲完了 ---")
);
效果: 你会看到文字像打字机一样一个字一个字(准确说是 token by token)地输出。
🎯 什么时候用
call(),什么时候用stream()?
- 简短问答、结构化输出、Tool Calling →
call(),简单直接- 长文本生成、聊天界面、需要打字机效果 →
stream(),用户体验更好- 流式响应需要额外引入
spring-boot-starter-webflux依赖
💡 关于 Flux:Flux是 Spring WebFlux 提供的响应式流类型,用来表示"随时间推移陆续到达的多个数据"。如果你在 Spring MVC 环境做演示,需要调用.subscribe()来触发流开始执行;如果你在 WebFlux 环境直接把Flux返回给浏览器,框架会自动处理,不需要手动 subscribe。流式响应需要额外引入spring-boot-starter-webflux依赖。
3.6 同时使用多个 Chat Model
现实场景中,你可能需要:
- 一个便宜模型处理简单任务,一个贵模型处理复杂推理
- 针对不同用户提供不同模型选择
- A/B 测试两个模型
java
@Configuration
public class ChatClientConfig {
@Bean
@Primary
public ChatClient openAiChatClient(OpenAiChatModel openAiModel) {
return ChatClient.create(openAiModel);
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel anthropicModel) {
return ChatClient.create(anthropicModel);
}
}
// 使用时注入
@Service
public class MultiModelService {
@Autowired
@Qualifier("openAiChatClient")
private ChatClient openAiClient;
@Autowired
@Qualifier("anthropicChatClient")
private ChatClient anthropicClient;
public String routeToModel(String task, String input) {
if (task.equals("code")) {
return anthropicClient.prompt(input).call().content(); // Claude 擅长代码
} else {
return openAiClient.prompt(input).call().content(); // GPT 做通用
}
}
}
3.7 使用不同的 OpenAI 兼容端点
很多服务(Groq、DeepSeek、通义千问)都兼容 OpenAI API 格式。Spring AI 允许你"派生"一个 API 实例:
java
@Service
public class MultiEndpointService {
@Autowired
private OpenAiApi baseApi;
@Autowired
private OpenAiChatModel baseModel;
public String callGroq(String prompt) {
// 派生一个指向 Groq 的 API
OpenAiApi groqApi = baseApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();
OpenAiChatModel groqModel = baseModel.mutate()
.openAiApi(groqApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("llama3-70b-8192")
.temperature(0.5)
.build())
.build();
return ChatClient.create(groqModel).prompt(prompt).call().content();
}
}
3.8 完整 Demo:一个带 Web 界面的聊天机器人
java
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个友好的助手,回答简洁,不超过 100 字。")
.build();
}
// 同步接口
@PostMapping
public Map<String, String> chat(@RequestBody ChatRequest request) {
String reply = chatClient.prompt()
.user(request.message())
.call()
.content();
return Map.of("reply", reply);
}
// 流式接口(SSE)
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.message())
.stream()
.content();
}
}
record ChatRequest(String message) {}