Claude Code 工具调用返回空怎么办?claude-opus-5.5 的 input_schema 校验踩坑记录与修复方案

上周三我在用 Claude Code 跑一个自动化重构任务,模型切到 claude-opus-5.5 之后,工具调用的结果突然全变成空的了。stop_reason 明明是 tool_use,content 数组里也有 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_reason=tool_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 写得糙一点也能跑。比如这样:

python 复制代码
# 有问题的写法
tool_schema = {
    "properties": {"location": {"type": "string"}},
    "required": ["location"]
}

注意到没?少了最外层的 "type": "object"。在旧模型上这居然能正常返回参数。但换到 claude-opus-5.5 之后,模型遇到不合规的 schema 会有两种表现,取决于问题的严重程度:

  • schema 格式不够严格 (如缺少 "type": "object"):不直接报 400,而是返回空的 input: {}------它识别出你定义了工具,也决定调用这个工具,但因为 schema 不合规,生成的参数被内部校验拦截,最终给你一个空对象。

  • schema 存在明显错误 (如整个 input_schema 字段都缺了 type):API 直接返回 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_use,HTTP 状态码还是 200,你不仔细看 input 的内容根本发现不了。

我那天遇到的情况是:5 个工具里有 3 个的 schema 写法不够严格,其中 2 个静默返回空 input,1 个直接 400。排查起来挺烦人的,因为表现不一致。

方案一:补全 input_schema 为符合 Anthropic 要求的 JSON Schema 格式

根本解决方法。每个 tool 的 input_schema 必须包含三个东西:

  1. 最外层 "type": "object"
  2. properties 里每个参数都有明确的 type
  3. required 数组列出所有必填参数

修复前(会触发空 input):

python 复制代码
# 缺少 type 和 required 的写法
bad_schema = {
    "properties": {"command": {"type": "string"}}
}

修复后:

python 复制代码
# 补全后的正确写法
good_schema = {
    "type": "object",
    "properties": {"command": {"type": "string"}},
    "required": ["command"]
}

就这么点区别。加上 "type": "object" 和 "required" 之后,claude-opus-5.5 立刻正常返回完整的 input 参数了。

如果你在 Claude Code 里用的是 settings.json 配置自定义工具,对应的修复片段长这样:

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 直接抛异常而不是静默继续:

python 复制代码
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,代码就这样:

python 复制代码
import anthropic

client = anthropic.Anthropic(
    api_key="your-ofox-key",
    base_url="https://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"}}

回传的代码:

python 复制代码
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。

常见问题 FAQ

Q: 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 写严格、解析时加空值校验、调用入口走聚合网关方便切模型排查。搞定之后再也没遇到过这个问题。希望这篇能帮你少踩半天坑。

相关推荐
科技每日热闻1 小时前
大模型 API 调用遭遇限制时,推荐哪些支持弹性扩展和稳定调用的云平台?
大数据·网络·人工智能·ai
疯狂敲代码的老刘1 小时前
MarvellousSuspender浏览器扩展:1.4K星开源标签页冻结神器,Chrome吃内存终结者的干净复活版
前端·chrome
码上上班1 小时前
shell 脚本知识点总结
前端·chrome
程序员Sunday1 小时前
Flex 和 Grid 有什么区别?一维布局、二维布局怎么选?
前端·css·面试
Jlzn88881 小时前
华南PACK线自动化率怎么统计才靠谱
前端·javascript·自动化
拖孩1 小时前
AI 时代,个人开发者到底能不能靠一个小程序赚到钱
前端·后端·微信小程序
Hilaku1 小时前
ERP选型时不问MCP,等于默认三年后再付一次集成费!
前端·javascript·程序员
程序人生8881 小时前
几百张固定位置扫描件如何离线批量清?DocConverter Web 批量去水印链路
前端
deli0071 小时前
一个汉字三种编码:UTF-8、GBK、UTF-16 逐字节对比实测
前端