这一章节我们在第10章节的基础上把不可预测的自然语言回答转换为可解析、可校验的 Java 对象,让模型输出真正进入业务流程。
前言
页面聊天可以直接使用字符串,但订单分类、表单提取、审核结果等业务需要稳定字段。Structured Output 的目标不是让模型"回答得像 JSON",而是建立从目标结构、输出约束、转换到业务校验的完整链路。
一、为什么需要结构化输出
自然语言适合人阅读,却不适合程序稳定判断:
text
这个问题大概属于售后咨询,可信度比较高。
业务更需要:
json
{"category":"AFTER_SALES","confidence":0.93,"reason":"用户询问退货进度"}
结构化输出带来字段明确、类型明确、便于校验和可直接进入后续流程等优势。
二、先定义业务 DTO
java
public record TicketClassification(
String category,
double confidence,
String reason) {
}
DTO 应尽量简单:字段含义清楚、层级不过深、类型可稳定生成。模型不擅长一次可靠生成极其复杂的对象图。
三、使用 entity() 转换
java
public TicketClassification classify(String content) {
return chatClient.prompt()
.system("识别工单分类,并按目标结构返回结果。")
.user(content)
.call()
.entity(TicketClassification.class);
}
entity() 负责把模型结果转换为目标类型。它减少了业务层手工解析 JSON 的代码,但不代表转换永远成功。
四、String 到 DTO 经历了什么
text
Java DTO
↓ 推导结构要求
Format Instructions / JSON Schema
↓ 加入请求或 Provider 约束
模型生成结构化文本
↓ 转换器解析
Java DTO
↓ 业务校验
可用结果
结构化输出同时依赖模型遵循能力、提示约束、Provider 能力和转换器。
五、Format Instructions
当 Provider 不提供原生 Schema 约束时,框架可以把格式说明加入 Prompt,告诉模型字段、类型及输出格式。
概念示例:
text
请只返回 JSON。
category: 字符串
confidence: 0 到 1 的数字
reason: 字符串
不要添加 Markdown 代码围栏。
Format Instructions 是自然语言约束,能够提高成功率,但不能提供绝对保证。
六、JSON Schema 与 Provider 原生约束
支持 Structured Output 的 Provider 可以在请求层接收 JSON Schema,由模型服务约束输出结构。相比单纯把格式写进 Prompt,这通常更稳定,也能减少格式说明占用的 Token。
但仍需确认:
- 当前模型是否支持;
- Provider 对 Schema 特性的支持范围;
- Spring AI 和集成版本是否开放对应选项;
- 不支持时是否回退到 Prompt 指令。
七、转换成功后仍要校验
Java 反序列化成功只说明结构可解析,不代表业务值可信。
java
TicketClassification result = classify(content);
if (result.confidence() < 0 || result.confidence() > 1) {
throw new IllegalArgumentException("confidence 超出范围");
}
还应校验:
- 必填字段是否为空;
- category 是否在枚举白名单中;
- 数值是否在业务范围;
- 文本是否包含敏感内容;
- 当前用户是否有权触发后续操作。
八、失败处理
常见失败包括缺字段、额外说明文字、类型错误、JSON 截断和枚举值不存在。
推荐策略:
- 记录可追踪但已脱敏的失败信息;
- 对格式类临时失败进行有限次数重试;
- 重试时把校验错误作为修正反馈;
- 超过次数后降级或转人工;
- 不让无法校验的结果进入核心业务。
不要无限重试。每次模型调用都有延迟和成本。
九、集合与嵌套结构
需要列表时,可以使用 ParameterizedTypeReference 等方式表达泛型目标,具体 API 以项目版本为准。
结构设计建议从单对象开始验证,再增加列表和嵌套字段。嵌套越深、约束越复杂,模型偏离结构的概率通常越高。
十、适用业务场景
| 场景 | 典型字段 |
|---|---|
| 工单分类 | category、confidence、reason |
| 简历信息提取 | name、skills、experience |
| 内容审核 | passed、riskLevel、reasons |
| 商品属性抽取 | brand、category、attributes |
| 查询条件生成 | keywords、dateRange、filters |
高风险场景中,结构化输出只能作为建议或中间结果,不能替代确定性规则和人工审核。
十一、从 API 进入源码
text
CallResponseSpec.entity(type)
↓
StructuredOutputConverter
├── 生成格式约束或 Schema
└── convert(modelText)
↓
Java Object
源码阅读重点:目标类型如何转换为格式说明、格式说明何时加入请求、响应文本在哪里解析、转换异常如何抛出,以及 Provider 原生结构化能力如何启用。
十二、常见误区
- 输出是 JSON 就等于 Structured Output:还需要类型转换和校验。
- 使用
entity()后不会失败:模型与转换器仍可能失败。 - DTO 越复杂越专业:复杂结构会提高生成和维护难度。
- Schema 能保证业务正确:Schema 只能约束结构,无法判断业务事实。
- 模型结果可以直接写库或执行支付:高风险操作必须经过程序校验和权限控制。
十三、本章总结
text
DTO 定义
↓
格式指令或 JSON Schema
↓
模型生成
↓
entity() 转换
↓
字段与业务校验
↓
进入后续流程
结构化输出的核心不是 JSON 外观,而是可约束、可解析、可校验和可失败处理。
完整源码
text
源码地址:待补充
对应章节:chapter-11-structured-output
参考资料
下一章:Advisor
下一章进入核心能力篇,学习 Advisor 如何在不污染业务代码的情况下增强模型调用链。