Opus 5.5 は安くなった。400 エラーは簡単なほうです
400 を返すリクエストは、何を直せばいいかをすでに教えてくれています。今回のアップグレードで実際にコストになる変更は 200 で返ってきます。テキストで始まらなくなったレスポンス、一度も設定しなかったせいで一段下がった effort、そしてデプロイなしで新しいモデルに切り替わったエイリアスです。
Claude Opus 5.5 は 9 月 22 日にリリースされました。価格は 100 万トークンあたり入力 $4、出力 $20 で、Opus 5 の $5 と $25 から下がっています。同じ日に Claude Code v2.1.280 がこれをデフォルトの Opus モデルにしました。Opus 5 ですでに動いているコードについて、Anthropic 自身が挙げる「壊れるもの」のリストは 4 項目です。4 つとも 400 で終わり、そのうち 3 つは最初のリクエストで起きます。
400 は良い種類の破損です。リクエストはその場で拒否され、メッセージが直し方を示してくれます。今回のアップグレードで午後をかける価値があるのは、200 で返ってくる変更のほうです。
どのリクエストが 400 を返すようになったのか
Opus 5 から移行する場合は 4 種類です。thinking をオフにすること、ツール呼び出しを強制すること、Claude API と Google Cloud での旧 computer use ツール、そしてそれより前の部分を編集したあとに thinking ブロックを再送すること。最初の 3 つには、ログを grep できるほど具体的なエラー文字列があります。computer use のエラーは、モデルが受け付けるツールタイプも列挙します。
"thinking.type.disabled" is not supported for this model. Use "thinking.type.adaptive" and "output_config.effort" to control thinking behavior.
tool_choice: type "tool" and "any" are not supported for this model.
'claude-opus-5-5' does not support tool types: computer_20251124.修正のうち 2 つは削除です。thinking フィールドを削除し(adaptive thinking は常にオンで、thinking: {"type": "adaptive"} は省略と同じ意味です)、思考の深さは output_config.effort で制御します。強制ツールのチェックはトークンカウントのエンドポイントにも適用されるので、本番リクエストをそのまま写したコスト見積もりツールも失敗します。computer use は削除では済みません。リクエストでは beta ヘッダーなしで computer_toolset_20260801 を宣言し、エージェントループも一緒に変える必要があります。アクションは input.action ではなく tool_use ブロックの名前として届き、1 ターンに複数届くことがあり、すべての結果が toolset_name を返します。Amazon Bedrock では旧 computer_20251124 ツールがそのまま動きます。
再送のケースは、アカウントの作成時期に左右されるので見落としやすいものです。2026 年 8 月 31 日 00:00 UTC 以降に作成されたアカウントでは、system プロンプト、tools、または以前のメッセージを編集したあとに thinking ブロックを再送すると、デフォルトで 400 が返ります。追記だけの会話では起きません。トークン節約のために履歴をその場で書き換えるコードでは起きます。古いブロックを破棄する設定(beta ヘッダー thinking-binding-controls-2026-08-01 のもとで thinking.block_binding.prefix_mismatch_behavior: "drop_block")を有効にしない限りは。
もっと古いバージョンから移行する場合は、5.5 が引き継いでいる以前の拒否も加わります。budget_tokens による手動の thinking 予算と、デフォルト以外の temperature、top_p、top_k(いずれも Opus 4.7 から拒否)、そしてアシスタントターンのプリフィル(4.6 から)です。
強制ツール使用を何で置き換えるか、そして同じ保証になるのか
完全には同じになりません。移行ガイドは、ツールに strict: true を付けて tool_choice: {"type": "auto"} を使い、そのツールをいつ使うかをプロンプトで伝えるよう勧めています。strict tool use が保証するのは、呼び出しが起きたときにそれがスキーマに合っていることです。呼び出しが起きること自体は保証しません。強制はそれを保証していました。(strict モードは JSON Schema の一部しか受け付けず、すべてのオブジェクトに additionalProperties: false が必要なので、有効にする前に各 input_schema を確認してください。)
ですから、なぜ呼び出しを強制していたのかを見直してください。JSON を返してほしかっただけなら、スキーマを structured outputs(output_config.format)に移します。こちらはレスポンスそのものを制約します。ただし拒否や max_tokens での打ち切りは、有効な JSON なしの 200 として返ってくることがあるので、パースする前に stop_reason を確認してください。モデルに本当に行動させる必要があるなら、リクエストはもうそれを約束できません。起きなかったときに気づくのはコードの役目です。
calls = [b for b in resp.content if b.type == "tool_use" and b.name == "get_weather"]
if not calls:
raise RuntimeError(f"no get_weather call (stop_reason={resp.stop_reason})")プロンプトは呼び出しを起こりやすくします。チェックは呼び出しを起こすわけではなく、呼び出しの欠落を、静かなテキスト応答ではなく明示的な失敗に変えるだけです。これは プロンプトは不変条件ではない で書いた「重み付け」と「拘束」の区別を、一段下のレベルに当てはめたものです。
エラーなしで壊れるもの
レスポンスの形です。Opus 5.5 ではすべてのリクエストで thinking が走るので、レスポンスは最初の text ブロックの前に 1 つ以上の thinking ブロックで始まることがあり、デフォルトの display: "omitted" ではそれらのブロックの thinking フィールドは空で届きます。レスポンスを位置で読むコードは、テキストを期待した場所で thinking ブロックを受け取ります。
このパターンは 2 回出荷しています。3 月には Beetroot のマルチプロバイダー統合 の Anthropic 部分を公開し、OpenAI との 4 つの違いの 1 つとして「Response shape: data.content[0].text」を挙げました。パッケージ agent-recall の API バックエンドも同じことをしています。resp.content[0].text を読み、max_tokens=4096 で、thinking フィールドはありません。全体は広い except で包まれ、失敗はログに記録されて None が返ります。どちらも現状のままではこの変更に当たりません。agent-recall の API エイリアスは Opus 4.6 に固定されていて、これは頼まない限り thinking なしで動きますし、Beetroot のマッピングは 5.5 より前のものです。どちらかを claude-opus-5-5 に向けると、リクエストは成功して課金され、パースだけが自分のコードの中で失敗する、ということが起こり得ます。
移行ガイドが説明する形の合成レスポンスで、anthropic Python SDK 1.8.0 で再現でき、修正は 1 行です。
from anthropic.types import Message
resp = Message.model_validate({
"id": "msg_x", "type": "message", "role": "assistant",
"model": "claude-opus-5-5", "stop_reason": "end_turn", "stop_sequence": None,
"usage": {"input_tokens": 10, "output_tokens": 50},
"content": [
{"type": "thinking", "thinking": "", "signature": "sig"},
{"type": "text", "text": "the answer"},
],
})
resp.content[0].text
# AttributeError: 'ThinkingBlock' object has no attribute 'text'
"".join(b.text for b in resp.content if b.type == "text")
# 'the answer'エラーなしで入ってくる変更はあと 3 つあります。
effort が一段下がりました。 effort を省略したリクエストは、Opus 5 では high でしたが、今は medium で動きます。何も失敗しません。デフォルトが一段下がっただけで、しかも同じレベルなら、このモデルは Opus 5 よりターンあたり多く考える傾向があります。effort を一度も設定していないなら、自分で選んでいない設定で動いていることになります。明示的に設定し、以前の値が正しいと判断した評価をもう一度回してください。
合間の説明文が静かになりました。 モデルがツール呼び出しの合間に書く短いメモは、今は thinking ブロックとして返り、デフォルトの表示設定では空です。それを進捗表示としてユーザーにストリーミングしていたプロダクトは、タスクの途中で更新が止まります。beta ヘッダー thinking-display-updates-2026-08-18 とともに thinking: {"type": "adaptive", "display": "updates"} を設定すれば、推論は隠したまま進捗メモが戻ります。"summarized" にすれば両方が混ざって返ります。そのうえで、空でないブロックを、それに続くツール呼び出しの前に表示してください。
thinking が出力予算を共有するようになりました。 Opus 4.8 以前では、thinking フィールドのないリクエストは thinking なしで動きました。5.5 では考えます。その thinking は、以前は回答だけが使えた同じ max_tokens を消費し、テキストが見えなくても出力として課金されます。トークンあたりの価格は下がりました。以前 thinking なしで動いていたワークロードでは、リクエストあたりのコストは逆に上がることがあります。リクエスト単位で測ってください。
なぜ価格よりデフォルトの切り替えが重要なのか
エイリアスはデプロイなしで移動させるからです。Claude Code v2.1.280 が Opus 5.5 をデフォルトの Opus にしたので、claude -p --model opus を呼び出すものはすべて、エイリアスを上書きしていない限り、Claude Code が更新された日に新しいモデルを受け取ります。こちら側では何も変えていないのに、です。
agent-recall は 1 つのパッケージで両方の面を見せています。API バックエンドはエイリアス opus を固定の claude-opus-4-6 に解決するので、レスポンス形状の変更からは守られていますが、同時にその変更が見えません。CLI バックエンドは claude -p --model opus を実行するので、Claude Code が更新されるたびにエイリアスに従います。Claude Code はレスポンス形状を自分でパースしてプレーンテキストを返すので、この経路はパースでは壊れません。吸収しないのはモデル自体の変化、つまり effort、長さ、振る舞いです。同じ設定キーで、モデルが 2 つあるわけです。
今月の初めに、モデルはじっとしていない依存関係である と書きました。バージョンを固定すれば、それが変わる瞬間を自分で握れる、という話です。今回の切り替えはその裏面です。固定は API 経路を破損から守りますが、同時に、固定を動かすその日まで破損を見えなくします。エイリアスはベンダーのスケジュールで新しいモデルを渡し、それが起きたことを知らせる 400 は返しません。
デフォルトの切り替えが届く前にどう捕まえるか
canary を用意してください。依存しているモデルごとに、切り替える前の次のモデルも含めて、実際の形をしたリクエストを 1 つ用意し、レスポンスをブロックタイプで読み、答えがなければ大きな音を立てて失敗させます。
import sys
import anthropic
client = anthropic.Anthropic()
MODELS = ["claude-opus-5", "claude-opus-5-5"]
failures = []
for model in MODELS:
try:
resp = client.messages.create(
model=model,
max_tokens=2048,
output_config={"effort": "medium"},
messages=[{"role": "user", "content": "Reply with the word ok."}],
)
except anthropic.BadRequestError as e:
failures.append(f"{model}: 400 {e.message}")
continue
kinds = [b.type for b in resp.content]
text = "".join(b.text for b in resp.content if b.type == "text")
print(f"{model}: blocks={kinds} stop={resp.stop_reason} out={resp.usage.output_tokens}")
if resp.stop_reason != "end_turn" or "ok" not in text.lower():
failures.append(f"{model}: stop={resp.stop_reason} text={text!r}")
if failures:
sys.exit("\n".join(failures))これは骨組みです。実際のリクエストを入れてください。自分のツール、自分の tool_choice、自分の max_tokens、そして join の代わりに本番のパーサーを。ツールループを回しているなら、ツール結果を含む 2 ターン目を追加してください。再送のルールはそこでしか表に出ないからです。狙いは、本番が送るものをそのまま新しいモデルに送り、まだスクリプトの段階で壊れるのを見届けることです。
changelog が出たら実行してください。Anthropic のリリースノートと Claude Code のリリースページは、どちらもモデルの変更を当日に載せます。エイリアス経由で呼び出すものについては、今エイリアスが指しているモデルではなく、これから指すモデルに対して実行してください。移行ガイドには自動化された手段もあり、Claude Code の /claude-api migrate がパラメーターを書き換え、手で確認するためのチェックリストを渡してくれます。それで直るのはリクエストです。レスポンスがまだパースできるかどうかは canary の仕事です。
今週何を変えるべきか
失敗の静かさの順に並べます。
- コンテンツブロックはどこでも
typeで読み、ツールループではthinkingブロックを変更せずに返してください。API はここで 200 を返します。失敗が表に出るとしても、それは自分のコードの中です。 - すべてのリクエストで
effortを明示的に設定し、デフォルトの変更に勝手に選ばせないようにしてください。 - 以前 thinking なしで動いていたものすべてで
stop_reason: "max_tokens"を確認し、上限を上げるか effort を下げ、リクエストあたりのコストを測り直してください。 - 強制ツール使用を置き換えてください。JSON が欲しかった箇所は structured outputs に、行動が欲しかった箇所は
autoと呼び出し欠落のチェックに、そしてスキーマが対応するツールにはstrictを。 - ユーザーがエージェントの作業を見ているなら、
thinking.displayを確認してください。 thinking: disabledと手動の予算を削除し、computer use をエージェントループごとツールセットに移行してください(Claude API と Google Cloud)。これは 400 がどのみち思い出させてくれます。
値下げは本物で、400 と同じようにきちんと告知されてやってきます。このリストのほかの項目は 200 で返ってきて、気づかれるのを待っています。