各厂商的 JSON Schema 子集

6 分钟读完

K10
深入解析 · 工具与能力设计

JSON Schema 不是同一件东西——Anthropic、OpenAI、Gemini 强制的是不同子集,一移植 schema,就会撞上这个不匹配。

Anthropic 在校验时会忽略 minLengthmaxLengthminimummaximum。Gemini 只强制 OpenAPI 3.0 子集。OpenAI 的 strict 模式要求 additionalProperties: false,并把所有字段标为 required。每家都有文档,都不是 JSON Schema draft-2020-12,且当你发一个它不强制的关键字时,都没有一家会大声报错——schema 被接收、约束悄悄消失、模型愉快地产出 schema 本应禁止的输出。这篇是你跨厂商移植 schema 时放在编辑器旁边的那张表——各家强制什么、会静默丢什么,以及三家都能扛住的可移植子集。

STEP 1

三个子集的鸟瞰。

Anthropic、OpenAI、Gemini 都把 JSON Schema 塞进同一个语法槽——分别叫 input_schemaparametersparameters——三家都称之为 "JSON Schema"。含义的差别足以让"为一家写的 schema"到另外两家都成为候选 bug。K7 厂商矩阵覆盖外层包装差异;本文往里再一层,聚焦 schema 关键字的强制。

三种形状先钉。Anthropic 把 "JSON Schema" 当作一份很宽松子集的粗略描述——schema 记录意图,模型的受限解码器兑现某些关键字、把另一些视作文档。OpenAI 有两种模式:非 strict(schema 是模型尽量遵循的提示)与 strict(schema 由受限解码器强制)。Gemini 用 OpenAPI 3.0 的 Schema 对象——一个定义良好的 JSON Schema draft-04 子集,缺少 draft-2020-12 新增的很多东西,一个可见的表面差异是类型名大写。

模式与契约一文覆盖设计原则——让非法状态无法表示、让安全相关字段必填——这正是 K10 纪律最要紧的地方:一个在目标厂商上静默丢失的关键字,就是一条模型不再兑现的承重约束。结构化工具 I/O一文覆盖同一故事的返回侧。这里我们把镜头拉近到你移植时需要的关键字表。

STEP 2

Anthropic 子集:字符串与数字在边界开放。

Anthropic 的 input_schema 兑现结构性关键字——typepropertiesrequiredenumitemsdescription——并丢弃范围类关键字:字符串的 minLengthmaxLength,数字的 minimummaximumexclusiveMinimumexclusiveMaximum。它也不在受限解码器层强制 patternformat。schema 文本里模型会读到它们、通常会遵守,但没有硬 mask 反违反——约束只是劝告。同理,const 不被强制;若约束是承重件,请使用只有一个元素的 enum

实操结果是:一份在 OpenAI strict 模式下明显把字符串封顶到 200 字符的 schema,在 Anthropic 上会静默接收 5000 字符。若上限重要——因为下游要存进一个有界列,或者你按输出令牌付费——schema 就不是 Anthropic 上的强制点;你要在应用代码里校验模型输出,或者把约束表达成允许值的 enum。数字范围同理:带 minimum: 1maximum: 100quantity 字段在 Anthropic 上是文档而非强制,负数量将是你在数据库而非模型那里抓到的 bug。

Anthropic 会好好强制的:enum 成员资格、required 字段、顶层 type、对象形状(属性名,设 additionalProperties: false 时不允许多余)。围绕这些设计你的 Anthropic schema;其他一切当作 description 里的散文,并在外壳边缘做校验。

STEP 3

OpenAI 的 strict 模式:处处 additionalProperties、全部 required。

OpenAI 的 strict 模式按设计是三家里最紧的——schema 被编译成真正的语法,模型输出保证匹配。为这份保证付出的代价是两条常让团队意外的规则。第一,schema 里的每个对象都必须设 additionalProperties: false,包括嵌套的——省略它,strict 校验器就拒绝该 schema。第二,properties 里列的每个属性也必须出现在 required 里;strict 模式不像 JSON Schema 别处那样区分"可选字段"。真正可选字段的绕开法是并集类型:"type": ["string", "null"],由调用方决定 null 表示"未提供"。

由此派生出两条规则。strict 模式在大多数位置上不支持 oneOfallOfanyOf(截至 2026 年文档;请核对兼容性矩阵,它在演化)。深嵌套 schema 会撞到实现层的深度上限,通常在几层左右;修法通常是展平。字符串关键字都可用——minLengthmaxLengthpatternenumformat——数字关键字也可用。开启强制的开关是 strict: true;不开,schema 只是提示,模型可能产出违反它的输出。

你会看到的失效模式很隐微:你的 schema 起草时对准 Anthropic(宽松、无 additionalProperties 纪律、有些可选),打开 OpenAI strict 模式,SDK 拒绝注册工具。这是好失败——跑之前就停。坏失败是你按 OpenAI strict 起草、发给 Anthropic,字符串长度上限静默消失,因为 Anthropic 忽略这些关键字。两向都咬人;K7 一文的"中间表示"模式是机械修法。

STEP 4

Gemini 的 OpenAPI 3.0 子集:大写类型与"没 draft-2020-12 花招"。

Gemini 的 schema 取自 OpenAPI 3.0 的 Schema 对象,本质上是 JSON Schema draft-04 加 OpenAPI 特定关键字。类型名是大写字符串(OBJECTSTRINGINTEGERNUMBERBOOLEANARRAY),不是小写,这是复制粘贴移植时第一件绊倒你的事。此外,OpenAPI 3.0 里不存在的构造会被拒绝:const$defs、跨文件 $ref,以及更奇异的 draft-2020-12 新增内容都过不了校验。

Gemini 可靠强制的:typepropertiesrequiredenumitemsdescription、字符串的 minLength / maxLength、数字的 minimum / maximum、常见 format。不可靠的:const、异类型的 oneOfpatternPropertiesnullable: true 是一个 OpenAPI 风格旋钮,而非 JSON Schema 的 "type": ["string", "null"] 并集;把它搞错是常见移植 bug。

| keyword                | Anthropic  | OpenAI strict | Gemini (OpenAPI) |
|------------------------|------------|---------------|------------------|
| type / properties      | enforced   | enforced      | enforced (UPPER) |
| required               | enforced   | enforced (all)| enforced         |
| enum                   | enforced   | enforced      | enforced         |
| additionalProperties   | honoured   | REQUIRED false| honoured         |
| minLength / maxLength  | ignored    | enforced      | enforced         |
| minimum / maximum      | ignored    | enforced      | enforced         |
| pattern                | advisory   | enforced      | partial          |
| format                 | advisory   | enforced      | well-known only  |
| const                  | ignored    | enforced      | rejected         |
| oneOf / anyOf / allOf  | partial    | limited       | not supported    |
| $defs / $ref           | supported  | limited       | not supported    |
| type: ["x", "null"]    | supported  | required opt  | use nullable     |

读某关键字对应的一行,就能知道它的可移植性。三列行为都不同的关键字,就是你要么在可移植规格里避开、要么放到"逐厂商 emitter"后面的关键字。写着"ignored"的部分是"静默丢弃"地带:既不报错、也不强制,你的测试套件必须在应用边界而非 schema 处抓到约束。

STEP 5

可移植子集:真的能不改就搬的部分。

把三列强制取交集,结果就是可移植子集。顶层 "type": "object"。一份含原语——stringintegernumberboolean——的 properties 映射,加上带 items 的原语数组。一个 required 数组。字符串上的 enum。到处 description。就这些。跨三家完好移植、三家都强制,涵盖真实工具 schema 的大部分而无需仪式。

# The portable subset — enforces the same way on all three vendors.
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "description": "The order identifier, e.g. 'o_42'."
    },
    "reason": {
      "type": "string",
      "enum": ["defective", "wrong_item", "other"],
      "description": "The reason for the refund."
    },
    "amount_cents": {
      "type": "integer",
      "description": "Refund amount in cents (must be positive)."
    }
  },
  "required": ["order_id", "reason", "amount_cents"]
}

该 schema 里缺席的内容是刻意的。amount_cents 上没有 minimum: 1,因为 Anthropic 不会强制它,而一份可移植 schema 不能在某家静默丢约束。描述里说了"必须为正"——这是模型通常会遵守的劝告性散文,应用层校验兜底。顶层没有 additionalProperties: false,因为它会在某些模式下破坏 Anthropic 的宽容校验器,并强制 OpenAI strict 模式才合法。把两者都在"逐厂商 emitter"这一步加回,不要放进共享规格。

把五步合起来读,JSON Schema 就不再是同一件东西。三家厂商、三个子集、一处可以依赖的窄交集,扩展则挂在 emitter 后面。让 schema 可移植性长期健康的团队做两件事——他们从不直接为某一家厂商起草 schema,且每次提交都跑一次逐厂商 CI 校验——两条实践搭建都要花几小时,却能在代码库一辈子里省下几周"为什么 enum 不再工作"的调试。