跳到主要内容

生产环境中的结构化输出可靠性:为什么 JSON 模式并非契约

· 阅读需 9 分钟
Tian Pan
Software Engineer

一个团队发布了一个文档提取流水线。它使用了 JSON 模式。QA 通过了。监控显示解析错误接近于零。六周后,一个隐蔽的失败浮出水面:语料库中的每一份风险评估都被标记为 “低” —— JSON 格式有效,字段名称正确,但答案是错的。该流水线已经在以符合架构(Schema)的格式自信地撒谎了好几周。

这是将 JSON 模式视为可靠性保证的核心问题。结构一致性(Structural conformance)和语义正确性(Semantic correctness)是系统的不同属性,混淆两者是生产级 AI 工程中最代价高昂的错误之一。

JSON 模式到底保证了什么

OpenAI 在 2023 年 11 月推出的 JSON 模式只保证一件事:输出将符合有效的 JSON 语法。没有未闭合的括号,没有多余的逗号,没有包裹响应的散文式文字。这就是保证的全部范围。

它对以下内容只字未提:

  • 你的代码所期望的字段是否存在
  • 字段值是否具有下游代码所假设的类型
  • 数据内容是否准确、相关或在逻辑上一致
  • 模型的结论是否源自输入

架构强制的结构化输出(Schema-enforced structured outputs)是下一个演进阶段,提供商将你的 JSON Schema 编译成一个约束 Token 生成的有限状态机 —— 这增加了更强的保证。OpenAI 在 2024 年 8 月发布的严格模式(Strict mode)可以将语法和架构一致性的失败率降低到 0.1% 以下。Anthropic 在 2025 年末增加了原生的结构化输出支持。到目前为止,每个主流提供商都有某种形式的架构强制生成。

但 “符合架构” 和 “正确” 仍然是两个独立的属性。一个具有完美架构强制执行力的系统可以可靠地产生 {"sentiment": "positive"},语法有效、类型正确且枚举值有效 —— 但仍然可能有 30% 的时间是错误的。

生产环境中真正重要的三种失败模式

失败模式 1:负载下的架构违规

尽管有提供商的保证,架构违规在生产环境中确实会出现。它们集中在特定条件下:输出非常长导致模型在最后段落发生漂移;深度嵌套的架构导致受约束的解码器难以在数百个 Token 中跟踪括号;以及高并发场景下,某些编排层中的微妙竞态条件与流式解析器发生交互。

仅通过 Prompt 的 JSON 提取 —— 没有受约束的解码,只有 “输出 JSON” 的指令 —— 在处理数百万次请求的生产系统中,有 8–15% 的调用会失败。即使有受约束的解码,失败只是发生了转移而非消失:它们从解析失败变成了拒绝响应(Refusal responses),即模型生成了触发安全机制的拒绝信息,而不是符合要求的输出。

实际意义在于:你仍然需要处理解析错误。一个返回模型拒绝而非有效 JSON 的架构强制端点,仍然会使预期结构化数据的下游解析器崩溃。

失败模式 2:语法有效但语义错误的输出

这是在生产系统中悄无声息地致命的失败模式。架构通过了。类型匹配。值在枚举范围内。但数据是错的。

“置信度总是 0.99” 的模式是最清晰的例子:一个分类器无论输入质量如何,始终输出 {"label": "positive", "confidence": 0.99},因为架构中没有任何内容约束 “置信度” 实际上应该衡量什么。模型学到高置信度是常态,并无条件地产生它。

字段顺序(Field ordering)会带来一种更微妙的变体。如果你的 JSON Schema 将答案字段放在推理字段之前 —— {"answer": ..., "reasoning": ...} —— 受约束的解码会迫使模型在生成推理之前就必须确定答案。这直接损害了思维链(Chain-of-thought)的质量。与自由格式生成相比,在受约束生成下进行推理的模型在复杂任务上的性能下降了 10–15%,而架构字段顺序是导致这一差距的重要驱动因素。解决方法是机械式的:在你的架构中,始终将推理字段放在结论字段之前。

必填字段(Required fields)引发了另一类语义失败。当给定输入时,如果一个必填字段没有好的答案,模型会产生幻觉。它会生成一个包裹在有效语法下的自信谎言,而不是表达不确定性。强制每次调用都填充所有字段的架构,实际上是在模型无话可说时暗示它去捏造。

失败模式 3:模型更新后的隐蔽行为漂移

架构一致性是一个时间点属性。当你的提供商更新底层模型时(他们会在没有预警的情况下按自己的进度更新),架构仍然可以完美通过验证,而其下的输出分布已经发生了偏移。

一个原本将 40% 的案例分类为 “中等” 的风险评估流水线,在模型更新后可能会转变为 25% 的 “中等” 和 15% 的 “高”。这两种分布都符合架构。你的监控显示零错误。你的业务指标漂移了数周,直到有人注意到。

这就是架构形状的漂移(Schema-shaped drift):结构保持完整,而语义发生了变化。对于任何只检查架构一致性的监控系统来说,这都是不可见的。

真正有效的验证架构

正确的多层架构包含三个不同的层级,每一层都能捕捉到其他层级遗漏的故障。

第 1 层:生成时强制执行(Generation-time enforcement)。 使用原生的结构化输出(Structured Outputs)或函数调用(Function Calling),在生成时确保符合 Schema。这样可以消除大部分语法错误,并避免生成后解析和修复的开销。这一层级目前已经足够成熟,在你需要结构化输出的任何地方,都应该默认使用它。

第 2 层:应用边界验证(Application-boundary validation)。 在下游代码使用结构化输出之前,每一项输出都应通过验证层。Python 中使用 Pydantic,TypeScript 中使用 Zod。这一层级可以捕获生成时强制执行所遗漏的边缘情况——例如当响应达到 Token 限制时导致的截断输出、类型强制转换的边缘情况,以及 JSON Schema 无法表达的跨字段约束冲突。

对于那些使用不支持原生结构化输出的供应商的团队,Instructor 库实现了“验证-修复-重试”循环:生成候选内容,根据 Schema 进行验证,将验证错误反馈给模型并附带修复指令,然后重试,直到达到设定的上限。重试率本身就是一个健康信号:如果在 2 次以上的尝试中持续出现重试,说明存在系统性的提示词(Prompt)或 Schema 问题,而不是运气不好。

第 3 层:语义验证(Semantic validation)。 这是大多数团队会跳过并随后感到后悔的一层。它无法被 Schema 强制执行所取代。

语义验证意味着测试结构化输出中的值是否真正正确,而不仅仅是在结构上存在。具体的实践方法取决于你正在构建的内容:

  • 对于分类任务,监控输出随时间的变化分布。标签频率的突然转变预示着模型漂移,即使 Schema 验证依然通过。
  • 对于提取任务,针对人工标记的参考集运行抽查评估。即使是 1% 的样本也能捕获静默退化。
  • 对于高风险决策,使用第二个模型来验证推理的一致性——证据字段是否真正支撑了结论字段?

从一开始就加入 Schema 版本控制。每次 Schema 变更都应增加一个版本字段,并随输出一起存储。当你六个月后调查生产环境异常时,了解当时哪个版本的 Schema 在运行比你预想的更重要。

现在该做什么

如果你正在生产环境中运行结构化输出,请进行以下四项检查:

检查你的字段顺序。 如果你的 Schema 将结论字段放在推理字段之前,请将它们调换位置。这一个细小的改变就能提高多步推理任务的输出质量。

检查你的必填字段。 任何可能没有真实答案的必填字段,要么应该使用可为空(Nullable)类型的可选字段,要么拆分为“是否存在值”的布尔值字段和“有条件的必填值”字段。在没有真实答案的字段上设置必填会导致幻觉。

检查你的监控。 如果你唯一的结构化输出健康信号是解析错误率,那么你对语义漂移(Semantic Drift)是视而不见的。为你的关键分类字段添加分布监控,并在发生显著变化时设置告警。

检查你的重试策略。 如果你的应用程序在遇到 Schema 违规时崩溃,而不是带着错误上下文进行重试,那么一次异常输入就足以引发生产事故。“验证-修复-重试”模式配合重试次数上限和安全回退方案是标准做法,且添加成本并不高。

你真正需要的保证

值得拥有的保证不是“我的输出是有效的 JSON”,而是“我的流水线在已知的错误预算内产生正确的结果,并且我有手段检测何时超出了该预算”。

Schema 强制执行是该保证中成本较低的部分——只需一个下午就能实现,而且供应商已经为你完成了大部分工作。难点在于语义验证层、分布监控,以及当模型更新在无预警的情况下改变输出分布时的事故响应方案。

那些上线了 Schema 强制执行就宣布胜利的团队只走了一半的路。另一半则是确保在你的用户发现之前捕获故障。

References:Let's stay in touch and Follow me for more thoughts and updates