从“自然语言”到“结构化数据”,让程序真正读懂AI的回复
01 引子
经过前六篇的实战,我们的AI已经能聊天、能记住上下文、能流式输出、能调用Java方法。但有一个问题始终存在:
AI返回的是自然语言文本,程序无法直接使用。
比如让AI“提取会议信息”,它可能返回:
“明天下午3点有一个项目评审会,在会议室A举行,预计持续1小时。”
这段文字对人类来说很好理解,但对程序来说——怎么提取时间?怎么拿到地点?怎么知道时长?——只能写正则表达式硬解析,脆弱又麻烦。
这一篇的目标:让AI直接返回结构化的JSON数据,程序拿来即用。
02 什么是Output Parser?
一句话概括: Output Parser是Spring AI提供的工具,用于将AI的输出从“自然语言文本”转换为“结构化数据”(如Java对象、Map、List等)。
| Parser | 输出格式 | 适用场景 |
|---|---|---|
| BeanOutputParser | Java对象 | 最常用,直接映射到业务对象 |
| MapOutputParser | Map<String, Object> | 简单场景,不需要预定义类 |
| ListOutputParser | List<?> | 返回列表数据 |
本篇重点:BeanOutputParser,最常用、功能最强大。
03 原理:AI怎么知道该输出什么格式?
你可能好奇:AI怎么知道要输出JSON?怎么知道该有哪些字段?
关键在于 Spring AI 会自动生成 JSON Schema,并附加到请求中。
text
用户问题 + JSON Schema(说明输出格式) → 大模型 → 按格式返回JSON → Spring AI 反序列化为Java对象
这个Schema描述了目标类的所有字段、类型和描述,AI根据它来生成符合格式的JSON。
你只需要:
- 定义一个Java类
- 创建
BeanOutputConverter<T> - 调用
.entity(converter)
剩下的全部由Spring AI自动完成。
04 代码实现
第一步:定义数据类
在 com.example.myai.model 包下创建 MeetingInfo.java:
关键要求: 必须有无参构造和Getter/Setter,否则JSON无法反序列化为Java对象。
第二步:在Service中使用OutputParser
修改 AIService.java,新增 extractMeetingInfo 方法:
关键点:
new BeanOutputConverter<>(MeetingInfo.class):创建转换器,指定目标类型.entity(converter):告诉Spring AI将输出转换为MeetingInfo对象- 不要将
BeanOutputConverter声明为Spring Bean(踩坑记录会详细说明)
第三步:暴露接口
在 AIController.java 中新增接口:
05 测试验证
启动项目,在浏览器中访问:
更复杂的输入测试:
换个说法再测:
三个不同场景全部成功,AI能准确提取信息并返回结构化JSON。 ✅
06 踩坑记录
坑:BeanOutputConverter不能声明为Spring Bean
一开始我这样写:
@Configuration
public class OutputParserConfig {
@Bean
public BeanOutputConverter<MeetingInfo> meetingInfoConverter() {
return new BeanOutputConverter<>(MeetingInfo.class);
}
}
然后在Service中注入:
public AIService(..., BeanOutputConverter<MeetingInfo> meetingInfoConverter) {
// ...
}
启动报错:
Unable to determine source type <S> and target type <T> for your Converter
[org.springframework.ai.converter.BeanOutputConverter];
does the class parameterize those types?
原因: BeanOutputConverter 虽然实现了 Converter<S, T> 接口,但它的泛型参数是在运行时通过构造函数传入的。Spring在启动时扫描所有 Converter 类型的Bean并注册到 ConversionService,由于泛型信息不完整,导致类型推断失败。
解决方法: 不在配置类中声明为Bean,而是在Service方法内直接创建:
// ✅ 正确做法
public MeetingInfo extractMeetingInfo(...) {
BeanOutputConverter<MeetingInfo> converter = new BeanOutputConverter<>(MeetingInfo.class);
return chatClient.prompt()
...
.entity(converter);
}
这样避免了Spring自动配置机制的干扰。
07 应用场景扩展
| 场景 | 用户输入 | OutputParser输出 | 后续用途 |
|---|---|---|---|
| 会议信息提取 | "明天下午3点开会" | {topic, time, location, duration} | 自动创建日历事件 |
| 订餐系统 | "我要一份宫保鸡丁,微辣" | {dish:"宫保鸡丁", spice:"微辣", quantity:1} | 生成订单 |
| 智能表单 | 用户任意描述需求 | {title, description, priority, assignee} | 自动创建工单 |
| 数据查询 | "帮我看看上个月销售额" | {total:1280000, month:7, growth:0.12} | 生成报表 |
08 下期预告
到此为止,我们已经完成了 Spring AI 的核心功能闭环:
| 功能 | 状态 |
|---|---|
| 首次调用 API | ✅ |
| 多轮对话 | ✅ |
| 流式输出 | ✅ |
| Function Calling | ✅ |
| Output Parser | ✅ |
接下来,我们将进入企业级AI应用的核心领域——RAG(检索增强生成)。
RAG让AI能够基于你的私有文档(PDF、Word、TXT等)回答问题,解决大模型“知识陈旧”和“幻觉”问题。这也是目前企业AI应用落地最广泛的技术方案。
下一篇,我们开始搭建第一个RAG知识库。
📌 我是超超不吵吵,10年Java全栈,正在转型AI应用开发。
每周一篇实战笔记,不贩卖焦虑,只分享能落地的技术。
掘金/CSDN同名,欢迎关注。