响应处理:从字符串到结构化实体
1 、响应处理的重要性
ChatClient 支持多种响应格式,从简单字符串到复杂结构化实体,满足不同业务需求。这种多样性反映了实际应用中的不同需求场景。
2、 返回原始 ChatResponse(含元数据)
ChatResponse 包含完整的响应信息(生成结果、token 使用量、模型信息等),适用于需要监控或调试的场景:
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.stereotype.Component;
@Component
class ResponseMetadataDemo {
private final ChatClient chatClient;
public ResponseMetadataDemo(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public void getFullResponse() {
ChatResponse response = chatClient.prompt()
.user("讲一个编程相关的笑话")
.call()
.chatResponse(); // 获取完整ChatResponse对象
// 提取响应内容
assert response != null;
Generation result = response.getResult();
String content = result.getOutput().getText();
System.out.println("AI响应:" + content);
// 提取元数据
int tokenCount = (int) result.getMetadata().get("tokenCount");
String modelName = (String) result.getMetadata().get("modelName");
System.out.printf("使用模型:%s,消耗Token:%d%n", modelName, tokenCount);
}
}
元数据的价值
响应元数据对于生产环境至关重要:
-
成本控制:监控 Token 使用量,优化 API 调用成本
-
性能分析:分析响应时间和模型性能
-
质量监控:追踪模型输出的质量和一致性
-
审计跟踪:记录详细的调用日志用于审计
3、映射为 Java 实体(结构化输出)
通过entity()方法可直接将 AI 响应映射为 Java 对象,无需手动解析 JSON:
步骤 1:定义实体类(使用 record 简化代码)
java
import java.util.List;
// 电影演员实体
public record ActorFilms(String actorName, List<String> representativeWorks, int debutYear) {}
Record 类型的优势
使用 Java Record 定义实体类有以下优势:
-
简洁性:自动实现构造函数、getter、equals、hashCode、toString
-
不可变性:所有字段都是 final 的,天然线程安全
-
语义清晰:明确表示这是一个不可变的数据载体
-
模式匹配:未来版本将支持模式匹配
步骤 2:直接映射实体
java
import org.springframework.ai.chat.client.ChatClient;
/*
author: atg
time: 2025/12/28 11:17
*/
public class EntityMappingDemo {
private final ChatClient chatClient;
public EntityMappingDemo(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public ActorFilms getActorInfo() {
// AI响应直接映射为ActorFilms实体
return chatClient.prompt()
.system("你是一个电影数据库助手,按照以下格式返回数据:{\"actorName\":\"演员名\",\"representativeWorks\":[\"电影1\",\"电影2\"],\"debutYear\":年份}")
.user("提供一个好莱坞男演员的信息")
.call()
.entity(ActorFilms.class); // 自动映射为实体类
}
}
实体映射的技术原理
Spring AI 使用以下技术实现实体映射:
-
JSON 解析 :使用 Jackson 或 Gson 等库解析 AI 响应
-
类型反射 :通过 Java 反射机制创建目标类型的实例
-
数据绑定 :将解析后的数据绑定到实体类的字段
-
验证机制:可选的数据验证确保映射结果的正确性
4、映射泛型集合(如 List<ActorFilms>)
对于集合类型,需使用ParameterizedTypeReference指定泛型信息:
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.core.ParameterizedTypeReference;
import java.util.List;
public class GenericEntityMappingDemo {
private final ChatClient chatClient;
public GenericEntityMappingDemo (ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public List<ActorFilms> getMultipleActors() {
return chatClient.prompt()
.system("返回3位好莱坞男演员的信息,格式为JSON数组:[{\"actorName\":\"\",\"representativeWorks\":[],\"debutYear\":}]")
.user("推荐3位好莱坞实力派男演员及其代表作")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {}); // 泛型集合映射
}
}
ParameterizedTypeReference 的必要性
Java 的类型擦除机制导致运行时无法直接获取泛型类型信息。ParameterizedTypeReference 通过创建一个匿名子类来保留泛型信息,使得 Spring 能够正确地进行类型转换。
5、流式响应(Streaming)
对于长文本生成(如文章、报告),流式响应可提升用户体验,避免长时间等待:
java
import org.springframework.ai.chat.client.ChatClient;
public class StreamingResponseDemo1 {
private final ChatClient chatClient;
public StreamingResponseDemo1(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
// 返回Flux<String>,支持流式处理
public Flux<String> streamArticle() {
return chatClient.prompt()
.system("你是一个技术作家,写一篇关于Spring AI的简短介绍,分3段")
.user("写一篇介绍Spring AI的文章")
.stream() // 启用流式响应
.content(); // 流式返回字符串片段
}
}
流式响应的技术优势
-
用户体验:用户可以立即看到部分内容,而不是等待完整响应
-
内存效率:不需要在内存中保存完整的响应内容
-
网络效率:可以更早开始传输数据
-
实时交互:支持实时对话和动态内容更新
流式响应映射为实体(需手动聚合)
目前流式响应不直接支持实体映射,需先聚合字符串再转换:
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.core.ParameterizedTypeReference;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.stream.Collectors;
@Component
class StreamingEntityDemo {
private final ChatClient chatClient;
public StreamingEntityDemo(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
// 返回Flux<String>,支持流式处理
public Flux<List<ActorFilms>> streamActors() {
// 配置实体转换器
var converter = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<ActorFilms>>() {});
// 流式获取响应片段并聚合
Flux<String> responseFlux = chatClient.prompt()
.user(u -> u.text("返回2位女演员的信息,格式为JSON数组:{\"actorName\":\"\",\"representativeWorks\":[],\"debutYear\":}")
.param("format", converter.getFormat()))
.stream()
.content();
// 聚合字符串并转换为实体
return responseFlux
.collectList() // 收集所有片段
.map(parts -> String.join("", parts)) // 拼接为完整字符串
.map(converter::convert) // 转换为实体
.flux(); // 转换为Flux返回
}
}