让 AI 稳定输出 JSON 的方案阶梯
“让模型输出 JSON”看起来是一个很小的需求,但它往往是 AI 应用从 demo 进入工程系统的第一道门槛。
在 demo 里,模型偶尔多说一句“下面是结果:”没有关系,人可以读懂。在生产系统里,这句话可能让 JSON parser 直接失败;字段少一个,工具调用就缺参数;类型错一个,工作流分支就走偏。
这篇文章不从语法技巧开始,而是从业务场景出发,按成本从低到高梳理一条方案阶梯:prompt、JSON mode、schema、修复、重试。
场景
假设一个销售 CRM 想用 AI 从客户来信中提取线索信息。
输入来自邮件、官网表单和客服转交的聊天记录。销售经理希望系统自动生成一个线索对象:
这个对象会被 CRM 消费:
companyName和contactName写入线索。priority决定销售跟进顺序。nextAction决定自动任务。missingFields决定是否发补充信息邮件。
常见失败样本包括:
- 模型输出 Markdown 代码块,解析器没有剥离。
priority输出成"紧急",但系统只接受low | medium | high。budgetRange不知道时编造"50k-100k"。missingFields本应是数组,却输出成字符串"phone"。- 邮件里没有公司名,模型为了完整性猜了一个。
这些问题不是“模型不会 JSON”这么简单,而是输出契约、未知值策略、业务容错和失败处理没有设计清楚。
问题类型
JSON 稳定性至少包含三层。
第一层是语法合法:输出能被 JSON parser 解析。
第二层是结构符合预期:字段、类型、枚举、嵌套层级符合 schema。
第三层是业务可用:字段值来自输入或可信上下文,能通过业务规则校验。
适合交给模型的是从非结构化输入里识别和抽取候选信息。应该由程序完成的是解析、校验、默认值处理和业务规则判断。
这里要特别注意“未知值”。很多 JSON 输出失败不是因为模型不会填字段,而是因为团队没有告诉模型:信息缺失时应该返回 null、"unknown"、空数组,还是把字段放进 missingFields。
风险/收益
稳定 JSON 的收益是让模型输出能进入自动化链路。
主要收益包括:
- 降低解析失败率。
- 减少下游字段兼容代码。
- 让错误能按字段归因。
- 让工具调用参数更稳定。
- 让线上监控可以量化。
成本也很现实:
- prompt 需要维护示例和字段规则。
- schema 需要和业务代码同步。
- 修复和重试会增加延迟与 token 成本。
- 复杂字段会增加模型出错概率。
- 失败处理需要产品和运营流程配合。
什么时候简单 prompt 足够?
如果字段少、低风险、输出主要给人工看、失败后用户可以重新生成或编辑,简单 prompt 往往够用。比如让模型输出一个包含 title、summary、tags 的内容草稿,用户会人工发布。
什么时候需要工程方案?
只要 JSON 会入库、触发工具、决定权限、影响 SLA、产生对外动作或进入无人值守工作流,就需要 JSON mode、schema、程序校验、重试和降级。
方案阶梯
第一层:Prompt
prompt 是最低成本的起点。
一个可用的 JSON prompt 至少要写清楚:
- 只输出 JSON,不要 Markdown,不要解释。
- 每个字段的含义。
- 必填字段和可选字段。
- 枚举值只能从给定集合选择。
- 未知信息如何表示。
- 不要猜测输入里没有的信息。
- 给出一两个正反示例。
示例:
prompt 的优点是便宜、灵活、上线快。缺点是它是软约束,遇到长输入、复杂 schema、强诱导或模型切换时会不稳定。
第二层:JSON Mode
JSON mode 主要解决“输出必须是合法 JSON”的问题。
它适合已经确定要程序解析,但字段结构还不复杂的场景。相比普通 prompt,它可以显著减少多余解释、Markdown 包裹和语法错误。
但 JSON mode 不是 schema 校验。它可能输出合法 JSON:
这段 JSON 语法合法,但业务结构仍然错误。
所以 JSON mode 通常不是终点,而是 schema 和程序校验前的一层语法保障。
第三层:Schema
当字段会进入下游系统时,需要明确 schema。
schema 的价值是把“你大概输出这些字段”变成“系统只接受这个契约”。
设计 schema 时要克制:
- 字段越少越稳定。
- 枚举越明确越稳定。
- 嵌套越浅越稳定。
- 未知值策略越清楚越稳定。
- 每个字段最好能对应明确业务用途。
不建议为了“以后可能用到”让模型一次输出大量字段。没有下游消费的字段会增加失败概率,也会让评估更困难。
第四层:解析与校验
无论用了 prompt、JSON mode 还是 schema,后端都必须校验。
校验至少包括:
- JSON parse 是否成功。
- 必填字段是否存在。
- 字段类型是否正确。
- 枚举值是否在范围内。
- 字符串长度、数组长度、数字范围是否合理。
- 业务规则是否通过。
解析失败和校验失败要分开记录。前者说明输出不是合法 JSON,后者说明 JSON 合法但不符合契约。
第五层:修复
修复适合模型输出里信息基本可用,但格式或局部字段错误的情况。
例如第一次输出:
校验错误可以反馈给模型:
修复的收益是保留已有抽取结果,成本通常低于完全重试。但如果原始输出已经明显偏离任务,修复会在错误基础上继续修补,不如重新生成。
第六层:重试
重试适合解析失败、模型跑偏或修复失败的情况。
重试必须有边界:
- 最多重试次数。
- 总超时时间。
- 最大 token 成本。
- 是否换模型或降低复杂度。
- 是否切换到更保守 prompt。
- 失败后进入什么降级路径。
一个常见策略是:
- 第一次使用正常 prompt 和 schema。
- 解析失败后尝试一次修复。
- schema 失败后带错误重试一次。
- 仍失败则转人工或要求用户补充信息。
不要无限重试,也不要把所有失败都吞掉。
第七层:降级和人工处理
当 JSON 仍然不稳定时,系统必须知道如何失败。
低风险场景可以提示用户重新生成。中风险场景可以创建待处理任务。高风险场景必须阻止自动执行,并把原始输入、模型输出、校验错误展示给人工。
稳定性工程的目标不是让失败永远不发生,而是让失败不会悄悄流进业务系统。
方案选型表
反模式
第一种反模式:把 JSON 包在 Markdown 代码块里再让后端剥离。
这在早期调试方便,但生产链路里会增加不必要的解析分支。既然下游只要 JSON,就要求模型只输出 JSON。
第二种反模式:字段未知时让模型“尽量补全”。
补全看起来让数据更完整,实际上会引入编造字段。对工具调用和 CRM 入库来说,null 通常比猜测更安全。
第三种反模式:用自然语言字段名。
例如 priority 允许 "低" | "中" | "高" | "比较急" | "马上处理",会让统计和路由变复杂。工程系统更适合稳定枚举,展示层再做本地化。
第四种反模式:一次抽取所有信息。
复杂销售线索、保险理赔、医疗问卷、合同审查都可能包含很多字段。一次输出大 JSON 容易失败,应该把分类、抽取、校验和补充提问拆开。
第五种反模式:校验失败后自动填默认值。
默认值会掩盖模型错误。特别是优先级、金额、权限、时间范围这类字段,错误默认值可能比失败更危险。
第六种反模式:只统计最终成功率。
如果一个请求经过三次重试才成功,最终成功率看起来很好,但成本和延迟已经恶化。应该同时看首轮通过率、修复成功率、重试次数和失败类型。
工程清单
- 场景边界:JSON 是给人看、入库、驱动 UI,还是调用工具。
- 字段设计:每个字段是否有明确下游用途。
- 未知值策略:缺失信息用
null、unknown、空数组还是missingFields。 - Prompt:是否禁止 Markdown、解释文本和猜测。
- JSON mode:是否用于需要机器解析的链路。
- Schema:是否定义必填字段、枚举、类型、长度和范围。
- 校验:是否区分 parse error、schema error 和 business error。
- 修复:是否把校验错误反馈给模型,并限制只修复结构。
- 重试:是否设置次数、超时、成本和模型策略。
- 降级:失败后是否转人工、提示补充信息或停止自动流程。
- 监控:是否记录首轮通过率、重试率、字段错误率和平均延迟。
- 评估:是否用真实失败样本做回归测试。
小结
稳定 JSON 输出不是一个 prompt 小技巧,而是一条从软约束到硬约束的工程链路。
简单 prompt 适合低风险、字段少、人工可复核的场景。JSON mode 解决语法稳定,schema 解决结构契约,程序校验负责真正把关,修复和重试负责恢复,降级和人工确认负责保护业务流程。
判断是否升级方案时,可以问一句最朴素的问题:如果这个 JSON 错了,下游会不会自动做错事?如果答案是会,就不要只靠 prompt。