Claude 5系のモデル (claude-sonnet-5) をAPIから使い始めたら、既存のコードが2段階で壊れた。1つ目は temperature パラメータの廃止による400エラー。2つ目は、リクエストは成功する (HTTP 200) のに応答本文が空で返ってくる現象で、原因は自動で発動する思考モードが max_tokens を使い切っていたことだった。この記事では実際のエラーログと再現実験でこの2つを確認し、動作確認済みの対処を示す。検証は2026年8月4日時点、claude-sonnet-5 で行った。他のClaude 5系モデルでは未確認。
temperatureを送ると400エラーになる
画像認識で、出力を安定させるため temperature: 0 を指定していた。モデル名を claude-sonnet-5 に変えたところ、即座に400が返った。
{"type":"error","error":{"type":"invalid_request_error",
"message":"`temperature` is deprecated for this model."},
"request_id":"req_011Cdh526oaEVvZLzV2NmUWi"}
Claude 5系では temperature が廃止されている。無視して受理されるのではなく、リクエスト自体がエラーになるので、旧モデル用のコードのモデル名だけ差し替えると確実に踏む。対処はパラメータを送らないこと。決定的な出力が欲しい場合でも指定する手段はなくなった。
temperatureを外すと今度は応答が空になる
400は解消したが、次のリクエストは20秒以上待たされた末に本文が空で返ってきた。HTTPステータスは200で、レスポンスの content 配列に text ブロックが1つも無い。こちらのコードは「textブロックを連結して空なら例外」としていたので、こう記録された。
Anthropic API returned empty content
エラーコードも無く、通信も成功している。textだけを見ているコードには「空応答」にしか見えないのが厄介な点だった。
原因は自動で発動する思考モード
レスポンス全体を観察するプローブを書いて調べると、原因が見えた。まず単純な質問 (「1+1は?」) では思考は発動しない。
stop_reason: end_turn
content_types: text
"output_tokens_details":{"thinking_tokens":0}
ところが同じパラメータのまま、画像読み取りという複雑なタスクを投げると、リクエストで何も指定していないのに思考モードが発動する。
elapsed: 20460ms
stop_reason: end_turn
content_types: thinking,text
"usage":{"input_tokens":3950,"output_tokens":1099,
"output_tokens_details":{"thinking_tokens":799}, ...}
content の先頭に thinking ブロックが入り、出力1099トークンのうち799トークンが思考に使われている。Claude 5系はタスクの複雑さに応じてモデル自身が思考するかを決める仕様で、思考トークンも max_tokens の枠から消費される。つまり複雑なタスクほど、本文にたどり着く前に max_tokens が尽きるリスクが上がる。
これを実証するために、同じリクエストの max_tokens を600に絞ってみた。
elapsed: 15729ms, http: 200
stop_reason: max_tokens
content_types: thinking
"output_tokens":600,"output_tokens_details":{"thinking_tokens":600}
text length: 0
600トークン全部が思考に消え、textブロックが生成される前に打ち切られた。それでもHTTP 200で正常に返ってくる。「長く待たされた末に空応答」の正体はこれだった。
max_tokensを増やしても解決しない
最初の対処として max_tokens を4000から16000に引き上げたが、スクショ全体を読ませるリクエストでは、それでも同じ結果になった。
Anthropic API returned empty content (stop_reason=max_tokens)
16000トークンを本文が出る前に使い切っている。81マスを1マスずつ検算するようなタスクでは思考が際限なく伸びるようで、上限を増やす対処はコストと待ち時間を増やすだけで根本解決にならなかった。
対処: thinkingを無効化するか、effortで制御する
制御パラメータを探すため、旧モデルの拡張思考で使っていた thinking: {"type":"enabled","budget_tokens":2000} を送ってみると、エラーメッセージが答えを教えてくれた。
{"type":"error","error":{"type":"invalid_request_error",
"message":"\"thinking.type.enabled\" is not supported for this model.
Use \"thinking.type.adaptive\" and \"output_config.effort\"
to control thinking behavior."}}
Claude 5系の思考制御は budget_tokens ではなく、thinking.type (adaptive/disabled) と output_config.effort の組み合わせに変わっている。今回の用途 (構造化JSONを吐かせるだけで、思考の恩恵より待ち時間の害が大きい) では無効化を選んだ。
{
"model": "claude-sonnet-5",
"max_tokens": 8000,
"thinking": { "type": "disabled" },
"messages": [ ... ]
}
これで同じ画像読み取りリクエストが thinking_tokens: 0、stop_reason: end_turn で完走し、本文のJSONが返るようになった。思考を残したまま量を抑えたい場合は output_config: {"effort":"low"} がリクエストとして受理されることも確認したが、思考量がどの程度減るかの比較まではしていない。
あわせて、空応答を検知したときに stop_reason を記録するようにした。これが無いと「たまに空が返る」以上の情報が残らず、今回のような原因特定に時間がかかる。
if (!text) {
throw new Error(
`empty content (stop_reason=${json.stop_reason})`
);
}
Claude 5系への移行では、(1) temperature を送っているコードは400で止まる、(2) 思考が自動発動する前提で max_tokens と thinking/output_config.effort を設計する、の2点を確認しておくと同じ穴にはまらずに済む。空応答に遭遇したら、まず stop_reason と usage.output_tokens_details.thinking_tokens を見るのが近道だ。