AI 开发

AI 为什么需要 JSON Schema?Structured Output、Function Calling 与 JSON Schema 完整解析

阅读约 14 分钟

2024 年起,产品页把 JSON Schema、Function Calling、Structured Output 写成同一件事。它们不是。Schema 是契约语言,Function Calling 是「请宿主执行」,Structured Output 是「模型必须吐出这个形状」。本文把三个词拆开,再说明为什么概率模型必须接到一份可校验的 Schema 上,而不是一句「请返回 JSON」。

阅读前速览

概念 一句话 落在哪
JSON Schema 字段、类型、必填、枚举的契约,不是模型功能 git 里的 .json,CI 里的 validator
Function Calling 模型提出调用,宿主执行,常有副作用 tools[] / arguments
Structured Output 模型最终回复必须符合这份形状,通常无副作用 json_schema / output_type

先记住分工:Schema 是口径,Function Calling 是伸手,Structured Output 是交回对象。把三者写成「让模型输出 JSON」,下一跳就会在 parse 散文。

三个词为什么总被混用?

文档、SDK 示例和推文喜欢把它们叠成一句话:给模型一份 Schema,它就会输出合法 JSON。演示里确实经常成功。线上事故通常发生在你以为「已经结构化」之后。

混用会掩盖三层完全不同的工作:

  • Function Calling 的 arguments 仍常是字符串。 宿主必须再 JSON.parse,类型、枚举、多余字段都还没被拦住。
  • Structured Output 只管最终回复。 它不替你查订单、不打款、不写数据库。有副作用的动作仍走 Function Calling。
  • Schema 如果只写在 prompt 里,CI 测不了。 「请返回 JSON」不能失败;一份进 git 的 Schema 可以。

所以问题不是「AI 会不会 JSON」,而是:谁在什么时刻,用哪一份契约,拦住非法对象。三个词回答的是三个不同的谁。

JSON Schema 是什么(先分清:它不是模型功能)

JSON Schema 是一份描述 JSON 形状的文档:有哪些键、各是什么类型、哪些必填、枚举取哪些值、能不能多出字段。它比「看起来像 JSON」严得多,也比 TypeScript 类型或 protobuf 更常被模型 API 直接引用。

Agent / Structured Output 真正用到的,通常就这几个关键字:

  • type / properties:对象长什么样。
  • required:缺了哪个键就不算过。
  • enum / pattern / 数值上下限:把「差不多」收成有限集合。
  • additionalProperties: false:多余键直接失败,挡住 prompt 注入带进来的额外字段。

语法细节见 JSON Schema 教程。这里只要先记住:Schema 先给 validator 和测试看。清晰的 description 能帮模型选对工具,但不能代替 enum 和 required。

{
  "$id": "https://example.com/schemas/order.json",
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,}$" },
    "amount": { "type": "number", "exclusiveMinimum": 0 },
    "status": { "type": "string", "enum": ["paid", "refunded", "pending"] }
  },
  "required": ["order_id", "amount", "status"],
  "additionalProperties": false
}

上面这份订单契约可以同时喂给 Function Calling 的 parameters、Structured Output 的 json_schema,以及宿主里的 ajv。改字段只改这一份。

Function Calling 是什么?

Function Calling(以及更泛的 Tool Calling)不是「模型去执行函数」。模型只做一件事:发出函数名和参数。宿主 parse、校验、执行,再把结果塞回对话。查库存、退款、写工单——副作用在这一层。

厂商把工具定义写成 tools[],其中的 parameters 就是一份 Schema 子集:

{
  "type": "function",
  "function": {
    "name": "lookup_order",
    "description": "Look up an order by ID",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "pattern": "^A-[0-9]{4,}$"
        }
      },
      "required": ["order_id"],
      "additionalProperties": false
    },
    "strict": true
  }
}

AI Agent 完整指南 把这一跳画进完整循环;MCP Tool Schema vs Function Calling 说明 MCP 的 inputSchema 和厂商 parameters 是同一本字典的两种写法。

看起来已经「结构化」了。缺口在于:arguments 经常仍是字符串;工具返回值往往没有 Schema;strict 只约束模型侧。没有宿主校验,amount 是数字还是字符串,仍然看今天的快照心情。

Structured Output 是什么?

Structured Output(Structured Outputs / JSON mode 的严格形态)约束的是模型自己的最终消息。分类、抽取、摘要对象、Agent 的 handoff——这些不需要外部副作用,但下游必须拿到固定形状。

各家字段名不一样:response_format、text.format.json_schema、output_type。内核相同:把一份 Schema 交给解码器,非法 token 路径直接剪掉。

{
  "type": "json_schema",
  "json_schema": {
    "name": "order",
    "strict": true,
    "schema": {
      "$id": "https://example.com/schemas/order.json",
      "type": "object",
      "properties": {
        "order_id": { "type": "string" },
        "amount": { "type": "number" },
        "status": {
          "type": "string",
          "enum": ["paid", "refunded", "pending"]
        }
      },
      "required": ["order_id", "amount", "status"],
      "additionalProperties": false
    }
  }
}

DevDay 2026 预测 把这一层和 Function Calling、Agent 运行时写成同一根 JSON 管。口径仍是 Schema:管子的直径先定,协议才能接上去。

打开 strict: true 之后,漏字段、枚举写错、多出键,常常在模型侧就被拦住。它替代的是「请用 JSON 回答」那句 prompt,不替代宿主再验一次,更不替代 Function Calling。

三者怎么叠在一起?

把三个词压成「让模型输出 JSON」,职责就丢了。拆开之后:

  1. JSON Schema 是共用口径:字段词典、必填、枚举。它不调用、不生成,只描述「合法长什么样」。
  2. Function Calling 是向外伸手。模型请求一次副作用,参数必须过 Schema,结果最好也过 Schema。
  3. Structured Output 是向回交对象。模型结束这一跳时,回复本身必须是那份形状。

一张对照表,避免下次再把它们写成同义词:

层 模型在做什么 没有 Schema 会怎样
Function Calling 提议调用,等待宿主 参数是散文;额外键可能打进账单系统
Structured Output 直接生成最终对象 下游用正则从段落里抠字段
JSON Schema 什么都不做,只当尺子 尺子若只存在于 prompt,CI 量不到

多步 Agent 把两层都用上:先 Structured Output 做分诊对象,再 Function Calling 查单、退款。AI Agent 与 JSON Schema 工作流 写的是门禁怎么排;本文只把三块积木先认清。

AI 为什么离不开 JSON Schema?

因为模型和程序不是同一种东西。模型按概率续写 token;程序按确定规则读对象。中间如果只靠「它今天写得像 JSON」,漏字段、数字变字符串、枚举写成 "Paid"、note 多出来——每一种都可能被下一跳当成事实。

2024 年以前,链路短:一次聊天、一次函数、失败了人能看见。2026 年的链路更长:分类 → 查单 → 决定退款或转人工 → 把对象交给另一个 Agent。任何一跳只是「像 JSON」,后面的步骤都会把错误固化。

同一份订单,模型随时可能吐出这种「看起来合法」的对象:

{
  "order_id": "A-1042",
  "amount": "99.00",
  "status": "Paid",
  "note": "please hurry"
}

amount 是字符串,status 大小写不对,还多了 note。没有 Schema,下游可能把 99.00 当字符串拼进 SQL,或把 Paid 当成未知状态重试。调试时先在本地 格式化 看清结构,再和上周能过的对象 JSON Diff;不要把带用户数据的响应贴到公共 JSON 站点。

Schema 的价值不在「教模型说话」,而在把非法对象挡在宿主门内。调用前拦一次,回复后拦一次,评测再断言一次。窗口越大、步骤越多,散文错误越容易变成「系统里的事实」。

同一份 Schema,两种投影

不要为 Function Calling 和 Structured Output 各手写一份「差不多」的定义。推荐形状是一份源文件,两套导出:

  1. 仓库里放 schemas/order.json,带稳定 $id 和版本。
  2. 导出 Function Calling:裁成 function.parameters,打开 strict。查单工具往往只需要 order_id,不要把整份订单对象塞进参数。
  3. 导出 Structured Output:同一份对象作为 json_schema / output_type,给抽取、分诊、最终回复用。

投影时注意子集:厂商 strict 常常要求每个 property 都在 required 里,并禁用部分 oneOf / $ref。源文件可以更完整,导出器按目标裁剪;CI 对「源 vs 投影」做结构 Diff,避免有人手改其中一侧。

宿主侧校验仍然必要

strict: true 只约束模型解码。换快照、厂商关掉 strict、arguments 二次 parse、工具自己返回了开放对象——都可能绕过模型侧。宿主仍要在三处跑同一份 Schema:

  • 执行工具前。 parse → validate → execute。失败就返回结构化错误,不要把原始字符串传给下一跳。
  • 收下模型最终对象后。 Structured Output 成功也不等于你的业务规则过了(金额上限、货币、权限)。
  • 写入状态 / 评测夹具时。 checkpoint 和 eval 必须是合法 JSON,不能从聊天记录猜进度。

模型侧约束和宿主侧校验是两份工作。关掉 strict 或换一家 API 之后,只留 prompt 里的 Schema,契约就消失了。

2026 年怎么落地(先写文件,再写 prompt)

  • 在仓库建 schemas/tools/、schemas/extract/、schemas/state/。每个文件有 $id 和版本。
  • 先写 Schema,再写「请返回 JSON」。prompt 里的句子过不了 CI。
  • 有副作用(查库、打款、发信)走 Function Calling;分类、抽取、handoff 走 Structured Output。
  • 新对象默认 additionalProperties: false 加明确 enum。要开放扩展,单独加 metadata,不要松开根对象。
  • 工具超过十个就先检索再调用。不要把整本目录塞进一次请求。
  • 密钥和用户字段只在浏览器里看:格式化、JSON Diff,零上传。

目录可以很薄。要紧的是文件先于 prompt。模型还会换,协议还会加;口径应该先稳定。

常见误区

  • 把 Structured Output 当成 Function Calling。 前者不执行代码。要查单、退款,仍需工具层。
  • 只在 prompt 写「请返回 JSON」。 没有进 git 的 Schema,就没有可失败的测试。
  • 只校验工具入参,不校验模型最终对象。 抽取和分诊的事故,往往发生在「看起来已经是 JSON」的回复上。
  • 为每种协议手写一份差不多的 Schema。 第三周就会分叉。导出,不要复制。
  • 用宽 type: object 逃避设计。 开放对象不是门禁。真要扩展,用版本号或显式 metadata。

FAQ

Structured Output 能替代 Function Calling 吗?

不能。Structured Output 约束最终回复的形状;Function Calling 请求宿主执行有副作用的动作。抽取订单字段用前者,查库存、退款用后者。两者应引用同一份 Schema,而不是互相替代。

Function Calling 已经有 parameters,还要不要单独的 Schema 文件?

要。parameters 是投影,不是源。单独文件才能同时喂给 Structured Output、MCP、评测和宿主 ajv。只改 tools[] 里的一份,三周后就会和抽取 Schema 对不上。

没有 Agent,只做一次信息抽取,需要 Schema 吗?

更需要。一次抽取的结果如果直接进数据库或表格,非法对象没有下一跳可以「再理解一次」。Structured Output + 宿主校验是最小完整集。

开了 strict: true,还要宿主校验吗?

要。strict 管模型解码;宿主管业务规则、二次 parse、以及厂商关掉 strict 之后的退化路径。两道门成本很低,少一道就会在换模型时露出来。

被拒的 JSON 怎么在本地查?

先格式化看结构,对照 Schema 找缺的 required 或错的 enum,再和上一版能过的对象 Diff。JSONSort 在浏览器本地做这些事,不上传数据。不要把带密钥的响应贴到公共站点。

结论

AI 需要 JSON Schema,不是因为模型突然读不懂自然语言,而是因为概率输出必须接到确定的门禁上。Function Calling 表达「去做」;Structured Output 表达「交回这个对象」;Schema 是两者共用的口径。

从今天起,先写 schemas/extract_order.json,再决定调用哪一家的 API。模型会换,协议会变,直径应该先稳定。

延伸阅读

更新记录: 首次发布