本文要点
- 数据点: OpenAI 官方说明,Structured Outputs 的新 Schema 首次请求会产生额外处理延迟,典型 Schema 通常在 10 秒以内完成,复杂 Schema 可能需要 1 分钟。这意味着,生产环境不能只把 strict: true 加进请求,还必须提前编译预热、验证失败状态,并准备回退路径。([OpenAI 官方说明](https://openai.com/index/introducing-structured-outputs-in-the-api/?utm_source=openai))
- 症状: 你的接口偶尔返回合法 JSON,但字段缺失、类型不对,或者工具参数能解析却触发了错误业务动作。
- 最快解法: 使用 OpenAI Structured Outputs + 严格 JSON Schema + 两层应用验证 + 回归样例,同时处理拒绝、输出截断和语义错误。
数据点: OpenAI 官方说明,Structured Outputs 的新 Schema 首次请求会产生额外处理延迟,典型 Schema 通常在 10 秒以内完成,复杂 Schema 可能需要 1 分钟。这意味着,生产环境不能只把 strict: true 加进请求,还必须提前编译预热、验证失败状态,并准备回退路径。(OpenAI 官方说明)
症状: 你的接口偶尔返回合法 JSON,但字段缺失、类型不对,或者工具参数能解析却触发了错误业务动作。 最快解法: 使用 OpenAI Structured Outputs + 严格 JSON Schema + 两层应用验证 + 回归样例,同时处理拒绝、输出截断和语义错误。
谁该看这篇?
如果你正在从 JSON mode 迁移到 Structured Outputs,需要先理解两者保证范围不同。 如果你在做数据抽取、数据库写入或工具执行,则不能把“JSON 能解析”当作“业务数据可信”。
维护工具调用项目时,还要区分两种 Schema:一种描述最终响应,另一种描述 Function Calling 的工具参数。它们配置位置不同,不能把旧的 Chat Completions 示例直接复制到当前的 Responses API。
第 1 步:先把下游契约写清楚
不要从提示词开始,而要从数据库、队列消费者或工具执行器反推输出。
例如,订单抽取服务真正需要的可能只有:
{
"order_id": "A-1001",
"status": "paid",
"amount": 199.5,
"currency": "CNY",
"customer_note": null
}
对应的 Schema 应明确:
order_id是否必填,是否允许空字符串;status是否只能是pending、paid、cancelled;amount是数字还是带货币符号的字符串;- 没有备注时使用
null还是省略字段; - 是否禁止额外字段;
- 日期、金额精度和跨字段关系由谁验证。
在严格模式下,建议把业务真正需要的字段全部写入 required,并对对象明确设置 additionalProperties: false。但要注意,严格模式只支持 JSON Schema 的一个子集;如果你把复杂的动态键、过深的联合结构或不受支持的关键字一次性塞进去,请求可能在生成前就失败。(OpenAI Structured Outputs 说明)
提醒: Schema 合规只说明输出的外形符合契约,不代表模型抽取的订单号、金额或状态一定真实。金额是否与原文一致、订单状态是否允许写入数据库,仍然属于业务校验。
JSON mode 和 Structured Outputs 的采购判断
你可以按下面的条件选择,不要仅凭“返回了 JSON”做判断:
| 方案 | 能保证什么 | 不能保证什么 | 适用场景 |
|---|---|---|---|
| JSON mode | 尽量返回可解析的 JSON | 不保证字段、类型和嵌套结构符合 Schema | 临时调试、人工查看 |
Structured Outputs,strict: false | 提供 Schema 方向约束 | 结构约束强度不如严格模式 | 兼容性试验、逐步迁移 |
Structured Outputs,strict: true | 在支持的 Schema、非拒绝、未截断条件下严格匹配结构 | 不保证业务事实、权限和跨字段逻辑 | 数据库写入、工作流、工具执行 |
OpenAI 当前 API 参考将 json<em>schema 作为较新的结构化响应格式,并把 json</em>object 标为旧的 JSON mode;支持该能力的模型优先使用 json<em>schema。(Responses API 参考)
第 2 步:在首次请求中放对配置
最终响应使用 text.format
如果模型不需要调用外部工具,而是直接返回抽取结果、分类结果或工作流节点数据,当前 Responses API 的配置重点是 text.format:
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-5",
input: "从这段文本中提取订单信息:客户已支付订单 A-1001,金额 199.50 元。",
text: {
format: {
type: "json_schema",
name: "order_record",
strict: true,
schema: {
type: "object",
properties: {
order_id: { type: "string" },
status: { type: "string", enum: ["pending", "paid", "cancelled"] },
amount: { type: "number" },
currency: { type: "string" },
customer_note: { type: ["string", "null"] }
},
required: [
"order_id",
"status",
"amount",
"currency",
"customer_note"
],
additionalProperties: false
}
}
}
});
console.log(response.output_text);
官方快速入门已经采用 client.responses.create() 作为 Responses API 的基本调用方式;模型名称应以你账户当前可用的模型列表和正式文档为准,不要把旧公告中的模型示例当作今天的推荐型号。(OpenAI API 快速入门)
你还需要在应用代码中确认输出读取路径。Responses API 的响应状态可能是 completed、failed、in_progress、cancelled、queued 或 incomplete,不能假设每个响应都有可以直接解析的完整文本。
工具调用使用函数参数 Schema
如果目标是让模型调用你的库存、支付或数据库函数,Schema 应放在自定义函数工具的参数定义中,并把 strict: true 配置在工具层。
工具参数的结构合规,不等于工具执行安全。你的服务器仍应检查用户权限、资源归属、金额上限、幂等键和重复调用;模型产生的参数只能作为待验证输入,不能直接拼接 SQL 或执行高风险操作。
Responses API 允许模型连接外部函数和工具;工具调用本身与最终文本响应是不同的输出项,工程上应分别记录调用名、调用参数、执行结果和最终回复。
如果一个请求可能产生多个工具调用,而你的业务要求严格按顺序执行,应评估关闭并行工具调用。官方 Structured Outputs 说明指出,并行函数调用与严格 Schema 存在兼容边界;需要单次只生成一个工具调用时,可设置 parallel<em>tool</em>calls: false。(OpenAI Structured Outputs 说明)
第 3 步:收到响应后做两层验证
第一层:验证 API 和生成状态
第一层不要立刻调用 JSON.parse(),而是依次检查:
- HTTP 状态和错误对象;
- Responses API 的
status; - 是否存在拒绝内容;
- 是否存在
incomplete_details; - 是否因为输出上限或其他停止条件导致结果不完整;
- 最后才读取结构化文本或工具参数。
Structured Outputs 允许模型拒绝不安全请求。官方方案为响应增加拒绝信息,使应用能够区分“模型拒绝”与“成功返回但 Schema 不匹配”。如果生成在完成前被截断,也不能把半截 JSON 送进数据库。(OpenAI Structured Outputs 说明)
第二层:验证业务语义
第二层由你的验证器完成,至少包含以下检查:
- 金额必须大于或等于 0,并符合币种精度;
status为paid时必须存在支付流水号;cancelled订单不能进入发货队列;- 日期不能晚于当前业务时间;
order_id必须能在你的数据库中找到;- 工具参数中的用户标识必须与当前会话一致。
这一步是 Structured Outputs 与业务系统之间的边界。模型可以稳定生成一个结构正确的对象,但仍可能把原文中的“预计支付”误判成“已支付”,或者把相邻订单的金额关联到错误客户。结构化输出不会消除 JSON 值内部的模型错误。
常见失败按类型处理
Schema 不受支持
表现为请求创建阶段直接报错,或者服务端提示 Schema 无法编译。处理顺序应是:
- 删除不必要的动态键和复杂分支;
- 把一个大对象拆成抽取、分类、工具参数等多个小对象;
- 用枚举替代自由文本状态;
- 用明确的
null类型表达空值; - 重新运行 Schema 编译检查。
不要通过把 strict 改成 false 来掩盖设计问题。那只能让请求更容易发出去,却会把结构风险转移到下游。
首次编译延迟
新 Schema 的第一次调用可能比重复调用慢,官方说明中典型 Schema 通常低于 10 秒,复杂 Schema 可能达到 1 分钟。部署时应在发布阶段完成预热,并把首次调用延迟纳入健康检查;Schema 名称、字段结构或版本变化后,重新执行这项检查。
输出长度中断
如果字段包含长文本、数组或嵌套对象,输出可能因上限或其他停止条件提前结束。遇到 incomplete、缺少结束结构或 incomplete_details 非空时,应:
- 降低单次任务的字段数量;
- 限制数组长度;
- 先抽取摘要,再异步获取长文本;
- 增大允许的输出预算;
- 将任务拆成多个独立步骤。
禁止对截断 JSON 做字符串补括号后继续入库,这会制造“格式正确但内容不完整”的隐蔽数据。
安全拒绝
拒绝不是普通解析失败。记录请求标识、模型、Schema 版本和业务任务类型后,返回明确的待处理状态;如果输入确实违反安全规则,就不要自动重试。涉及付款、身份、医疗或权限变更的工作流,应直接转入人工队列。
语义校验失败
语义失败可以有限重试,但重试请求应携带具体错误,例如“支付状态缺少流水号”,而不是笼统地说“请重新输出 JSON”。如果连续失败,保存原始文本、验证错误和版本信息,方便定位是提示词、模型、Schema 还是业务规则变化。
FAQ:迁移和排障时最容易混淆的地方
如何让 OpenAI 输出符合 JSON Schema?
严格模式不是一句提示词,而是 API 层的 Schema 配置。你需要在响应格式或工具参数中使用 json<em>schema,并设置 strict: true;同时保证 Schema 属于支持范围。即使结构符合,也必须继续验证业务语义、权限和跨字段关系。(OpenAI Structured Outputs 说明)
Structured Outputs 和 JSON mode 怎么选?
如果下游只需要一个大致可解析的 JSON,JSON mode 可以用于早期实验;如果字段要直接进入数据库或驱动工具执行,应优先使用 Structured Outputs。JSON mode 不负责保证字段契约,严格结构化输出也不负责证明内容真实,两者都不能替代业务验证。
strict: true 后为什么仍然解析失败?
先排查四件事:Schema 是否使用了不支持的关键字;模型是否拒绝;响应是否被截断;代码是否读取了错误的 Responses API 节点。不要只检查 JSON.parse() 的异常,因为真正的问题可能发生在生成状态、拒绝状态或输出路径。(Responses API 参考)
结构化输出遇到拒绝怎么办?
将拒绝作为独立业务状态处理,不要把它当作“模型忘记了 Schema”。如果输入可以安全改写,经过权限和策略判断后再发起新请求;如果任务本身不应自动完成,就进入人工队列,并保留请求标识和拒绝记录。
JSON Schema 太复杂如何拆?
以一次下游动作对应一个 Schema 为原则。先抽取稳定字段,再做分类或工具调用;将跨字段关系交给代码验证。这样不仅更容易定位失败,也能减少首次编译延迟,并降低 Schema 结构变化对整个工作流的影响。
第 4 步:上线前建立回归样例
至少准备以下测试批次:
- 正常输入:所有字段齐全,数据格式标准;
- 边界输入:最小金额、最大允许长度、枚举边界;
- 空值输入:缺少备注、日期或可选关联信息;
- 超长输入:长文档、长数组、重复段落;
- 冲突输入:同一订单出现两个金额或两个状态;
- 安全拒绝:应拒绝的请求;
- 工具异常:参数合法但下游返回权限错误或资源不存在。
每个样例都记录:
- 模型标识;
- Responses API 或工具调用配置;
- Schema 名称与版本;
- 提示词版本;
- 验证器版本;
- API 状态、拒绝信息和截断原因;
- 最终进入哪一种失败队列。
模型列表会随着平台更新,官方模型接口提供当前可用模型的查询方式;不要只在代码里写一个长期不变的模型假设。(OpenAI 模型接口参考)
经验: 回归测试不应只断言“能否解析 JSON”。更有价值的断言是“错误订单不会进入发货队列”“缺少流水号会进入人工队列”“拒绝不会触发工具执行”。
第 5 步:把 Schema 当作长期 API 契约
Schema 一旦被数据库、队列或工具执行器消费,就不再只是提示词附件,而是接口契约。建议采用显式版本,例如 order<em>record</em>v1、order<em>record</em>v2,并在发布前检查:
- 新增必填字段是否会破坏旧消费者;
- 枚举删除是否影响历史数据;
- 字段类型变化是否需要迁移;
- 空值策略是否保持一致;
- Schema 变化是否触发首次编译延迟;
- 下游缓存和验证器是否同步更新;
- 灰度期间旧版与新版是否能同时消费。
可以先增加可选兼容字段,再在新版本中升级为必填;不要直接修改生产 Schema 的含义。对于工具参数,尤其要避免把一个字段从“预览操作”改成“立即执行”,因为这不仅是类型变化,也是权限和风险变化。
Responses API 默认可能保留应用状态,官方数据控制说明提到,Responses API 在默认或 store 为 true 时存在 30 天的应用状态保留周期;处理敏感数据时,应把存储策略、区域要求和日志脱敏纳入验收,而不是只检查 Schema。(OpenAI 数据控制说明)
你可以直接照做的发布清单
✅ 先从数据库和工具消费者反推字段。 ✅ 明确必填、枚举、空值和额外字段策略。 ✅ 最终响应使用 text.format,工具参数使用函数工具 Schema。 ✅ 支持的场景统一设置 strict: true。 ✅ 检查拒绝、incomplete 和 API 错误后再解析。 ✅ 使用独立验证器检查金额、权限、状态和跨字段关系。 ✅ 为正常、边界、空值、超长和拒绝输入建立回归样例。 ✅ 记录模型、接口、Schema 和验证器版本。 ✅ 修改契约前做兼容检查,并安排灰度发布。 ❌ 不要把 JSON mode 当作 Schema 保证。 ❌ 不要把合法 JSON 当作正确业务数据。 ❌ 不要把截断结果补括号后直接入库。 ❌ 不要让模型输出未经验证的高风险工具参数。
如果你目前用的是本地 Windows、Linux 或临时云主机来跑这套回归,常见缺点是环境镜像不一致、远程调试链路较长、批量测试时资源不稳定,而且团队成员很难复现同一套客户端与验证器版本。对于需要短期并行测试多个 Schema、运行 Node.js 验证脚本或保留固定开发环境的团队,直接维护一台长期机器往往又会产生闲置成本和运维负担。
此时,租赁 kvmboot 的 Mac 测试环境可以作为更灵活的补充:你可以先复制一份不含业务数据的验收框架,在批量回归或跨环境复现阶段使用;如果需要临时 Mac 算力与远程开发支持,可先查看 kvmboot 帮助中心,再根据测试周期评估 美国东部 Mac 环境 是否适合。
但如果你的任务是长期稳定的高并发生产推理、需要物理 USB 外设,或已经拥有成熟的容器化基础设施,租赁 Mac 未必是最佳长期方案。对这类场景,自购硬件或继续使用现有云资源通常更合理;kvmboot 更适合临时算力、跨环境验收、远程调试和短周期回归测试。
用 kvmboot 远程 Mac,加速结构化输出落地
需要在真实 Mac 环境中验证结构化输出、接口集成与自动化流程时,kvmboot 可提供即开即用的远程 Mac。
结构化输出、AI Agent 与 JSON Schema:从普通 JSON 到可靠契约 · AI Agent 技术栈中的 JSON Schema:连接模型、工具与工作流 · Spec-Driven Development:用规范驱动实现、验证与回归测试