上周三我在用 Claude Code 跑一个自动化重构任务模型切到claude-opus-5.5之后工具调用的结果突然全变成空的了。stop_reason 明明是tool_usecontent 数组里也有type: tool_use的块但input字段要么是{}要么直接被客户端解析逻辑静默丢弃。折腾了大半天才搞清楚——不是 MCP 配置错了也不是网络问题是input_schema定义如果缺了type字段或者required不完整模型会返回空的input对象而不是报错。修复方法很简单把每个 tool 的input_schema补全为符合 Anthropic 要求的 JSON Schema 格式就行。下面是完整的排查过程和修复代码。先看完整的调用链路搞清楚问题出在哪一环之前得先理解 Claude Code 工具调用到底是怎么跑的sequenceDiagram participant U as 你的代码/Claude Code participant A as Anthropic API (claude-opus-5.5) participant T as 本地工具(bash/文件读写等) U-A: messages.create(tools[...], messages[...]) A-U: stop_reasontool_use, content[{type:tool_use, input:{...}}] U-T: 解析 input执行工具 T-U: 返回执行结果 U-A: messages.create(messages[..., {role:user, tool_result}]) A-U: 最终回复关键在第二步API 返回的tool_use块里input字段存放着模型希望传给工具的参数。如果这个input是空对象{}你的工具执行逻辑拿不到参数自然就返回空了。为什么会出现这个问题一句话claude-opus-5.5比之前的模型对input_schema的格式要求更严格了。之前用claude-opus-4.7甚至claude-sonnet-5的时候input_schema写得糙一点也能跑。比如这样# 有问题的写法 tool_schema { properties: {location: {type: string}}, required: [location] }注意到没少了最外层的type: object。在旧模型上这居然能正常返回参数。但换到claude-opus-5.5之后模型遇到不合规的 schema 会有两种表现取决于问题的严重程度schema 格式不够严格如缺少type: object不直接报 400而是返回空的input: {}——它识别出你定义了工具也决定调用这个工具但因为 schema 不合规生成的参数被内部校验拦截最终给你一个空对象。schema 存在明显错误如整个input_schema字段都缺了typeAPI 直接返回 400错误信息如下Error: 400 Bad Request {type:error,error:{type:invalid_request_error, message:tools.0.input_schema: JSON Schema must have a type field}}第一种情况特别阴间因为它不报错。stop_reason还是tool_useHTTP 状态码还是 200你不仔细看input的内容根本发现不了。我那天遇到的情况是5 个工具里有 3 个的 schema 写法不够严格其中 2 个静默返回空 input1 个直接 400。排查起来挺烦人的因为表现不一致。方案一补全 input_schema 为符合 Anthropic 要求的 JSON Schema 格式根本解决方法。每个 tool 的input_schema必须包含三个东西最外层type: objectproperties里每个参数都有明确的typerequired数组列出所有必填参数修复前会触发空 input# 缺少 type 和 required 的写法 bad_schema { properties: {command: {type: string}} }修复后# 补全后的正确写法 good_schema { type: object, properties: {command: {type: string}}, required: [command] }就这么点区别。加上type: object和required之后claude-opus-5.5立刻正常返回完整的input参数了。如果你在 Claude Code 里用的是settings.json配置自定义工具对应的修复片段长这样{ tools: [{ name: bash, description: Run a bash command, input_schema: { type: object, properties: { command: { type: string, description: The bash command to execute } }, required: [command] } }] }每个工具都检查一遍别偷懒。我当时就是因为觉得之前能跑就不改了结果在 opus-5.5 上翻车。方案二加一层 input 校验防御避免静默失败即使 schema 写对了网络抖动、API 行为变更这些事谁也说不准。加一个防御性检查遇到空 input 直接抛异常而不是静默继续for block in response.content: if block.type tool_use: if not block.input: raise ValueError(f空 input: {block.name}) # 正常处理逻辑这段加在你解析tool_use块的地方就行。比静默丢弃好一万倍——至少你能在日志里看到是哪个工具出了问题。方案三用聚合 API 网关统一管理减少配置出错的概率我这次踩坑还有一个背景我同时在测好几个模型claude-opus-5.5、claude-sonnet-5.5、gpt-5.5每个模型的 base_url 和 key 都不一样切来切去特别容易搞混。后来我把调用统一走了聚合网关像 OpenRouter、ofox.io 这类平台改一个 base_url 就能切模型不用每次都改 key 和 endpoint。比如通过这类聚合平台调 opus-5.5代码就这样import anthropic client anthropic.Anthropic( api_keyyour-ofox-key, base_urlhttps://api.ofox.io/v1 # 示例值以平台实际文档为准 )注意base_url的具体格式取决于平台实现。若平台走 OpenAI 兼容接口应改用openai库若平台实现了完整 Anthropic 协议则可直接用anthropic库以平台文档为准。ofox.io 为第三方平台使用前请自行核实其服务条款与可用性。模型名、tools 定义、schema 格式这些完全不变。好处是切模型的时候只改model参数不用动 key 和 URL出问题排查范围小很多。当然聚合网关只是解决了调用入口统一的问题schema 写错该报错还是报错这个锅谁也替不了你。完整的 agentic loop 别忘了回传 tool_result顺便提一个相关问题。有些人的工具调用返回空其实不是 input 空而是忘了把工具执行结果回传给 API。Claude 的工具调用是一个往返协议graph TD A[发送请求] -- B{stop_reason?} B --|end_turn| C[直接用 response] B --|tool_use| D[解析 input 执行工具] D -- E[构造 tool_result 回传] E -- A如果你收到stop_reason: tool_use之后直接取content当最终结果用那当然是空的——模型还没给你最终答案呢它在等你把工具结果喂回去。漏了 tool_result 回传的话下一轮请求会直接报这个Error: 400 Bad Request {type:error,error:{type:invalid_request_error, message:tool_use block at index 1 has no corresponding tool_result block}}回传的代码tool_results [] for block in response.content: if block.type tool_use: result run_tool(block.name, block.input) tool_results.append({ type: tool_result, tool_use_id: block.id, content: result })然后把tool_results作为 user message 的 content 传入下一轮messages.create。常见问题 FAQQ: Claude Code 报 model not found模型名到底怎么写直连 Anthropic API 的话模型名写claude-opus-5.5就行不需要厂商前缀。通过聚合平台的话部分平台要求加anthropic/前缀写成anthropic/claude-opus-5.5具体以你使用的平台文档为准。Q: 并行工具调用parallel tool use的多个 tool_use 块怎么处理claude-opus-5.5支持一次返回多个tool_use块。你需要遍历response.content收集所有type tool_use的块分别执行然后把所有tool_result打包成一个 user message 回传。不能只处理第一个就把剩下的扔了。Q: 工具调用频繁报 529 Overloaded 怎么办实现指数退避重试。初始等待 1 秒每次翻倍最多重试 5 次具体参数可根据实际情况调整。opus-5.5 的推理开销比较大高峰期 529 不少见。报错长这样Error: 529 {type:error,error:{type:overloaded_error, message:Overloaded}}Q: 修完 schema 还是返回空 input还有什么可能检查一下你的tool_choice设置。如果设成了{type: none}模型不会调用工具。设成{type: auto}让模型自己决定或者{type: tool, name: 你的工具名}强制调用特定工具。另外确认max_tokens给够了——token 预算太小的话模型可能来不及生成完整的 input 就被截断。Q: opus-5.5 的工具调用成本大概多少我的最终做法这个问题的根因很简单——input_schema格式不规范。但之所以排查了大半天是因为claude-opus-5.5的静默丢弃行为太坑了schema 格式不够严格时不报错不警告200 OK 给你一个空对象只有 schema 存在明显错误时才会直接返回 400。两种情况混在一起表现不一致排查起来格外费劲。我现在的做法是三管齐下schema 写严格、解析时加空值校验、调用入口走聚合网关方便切模型排查。搞定之后再也没遇到过这个问题。希望这篇能帮你少踩半天坑。