Claude Code CLI 2.1.235 Structured Output:--json-schema 怎样变成一次可验证的 Agent Loop 终态¶
版本:Claude Code CLI
2.1.235| 证据:Static | 范围:主线程
60 秒模型¶
读者问题: 为什么传了 --json-schema 以后,Claude Code 不是等模型输出一段 JSON 再 JSON.parse,而是多出一个 StructuredOutput 工具、可能继续一轮,甚至以 error_max_structured_output_retries 结束?
一句话模型: 客户端把调用方的 JSON Schema 编译成本地验证器并包装成一个强制收尾工具;模型只有用符合 schema 的参数调用该工具,客户端才会保存 structured_output attachment、结束当前 Agent turn,并把最后一个仍存活的对象放进最终 result。
贯穿场景:调用方要求 Claude 分析一次构建失败,并必须返回:
{
"type": "object",
"properties": {
"cause": { "type": "string" },
"fixes": { "type": "array", "items": { "type": "string" } }
},
"required": ["cause", "fixes"],
"additionalProperties": false
}
模型可以先读文件、运行测试和修正判断;但准备结束时不能只写一段看起来像 JSON 的文字,而要调用 StructuredOutput({cause, fixes})。客户端对这份参数做 AJV 校验,成功后把对象保存为结构化附件并让工具 endsTurn: true。
图的结论:schema 不直接替代 Agent Loop。它新增了一条“必须通过本地验证的结束通道”;普通推理和工具执行仍在前面发生。
| Object | Before | Transformation | After | User-visible effect |
|---|---|---|---|---|
| CLI 参数 | JSON 字符串 | 解析并要求 schema 文档本身是 object | 内存中的 schema object | 非法 JSON 或数组在启动阶段直接失败 |
| schema 合同 | 调用方原始 JSON Schema | AJV 编译;另尝试生成受限 strict schema | 本地 validator + 可选 wire strict schema | 本地始终校验;服务端 strict 取决于 model/provider/gate |
| tools surface | 普通工具集合 | 追加 StructuredOutput |
当前请求可见的收尾工具 | 工具 schema 占用上下文,模型被告知结尾必须调用一次 |
| model 结果 | 文本、普通工具调用或收尾调用 | 对 StructuredOutput.input 做 schema 校验 |
attachment 或 tool error | 不合格参数进入修正轮次,不会冒充有效对象 |
| persisted result | 尚无结构化终态 | 保存 attachment,追踪 tombstone | 最后一个仍存活的 structured_output |
JSON/stream-json result 直接带对象;fallback 撤回的对象不会泄漏到终态 |
状态所有权¶
| 状态 | Owner | 生命周期 | 为什么不能混为一谈 |
|---|---|---|---|
原始 jsonSchema |
CLI 参数解析器或 SDK initialize | 当前进程/session 配置 | 它只是调用方意图,还没有证明 schema 合法 |
| AJV validator | z2r / Xzb 的 WeakMap cache |
schema object 身份存活期间复用 | 它验证最终 tool input,不是模型生成质量评分器 |
strictInputJSONSchema |
strict 转换器 jZo |
随生成的工具对象存在 | 只是一份可选的 wire 优化;生成失败会回落原 schema |
requiresStructuredOutput |
SDK/main Agent Loop options | 当前 query | 只有 schema 存在且工具仍在实际 tools 集合中才为 true |
| 当前 turn 尝试数 | SDK result assembler | 当前用户 turn | 排除历史调用,计入本轮普通调用和被 fallback tombstone 的调用 |
structured_output attachment |
transcript/message graph | 可持久化,并受 tombstone/eviction 管理 | 它是最终结果的权威来源,普通 assistant 文本不是 |
最终 result.structured_output |
stdout/SDK result envelope | turn terminal frame | 取最后一个仍存活 attachment;没有对象时不能靠文本补造 |
关键区别是:schema、strict schema、tool schema、tool call、attachment、terminal result 是六个不同状态。 把它们缩成“开启 JSON 模式”会丢掉失败位置,也无法解释为什么 fallback 后已经出现过的对象最终消失。
完整调用顺序¶
- 解析 JSON Schema。
--json-schema <schema>只在 non-interactive / background 路径进入 structured-output 初始化。客户端先JSON.parse;语法错误返回--json-schema is not valid JSON,解析值若不是非数组 object,则返回--json-schema must be a JSON object。 - 编译本地 validator。
z2r(schema)以 schema object 为 WeakMap key 复用Xzb结果。Xzb先执行大小/深度保护,再以 AJVallErrors:true, validateFormats:false校验 schema 自身并编译验证函数。这里关闭 format 校验意味着format: email一类约束即使能出现在原 schema,也不会由这份 AJV validator 执行格式语义;而 strict 转换器本身又不接受formatkeyword。 - 尝试派生 strict schema。
jZo只接受能规范化为 root object 的受限子集。成功时工具同时保存inputJSONSchema和strictInputJSONSchema;失败不会关闭 structured output,而是记录converted/fallback遥测并继续使用原 schema 的本地 AJV validator。 - 把 schema 包装成工具。 客户端基于内置
StructuredOutputobject 创建本次专用工具,替换其输入 schema 和call。工具声明为 read-only、concurrency-safe、permission always allow,prompt 明确要求“在响应结尾恰好调用一次”。它不写业务文件,也不跳过之前的 Agent Loop。 - 确认工具真的进入当前 tools surface。 CLI 将专用工具追加到已加载工具集合;SDK query 只有在
jsonSchema存在且刷新后的工具集合仍包含StructuredOutput时,才设置requiresStructuredOutput:true。这避免只有 schema 配置、没有收尾工具时仍声称强制成功。 - 序列化 wire schema。 普通路径发送原
input_schema。当模型支持相应 tool schema、tengu_structured_output_strictgate 开启且 strict 转换成功时,wire tool 可带strict:true和规范化 schema。Foundry 若以 400 表明 workspace 不支持structured_outputs,客户端会记住该 deployment 的 capability 缺口,后续重试移除strict,但保留本地验证路径。 - 模型执行普通 Agent Loop。 模型仍可调用 Read、Bash、Grep 等工具并吸收结果。若它准备结束却还没调用
StructuredOutput,Stop 阶段注入一次带[structured-output-enforce]sentinel 的 meta user message:You MUST call the StructuredOutput tool ... Call this tool now.,从而让 Agent Loop继续,而不是接受普通文字作为结构化终态。 - 校验
tool_use.input。 专用call(input)用已编译 AJV validator 校验参数。失败抛出Output does not match required schema,并保留具体 instance path、message 和 keyword 供工具错误链反馈;成功返回{structured_output: input, endsTurn: true}。 - 形成 attachment 并结束。 工具结果映射层把
structured_output转成带toolUseID的 attachment。SDK result assembler保存该 attachment;因为endsTurn:true,Agent Loop可以结束当前 turn,而不需要模型再把对象复制成自然语言。 - 处理 fallback 回撤。 若模型 fallback tombstone 覆盖了一条含
StructuredOutput的 assistant message,客户端记录对应 tool-use ID、删除与之关联的 attachment,并把该次调用计入 retracted attempt。晚到的同 ID attachment也会直接丢弃。 - 发布 terminal result。 成功 result 的
structured_output取tr.at(-1)?.data,即最后一个仍存活对象;当普通result文本为空时,客户端还会把该对象JSON.stringify成 text result,兼容只消费result字符串的调用方。
为什么这不是“最后 JSON.parse 一下”¶
如果只在最终文字上做 JSON.parse,客户端无法可靠完成四件事:
- 在模型准备停止时强制它改走结构化通道;
- 把 schema 错误作为带
tool_use_id的可修正反馈送回下一轮; - 在 model fallback 撤回旧 assistant message 时同步撤回旧对象;
- 用
endsTurn把“通过验证的对象”定义成 Agent Loop 的结束条件。
代价是它至少增加一个工具 schema 和一次工具调用,并可能触发额外模型轮次;收益是终态与消息图、fallback 和工具错误协议保持一致。
Gate、优先级与阈值¶
入口与优先级¶
--json-schema是--print/ non-interactive structured result 入口;CLI 先于普通 query 验证 JSON 文本和 schema。- SDK initialize 也可传
jsonSchema。动态 bridge 若收到 init schema,会在当前工具集合没有固定 CLI schema 时尝试添加;失败只记录一次并禁用该动态 structured-output tool。 requiresStructuredOutput不是只看配置字段,而是jsonSchema !== undefined && tools contains StructuredOutput。- strict wire schema 低于本地 AJV 合同:strict 不可生成、gate 关闭、model 不支持或 provider stripping 时,本地 AJV 仍是成功必要条件。
- Foundry capability 记忆优先于之后的 wire tool serialization;一旦 deployment 已被识别为不支持
structured_outputs,strict会被剥离。
schema 限制¶
| 限制 | 2.1.235 值 |
作用 |
|---|---|---|
| CLI schema 文档 | 必须是 JSON object,不能是 null 或 array |
拦截无效 CLI 输入 |
| AJV options | allErrors:true、validateFormats:false |
一次返回多个结构错误;不执行 format validator |
| 预检查 node budget | 100,000 |
防止超大 schema 遍历/编译 |
| 预检查 recursion depth | 10,000 |
防止极端嵌套耗尽调用栈/CPU |
| strict conversion depth | 32 |
超过后回落 non-strict,不使整个功能失败 |
| strict conversion nodes | 100,000 |
限制规范化工作量 |
| strict keywords | $schema,type,description,title,properties,required,additionalProperties,items,enum,const,anyOf |
其他 keyword 触发 strict fallback |
| object properties | 必须存在且为 object | strict root/object 子节点不能是空声明式 object |
additionalProperties |
缺省或 false;输出统一写成 false |
strict schema 不允许开放字段 |
required |
唯一字符串,且必须指向真实 property | 避免不可满足/悬空 required |
array items |
必须是单个 schema,不能是 tuple array | tuple schema 触发 fallback |
enum |
非空、值唯一、仅 primitive/null | 复杂 enum 触发 fallback |
| 默认尝试数 | 5 |
本轮达到上限且没有存活对象时输出专用错误 |
| override | MAX_STRUCTURED_OUTPUT_RETRIES integer |
内部环境覆盖;该字段在这里没有额外 min 约束 |
| tool result display cap | 100,000 chars |
限制工具结果呈现规模,不等于 schema node budget |
anyOf 还有一条容易漏掉的约束:strict 转换时,包含 anyOf 的节点只能再带 $schema、description、title;不能把 type、properties 等并列进去。原 schema仍可能通过 AJV,但 strict 派生会记录 fallback。
尝试数怎样计算¶
SDK result assembler在 query 开始时记录历史 StructuredOutput 调用基线 Ft。之后计算:
current attempts
= current message graph 中的 StructuredOutput 调用数
+ 本轮被 tombstone 的 StructuredOutput 调用数
- query 开始前的历史调用数
因此 resume 进来的历史结构化调用不会消耗当前 turn 的 5 次额度;fallback 撤掉的调用却仍消耗,因为那次模型/API工作已经真实发生。这个定义比简单数 attachment 更准确:无效输入不会形成 attachment,但仍是一轮失败尝试。
失败与恢复¶
| Failure | Detection | Retry/change | State retained | Final user effect |
|---|---|---|---|---|
--json-schema 不是合法 JSON |
CLI JSON.parse 抛错 |
不启动 Agent Loop | 无 session query side effect | 立即返回 not valid JSON |
| JSON 值不是 object | CLI type guard | 不启动 Agent Loop | 无模型调用 | 立即返回 must be a JSON object |
| schema 自身无效或过大 | size guard / AJV validateSchema |
不创建专用工具 | 已完成的启动加载可能存在;尚未执行用户 query | CLI 返回 not a valid JSON Schema |
| strict 子集转换失败 | jZo 返回 reason 或抛错 |
回落原 schema,本地 AJV 继续 | 原 schema、compiled validator | 用户通常无可见错误;wire 可能没有 strict:true |
| provider 不支持 strict structured tool | Foundry 400 capability classifier | 记忆 capability,重试时剥离 strict |
messages、原 tool、本地 validator | 多一次 API attempt/延迟,仍可走本地验证 |
| 模型只写文字不调用工具 | Stop 阶段发现本 turn 无调用 | 注入一次 enforce meta message并继续 | 原推理、普通工具结果 | 多一轮 token/延迟;调用方不会把普通文字误认成对象 |
| tool input 不匹配 schema | AJV validator false | 标准 tool error 回灌,让模型修正 | transcript、已完成的普通工具副作用 | 可在剩余额度内重试;错误含 instance path/keyword |
| fallback 撤回已验证调用 | tombstone扫描 assistant tool_use ID | 删除关联 attachment,晚到结果也丢弃 | retracted attempt counter、其他存活消息 | 旧对象不进入最终 result;模型必须再产出 |
| 失败尝试达到上限且无存活对象 | 当前 turn attempts >= 5,可被 env 覆盖 |
不再继续 Agent Loop | transcript 与已发生外部副作用保留 | error_max_structured_output_retries |
| 只有被 fallback 撤回的调用,loop 最终结束 | Ct > 0 && tr.length === 0 |
终态兜底拒绝成功 | tombstone后的消息图 | 同一专用错误,说明结果被 retracted |
| Agent Loop abort / budget / max turns | 各自更高层 terminal reason | 按对应终态结束 | 已持久化消息、已完成工具副作用 | 不保证拿到 structured output |
两类“重试”不能混为一谈¶
- schema 修正轮次:模型已经调用
StructuredOutput,但参数不匹配。工具错误成为下一条 user/tool result,模型再调用一次;每次都消耗模型输出、一次 tool call 和当前 turn attempt。 - provider capability 重试:请求在模型生成前就因 Foundry capability 400 失败。客户端剥离 wire
strict后重发;它增加 API attempt,但不是一次模型产出的 structured-output attempt。
外部副作用不会回滚¶
Structured Output 通常出现在工作尾声。此前 Bash、Edit、MCP、网络请求或部署可能已经成功。schema 失败、尝试耗尽或 fallback tombstone只能改变 transcript和终态对象,不能撤销那些动作。调用方若把“拿到结构化报告”和“业务操作成功”绑定成原子事务,必须自行提供幂等键、checkpoint 或补偿操作。
用户影响:token、成本、隐私与副作用¶
Token 与缓存¶
- 专用工具的 name、description 和完整 input schema进入 tools surface;schema 越大,输入 token越多。
- tool schema cache key 包含序列化后的
inputJSONSchema。同一 schema object/内容可复用序列化结果;改变字段、描述或 required 会改变缓存身份。 - 每次失败修正都增加 assistant tool call、tool error user message和下一次模型请求。默认 5 次是终止上限,不是免费重试额度。
- strict 转换成功不代表一定减少 token;它主要把更多约束放到 wire schema和服务端解码,客户端仍保存原 schema与本地 validator。
延迟与成本¶
普通成功路径至少多一次收尾 tool call,但不必再追加一轮自然语言复制。模型忽略工具、schema mismatch、provider capability strip或 fallback都会增加 API attempt或 Agent iteration。最终 num_turns、usage、modelUsage和 total_cost_usd 仍由标准 result envelope报告。
隐私¶
schema 会进入模型请求的 tool definition,因此 property name、description、enum 和业务术语都会发送给所选 provider。成功对象会进入本地 transcript attachment和 stdout/SDK result;其中若含密钥、个人数据或内部路径,它们会同时出现在持久会话和调用方日志。structured_output 是格式保障,不是脱敏器。
安全与副作用¶
StructuredOutput 自身声明 read-only、permission always allow、closed-world;其直接 side effect 是本地消息/结果状态,而不是文件或网络写入。但它不会限制前置工具,也不会验证业务真实性。例如 { "testsPassed": true } 通过 boolean schema,只证明类型正确,不证明测试真的运行或成功。
调用方消费建议¶
- 以 result subtype 和
structured_output字段为权威,不要只解析result字符串。 - 单独处理
error_max_structured_output_retries、error_max_turns、budget和 execution error。 - schema description避免放秘密;输出对象在进入日志前另做脱敏。
- 需要业务级正确性时,在 schema验证之外增加枚举约束、交叉字段验证和外部事实检查。
证据索引¶
| 结论 | 2.1.235 readable source |
证据等级 |
|---|---|---|
| strict schema 规范化规则、32 depth、100k nodes | reverse/javascript/cli.readable.js:155369-155447 |
Static / consumer |
| schema size guard、AJV options、strict fallback、专用 call | reverse/javascript/cli.readable.js:155464-155493 |
Static / runtime |
StructuredOutput 工具属性、prompt、permission、result mapping |
reverse/javascript/cli.readable.js:155494-155532 |
Static / runtime |
| Stop 阶段强制收尾 reminder | reverse/javascript/cli.readable.js:270482-270507、270595-270596 |
Static / runtime |
| 默认 5 次与 terminal error text | reverse/javascript/cli.readable.js:287100-287103 |
Static / constant |
| tool result 转 attachment | reverse/javascript/cli.readable.js:316371 |
Static / consumer |
| wire strict gate 与 Foundry capability stripping | reverse/javascript/cli.readable.js:154389-154449、408317-408340 |
Static / runtime |
| CLI JSON/object guard与工具注入 | reverse/javascript/cli.readable.js:592685-592697、592760-592774 |
Static / runtime |
query-level requiresStructuredOutput gate |
reverse/javascript/cli.readable.js:597607-597614 |
Static / consumer |
| tombstone删除、晚到丢弃、attempt计数、终态 | reverse/javascript/cli.readable.js:597686-597701、597754-597760、597826-597884 |
Static / runtime |
| hidden CLI option与 SDK initialize schema | reverse/javascript/cli.readable.js:603757-603768、595960-595971 |
Static / surface + consumer |
对应结构化机制 claim 将记录为 structured-output-*,由 analysis/mechanism-evidence.jsonl 对源码 anchor和证据类型做机器校验。现有总协议章节 CLI、SDK 与输出协议 继续负责 stdout subtype、control frame和 session terminal envelope;本文负责 schema到终态对象的完整状态机。
Boundary¶
- 本文证明的是
2.1.235发布客户端的静态可达路径,没有伪装成一次真实 Anthropic 服务端 structured-output 成功 Probe。 strict:true在服务端怎样约束 token生成、不同 provider如何实现 constrained decoding,不存在于客户端发布物中。- 当前公开 API 的
output_config.format是另一条 API原生 structured-output surface。主 CLI--json-schema在这里采用StructuredOutput工具收尾;不能把 side query 的output_config.format行为静默回填成主 Agent Loop实现。 - AJV
validateFormats:false和 strict keyword子集只能说明客户端校验合同;调用方业务规则、事实正确性、数据库一致性和跨字段语义仍需额外验证。 - 远程 feature flag、provider deployment capability和模型实际遵循率是运行时变量。本文只记录客户端 gate、fallback和 fail terminal,不把某个账号必然获得 strict wire path写成静态事实。