楼层: 首页/ 软件技术/ Spring AI/ 结构化输出:让 AI 吐 JSON 而不是废话
四

结构化输出:让 AI 吐 JSON 而不是废话

Structured Output

大模型默认返回自然语言——"根据您的信息,金额是 128.5 元,日期是 2026 年 9 月 16 日……"。但程序要的不是这段话,是一个能直接解析的对象。结构化输出就是让 AI 严格按你给的格式返回,Spring AI 直接帮你转成 Java Bean。

论为什么需要结构化输出

没有结构化时,你得写正则从一大段话里抠数字,抠错就崩。有了结构化输出,Spring AI 在后台告诉模型"你必须返回 JSON,字段叫 amount、date",然后自动把 JSON 反序列化成 Java 对象。从"猜 AI 说了啥"变成"拿一个确定类型",企业可用。

定义一个 Java Bean,让 AI 把结果填进来

// 你想要 AI 从合同里抽出来的字段 public class ContractInfo { private String partyA; // 甲方 private String partyB; // 乙方 private BigDecimal amount; // 金额 private LocalDate signDate; // 签署日期 // getters / setters 省略 }

一行调用,直接拿到 ContractInfo 对象

String contractText = "甲方:某某公司,乙方:某某科技,金额 128000 元,2026年9月16日签订……"; ContractInfo info = chatClient.prompt() .user("从下面合同里抽取信息:" + contractText) .call() .entity(ContractInfo.class); // 关键!直接转成对象 System.out.println(info.getAmount()); // 128000,程序直接用

用结构化输出做"信息抽取"实战

信息抽取是结构化输出最典型的应用:从一大段非结构化文本(合同、简历、邮件)里,把关键字段抽成结构化数据,直接进数据库。下面是一个从合同抽字段的完整例子。

抽出来的字段 Bean

public class ContractInfo { private String title; // 合同标题 private String partyA; // 甲方公司名 private String partyB; // 乙方公司名 private BigDecimal amount; // 合同金额 private LocalDate signDate; // 签署日期 private Integer durationMonths; // 合同期限(月) // getters / setters 省略 }

抽取调用 + 字段校验

String contract = "本合同由甲方北京某某科技与乙方上海某某贸易签订," + "合同金额人民币 580000 元,签署日期 2026 年 9 月 1 日,有效期 12 个月。"; ContractInfo info = chatClient.prompt() .system("你是合同信息抽取器,只输出 JSON,不要解释。") .user(contract) .call() .entity(ContractInfo.class); // 拿到对象后必须校验,别直接存库 if (info.getAmount() == null || info.getAmount().signum() <= 0) { throw new IllegalStateException("合同金额为空或非法,人工复核:" + info); }

Map 输出与 JSON Schema

除了 Bean,还能直接拿 Map<String,Object>,字段不固定时用。更精细的场景可以自定义 JSON Schema,精确控制每个字段类型、是否必填。企业级抽取建议用 Bean——字段固定、类型安全、能加校验。

拿 Map 输出(字段不固定时)

Map<String, Object> result = chatClient.prompt(q) .call() .entity(new ParameterizedTypeReference<Map<String, Object>>() {}); // 字段不固定、探索性需求时用,生产建议还是定成 Bean
结构化输出不是 100% 可靠

AI 偶尔会少返字段、多返字段、金额算错。拿到对象后一定要做校验:金额不能是负数、日期不能是未来。该抛异常抛异常,别直接存库。RAG 和结构化输出都是"提高准确率",不是"消除幻觉"。

完整案例:带 JSON Schema 注解的实体抽取

想让 AI 严格按字段类型和描述返回,给 Bean 字段加 @JsonPropertyDescription,Spring AI 会把这些描述写进给模型的 JSON Schema 里,AI 照方抓药。

用 @JsonPropertyDescription 描述每个字段

import com.fasterxml.jackson.annotation.JsonPropertyDescription; public class MeetingNote { @JsonPropertyDescription("会议主题,一句话概括") private String topic; @JsonPropertyDescription("参与人姓名列表") private List<String> attendees; @JsonPropertyDescription("待办事项,每条带责任人和截止日期") private List<Todo> todos; public static class Todo { private String what; private String owner; } }

从一段会议纪要抽成结构化对象

String minutes = "今天和张总、李工开会,讨论了官网改版。" + "张总说下周五前出设计稿,李工负责接口联调。"; MeetingNote note = chatClient.prompt() .system("你是会议纪要整理助手,只输出结构化 JSON。") .user(minutes) .call() .entity(MeetingNote.class); // 输出: // topic = "官网改版讨论" // attendees= ["张总", "李工"] // todos = [{what:"出设计稿", owner:"张总", due:"下周五"}, // {what:"接口联调", owner:"李工"}] System.out.println(note.getTopic()); // 官网改版讨论
实体抽取前,先想"AI 该不该知道"

结构化输出把非结构化文本变结构化数据,适合合同、简历、会议纪要、工单。但它只能抽"文本里明写或能合理推断"的字段。文本没提截止日期,别指望它编出来——真要补全,那得配合 Function Calling 去查数据库。