Spring AI ChatClient 响应处理

响应处理:从字符串到结构化实体

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);
    }
}
元数据的价值

响应元数据对于生产环境至关重要:

  1. 成本控制:监控 Token 使用量,优化 API 调用成本

  2. 性能分析:分析响应时间和模型性能

  3. 质量监控:追踪模型输出的质量和一致性

  4. 审计跟踪:记录详细的调用日志用于审计

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 定义实体类有以下优势:

  1. 简洁性:自动实现构造函数、getter、equals、hashCode、toString

  2. 不可变性:所有字段都是 final 的,天然线程安全

  3. 语义清晰:明确表示这是一个不可变的数据载体

  4. 模式匹配:未来版本将支持模式匹配

步骤 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 使用以下技术实现实体映射:

  1. JSON 解析 :使用 Jackson 或 Gson 等库解析 AI 响应

  2. 类型反射 :通过 Java 反射机制创建目标类型的实例

  3. 数据绑定 :将解析后的数据绑定到实体类的字段

  4. 验证机制:可选的数据验证确保映射结果的正确性

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(); // 流式返回字符串片段
    }
}
流式响应的技术优势
  1. 用户体验:用户可以立即看到部分内容,而不是等待完整响应

  2. 内存效率:不需要在内存中保存完整的响应内容

  3. 网络效率:可以更早开始传输数据

  4. 实时交互:支持实时对话和动态内容更新

流式响应映射为实体(需手动聚合)

目前流式响应不直接支持实体映射,需先聚合字符串再转换:

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返回
    }
}
相关推荐
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2025-12-28)
ai·开源·大模型·github·ai教程
GPUStack2 小时前
GPUStack Windows(WSL2)部署指南
ai·wsl2·模型推理·gpustack·高性能推理
艺术是真的秃头2 小时前
Trae:当编程从“编写”转向“对话”与“委派”
人工智能·python·ai·aigc
若丶相见2 小时前
Java对比Python 3.10+ 全栈语法与底层进阶百科全书
后端
奕成则成2 小时前
Django使用
后端·python·django
superman超哥2 小时前
Rust impl 块的组织方式:模块化设计的艺术
开发语言·后端·rust·模块化设计·rust impl块·impl块
superman超哥2 小时前
仓颉跨语言编程:FFI外部函数接口的原理与深度实践
开发语言·后端·仓颉编程语言·仓颉·仓颉语言·仓颉跨语言编程·ffi外部函数接口
咕白m6252 小时前
通过 Python 提取 PDF 表格数据(导出为 TXT、Excel 格式)
后端·python
悟空码字2 小时前
SpringBoot读取Excel文件,一场与“表格怪兽”的搏斗记
java·spring boot·后端