
openai-node 结构化输出完全指南用 Zod 与 Standard Schema 在 Responses API 中校验模型 JSON【免费下载链接】openai-nodeOfficial JavaScript / TypeScript library for the OpenAI API项目地址: https://gitcode.com/GitHub_Trending/op/openai-node导读Structured Outputs结构化输出允许模型按照你给定的 JSON Schema 返回符合约束的 JSON是构建可靠 Agent 工作流的关键能力。在 openai-node 官方 SDK 中推荐通过client.responses.parse()配合可解析的text.format来使用这一能力并从response.output_parsed读取经过类型校验的结果。本文以 docs/structured-outputs.md 为主线结合仓库源码完整讲解 Zod 与 Standard Schema 两种校验体系下的文本解析、函数参数解析、Schema 编写约束与 Chat Completions 兼容用法帮助你写出开箱即用、类型安全的严格结构化输出代码。核心思路parse() output_parsedStructured Outputs 的整体调用模式非常简洁调用client.responses.parse()时传入一个可解析的text.formatSDK 会在内部把校验器转换为发送给 API 的严格 JSON Schema并在收到模型输出后执行反向校验最终把校验通过的结果放到response.output_parsed上。从源码看这一机制由两层协作完成src/lib/ResponsesParser.tsparseResponse()遍历响应中的所有message输出项对每个output_text内容调用parseTextFormat()也就是统一的 parseResponseFormatContent() 入口output_parsed是一个 getter它从response.output中返回第一个解析成功content.parsed ! null的output_text内容找不到时返回nullsrc/lib/ResponsesParser.ts。需要注意的是只有传给.parse()的格式才是可自动解析的。文档与源码均明确若把同样的格式传给client.responses.create()并不会触发自动解析。用 Zod 解析响应文本基础用法zodTextFormat()会把一个 Zod Schema 转换为发送给 API 的严格 JSON Schema并用同一 Schema 校验模型返回的内容。以下代码来自 docs/structured-outputs.md 的完整示例import OpenAI from openai; import { zodTextFormat } from openai/helpers/zod; import { z } from zod/v4; const MathResponse z.object({ steps: z.array( z.object({ explanation: z.string(), output: z.string(), }), ), final_answer: z.string(), }); const client new OpenAI(); const response await client.responses.parse({ model: gpt-5.5, input: Solve 8x 31 2., text: { format: zodTextFormat(MathResponse, math_response), }, }); if (response.output_parsed) { console.log(response.output_parsed.final_answer); }zodTextFormat(schema, name)的第一个参数是 Zod Schema第二个参数name是模型可见的严格 JSON Schema 名称。仓库中对应的可运行示例见 examples/responses/structured-outputs.ts其中模型示例为gpt-4o-2024-08-06完整展示了从定义Step/MathResponse到打印rsp.output_parsed?.final_answer的流程。output_parsed 为 null 的情况response.output_parsed返回第一个成功解析的文本输出或者在没有可解析输出时返回null。一个典型场景是不完整响应当响应未完成例如因 token 上限中断时parseResponse()中的shouldParse判定为false!response.status || response.status completed才为真此时所有解析结果保持为nullsrc/lib/ResponsesParser.ts。这样做的意义在于不完整响应保持未解析状态其status与incomplete_details仍然可用方便应用层区分解析失败与生成被截断。支持多个 Zod 版本Zod 助手支持从zod/v3、zod/v4、zod/v4-mini导入的 Schema。请选用与你应用内 Zod 版本一致的导入路径。从源码看SDK 通过结构化的ZodTypeLike形状同时识别 Zod v3_output与 Zod v4_zod.output的推断类型src/helpers/zod.ts并在运行时用isZodV4()区分两条 Schema 转换路径。用 Zod 解析函数参数当模型需要调用工具时可以使用zodResponsesFunction()为 Responses API 定义带校验的函数工具。把该工具传给responses.parse()后每个匹配的function_call输出项都会带上校验过的parsed_argumentsimport OpenAI from openai; import { zodResponsesFunction } from openai/helpers/zod; import { z } from zod/v4; const GetWeather z.object({ city: z.string(), unit: z.enum([c, f]), }); const client new OpenAI(); const response await client.responses.parse({ model: gpt-5.5, input: What is the weather in Paris in Celsius?, tools: [ zodResponsesFunction({ name: get_weather, description: Look up the current weather for a city., parameters: GetWeather, }), ], }); for (const item of response.output) { if (item.type function_call) { console.log(item.name, item.parsed_arguments); } }源码机制与边界zodResponsesFunction()生成的工具会被makeParseableResponseTool()打上非枚举的$brand: auto-parseable-tool标记并挂载$parseRaw参数解析器与可选的$callbacksrc/lib/ResponsesParser.ts。解析时parseToolCall()按函数名含命名空间在请求工具中反查输入工具命中可解析工具后调用$parseRaw(toolCall.arguments)得到parsed_argumentssrc/lib/ResponsesParser.ts。该助手会生成strict: true的严格工具并校验参数。它不会执行函数也不会把执行结果回传给模型完整的调用-执行-回传工具循环需要应用层自己实现。关于 Responses API 完整工具循环见 docs/tools.md可运行示例见 examples/responses/structured-outputs-tools.ts该示例用z.enumz.union定义了一套结构化数据库查询工具query并在解析后检查响应status、查找function_call输出项读取parsed_arguments。Standard Schema 校验器如果你的校验器实现了 Standard Schema 接口可以改用standardTextFormat()与standardResponsesFunction()。这两个助手使用~standard.jsonSchema.input({ target: draft-07 })生成模型侧 Schema并使用~standard.validate()解析模型输出import OpenAI from openai; import { standardResponsesFunction, standardTextFormat } from openai/helpers/standard-schema; import { z } from zod/v4; const Weather z.object({ city: z.string(), unit: z.enum([c, f]), }); const client new OpenAI(); const response await client.responses.parse({ model: gpt-5.5, input: Return the weather in Paris in Celsius., text: { format: standardTextFormat(Weather, weather), }, tools: [ standardResponsesFunction({ name: get_weather, parameters: Weather, }), ], }); console.log(response.output_parsed);从源码看src/helpers/standard-schema.tsparseStandardSchema()通过bindStandardSchema()缓存~standard绑定后执行校验SDK 对 Standard Schema 的实现要求是version: 1与vendor字段可选的types元数据用于类型推断。校验必须同步SDK 的解析助手要求Standard Schema 的validate()必须是同步的。源码中parseStandardSchema()在拿到校验结果后先做isPromiseLike()检测若返回 Promise 会直接抛出OpenAIErrorStandard Schema helpers only support synchronous validation...src/helpers/standard-schema.ts。这是因为 SDK 的解析链路本身是同步的异步校验无法接入。提供 JSON Schema 覆盖override实现了~standard.validate()但没有提供~standard.jsonSchema.input()的校验器需要显式传入 JSON Schema通过schema选项传给文本格式助手或函数工具助手import { standardResponsesFunction, standardTextFormat } from openai/helpers/standard-schema; const weatherValidator { ~standard: { version: 1 as const, vendor: example, validate(value: unknown) { if ( typeof value object value ! null city in value typeof value.city string unit in value (value.unit c || value.unit f) ) { return { value: { city: value.city, unit: value.unit } }; } return { issues: [{ message: Expected a city and a temperature unit. }] }; }, }, }; const weatherSchema { type: object, properties: { city: { type: string }, unit: { type: string, enum: [c, f] }, }, required: [city, unit], additionalProperties: false, }; const textFormat standardTextFormat(weatherValidator, weather, { schema: weatherSchema, }); const weatherTool standardResponsesFunction({ name: get_weather, parameters: weatherValidator, schema: weatherSchema, });resolveStandardJSONSchema()的逻辑是优先使用schema覆盖参数否则回退到~standard.jsonSchema?.input({ target: draft-07 })两者都不可用时抛出OpenAIErrorsrc/helpers/standard-schema.ts。严格化处理与类型推断两个助手都会对兼容的 JSON Schema 做严格化归一化normalizeStructuredOutputSchema()toStrictJsonSchema()并拒绝不受支持的 Schema 特性以及无法证明互斥性的oneOf分支src/helpers/standard-schema.ts。从源码可看到其互斥性证明手段JSON 类型不相交、字面量值不相交、判别字段discriminator字面量不相交、闭合对象属性集不相交等。若oneOf分支无法证明互斥会抛出OpenAIError并建议改用anyOf或添加带不同字面量的判别字段。另外实现了 Standard Schema 类型元数据~standard.types的校验器会保留推断的输出类型没有该元数据的校验器解析结果类型为unknownsrc/helpers/standard-schema.ts。Schema 编写要求Structured Outputs 只支持 JSON Schema 的一个子集定义 Zod 或 Standard Schema 校验器时请遵守以下约束docs/structured-outputs.md 与 docs/helpers.md根 Schema 必须是对象。根级 union 不受支持。从 src/helpers/zod.ts 的zodV4ToJsonSchema()可看到根级 union 会生成anyOf/oneOf这超出严格 Schema 的表达范围。对象属性必须全部必填。需要用可空字段表达可能缺席的值例如z.string().nullable()而不是z.string().optional()——因为严格 Schema 下模型必须返回该字段值或null可选字段无法在发给模型的 Schema 中忠实地表达。嵌套 union在满足以下条件时可正常工作JSON 类型或字面量值不相交、枚举、数组、JSON 原生字面量、可空值、判别联合discriminated union等能在受支持的严格 JSON Schema 子集中表达的场景。z.union()可以嵌套在对象内但根级z.union()或z.discriminatedUnion()不支持。模型可见的描述必须来自 Schema 本身例如z.string().describe(...)。TypeScript 注释在运行时不可用不会发送给 API。Zod v3 严格 Schema 会拒绝原生Date、BigInt、Map、Set、Promise值以及自定义 refinements、transforms、pipelines、intersections 和歧义 union——这些行为无法在发送给模型的 JSON Schema 中忠实表达。此限制仅作用于严格模式助手非严格模式的 Realtime 工具保持原有行为。源码中 src/helpers/zod-v3-strict-schema.ts 的assertSupportedZodV3Schema()实现了上述检查unsupportedReasons表逐一列出ZodBigInt、ZodDate、ZodMap、ZodSet、ZodPromise、ZodEffects、ZodPipeline、ZodIntersection、ZodCatch、ZodTuple、ZodRecord的拒绝原因如大整数请编码为十进制字符串并在解析后用BigInt转换assertUnambiguousUnion()则要求 union 各分支的 JSON 域类型/字面量/判别字段两两不相交。此外还校验了 JSON 可序列化性拒绝负零、循环引用、非 JSON 原生对象、toJSON钩子等见assertJSONSerializableSchema()。大整数用字符串表示JSON number 在 Zod 校验之前就可能丢失整数精度。因此应在模型侧 Schema 中把大整数表示为十进制字符串解析完成后再转换import { z } from zod/v3; const Invoice z.object({ amount: z.string().regex(/^-?(?:0|[1-9][0-9]*)$/u), }); const invoice Invoice.parse(JSON.parse({amount:90071992547409931234567890})); const exactAmount BigInt(invoice.amount);同样的 JSON 原生模式适用于 Zod v4可避免依赖有损的数字强制转换。这是文档与源码共同推荐的唯一精确方案ZodBigInt在严格模式下会被拒绝因为bigint没有同步的 JSON 表示。Chat Completions 兼容用法已有 Chat Completions 集成的项目无需迁移到 Responses API 也能使用结构化输出用zodResponseFormat()或standardResponseFormat()搭配client.chat.completions.parse()即可。解析后的内容在message.parsed而不是response.output_parsedimport OpenAI from openai; import { zodResponseFormat } from openai/helpers/zod; import { z } from zod/v4; const Answer z.object({ value: z.string() }); const client new OpenAI(); const completion await client.chat.completions.parse({ model: gpt-5.5, messages: [{ role: user, content: Answer in one word. }], response_format: zodResponseFormat(Answer, answer), }); console.log(completion.choices[0]?.message.parsed?.value);Chat Completions 的函数参数解析则使用zodFunction()或standardFunction()。从 src/lib/parser.ts 可以看到parseChatCompletion()对finish_reason length抛出LengthFinishReasonError、对content_filter抛出ContentFilterFinishReasonError并在没有 refusal 且内容非空时填充message.parsed函数调用的parsed_arguments则由parseToolCall()借助工具的$parseRaw或严格标记生成。更完整的 Chat Completions 解析、流式与runTools()用法见 docs/helpers.md。小结openai-node 的 Structured Outputs 支持可以概括为一条主线用校验器生成严格 JSON Schema 交给模型再用同一个校验器解析模型输出。Zod 用户直接使用zodTextFormat/zodResponsesFunctionStandard Schema 兼容校验器则使用standardTextFormat/standardResponsesFunction两者都要求同步校验、根 Schema 为对象、属性必填并严格遵循 Schema 子集约束。理解output_parsed与parsed_arguments的生成机制src/lib/ResponsesParser.ts、统一的解析入口src/lib/parser.ts以及严格化校验src/helpers/zod.ts、src/helpers/standard-schema.ts将帮助你在实际项目中写出既类型安全又符合 API 约束的代码。【免费下载链接】openai-nodeOfficial JavaScript / TypeScript library for the OpenAI API项目地址: https://gitcode.com/GitHub_Trending/op/openai-node创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考