跳转到主要内容

Claude 结构化输出(Structured Outputs)用法:哪些 schema 会 400

用 output_config.format 传 JSON Schema 即可拿到合法 JSON;经网关实测 minimum 报 400,minLength 被接受却不执行。

LaoZhang AI Team发布于20 分钟阅读
文章目录
Claude 结构化输出:minimum 返回 400,minLength 返回 200 但不执行,enum 等写法正常

想让 Claude 返回程序能直接 json.loads 的结果,不要再靠提示词里写"只返回 JSON",改用结构化输出:在 Messages API 的请求体里加 output_config.format,type 写 json_schema,schema 放一份 JSON Schema,Claude 会把符合这份 schema 的 JSON 写进响应 content 里的 text 块。

截至 2026 年 10 月 30 日,结构化输出是正式功能,不需要 beta 请求头;Anthropic 结构化输出文档列出的支持模型包括 Opus 5.5、Sonnet 5.5、Haiku 5.5,以及 Sonnet 4.5、Opus 4.5、Haiku 4.5 之后的各个版本,Claude API、Amazon Bedrock、Google Cloud 和 Microsoft Foundry 上都能用。如果要约束的是工具调用参数而不是回复本身,用的是另一个开关:在工具定义上加 strict: true。

这项功能保证输出能按 schema 解析,不保证每次都能用。2026 年 10 月 30 日经老张API 用 claude-haiku-5-5 测试:schema 里写 minimum 直接返回 400;写 minLength: 12 却被接受,要求返回 "ABC" 时照样给了 3 个字符;max_tokens 不够时返回 200 和半截 JSON;旧教程里的顶层 output_format 也返回 200,但 schema 被悄悄丢掉,回的是普通文字。所以代码里至少要做三件事:先看 stop_reason,再用 Pydantic 或 Zod 在本地校验长度、范围这类约束,换了网关或平台后确认输入 token 确实变多了。

最小可用请求:把 JSON Schema 放进 output_config.format

直连 Anthropic 官方 API 时,下面这个请求就能拿到结构化结果。schema 里每个 object 都写上 additionalProperties: false,需要返回的字段全部放进 required,这两处的原因在后面"哪些写法会 400"和"拿到 200 之后"两节里。

bash
curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-haiku-5-5",
    "max_tokens": 1024,
    "messages": [
      {
        "role": "user",
        "content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
      }
    ],
    "output_config": {
      "format": {
        "type": "json_schema",
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "email": {"type": "string"},
            "plan_interest": {"type": "string"},
            "demo_requested": {"type": "boolean"}
          },
          "required": ["name", "email", "plan_interest", "demo_requested"],
          "additionalProperties": false
        }
      }
    }
  }'

2026 年 10 月 30 日,这个请求原样发到老张API(laozhang.ai)的 Anthropic 兼容端点 https://api.laozhang.ai/v1/messages,只把地址换掉、x-api-key 换成老张API的密钥,返回 HTTP 200,stop_reason 是 end_turn,耗时 3.61 秒。JSON 在 content[0].text 里,是一个字符串,需要你自己解析一次:

说明:laozhang.ai 是我们自己的服务,其价格是平台报价,不是独立比较的结论。

json
{
  "content": [
    {
      "type": "text",
      "text": "{\"demo_requested\": true, \"email\": \"john@example.com\", \"name\": \"John Smith\", \"plan_interest\": \"Enterprise\"}"
    }
  ],
  "stop_reason": "end_turn",
  "usage": { "input_tokens": 333, "output_tokens": 46 }
}

下文所有"经老张API"的结果都来自这条通道,直连 api.anthropic.com 的行为没有对照。老张API的文档说明,部分 Claude 请求会经过 AWS 官方通道或 Bedrock 相关通道,这些通道对请求体的校验规则可能和 Anthropic 自己的 API 不一样,所以网关上的测试结果只代表网关,和官方文档冲突的地方两边都会写出来。

Python 用 messages.parse,TypeScript 用 zodOutputFormat

用官方 SDK 时不必手写 JSON Schema:Python 把 Pydantic 模型传给 client.messages.parse(),TypeScript 把 Zod schema 包进 zodOutputFormat(),SDK 会转换 schema、发出 output_config.format、校验返回值,结果放在 parsed_output 里。下面的 Python 代码用 anthropic 2.3.0 和 pydantic 2.14 跑过,Lead 故意带了几个 API 不支持的约束:

python
from typing import Literal, Optional

import anthropic
from pydantic import BaseModel, Field


class Lead(BaseModel):
    name: str = Field(min_length=2)
    email: str
    plan: Literal["Starter", "Team", "Enterprise"]
    seats: int = Field(ge=1, le=500)
    phone: Optional[str] = None


client = anthropic.Anthropic()  # 走网关时加 base_url="https://api.laozhang.ai"

resp = client.messages.parse(
    model="claude-haiku-5-5",
    max_tokens=512,
    messages=[{"role": "user", "content": "John Smith (john@example.com) wants the Enterprise plan for 40 seats."}],
    output_format=Lead,
)
print(resp.parsed_output)
# name='John Smith' email='john@example.com' plan='Enterprise' seats=40 phone=None

这里的 output_format 是 parse() 方法的参数名,SDK 实际发出去的是 output_config.format;已经弃用的是请求体顶层的同名字段,两者不是一回事。

SDK 发出去之前改了什么,可以用 anthropic.transform_schema(Lead) 在本地看,它只转换、不发请求。2026 年 10 月 30 日的输出里,min_length=2 从约束变成了字段描述 {minLength: 2},ge=1, le=500 变成描述 {maximum: 500, minimum: 1},Optional[str] 变成 anyOf 字符串或 null 且不在 required 里,每个 object 补上了 additionalProperties: false。也就是说,Claude 收到的只是描述里的一句提示,真正执行约束的是你本地的 Pydantic。

同一天经老张API发了一条会违反约束的请求,内容写明对方"现在 0 个席位,请照原样报告"。API 正常返回 seats: 0,messages.parse() 随即抛出 pydantic 的 ValidationError:"seats Input should be greater than or equal to 1"。所以 parse() 要包在 try/except ValidationError 里,违反约束时决定重问、转人工还是丢弃。

TypeScript 的写法来自官方文档,parse() 同样会校验并返回带类型的 parsed_output:

typescript
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";

const ContactInfo = z.object({
  name: z.string(),
  email: z.string(),
  plan_interest: z.string(),
  demo_requested: z.boolean(),
});

const client = new Anthropic();

const response = await client.messages.parse({
  model: "claude-haiku-5-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Extract the key information from this email: ..." }],
  output_config: { format: zodOutputFormat(ContactInfo) },
});

console.log(response.parsed_output);

如果你手里是现成的 JSON Schema 而不是 Zod,jsonSchemaOutputFormat() 只做转换、不校验返回值,校验要自己补。Java SDK 的做法不同:遇到不支持的约束直接在本地报错,而不是挪进描述。

JSON 输出还是 strict 工具:看结构化数据交给谁

判断标准是这份结构化数据最后交给谁。要把 Claude 的回答本身存库、渲染或交给下游程序,用 JSON 输出(output_config.format);要 Claude 调用你的函数、参数直接进入业务逻辑,用 strict 工具(strict: true);Agent 既要可靠地调工具、最后又要一份结构化结论时,两者可以放在同一个请求里。Claude 没有 OpenAI 那种单独的"JSON mode"开关,早年用强制工具调用模拟 JSON 的做法,现在由 JSON 输出直接替代。

对照项JSON 输出strict 工具
约束的对象Claude 的回复正文工具名和工具的 input
写在哪里请求体的 output_config.format每个工具定义顶层的 "strict": true
结果在响应的位置text 块里的 JSON 字符串,要自己解析tool_use 块的 input,已经是对象
正常的 stop_reasonend_turntool_use
适合的任务信息抽取、分类、生成报告预约、写库、下单这类函数调用
数量限制计入同一请求的 schema 复杂度每个请求最多 20 个 strict 工具

两者共用同一套 JSON Schema 限制,下一节的 400 和"不生效"对它们都适用。Anthropic 的 strict 工具文档举的例子是:订票函数要求 passengers 是整数,不开 strict 时 Claude 可能给出 "two" 或 "2",开了以后总是 2。

2026 年 10 月 30 日经老张API,用同一封邮件测了 strict 工具:工具 create_demo_booking 带 "strict": true,plan 和 weekday 用 enum,email 用 format: email,hour_24 是整数,并用 tool_choice 强制调用。返回 stop_reason: "tool_use",input 是 {"email": "john@example.com", "hour_24": 14, "plan": "Enterprise", "weekday": "Tue"},"下午 2 点"被换成了整数 14,"下周二"落在了 enum 里的 Tue。

json
{
  "name": "create_demo_booking",
  "description": "Book a product demo for a lead.",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "email": {"type": "string", "format": "email"},
      "plan": {"type": "string", "enum": ["Starter", "Team", "Enterprise"]},
      "weekday": {"type": "string", "enum": ["Mon", "Tue", "Wed", "Thu", "Fri"]},
      "hour_24": {"type": "integer"}
    },
    "required": ["email", "plan", "weekday", "hour_24"],
    "additionalProperties": false
  }
}

两者一起用时,JSON 输出的约束只作用于 Claude 最后的回复,不管工具调用、工具结果和思考内容;工具参数由 strict 负责。OpenAI 那边的 text.format 和函数工具怎么选,见Structured Outputs 与 Function Calling:怎么选、怎么验收。

哪些 schema 写法会 400,哪些被接受却不生效

能不能用,不能只看有没有报错。官方文档说,用了不支持的关键字会收到带细节的 400;但 2026 年 10 月 30 日经老张API测试,minimum 和递归 $ref 确实返回 400,minLength、maxLength 和省略 additionalProperties 却都返回 200,其中长度约束完全没有执行。下表左边是官方文档的说法,右边是网关上的实际结果,模型都是 claude-haiku-5-5。

schema 写法官方文档经老张API(2026-10-30)你的代码该怎么做
enum、format: email、anyOf 加 null支持200,结果符合 schema直接用
省略 additionalProperties必须写 false200,输出仍是 4 个字段显式写上 false
整数上写 minimum不支持,返回 400400从 schema 删掉,在本地校验
字符串上写 minLength、maxLength不支持,返回 400200,但要求 12 位时返回了 "ABC"必须在本地校验
递归 $ref(节点引用自己)不支持400按最大层数展开成固定结构

两条 400 的原文如下,type 都是 invalid_request_error。看到 output_config.format.schema: 开头的报错,就去 schema 里找它点名的关键字:

output_config.format.schema: For 'integer' type, property 'minimum' is not supported

output_config.format.schema: Invalid schema: Circular reference detected in schema definitions: node -> node. Self-referencing or mutually-referencing definitions are not supported.

minLength 那一行最危险。schema 写着 "minLength": 12, "maxLength": 12,提示词让 Claude 返回代码 ABC,返回的就是 {"code": "ABC"},没有报错,也没有特殊的 stop_reason。官方文档说这种写法会被 400 拒绝,网关上却被接受了,直连官方 API 是哪种结果没有对照;无论哪种,长度和数值范围都要在你自己的代码里检查。用 Python 的 messages.parse() 或 TypeScript 的 zodOutputFormat() 时,SDK 会把这些约束挪进描述并在本地校验,正好补上这一层。

表里没列的几类,官方文档同样列为不支持:maximum 和 multipleOf,enum 里放对象或数组,外部 URL 形式的 $ref,minItems 取 0 和 1 以外的值以及其他数组约束。字符串 format 只支持 date-time、time、date、duration、email、hostname、uri、ipv4、ipv6、uuid 这十种。pattern 正则支持锚点、* + ?、简单的 {n,m}、字符类和分组,不支持反向引用、前后断言和 \b,复杂的正则也可能触发 400。

schema 单个写法都合规,合在一起也可能太复杂。官方文档给的上限按一次请求里所有 strict schema 合计:strict 工具最多 20 个,可选参数(不在 required 里的字段)最多 24 个,用 anyOf 或 ["string", "null"] 这类联合类型的参数最多 16 个;超过内部的文法规模会返回 400 "Schema is too complex for compilation",编译超过 180 秒也会失败。常见的处理是把可选字段改成必填,但如果改成"必填 + 可为 null",用的就是 anyOf,会从可选参数的 24 个额度转到联合类型的 16 个额度,字段多时要两边一起算。

返回普通文字或报 Extra inputs are not permitted:先看调用通道

请求成功却拿回一段普通文字,而且 usage.input_tokens 和不带 schema 时一样,说明格式参数根本没到模型那里,最常见的原因是代码还在用 beta 时期的写法:请求体顶层的 output_format,有时还带着 anthropic-beta: structured-outputs-2025-11-13。按官方迁移说明,这个字段已经弃用,直连官方 API 时不带 beta 头会返回 400,Python SDK 1.0 及以上在 client.beta.messages.create() 里传 output_format={...} 会直接抛 TypeError。

经过网关时可能连报错都没有。2026 年 10 月 30 日经老张API,用顶层 output_format 发了两次,一次不带 beta 头、一次带,都返回 HTTP 200,回复是一段 Markdown 列表,输入 64 个 token,和完全不带 schema 的请求一模一样;换成 output_config.format 后,同一请求是 333 个 token 加合法 JSON。网关丢掉不认识的字段、照常转发,你的程序只会在 json.loads 那一步才发现问题。

同一请求两种写法对照:output_config.format 返回 200、输入 333 token、text 为 JSON;顶层 output_format 返回 200、输入 64 token、text 为 Markdown 列表

另一种情况是直接报 400 Extra inputs are not permitted,完整报错常见的样子是 "Api error 400 output_config effort extra inputs are not permitted"。这类报错来自对请求体做严格校验的通道:老张API的文档写明,走 AWS 官方通道或 Bedrock 相关通道时,通道不接受的字段会返回 400 ValidationException 或 Extra inputs are not permitted。同一天在老张API上用 Sonnet 5.5 同时发 output_config.effort 和 output_config.format,返回的是 200,所以会不会报这个错,取决于你的请求最后落在哪条通道。Bedrock 上其他 400 报错原文的对照,见AWS Claude 报 400 怎么办:按报错原文逐条排查。

换平台、换网关或升级 SDK 之后,按这几项确认格式参数真的生效了:

  • 请求体里是 output_config.format,不是顶层的 output_format,也不需要任何 beta 请求头。
  • 模型 ID 在官方支持列表里;Claude 3.x 和 Opus 4.1 不在列表中。
  • 对同一个提示词,带 schema 的 usage.input_tokens 明显高于不带 schema,4 个字段的 schema 大约高 269 个。
  • text 块能被 json.loads 解析,而不是 Markdown 或自然语言。
  • 报 Extra inputs are not permitted 时,逐个去掉 output_config.effort、beta 请求头这类附加字段,找出通道不接受的那一个。
  • 请求里没有同时开启 Citations,两者一起用时官方 API 返回 400。

拿到 200 之后还要检查:拒答、截断、null、键顺序和 enum

HTTP 200 加上结构化输出,仍然有五种情况会让你拿到不能直接用的数据,官方文档点名了其中三种,另外两种是 2026 年 10 月 30 日经老张API测出来的。逐项写进代码比事后排查便宜得多。

  • stop_reason 是 refusal:Claude 出于安全原因拒答,状态码仍是 200,生成的 token 照样计费,内容可能不符合 schema。官方文档还提醒,schema 里放一个"逐步推理过程"字段可能触发 reasoning_extraction 拒答,要解释就改成"简短说明"。
  • stop_reason 是 max_tokens:输出被截断。经老张API把 max_tokens 设成 30、让 Claude 列 15 个选题,返回 200,text 是 {"ideas": ["REST vs GraphQL vs gRPC: How to Choose the,没法解析。调高 max_tokens 重试;走老张API时单次输出上限是 32,768 tokens。
  • 可选字段返回 null:schema 里 phone 是普通字符串、不在 required 里,邮件中也没有电话,经老张API返回的是 "phone": null,而不是省略这个键。可选字段要按"可能缺失,也可能是 null"处理,Pydantic 里写成 Optional[str] = None。
  • 键顺序和 schema 不同:官方文档说输出按 schema 顺序、必填字段排在前面;经老张API的每一次 JSON 输出都是按字母排序(demo_requested、email、name……)。任何时候都按键名取值,不要按位置。
  • enum 大小写不一致:官方文档说 enum 和 const 的大小写不受保证,"Conversation topic 3" 可能回成 "Conversation Topic 3",响应正常结束、没有报错。经老张API连续 5 次都原样返回,但比较时仍然要忽略大小写,也不要设计只差大小写的 enum 值。

HTTP 200 之后的处理流程:先按 stop_reason 区分 refusal、max_tokens 和 end_turn,再用本地校验区分 ValidationError 与通过

这些检查可以收进一个函数。下面的写法用 transform_schema() 把 Pydantic 模型转成 API 接受的 schema,自己发请求,所以能在解析之前先看 stop_reason,最后再用原模型做完整校验:

python
import logging

import anthropic
from anthropic import transform_schema
from pydantic import ValidationError

log = logging.getLogger(__name__)
client = anthropic.Anthropic()
LEAD_SCHEMA = transform_schema(Lead)  # 上一节定义的 Lead


def extract_lead(text: str, max_tokens: int = 1024) -> Lead | None:
    resp = client.messages.create(
        model="claude-haiku-5-5",
        max_tokens=max_tokens,
        messages=[{"role": "user", "content": text}],
        output_config={"format": {"type": "json_schema", "schema": LEAD_SCHEMA}},
    )
    if resp.stop_reason == "refusal":
        log.warning("refused: %s", resp.id)  # 不要当 JSON 解析,记录后转人工
        return None
    if resp.stop_reason == "max_tokens":
        if max_tokens >= 8192:
            raise RuntimeError("输出太长:拆分任务或精简 schema")
        return extract_lead(text, max_tokens * 2)
    raw = next(b.text for b in resp.content if b.type == "text")
    try:
        return Lead.model_validate_json(raw)  # min_length、ge、le 在这里执行
    except ValidationError as err:
        log.warning("schema ok but constraint failed: %s", err)
        return None

预填充 { 来逼出 JSON 的老办法不能和结构化输出一起用:同一天在 Haiku 5.5 上把 assistant 消息 { 和 output_config.format 一起发出,返回 400 "This model does not support assistant message prefill. The conversation must end with a user message."。哪些模型拒收预填充、原来的用途改用什么,见Claude 预填充(prefill)400 报错:4.6 起模型全拒收,按用途替换。

结构化输出多花多少 token:4 个字段多 269 个输入 token

开启结构化输出后,Claude 会额外收到一段说明输出格式的系统提示,这段提示和你的 schema 一起按输入 token 计费。2026 年 10 月 30 日经老张API,同一句抽取请求在 Haiku 5.5 上不带 schema 时输入 64 个 token,带上前面那份 4 个字段的 schema 是 333 个,多出 269 个。按截至同日的 Anthropic 价格页标准输入价算,每 100 万次请求多出 269 × 1,000,000 = 2.69 亿个 token,也就是 269 MTok:

模型输入标价(每 MTok)每次请求多付每 100 万次请求多付
Haiku 5.5$0.10(提示不超过 10 万 token)$0.0000269$26.90
Sonnet 5.5$2$0.000538$538
Opus 5.5$4$0.001076$1,076

这组数字只对应一份 4 个字段、没有描述的 schema。字段越多、描述越长,多出来的 token 越多;同一封邮件改用 strict 工具抽取时,输入是 682 个 token,工具定义本身也要计费。输出那边反而可能变少:同一个样本里,普通文字回复是 91 个输出 token,JSON 只有 46 个。以 Sonnet 5.5 每 MTok $10 的输出价算,少的 45 个输出 token 约省 $0.00045,和多付的 $0.000538 输入费差不多抵消,但这取决于你的提示词原本会让 Claude 写多少废话,只能用自己的请求量去算。

还有两处会影响账单。修改 output_config.format 会让该对话已有的提示缓存失效,所以同一条对话里不要来回切换 schema。官方文档说结构化输出可以配合批处理接口享受 5 折,但老张API的文档写明不提供 Message Batches 和 /v1/messages/count_tokens 接口,要用批处理得直连官方或其他支持批处理的平台。Claude 和 OpenAI 按任务算的整体价格差,见Claude 与 OpenAI API 价格对比:按任务算谁更省。

Claude 结构化输出的常见问题

开启思考模式时还能用结构化输出吗?

能。官方文档说明,结构化输出的约束只作用于 Claude 最后的回复,不作用于思考内容,所以思考过程可以自由展开,最终回复仍按 schema 输出。2026 年 10 月 30 日经老张API,在 Sonnet 5.5 上同时设置 thinking: {"type": "adaptive"}、output_config.effort: "low" 和 output_config.format,返回 200,响应里只有一个 text 块(低 effort 下没有出现思考块),内容是合法 JSON。

流式输出能用结构化输出吗?

能。官方文档列明结构化输出支持流式,按普通流式响应接收即可;完整 JSON 要等流结束后再解析,中途的片段本身不是合法 JSON。

Amazon Bedrock 和 Google Cloud Vertex AI 上能用 Claude 结构化输出吗?

能。截至 2026 年 10 月 30 日,Anthropic 文档把 Claude API、Claude Platform on AWS、Amazon Bedrock(包括 InvokeModel)、Google Cloud 上的 Claude 和 Microsoft Foundry 都列为正式可用;Claude Managed Agents 不支持。不同平台对请求体的校验不一样,迁移后按前面"调用通道"一节的清单确认一次。

Claude Code 和 Agent SDK 的 JSON 输出也是 output_config 吗?

不是。Agent SDK 用的是另一套机制:给 query() 传 outputFormat(TypeScript)或 output_format(Python),SDK 校验结果、不符合时让 Claude 重写,结果放在 structured_output 里,具体见 Claude Agent SDK 结构化输出文档。

第一次用一个新的 schema 会更慢吗?

官方文档说会:每个 schema 第一次使用时要编译成文法,之后缓存 24 小时(从最后一次使用算起),改了 schema 的结构或工具集合才会重新编译,只改 name 和 description 不会。2026 年 10 月 30 日经老张API发了一份从没用过的 schema,再原样重复两次,耗时分别是 4.23、3.33 和 4.28 秒,经网关时这点差异被网络波动盖住了,看不出来。

零数据保留(ZDR)和 HIPAA 场景下,schema 会被保存吗?

会短期保存 schema,但不保存内容。按 Anthropic 文档,开启零数据保留时提示和输出都不存储,只有 JSON schema 会被缓存,最长 24 小时(从最后一次使用算起)。结构化输出符合 HIPAA 要求,但受保护的健康信息不能出现在 schema 的属性名、enum 和 const 值或 pattern 正则里,只能放在消息内容中。

参考来源4

本文引用的外部页面,按正文出现顺序排列。最后更新于 2026年10月30日。

  1. 1.Anthropic 结构化输出文档platform.claude.com/docs/en/build-with-claude/structured-outputs
  2. 2.Anthropic 的 strict 工具文档platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use
  3. 3.Anthropic 价格页platform.claude.com/docs/en/about-claude/pricing
  4. 4.Claude Agent SDK 结构化输出文档code.claude.com/docs/en/agent-sdk/structured-outputs
结构化最终回答与应用执行工具的两条路径及结果校验示意
ChatGPT 与 OpenAI

Structured Outputs 与 Function Calling:怎么选、怎么验收

只整理已有信息,选 Structured Outputs;需要应用查数据或执行操作,选 Function Calling;查完还要交给 UI 固定对象,再组合两者。严格 schema 管住结构,业务值和实际结果仍由应用验收。

15 分钟