开始之前
你需要第 1 课的准备,也就是 OPENAI_API_KEY 里的 API 密钥和官方 SDK,再加一个 schema 库。Python 用 Pydantic,运行 pip install openai pydantic。JavaScript 用 Zod,示例会和 SDK 一起导入它,运行 npm install openai zod。Pydantic 的 BaseModel 和 Zod 的 z.object 都是在代码里描述数据结构,SDK 会替你把结构转成 JSON Schema。
JSON Schema 是描述 JSON 数据的标准写法,规定一个对象有哪些键、每个值是什么类型、哪些键必须出现。
结构化输出适用于 GPT-4o 及之后的 OpenAI 模型,gpt-4-turbo 这类更早的模型只能退回 JSON 模式。指南建议新项目用 gpt-6-astra,也就是 2026 年 11 月 5 日本课所有示例用的模型。它有两种形式。第 2 课已经用过一种:函数定义上的 strict,让模型给出的参数符合你的 schema。这一课讲另一种,text.format,让模型回复用户的内容符合 schema。
指南划的界线很简单。模型要连接你的函数、数据或工具时,用函数调用。你想规定模型回复的样子时,比如一个数学辅导应用要在界面上逐步渲染解题步骤,就用 text.format。查询订单的退款流程是函数,界面要拆成摘要和后续步骤的客服回复是回复格式。从 Chat Completions 迁移过来的代码要注意,response_format 在 Responses API 里变成了 text.format。
指南展示了它的四种用法。数学辅导按顺序返回解题步骤。信息抽取从研究论文里取出标题、作者、摘要和关键词。界面生成把 HTML 表示成一棵受枚举约束的递归元素树。内容审核把输入归入不同类别。
用 Pydantic 拿到带类型的回复
附上一个 schema,问数学辅导“how can I solve 8x + 7 = -23”,文档给出的示例回复就是纯粹的 JSON。它有一个包含五个对象的 steps 列表,每个对象都把一句 explanation 和一个 output 配成一对,前者解释这一步做了什么,后者是这一步得到的式子。最后是 "final_answer": "x = -15 / 4"。JSON 外面没有包任何文字,也没有缺任何键。
指南里,正是下面的代码生成了这个结构。两个 Pydantic 类描述了它:一个 Step 带两个字符串,一个 MathReasoning 装着步骤列表和最终答案。client.responses.parse 把这个类作为 text_format 接收,打开严格模式发送对应的 JSON Schema,再把回复解析回一个 MathReasoning 实例。
接着你的代码把 response.output_parsed 当作带类型的对象来读,所以 math_reasoning.final_answer 是一个编辑器认得的字符串。指南列出了这样做省掉的工作:不用再校验和重试格式错误的回复,拒答可以在代码里检测出来,提示词也不必再苦苦要求某种格式。
同一个示例的 JavaScript 版本用 z.object 构建 Step 和 MathReasoning,列表用 z.array(Step) 表示。它把 zodTextFormat(MathReasoning, "math_reasoning") 作为 text.format 传入,读取的也是同一个 output_parsed 字段。
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class Step(BaseModel):
explanation: str
output: str
class MathReasoning(BaseModel):
steps: list[Step]
final_answer: str
response = client.responses.parse(
model="gpt-6-astra",
input=[
{
"role": "system",
"content": "You are a helpful math tutor. Guide the user through the solution step by step.",
},
{"role": "user", "content": "how can I solve 8x + 7 = -23"},
],
text_format=MathReasoning,
)
math_reasoning = response.output_parsed- 用
pip install openai pydantic安装 SDK 和 Pydantic。 - 为你想拿回的结构里的每个对象写一个
BaseModel类。 - 调用
client.responses.parse,传入你的输入,并把text_format设为最顶层的类。 - 读取结果之前,先确认模型既没有拒答,也没有中途停下。
- 读取
response.output_parsed,像使用普通 Python 对象一样使用它的字段。
用 Zod,或者自己传入 JSON Schema
在 JavaScript 里,同样的思路靠 Zod 实现。指南开头的示例定义了一个 CalendarEvent,带名称、日期和参与者列表,然后把 zodTextFormat(CalendarEvent, "event") 作为 text.format 传给 openai.responses.parse。第二个参数是这个格式的名字。输入“Alice and Bob are going to a science fair on Friday.”,回复会被解析成这个事件结构,response.output_parsed 里就是带类型的对象。
不用辅助方法时,你要手写 schema。给 responses.create 传入 text: { format: { type: "json_schema", name: "math_response", schema: {...}, strict: true } },再从 output_text 读取 JSON 字符串。name 字段是这个格式的名字,schema 里放的就是 JSON Schema 本身。schema 里的每个对象都要有 additionalProperties: false,每个属性都要列进 required,和第 2 课严格模式函数的规则一样。模型会按 schema 里键的顺序输出。数学示例把 steps 放在 final_answer 前面,所以解题过程会先于答案写出来。
任何新 schema 的第一次请求,都会因为 API 要处理它而多一些延迟。之后用同一个 schema 的请求就没有这笔开销。每次调用都换 schema,就每次都要付这笔延迟。
两种写法也可以混用。指南里有一个 Python 示例,格式是手写的,schema 却是用 CrateCount.model_json_schema() 从 Pydantic 类生成的。这个类设置了 model_config = ConfigDict(extra="forbid"),生成的 schema 因此不允许多余的键。
手写 schema 还有一个风险:schema 和你代码里的类型会渐渐对不上。指南正是因此强烈建议使用 SDK 的辅助方法。如果你坚持手写,它建议在 CI 里加一步,从类型定义生成 schema,或者加一条规则,任何一边被改动就发出提醒。至于 schema 本身,键名要清楚,重要的键要有标题和描述,再用评估来找出效果最好的结构。
JSON 模式是更早、更弱的设置,下表列出了两者的区别。它只保证 JSON 合法,不保证符合你的 schema。上下文里还必须出现“JSON”这个词,否则 API 会报错。如果缺了这条指示,模型可能一直输出空白,直到碰到 token 上限。只有在模型不支持结构化输出时才用它,并且要配上校验库和重试。
import OpenAI from "openai";
import { zodTextFormat } from "openai/helpers/zod";
import { z } from "zod";
const openai = new OpenAI();
const CalendarEvent = z.object({
name: z.string(),
date: z.string(),
participants: z.array(z.string()),
});
const response = await openai.responses.parse({
model: "gpt-6-astra",
input: [
{ role: "system", content: "Extract the event information." },
{
role: "user",
content: "Alice and Bob are going to a science fair on Friday.",
},
],
text: {
format: zodTextFormat(CalendarEvent, "event"),
},
});
const event = response.output_parsed;| 结构化输出 | JSON 模式 | |
|---|---|---|
| 合法的 JSON | 是 | 是 |
| 符合你的 schema | 是,限于支持的子集 | 否 |
| 怎样打开 | text.format 类型设为 json_schema,并设 strict: true | text.format 类型设为 json_object |
| 适用模型 | 从 gpt-4o-mini 和 gpt-4o-2024-08-06 开始 | 较早的 GPT-3.5、GPT-4 和 GPT-4o 模型,以及兼容的 GPT-5 模型 |
处理拒答和被截断的回复
回复对不上你的 schema,有两种情况,而且都很常见。模型可能出于安全原因拒绝回答,也可能在 JSON 闭合之前就用完了篇幅。
拒答不遵循你的 schema,所以它以单独的内容类型返回。在 message 项里,这个内容部分的 type 是 "refusal",还有一个 refusal 字段装着模型的原话。指南的示例 JSON 里,这句话是“I'm sorry, I cannot assist with that request.”。此时响应状态仍然是 completed,所以只检查状态的代码,会去解析一个拒答。
下面的循环是指南拒答示例的结尾部分。它跳过不是消息的输出项,打印拒答内容,遇到没有解析结果的部分就抛出错误。在真正的应用里,你会把拒答显示在界面上,或者转去一个备用流程,而不是把它打印出来。
被截断的回复是另一个样子。如果你设置了 max_output_tokens,而模型用到了上限,response.status 就是 "incomplete",response.incomplete_details.reason 是 "max_output_tokens"。JSON 停在半路,无法解析,所以要先检查这个状态,再提高上限或者把任务缩短。指南里更完整的边界情况示例就是按这个顺序检查的:先看未完成状态,再找第一个 message 项,最后看它的第一个内容部分是拒答还是文本。
带 schema 也能流式输出。在 Python 里,client.responses.stream 接收同样的 text_format,循环里检查 response.refusal.delta、response.output_text.delta、response.error 和 response.completed。流结束后,stream.get_final_response() 返回完整的响应。在 JavaScript 里,openai.responses.stream 接收一个 zodTextFormat,用 .on("response.output_text.delta", ...) 和对应的拒答事件来监听,await stream.finalResponse() 返回最终结果。指南建议让 SDK 来处理流式的结构化输出,而不是自己解析那些片段。
用户输入还需要再加一道防护。模型总会尽力套进 schema,所以一个和任务毫不相干的输入,可能换来一个格式合法、内容却是编出来的结果。要在 instructions 里写明这种情况下该返回什么,比如空字段或者一句固定的话。少了这条指示,一条凭空编出、看着却很正常的记录,到了下游就很难被发现。
for output in response.output:
if output.type != "message":
continue
for item in output.content:
if item.type == "refusal":
# If the model refuses to respond, you will get a refusal message
print(item.refusal)
continue
if not item.parsed:
raise Exception("Could not parse response")
print(item.parsed)schema 里能放什么,不能放什么
结构化输出支持 JSON Schema 的一个子集。支持的类型有字符串、数字、布尔值、整数、对象、数组、枚举和 anyOf。字符串可以带 pattern,也就是值必须匹配的正则表达式,还可以带 format,可选 date-time、time、date、duration、email、hostname、ipv4、ipv6 或 uuid。数字可以带 minimum、maximum、它们的不含边界版本,以及 multipleOf。数组可以带 minItems 和 maxItems。
有些关键字暂不支持:allOf、not、if、then、else、dependentRequired 和 dependentSchemas。带着 strict: true 发送其中任何一个,API 会直接报错,而不是悄悄地尽力而为。微调模型还不支持上面那些字符串、数字和数组的约束,以及对象上的 patternProperties。
schema 的根节点必须是对象,而且不能是 anyOf。这一条常常绊倒 Zod 用户。判别联合,比如以 status 区分的成功结构和错误结构,编译后在最顶层就是 anyOf,指南演示了它会失败。办法是把这个联合包进对象的一个字段里。在 schema 内部,只要每个分支本身都是同一子集下合法的 schema,anyOf 就能用。
每个字段都必须在 required 里,所以模型总会给它一个值。要让字段可选,就保留在 required 里,并在它的类型里允许 null,在 Pydantic 里就是把字段类型写成 str | None。指南的内容审核示例展示了效果。它的模型里有 category: Category | None 和 explanation_if_violating: str | None。问“怎样准备工作面试”时,文档的示例回复把 is_violating 设为 false,那两个字段都是 null。每个对象还都要有 additionalProperties: false。$defs 下的定义可以用,用到两次的结构只写一次再引用即可。指南的示例把 step 定义在那里,再用 "$ref": "#/$defs/step" 让 steps 数组指向它。递归 schema 也支持,用 # 指回根节点。
大小有硬性上限,见下表。手写的 schema 很少碰到它们。当你从一个大目录生成 schema 时,比如一个列出所有产品名的枚举,这些上限就要紧了,所以要在第一次请求替你发现问题之前,先把数量核对一遍。
结构化输出发布
模型输出可靠地遵循你提供的 JSON Schema。
Responses API 发布
一个为智能体和工具打造的新 API,也就是本课程用的那个。
strict 模式支持并行调用,关键字也更多了
字符串的正则和
email这类格式加入了支持的子集。
来源:OpenAI Docs: Changelog (2026年11月5日 查阅)
| 限制项 | 上限 |
|---|---|
| 整个 schema 的对象属性数 | 5,000 |
| 嵌套层数 | 10 |
| 属性名、定义名、枚举值和常量值的总字符数 | 120,000 |
| 所有枚举属性的枚举值总数 | 1,000 |
| 单个超过 250 个值的字符串枚举的总字符数 | 15,000 |
Michelle Pokrass 和 OpenAI Developers 谈 schema 的可靠性
Michelle Pokrass 在 OpenAI 工作。结构化输出在 2024-08-06 发布,她当天就在 X 上宣布了它,说模型输出从此会遵循你给的 JSON schema。她还给出 OpenAI 自己的数字:开启这个功能后 schema 可靠性是 100%,而之前的 GPT-4o 模型是 86%。
这些数字来自 OpenAI 当年对 GPT-4o 的测试,不是当前模型的成绩。道理倒是没变:开启 strict 之后,schema 是被强制执行的,不是靠提示词去求。
OpenAI Developers 在 2025-05-20 发帖说,strict 模式现在能和并行函数调用一起用,支持的 schema 关键字也多了很多:字符串可以用正则或 email 这类格式,数字可以设范围,数组可以限定元素个数。它们就是本课 schema 规则里的 pattern、format、minimum 和 minItems。把这些限制写进 schema。只有微调模型会跳过它们,也只有那时,你的代码才需要再查一遍。
开启这个功能后,schema 可靠性现在是 100%,而我们之前的 gpt-4o 模型是 86%。
X 官方嵌入卡片的截图,拍摄于 2026-11-05,时间为 UTC。点击截图可到 X 上打开原帖。X 用 Show more 截断了 OpenAI Developers 的帖子。
常见错误
- 还没检查拒答或未完成状态,就去解析
output_text。 - 把
completed状态当成解析结果一定存在、可以直接使用的证明。 - 为了让字段可选,把它从
required里去掉,结果请求失败。 - 把 Zod 判别联合用作根 schema。
- 每次请求都换 schema,每次都付第一次请求的延迟。
- 手写的 schema 和另外定义的类型各管各的,慢慢就对不上了。
- 模型支持结构化输出,却选了 JSON 模式。
- 在严格模式的 schema 里用
allOf或if加then,而这个子集并不支持它们。 - 发送用户输入时,没说明输入对不上结构时该返回什么。
自己试一试
- 运行数学辅导示例,再把
math_reasoning.steps一步一行地打印出来。 - 把
final_answer改成str | None,问一个无解的问题。会返回什么? - 给 Zod 日历示例发一句不含任何事件的话,再为这种情况加一条指示。
- 把
max_output_tokens设得足够低,让回复被截断,读一读incomplete_details。 - 把数学 schema 手写成
json_schema,去掉additionalProperties,读一读报错。 - 写一个 Zod 判别联合,把它作为根节点传入,看请求怎样失败。
- 用一个无害的问题运行指南里的内容审核示例,打印
category。
接下来
第 4 课往上走一层,进入 OpenAI Agents SDK。那里的智能体可以接收一个 output_type,也就是一个 Pydantic 模型或 Zod schema,SDK 会用同一套结构化输出机制,让它的最终回答符合这个结构。
SDK 还会替你运行第 2 课的工具调用循环,在智能体之间交接工作,用护栏检查输入,并把每一步记进追踪记录。这些都建立在本课程到目前为止亲手发出的调用之上。那一课的示例用 Python,文档给出 TypeScript 写法的地方也会一并列出。
自测一下
用 Pydantic 类调用 client.responses.parse 之后,带类型的对象在哪里?
查看答案
A. response.output_parsed parse 辅助方法把你那个类的实例放在 output_parsed 里。
你的 schema 需要一个可选字段,在结构化输出里该怎么写?
查看答案
C. 保留在 required 里,并在类型里允许 null 每个字段都必须在 required 里,所以可选字段要留在 required 中,并把 null 列为允许的类型。
模型出于安全原因拒答,这会怎样体现?
查看答案
B. 出现一个类型为 refusal 的内容部分 拒答作为消息里单独的内容类型返回,而状态仍可能是 completed。
你用 json_object 打开 JSON 模式,上下文里必须有什么?
查看答案
D. JSON 这个词 上下文里没有出现 JSON 这个词,API 就会对 JSON 模式报错。
常见问题
结构化输出的回复还会出错吗?
会。结构被 schema 约束住了,但里面的值仍然可能有错。遇到这种情况,指南建议把指示写得更清楚,在系统指示里给出示例,或者把任务拆成更简单的子任务。你的应用依赖的那些值,比如总数或日期,仍要像检查任何模型输出一样去核对。
能在对话中途更换 schema 吗?
能,前提是在标准单智能体模式下使用受支持的 GPT-6 及之后的模型。在下一条用户消息之前,往 input 里追加一个带新 text.format 的 configuration_update 项,每次请求都保留原来顶层的格式,这样提示词缓存仍然有效。每次更新都会替换整个格式,用 {"type": "text"} 可以切回纯文本。更新只接受 json_schema 和 text 两种。
除了 Responses,还有哪些接口支持结构化输出?
Chat Completions、Assistants、Fine-tuning 和 Batch 都支持,JSON 模式也一样。在 Chat Completions 里,schema 放在类型为 json_schema 的 response_format 里,需要 gpt-4o-mini、gpt-4o-2024-08-06 或更新的快照。
用函数调用时,JSON 模式是打开的吗?
是的。指南说明,用函数调用时 JSON 模式始终打开,所以参数总是合法的 JSON。完全符合你的 schema 则是另一项保证,需要在每个函数上打开 strict,函数调用指南对此有详细说明。严格模式的函数遵守的 schema 规则,和这一课讲的完全相同。
资料来源
- OpenAI Docs: Structured model outputs https://developers.openai.com/api/docs/guides/structured-outputs
- OpenAI Docs: Function calling https://developers.openai.com/api/docs/guides/function-calling
- OpenAI Docs: Migrate to the Responses API https://developers.openai.com/api/docs/guides/migrate-to-responses
- OpenAI Docs: Changelog https://developers.openai.com/api/docs/changelog
- X: Michelle Pokrass announces Structured Outputs https://x.com/michpokrass/status/1820881057824305567
- X: OpenAI Developers on strict mode with parallel calls and new schema keywords https://x.com/OpenAIDevs/status/1924915341052019166


