20 让大模型稳定吐 JSON:Java 端怎么校验兜底
面试官你们那个 AI 应用模型返回的结构化数据是怎么保证不出错的候选人我在 Prompt 里写了请返回 JSON 格式。面试官它每次都乖乖只返回 JSON 吗有没有给你在前面加一句好的这是结果候选人……有过我用了正则把 JSON 抠出来。面试官抠出来之后呢字段少了怎么办数量给你返回个负数怎么办提示里返回的日期是下周一这种词你怎么处理候选人想了半天这个我们后面再统一处理……面试官心里已经打分了。让模型吐 JSON是一句话的事让模型每次都吐对的 JSON是一整套工程。面试官问这个考的不是你会不会调 API而是你有没有把模型的输出当成不可信的外部输入来对待。这篇就把这件事拆开讲怎么让模型稳定吐结构化数据以及 Java 端拿到之后怎么校验、怎么重试、怎么兜底。先想清楚为什么非要结构化大模型天生输出的是自然语言。它喜欢说好的根据您的描述我帮您整理如下……喜欢在结尾加一句希望对您有帮助。但你的下游系统不是人是代码。代码要的是字段product、quantity、deliveryDate。你不可能让代码去读一段散文那不如直接上正则乱猜。结构化输出的本质是让模型把它理解的世界翻译成一份程序能消费的契约。这份契约一旦不稳定下游全乱套——订单建错了、金额算错了、工单派错了。所以在 AIGC 应用里结构化输出不是锦上添花是有下游依赖时的刚需。三条路让模型吐出结构路一JSON Moderesponse_formatOpenAI 兼容接口里有个参数叫response_format。你把它设成 JSON 对象模式模型就会保证返回值是一个合法的 JSON{ model: gpt-4o, messages: [ {role: system, content: 你是订单信息抽取器只返回 JSON。}, {role: user, content: 帮我订 2 台显示器下周一送到上海} ], response_format: { type: json_object } }开了这个之后模型不会再给你前面加好的这是结果了返回的就是一个能直接 parse 的 JSON。但它有个关键限制它只保证这是一个合法 JSON不保证字段名、字段类型、字段值符合你的预期。你想要的字段叫product它可能给你写成productName你想要数量是整数它可能给你写成字符串2。所以 JSON Mode 解决的是语法层的问题语义层还得你自己管。顺带提一句现在的接口还支持更严格的jsonschema模式业界叫 Structured Outputs你直接给模型一份 JSON Schema它会严格按 schema 输出。这比单纯的 jsonobject 强但要注意不是所有模型、所有版本都支持用之前得确认你接的模型能力。路二Function Calling工具调用如果你本来就在用工具调用Function Calling其实可以让抽数据这件事借它的壳。思路是把你想抽取的字段定义成一个工具的入参parameters这个 parameters 就是一份 JSON Schema。然后告诉模型调用这个工具。模型会返回一个tool_call里面的arguments就是它按 schema 填好的结构化数据。这么做的额外好处是字段有 schema 约束模型填得会更规矩。代价是语义上有点绕——你本来不是真的要调工具只是想让它填空。所以 Function Calling 抽数据适合顺便不适合专门为了抽数据硬造一个假工具。路三框架转换器——Spring AI 的 BeanOutputConverter如果你在 Java 里用 Spring AI有一个更省事的办法让框架根据你的 Java 类型反向生成格式说明再把模型返回的文本转回对象。import org.springframework.ai.converter.BeanOutputConverter; // 泛型里传你要的 Java 类型 BeanOutputConverterOrder converter new BeanOutputConverter(Order.class); // getFormat() 会生成一段请按以下格式输出的说明拼进 Prompt String format converter.getFormat(); String prompt 从用户输入中抽取订单信息。 %s .formatted(format); String raw chatClient.prompt().user(prompt).call().content(); // 把模型返回的文本转回 Order 对象 Order order converter.convert(raw);它的价值在于你不用手写那段请返回包含 xxx 字段的 JSON的说明框架会根据Order这个类的结构自动生成。字段改了说明跟着变不容易出现代码改了 Prompt 没改的错位。Spring AI 还提供了更短的写法直接在调用上接一个entity()Order order chatClient.prompt() .user(帮我订 2 台显示器下周一送到上海) .call() .entity(Order.class);.entity(Order.class)一步到位它内部就帮你做了生成格式说明 → 调模型 → 把返回转成对象这三件事。用起来最舒服。三种路子不互斥。实际项目里常见的组合是用框架转换器生成格式说明用 JSON Mode 兜住语法自己再做一层业务校验。下面重点讲这层校验。关键一步把模型输出当不可信输入到这里你拿到了一个Order对象。但请立刻停止相信它。这个对象是模型猜出来的不是你的数据库查出来的。它会怎么骗你举个典型场景- 用户说帮我订两箱牛奶模型把quantity填成2——对- 用户说多订点模型可能填quantity 10——它凭空替你决定了数量- 用户压根没提数量模型可能编一个quantity 1——因为你的 schema 里quantity是必填它不敢留空再看类型和范围。你写的是Integer quantity但 JSON 里给了2字符串反序列化时可能直接抛异常也可能被宽松地转成 2。你期望数量是正数模型给了-1语法上是合法 JSON业务上是胡说。所以合法 JSON 和 业务合法的数据 是两码事。前者是解析器的事后者得靠校验。Java 里现成的校验工具就是Bean ValidationJakarta Bean Validation实现一般是 Hibernate Validator。你在类上挂注解声明这个字段不能为空、这个数必须为正、这个字符串必须匹配某个格式然后让校验器去跑import jakarta.validation.constraints.*; import java.util.List; public record Order( NotBlank(message 商品名不能为空) String product, NotNull(message 数量不能为空) Min(value 1, message 数量至少为 1) Max(value 999, message 数量过大疑似抽取错误) Integer quantity, // 把下周一这种自然语言日期在抽取阶段就要求模型规整成 yyyy-MM-dd NotBlank(message 交付日期不能为空) Pattern(regexp \\d{4}-\\d{2}-\\d{2}, message 日期格式须为 yyyy-MM-dd) String deliveryDate, String address, NotEmpty(message 订单项不能为空) Valid // 级联校验把校验传递到列表里的每个元素 ListOrderItem items ) {} public record OrderItem( NotBlank String product, Positive Integer quantity ) {}这段就是契约。业务规则用注解表达出来数量必须是 1 到 999日期必须是那个格式订单项不能为空。Valid是级联校验——如果items是个列表光校验列表本身不够得让校验器钻进每个OrderItem里也校验一遍。校验怎么触发在业务入口拿到对象后用校验器跑一遍import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ConstraintViolation; import java.util.Set; Validator validator Validation.buildDefaultValidatorFactory().getValidator(); SetConstraintViolationOrder violations validator.validate(order); if (!violations.isEmpty()) { // 有字段不合法 String errors violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining()); // → 走重试或兜底 }Validation.buildDefaultValidatorFactory()会拿到一个默认校验器通常是 Hibernate Validatorvalidate(order)返回所有违反约束的项。每一项都带着错误消息就是你注解里写的那句中文。校验和模型调用是解耦的哪怕哪天你换了个模型、换了家供应商只要它返回的对象过不了校验照样被拦下来。这层是模型无关的安全网。校验不过怎么办重试 兜底校验失败的常见处理是带错误信息重试把你上次哪几个字段错了拼回 Prompt让模型改。public Order extractOrder(String userText) { String feedback ; int maxAttempts 3; for (int i 0; i maxAttempts; i) { try { Order order chatClient.prompt() .user(userText feedback) .call() .entity(Order.class); SetConstraintViolationOrder violations validator.validate(order); if (violations.isEmpty()) { return order; // 校验通过收工 } // 把错误拼回去让模型照着改 feedback \n上次输出存在以下问题请修正后重新输出\n violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(\n)); } catch (Exception e) { // 反序列化失败模型返回了非法 JSON 或类型对不上 feedback \n上次输出无法解析请严格按格式要求返回合法 JSON。; } } // 重试耗尽 → 兜底 throw new IllegalStateException(结构化输出重试 maxAttempts 次仍未通过校验); }几个细节值得说第一捕获异常。.entity()在模型返回的 JSON 不合法、或者字段类型对不上时会抛异常。不 catch 的话一次抖动就把整个请求打挂了。第二重试要有上限。别写成 while(true)模型要是理解不了你的反馈它会一直错下去把你的 token 额度烧光。三次是个常见的经验值。第三重试可以带退避。如果失败是因为限流rate limit立刻重试只会更糟。可以加个短延迟或者用 Spring Retry、Resilience4j 这类库做退避。这段和第 5 篇《大模型 API 容错》是一脉相承的。第四重试耗尽必须有兜底而且不能静默。兜底策略看业务客服对话场景可以转人工数据抽取场景可以进人工复核队列实在不行就返回一个明确的错误让上游知道这次没抽出来绝不能返回一个半成品对象让下游当真。静默失败比报错可怕得多。还有个提升成功率的实用招结构化任务temperature 调低甚至调到 0。抽取是确定性任务不需要模型发挥创造力温度越低越稳。Schema 演进别让上游一改下游全炸还有一个容易被忽略的坑schema 是会变的。今天Order只有三个字段后来业务要加一个couponCode再后来deliveryDate要拆成开始和结束。你的 Java 类一改旧的调用方、旧的缓存数据、旧的测试用例全可能挂。三条实践加字段别删字段。新增字段给默认值或设为可空老代码不传也不受影响。真要废弃某个字段先标记弃用、观察一段时间、确认没人用了再删。容忍未知字段。反序列化时模型可能多返回了你没定义的字段它热情地加戏。用 Jackson 的话加上JsonIgnoreProperties(ignoreUnknown true)让多出来的字段被忽略而不是直接抛异常。import com.fasterxml.jackson.annotation.JsonIgnoreProperties; JsonIgnoreProperties(ignoreUnknown true) public record Order(String product, Integer quantity) {}需要断代时加版本号。如果新老结构没法兼容比如字段含义变了与其偷偷改不如把新旧两版做成两个类型、加一个version字段区分平滑过渡。schema 演进这件事本质上和传统的接口版本管理是一回事。把模型输出当成对外接口来管思维就对了。一张表三种产出结构化数据的方式方式原理优点局限JSON Moderesponse_format请求声明要 JSON语法稳定接入简单只保证是 JSON字段名/类型不保证Structured Outputs / json_schema给定 JSON Schema模型严格照填字段约束强最接近契约并非所有模型/版本都支持Function Calling把字段定义成工具入参有 schema 约束适合顺带抽参语义上略绕框架转换器BeanOutputConverter / entity()Java 类型反向生成格式说明再转回与 Java 类型一一对应最省事转换失败仍需兜底记住一条主线无论用哪种都逃不过Bean Validation 校验 重试 兜底这道关。 面试官视角的标准回答如果面试官问怎么让大模型稳定输出 JSONJava 端怎么处理我会分三层来做。第一层让模型尽量吐对。可选的手段有三个用 response_format 开 JSON Mode保证返回是合法 JSON或者用 Structured Outputs 给一份 JSON Schema字段约束更强或者用 Function Calling把想要的字段定义成工具入参。在 Spring AI 里我一般直接给 ChatClient 的 call() 接一个.entity(Order.class)或者用 BeanOutputConverter 根据 Java 类型生成格式说明再转换省得手写 Prompt。结构化任务我会把 temperature 调低抽取是确定性任务不需要创造力。第二层把模型输出当成不可信的外部输入。语法合法不代表业务合法。我会用 Jakarta Bean Validation 在 record 上挂注解——NotBlank、Min、Positive、Pattern 之类把必填、取值范围、格式这些业务规则声明出来然后在校验器里跑一遍。这里要注意 Valid 做级联校验覆盖嵌套对象和列表。第三层校验不过就重试和兜底。把校验失败的错误信息拼回 Prompt让模型照着改重试设上限一般三次失败可能来自非法 JSON 或限流要 catch 异常、必要时带退避。重试耗尽必须兜底——转人工、进复核队列或者明确报错绝不能返回半成品让下游当真静默失败最可怕。补一点 schema 演进把模型输出当对外接口管加字段别删字段反序列化加 JsonIgnoreProperties(ignoreUnknowntrue) 容忍未知字段不兼容时用版本号断代。下一篇聊每个上线项目都躲不开的现实问题——成本与并发。线上 QPS 只有 100业务却要 1000怎么设计语义缓存、限流、批量、连接池怎么搭聊完这个你的 AIGC 方案才算能扛住生产。

相关新闻

一个月跑了 5 次的“月度对账”:Cron 表达式的 7 个坑与 3 分钟自检法

一个月跑了 5 次的“月度对账”:Cron 表达式的 7 个坑与 3 分钟自检法

上个月一个做财务系统的朋友半夜在群里 我:“我们那个对账任务,10 月跑了 5 次,账被重复冲了 4 遍。” 我让他把配置贴出来,就一行: 0 12 1 * 1 /opt/job/reconcile.sh他说他的理解是“每月第一个周一 12 点跑一…

2026/10/12 20:48:25 阅读更多 →
Excel 管库存为什么越管越乱?三个天生缺陷,和三个能救急的笨办法

Excel 管库存为什么越管越乱?三个天生缺陷,和三个能救急的笨办法

我见过太多小公司,库存一开始都是一张 Excel 表,管着管着就成了糊涂账。很多人第一反应是"我们的人不够细心",但我这些年看下来,结论恰恰相反——不是人不用心,是 Excel 这个工具天生扛不住库存这件事。 具体…

2026/10/13 18:38:40 阅读更多 →
高并发下的排行榜系统:如何用Redis快速实现千万用户实时排名

高并发下的排行榜系统:如何用Redis快速实现千万用户实时排名

高并发、千万用户、实时排名——听起来吓人,拆开来看全是套路。本文从数据结构选型到工业级落地,手把手带你打出一套 Redis 排行榜的"组合拳",附带完整代码,可直接抄到生产环境。一、为什么是 Redis ZSet?排…

2026/10/12 23:40:44 阅读更多 →

最新新闻

矩阵论期末复习:Jordan标准形、QR分解与广义逆手算全攻略

矩阵论期末复习:Jordan标准形、QR分解与广义逆手算全攻略

简介:矩阵论期末考试参考答案是一份针对矩阵论课程期末考试的完整解题资料,面向正在复习矩阵论、线性代数的本科生及考研备考生,也适用于需要快速回顾核心计算方法的工程类学生。资源包内共1个PDF文件,大小约284KB,文件…

2026/10/13 19:15:31 阅读更多 →
OSPF协议细节深度解析:LSA类型、邻居状态机与SPF算法

OSPF协议细节深度解析:LSA类型、邻居状态机与SPF算法

1. 为什么OSPF不是“配通就行”,而是网络工程师的分水岭 在某高校网络实验室带学生做路由实验时,我见过太多次这样的场景:A同学花15分钟把OSPF邻居关系配通, show ip ospf neighbor 显示FULL,ping测试全通&#xff0…

2026/10/13 19:15:30 阅读更多 →
夸克网盘免费扩容1TB全攻略:从活动入口到空间验证一步不落

夸克网盘免费扩容1TB全攻略:从活动入口到空间验证一步不落

夸克网盘免费扩容1TB这个话题,隔一阵子就会有人翻出来问一遍。网上教程截图满天飞,但真正把入口在哪、任务怎么算完成、到账怎么验证、有效期怎么查讲清楚的并不多。我这次直接把2026年还能跑通的路径重新过了一遍,从准备工作到领取完成&…

2026/10/13 19:15:30 阅读更多 →
免费开源ERP系统选型与实施:进销存+财务+生产一体化实战指南

免费开源ERP系统选型与实施:进销存+财务+生产一体化实战指南

这几年在圈子里被问得最多的一个问题,就是中小公司和创业团队到底要不要上ERP。我的答案一直很干脆:要上,但别一上来就盯着那套贵得离谱的商业软件。免费开源的进销存财务生产ERP系统,这些年成熟度已经很高了,尤其适合…

2026/10/13 19:15:30 阅读更多 →
免费开源一体化ERP实战:进销存+财务+生产+SaaS部署与权限管理

免费开源一体化ERP实战:进销存+财务+生产+SaaS部署与权限管理

我一个干制造业信息化多年的人,这几年最深的感受就是:太多中小工厂被Excel管生产、管库存、管账的方式拖住了。业务一多,采购下单靠拍脑袋,仓库月底盘点对不上数,财务月底对账加班到深夜,生产领料不知道库存…

2026/10/13 19:15:30 阅读更多 →
多智能体电梯群控:基于目的楼层预约与PPO的深度强化学习实现

多智能体电梯群控:基于目的楼层预约与PPO的深度强化学习实现

简介:本资源是一套面向计算机、人工智能、自动化等专业学生的毕业设计与课程设计实践项目,聚焦于多智能体协同下的电梯群控优化问题,采用深度强化学习实现目的楼层预约调度算法,解决传统电梯响应延迟高、能耗大、乘客等待时间长等…

2026/10/13 19:14:30 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →