AI 如何生成符合 JSON Schema 的 JSON?从 Prompt 到 Structured Output

从纯提示词、JSON Mode 到各厂商 Structured Outputs,讲清 JSON Schema 如何约束模型输出、OpenAI / Gemini / Anthropic 对照、落地校验流水线,以及与 Tool Calling 的分工。

本系列前几篇分别讲了为什么 Agent 需要 JSON(《Tool Calling 到 MCP 的数据流》)、JSON Schema 为何成为基础设施(《Schema、Function Calling 与 MCP 演进》),以及 Gemini 一家的 Structured Output 配置(《Gemini API 教程》)。

本文把视角拉高:不论你用的是哪家模型 API,怎样从「请在回复里输出 JSON」走到「输出必定符合这份 JSON Schema」。这是 2024–2026 年各厂商 converged 的能力,名字都叫 Structured Outputs / JSON Schema mode,但配置字段、支持子集、与 Tool Calling 的边界略有差异。

四个层级:一层比一层硬

团队里常见四种「让模型出 JSON」的做法,可靠性差一个数量级:

层级做法你控制什么典型失败
L0只在 Prompt 写「请输出 JSON」软约束```json 围栏、解释文字、单引号、尾逗号
L1Prompt + 示例(few-shot JSON)形状有样例,无硬约束字段名拼写漂移、缺字段、类型混用
L2JSON Mode(response_format: json_object 等)输出必须是合法 JSON能 parse,但 price 可能是字符串
L3Structured Output + JSON Schema字段、类型、enum、必填语义幻觉、截断、个别关键字被忽略

生产抽取、分类标签、填表入库,至少做到 L3。L0–L1 适合探索;L2 适合形状多变、只需保证能 JSON.parse 的场景。L3 才是「程序可以直接消费」的契约。

JSON Schema 管什么、不管什么

JSON Schema 是一份描述 JSON 文档结构的元数据:有哪些字段、各是什么类型、哪些必填、枚举取值范围、数组元素形状等。各厂商 Structured Output 本质上都是把这份 Schema 编译进生成过程,而不是只贴在 Prompt 里当说明。

Schema 能管:语法形状(object / array / string / integer)、required、enum、minimum / maximum、additionalProperties: false(禁止未声明字段)、嵌套对象与数组。

Schema 管不了:业务正确性。例如「total_cents 必须等于各行 qty × unit_price 之和」——这类不变量要在 Schema 校验通过后再用代码断言。也不要指望 Schema 替你做事实核查:类型对的幻觉值(编造的发票号)仍可能出现。

和 Tool Calling 里工具的 inputSchema 是同一套语言;区别只是 Structured Output 约束的是最终答复,Tool Calling 约束的是工具参数。详见《数据流解析》。

约束解码:为什么 Schema 比 Prompt 硬

Prompt 只能提高「模型愿意遵守」的概率。Structured Output 走的是约束解码(constrained decoding):在生成每个 token 时,解码器根据当前已输出片段和 Schema,把会导致 JSON 语法错误或 Schema 违规的 token 概率压到接近零。

因此你拿到的文本通常已经是一份可解析、形状正确的 JSON,而不必先写正则剥 markdown 围栏。各厂商实现细节不同(有限状态机、grammar、logit mask 等),但对开发者暴露的接口一致:把 Schema 交给 API,而不是只写在 Prompt 里。

注意:约束解码保证的是结构,不是语义。上线后仍应用同一份 Schema 做二次校验,并加上业务规则层。

OpenAI、Gemini、Anthropic 对照

概念相同,字段名各有一套。下面以「抽取一张发票对象」为例:

厂商JSON ModeStructured Output / Schema备注
OpenAIresponse_format: { type: "json_object" }response_format: { type: "json_schema", json_schema: { name, schema, strict: true } }strict: true 时尽量拒绝 Schema 外字段;配合 Pydantic model_json_schema()
Google GeminiresponseMimeType: "application/json"同上 + responseJsonSchema 或 SDK response_schema详见《Gemini 教程》
Anthropic依赖 Prompt + 解析output_format(Claude 结构化输出)或 Messages API 中的 schema 约束字段随 SDK 版本更新;Schema 建议保持扁平

跨厂商迁移时,Schema 本体尽量用标准 JSON Schema(type、properties、required、enum),各 SDK 只负责包一层请求体。不要把 OpenAPI 3.0 大写类型(OBJECT、STRING)和 JSON Schema 小写(object、string)交叉粘贴。

写好一份 Schema:从 Pydantic 到线上

推荐工作流:先在代码里用 Pydantic / Zod 定义类型 → 导出 JSON Schema → 微调后送进 API。字段说明写进 description:它会进入模型上下文,决定「qty 是件数还是箱数」这类语义,光靠 type: integer 挡不住。

from pydantic import BaseModel, Field


class LineItem(BaseModel):
    name: str = Field(description="商品名称")
    qty: int = Field(description="数量,正整数", ge=1)
    unit_price_cents: int = Field(description="单价,分", ge=0)


class Invoice(BaseModel):
    vendor: str
    currency: str = Field(description="ISO 4217,如 CNY")
    items: list[LineItem]
    total_cents: int

schema = Invoice.model_json_schema()
# 生产建议补上 additionalProperties: false

实用原则:

  • 根类型优先用 object,少用根级 array;部分 API 对 { "items": [...] } 更稳。
  • 先上 type / properties / required / enum,再加 additionalProperties、min/max;别把完整 Draft 2020-12 一股脑塞进去,部分关键字会被忽略。
  • 嵌套不要太深;循环引用会被拒绝,应把 Schema 写扁。
  • Schema 与 Prompt 分工:Schema 管形状;Prompt 管语义(「从下面文本抽取发票……」)。

OpenAI Structured Outputs 示例

OpenAI Chat Completions 在 2024 年后支持 json_schema 响应格式。strict: true 时模型应只输出 Schema 内字段:

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class Invoice(BaseModel):
    vendor: str
    total_cents: int

response = client.chat.completions.create(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "user", "content": "从文本抽取发票:Acme 卖了 2 个键盘,共 398 元。"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "invoice",
            "strict": True,
            "schema": Invoice.model_json_schema(),
        },
    },
)

data = response.choices[0].message.content  # JSON 字符串
import json
invoice = json.loads(data)

Gemini 侧对应 response_mime_type + response_json_schema,完整示例见 Gemini 专题文。Anthropic 请查阅当前 SDK 的 structured output 文档——概念一致,字段名以官方为准。

落地流水线:生成 → 解析 → 校验 → 重试

Structured Output 不是「调一次 API 就结束」。建议固定四步:

  1. 生成:带 Schema 调模型 API;记录 prompt、Schema 版本、原始 content。
  2. 解析:JSON.parse(或 SDK 的 parsed);失败则整段重试,不要半解析。
  3. Schema 校验:用同一份 JSON Schema 跑 AJV / jsonschema / Pydantic;失败则重试或降级。
  4. 业务校验:自定义断言(金额合计、外键存在等);失败则人工或规则引擎。

开发阶段把 Schema 与 2~3 组正/反例存进仓库,用 JSON 工具箱在浏览器本地看结构、做 Diff——这和测 REST 契约同一思路,只是消费者换成了 LLM。

常见坑:开了 JSON Mode 后仍要求「先解释再输出 JSON」;超长输出被截断(提高 max tokens 或拆任务);API Key 进前端演示仓库;Schema 版本与 Prompt 不同步导致 silently ignore 新字段。

和 Tool Calling 怎么分工

Structured OutputTool Calling / MCP
约束对象模型对用户的最终 JSON工具参数 JSON(inputSchema)
谁执行副作用无;只是数据宿主 / MCP Server
典型场景抽取、分类、填表、Agent 间传递查库存、写文件、调外部 API
失败回流校验失败 → 重试或人工错误写入 tool 消息 → 再问模型

一条完整 Agent 链路常常是:Structured Output 抽出结构化意图 → Tool Calling 执行动作 → Structured Output 或自然语言总结给用户。不要用 Structured Output 假装「已经调过支付 API」——模型没有真的调。

常见问题 FAQ

只在 Prompt 里写「请输出 JSON」够吗?

不够。提示词只能提高模型遵守的概率,仍可能出现 markdown 围栏、尾逗号、字段名漂移。生产环境应至少开启 JSON Mode,最好把 JSON Schema 交给 API 的 Structured Output 通道,让解码阶段就排除非法 token。

JSON Mode 和 Structured Output 有什么区别?

JSON Mode 只保证输出是合法 JSON 文本,不约束字段名、类型和必填。Structured Output 在 JSON Mode 基础上附带 JSON Schema,生成每个 token 时按 Schema 过滤,形状才稳定,才能直接入库或传给下一跳程序。

OpenAI、Gemini、Anthropic 的配置字段一样吗?

概念相同、字段名不同。OpenAI 用 response_format: { type: json_schema, json_schema: { schema, strict } };Gemini 用 responseMimeType + responseJsonSchema;Anthropic 用 output_format 或 tools 里的 structured output。Schema 本体尽量用标准 JSON Schema,各 SDK 再包一层。

Structured Output 能替代 Tool Calling 吗?

不能。Structured Output 约束的是模型对用户的最终 JSON 答复;Tool Calling 约束的是工具参数 JSON,且需要宿主真正执行工具。抽取、分类、填表用前者;查库存、写文件、调 MCP 用后者。完整 Agent 链路常常两者都用。

模型输出还需要再校验吗?

需要。约束解码能大幅降低语法错误和类型漂移,但不能保证语义正确(字段类型对、值是编的)。生产环境应把同一份 JSON Schema 再跑一遍校验器,失败则重试、降级或人工审核。

如何在本地验证 Schema 与样例输出?

把 JSON Schema 和两三组模型输出样例存成 JSON 文件,用 JSON 工具箱在浏览器本地做语法校验与结构对照,数据不上传服务器。

总结

让 AI 生成符合 JSON Schema 的 JSON,正确顺序是:先定 Schema,再开 JSON Mode / Structured Output,最后才写 Prompt。Prompt 负责语义;Schema 负责形状;Pydantic / Zod 是作者友好的前端,线上走各厂商的 Schema 通道。

建议你用一张真实发票或一段客服对话跑通:写出 Schema → 调一次 API → 把输出贴进校验器对照。对得上再接数据库或下一跳 Agent。工具参数那一侧仍然走 Tool Calling / MCP,不要混成一种 API。