ChatClient API
ChatClient 是 Spring AI 提供的核心 API,用于与 AI 模型进行交互。本章介绍 ChatClient 的基本用法和高级功能。
概述
ChatClient 提供了流畅的 API 风格,类似于 Spring 的 WebClient 和 RestClient:
String response = chatClient.prompt()
.user("你好,请介绍一下自己")
.call()
.content();
基本使用
创建 ChatClient
方式一:自动注入
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
// 或直接注入
// @Autowired
// private ChatClient chatClient;
}
方式二:编程创建
ChatClient chatClient = ChatClient.create(chatModel);
简单对话
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
使用系统提示
@GetMapping("/expert")
public String expertChat(@RequestParam String question) {
return chatClient.prompt()
.system("你是一个资深的 Java 开发专家,请用专业但易懂的语言回答问题。")
.user(question)
.call()
.content();
}
模型选项
请求级选项
String response = chatClient.prompt()
.user("讲一个笑话")
.options(ChatOptionsBuilder.builder()
.withModel("gpt-4o")
.withTemperature(0.8)
.withMaxTokens(500)
.build())
.call()
.content();
常用选项参数
| 参数 | 说明 | 默认值 |
|---|---|---|
model | 模型名称 | gpt-3.5-turbo |
temperature | 随机性(0-2) | 0.7 |
maxTokens | 最大 Token 数 | 模型限制 |
topP | 核采样参数 | 1 |
stopSequences | 停止序列 | 无 |
配置默认选项
spring:
ai:
openai:
chat:
options:
model: gpt-4o
temperature: 0.5
max-tokens: 1000
多轮对话
使用 ChatMemory
@Service
public class ChatService {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
public ChatService(ChatClient.Builder builder) {
this.chatMemory = new InMemoryChatMemory();
this.chatClient = builder
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}
public String chat(String sessionId, String message) {
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)
.param(MessageChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.content();
}
}
手动管理对话历史
@GetMapping("/conversation")
public String conversation(
@RequestParam String message,
@RequestParam(required = false) String history) {
List<Message> messages = new ArrayList<>();
// 添加历史对话
if (history != null) {
messages.add(new UserMessage(history));
}
// 添加当前消息
messages.add(new UserMessage(message));
return chatClient.prompt()
.messages(messages)
.call()
.content();
}
流式响应
基本流式输出
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
WebFlux 控制器
@RestController
public class StreamController {
private final ChatClient chatClient;
public StreamController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content()
.map(content -> ServerSentEvent.<String>builder()
.data(content)
.build());
}
}
前端接收流式响应
const eventSource = new EventSource('/api/chat/stream?message=你好');
eventSource.onmessage = (event) => {
console.log(event.data); // 每次接收的文本片段
};
eventSource.onerror = () => {
eventSource.close();
};
结构化输出
输出为 POJO
// 定义输出结构
record Person(
String name,
int age,
String occupation,
List<String> skills
) {}
@GetMapping("/person")
public Person getPerson(@RequestParam String description) {
return chatClient.prompt()
.user("根据描述生成人物信息:" + description)
.call()
.entity(Person.class);
}
输出为 List
@GetMapping("/books")
public List<Book> getBooks(@RequestParam String topic) {
return chatClient.prompt()
.user("推荐5本关于" + topic + "的书籍")
.call()
.entity(new ParameterizedTypeReference<List<Book>>() {});
}
record Book(String title, String author, String description) {}
输出为 Map
@GetMapping("/analyze")
public Map<String, Object> analyze(@RequestParam String text) {
return chatClient.prompt()
.user("分析以下文本的情感和关键词:" + text)
.call()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {});
}
响应处理
获取完整响应
@GetMapping("/details")
public ChatResponse getDetails(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.chatResponse();
}
响应元数据
ChatResponse response = chatClient.prompt()
.user(message)
.call()
.chatResponse();
// 获取使用情况
Usage usage = response.getMetadata().getUsage();
System.out.println("Prompt Tokens: " + usage.getPromptTokens());
System.out.println("Generation Tokens: " + usage.getGenerationTokens());
System.out.println("Total Tokens: " + usage.getTotalTokens());
// 获取模型信息
String model = response.getMetadata().getModel();
图片输入
多模态对话
@PostMapping("/analyze-image")
public String analyzeImage(@RequestParam String imageUrl, @RequestParam String question) {
var userMessage = new UserMessage(
question,
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageUrl))
);
return chatClient.prompt()
.messages(userMessage)
.call()
.content();
}
上传图片分析
@PostMapping("/upload-image")
public String uploadImage(@RequestParam("file") MultipartFile file,
@RequestParam String question) throws IOException {
var userMessage = new UserMessage(
question,
List.of(new Media(MimeTypeUtils.IMAGE_JPEG, file.getBytes()))
);
return chatClient.prompt()
.messages(userMessage)
.call()
.content();
}
异常处理
常见异常类型
| 异常 | 说明 |
|---|---|
AiClientException | AI 客户端通用异常 |
ApiException | API 调用异常 |
RateLimitException | 请求频率限制 |
TokenLimitExceededException | Token 超出限制 |
全局异常处理
@RestControllerAdvice
public class AiExceptionHandler {
@ExceptionHandler(RateLimitException.class)
public ResponseEntity<String> handleRateLimit(RateLimitException e) {
return ResponseEntity.status(429)
.body("请求过于频繁,请稍后重试");
}
@ExceptionHandler(TokenLimitExceededException.class)
public ResponseEntity<String> handleTokenLimit(TokenLimitExceededException e) {
return ResponseEntity.badRequest()
.body("输入内容过长,请精简后重试");
}
@ExceptionHandler(AiClientException.class)
public ResponseEntity<String> handleAiClient(AiClientException e) {
return ResponseEntity.internalServerError()
.body("AI 服务暂时不可用:" + e.getMessage());
}
}
最佳实践
1. 使用配置类
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是一个有帮助的助手")
.defaultOptions(ChatOptionsBuilder.builder()
.withModel("gpt-4o")
.withTemperature(0.7)
.build())
.build();
}
}
2. 封装服务层
@Service
public class AiService {
private final ChatClient chatClient;
public AiService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个专业的技术顾问")
.build();
}
public String ask(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
public <T> T askAndParse(String question, Class<T> type) {
return chatClient.prompt()
.user(question)
.call()
.entity(type);
}
}
3. 添加日志
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
小结
本章我们学习了:
- 基本使用:创建 ChatClient 和简单对话
- 模型选项:配置温度、Token 限制等参数
- 多轮对话:使用 ChatMemory 管理对话历史
- 流式响应:实现实时输出
- 结构化输出:将响应映射为 POJO
- 异常处理:处理各种 AI 相关异常
练习
- 创建一个简单的聊天接口
- 实现多轮对话功能
- 实现流式响应接口
- 将 AI 响应解析为自定义对象