Spring AI 从入门到精通-结构化输出

6. 结构化输出:让 AI 交作业,不是写散文

6.1 问题:AI 输出的是文本,但你要的是对象

AI 默认返回的是自然语言文本。但你的程序需要的是结构化的 Java 对象。怎么办?

java 复制代码
// 你想要的是这个
record Person(String name, int age, String city) {}

// 但 AI 给你的是这个
"张三,今年 25 岁,住在北京。他的朋友李四..."

6.2 BeanOutputConverter:文本 → Java Bean

java 复制代码
// 1. 定义目标类型
record Person(String name, int age, String city) {}

// 2. 用 ChatClient 的 entity() 方法
Person person = chatClient.prompt()
        .user("生成一个随机的人物信息")
        .call()
        .entity(Person.class);

System.out.println(person.name());  // 张三
System.out.println(person.age());   // 28
System.out.println(person.city());  // 上海

发生了什么? Spring AI 在后台做了两件事:

  1. 根据 Person 类生成 JSON Schema,追加到 Prompt 后面作为格式指令
  2. 拿到 AI 返回的 JSON 后,用 Jackson 反序列化成 Person 对象

6.3 泛型类型:返回 List

java 复制代码
// 返回 List<Person>
List<Person> people = chatClient.prompt()
        .user("生成 5 个随机的人物信息")
        .call()
        .entity(new ParameterizedTypeReference<List<Person>>() {});

people.forEach(p -> System.out.println(p.name() + ", " + p.age()));

💡 ParameterizedTypeReference 是什么? 因为 Java 在运行时会擦除泛型类型(List<Person> 在运行时只是 List),Spring AI 需要通过这个匿名内部类的方式拿到实际的泛型参数类型。记住这个写法模板就行,每次需要返回泛型集合时照抄。

6.4 MapOutputConverter:返回 Map

java 复制代码
Map<String, Object> result = chatClient.prompt()
        .user(u -> u.text("列出 {subject} 的 {count} 个属性,用 JSON 格式")
                .param("subject", "一个 Java 开发者的简历")
                .param("count", 5))
        .call()
        .entity(new ParameterizedTypeReference<Map<String, Object>>() {});

System.out.println(result);
// {name=张三, experience=5年, skills=[Java, Spring, MySQL, Redis], ...}

6.5 ListOutputConverter:返回列表

java 复制代码
List<String> flavors = chatClient.prompt()
        .user(u -> u.text("列出 {count} 种 {subject}")
                .param("count", 10)
                .param("subject", "冰淇淋口味"))
        .call()
        .entity(new ListOutputConverter(new DefaultConversionService()));

flavors.forEach(System.out::println);
// 巧克力
// 香草
// 草莓
// ...

6.6 Native Structured Output:更可靠的结构化

一些模型(GPT-4o、Claude 3.5 等)原生支持结构化输出。Spring AI 用 Advisor 参数来启用:

java 复制代码
Person person = chatClient.prompt()
        .advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
        .user("生成一个随机的人物信息")
        .call()
        .entity(Person.class);

区别: 普通模式是在 Prompt 里加"请输出 JSON",AI 可能不听话。Native 模式是把 JSON Schema 传给模型的原生 API,模型保证输出符合 Schema。

全局启用:

java 复制代码
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
    return builder
            .defaultAdvisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
            .build();
}

6.7 底层用法:手动使用 BeanOutputConverter

有时候你需要手动控制转换过程:

java 复制代码
BeanOutputConverter<Person> converter = new BeanOutputConverter<>(Person.class);
String format = converter.getFormat();  // 拿到格式指令

String userTemplate = """
    生成一个随机人物信息。
    {format}
    """;

Prompt prompt = new Prompt(
    PromptTemplate.builder()
        .template(userTemplate)
        .variables(Map.of("format", format))
        .build()
        .createMessage()
);

Generation generation = chatModel.call(prompt).getResult();
Person person = converter.convert(generation.getOutput().getText());

💡 什么时候用底层? 当你需要自定义 Prompt 模板结构,不想用 ChatClient 的自动拼接时。


相关推荐
guo_xiao_xiao_几秒前
YOLO室内训练场橙色环形训练圈目标检测数据集-156张
人工智能·yolo·目标检测
凡泰AI3 分钟前
金融机构如何选择自己的企业级 AI桌面终端?
人工智能·agent·企业级ai·企业级agent
微硬创新5 分钟前
耐达讯自动化:16路4-20mA模拟信号,该怎样平稳接入PROFINET控制系统?
人工智能·物联网·网络协议·自动化·信息与通信
水如烟9 分钟前
孤能子视角:原始思维篇·0 总纲——关系场的直接编织:认知的底层呼吸
人工智能
Luminbox紫创测控10 分钟前
太阳模拟器如何模拟真实太阳?积分球与氙灯光源技术解析
人工智能·测试工具·安全性测试·uv·测试标准
mlidongfeng11 分钟前
[AI][shmem] Aiv 直驱RDMA
人工智能
杰克尼13 分钟前
天机学堂面试题
java
Web极客码14 分钟前
用 Python 搭建工具调用 Agent 的调试过程
java·服务器·前端
九硕智慧建筑一体化厂家18 分钟前
从电费支出到碳资产收益,直流照明如何让商业楼宇实现柔性用能
运维·人工智能·智慧城市
资深电气设计25 分钟前
800V直流断路器选型指南:ABB电气产品技术解析
人工智能·科技·创业创新·业界资讯