try で囲んだはずなのに例外は飛んでこない。ダッシュボードのエラー率も平常運転のまま。それなのにユーザーには空っぽの返事が届いている。Claude Fable 5 や Claude Opus 5 を本番で叩いていると、この「静かな失敗」に出くわすことがある。犯人はモデルの安全分類器(safety classifier)がリクエストを断ったときの、返し方の作法だ。
Anthropic は 2026年7月24日、この断りを別モデルへ自動で回す fallbacks: "default" という仕組みをベータ公開した。地味な API パラメータひとつだが、背後には「安全分類器の誤爆をどう扱うか」という設計思想が透けて見える。まずは、なぜ静かに失敗するのかから押さえたい。
拒否はエラーではない、HTTP 200 で返ってくる
大前提として、Claude の安全分類器による拒否は例外でもエラーでもない。公式ドキュメントはこう書く。
A refusal is a successful HTTP 200 response with
stop_reason: "refusal".
(Refusals and fallback)
つまり HTTP ステータスは 200 のまま、content は空配列で、stop_reason だけが "refusal" になる。実際のレスポンスはこうだ。
{
"type": "message",
"model": "claude-fable-5",
"content": [],
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request was declined because it could enable cyber harm."
},
"usage": { "input_tokens": 412, "output_tokens": 0 }
}
ここが実務上の罠になる。5xx やタイムアウトを見張る監視は 200 の拒否を素通しするし、try/except も発火しない。ドキュメント自身が「拒否は HTTP 200 なので、エラー率や 5xx を基準にした監視には一切見えない」と釘を刺し、拒否とフォールバック成立をそれぞれ独立したイベントとして計測せよと勧めている。分岐は content の中身ではなく stop_reason を直接見るのが正しい。stop_details の category と explanation は null になり得るからだ。
正当な仕事でも踏む地雷
拒否される理由は stop_details.category に入る。カテゴリと、それぞれ「善良な用途でも引っかかりうる」という注記が公式表に並んでいるのが正直で興味深い。
| category | 何を意味するか |
|---|---|
cyber |
マルウェアやエクスプロイト開発など。正当なセキュリティ業務でも発火しうる |
bio |
危険な実験手法など。有益なライフサイエンス研究でも発火しうる |
frontier_llm |
競合モデルの開発支援。ふつうの機械学習作業でも発火しうる |
reasoning_extraction |
内部推論をそのまま本文に吐かせる要求 |
general_harms |
有害と判定された領域。善良な作業でも時に発火 |
要は、ペネトレーションテストの補助や病原体まわりの文献整理といった真っ当な依頼でも、確率的に断られる。reasoning_extraction は 2026年6月9日に追加された比較的新しいカテゴリで、cyber・bio はそれ以前から存在した(リリースノート)。誤爆を前提に運用を組む必要がある、というのが出発点になる。
fallbacks: "default" が肩代わりするもの
従来の逃げ道は「拒否を検知したら自分で別モデルに投げ直す」だった。これはこれで動くが、投げ直すモデルのリストを自前で保守する手間がかかるうえ、プロンプトキャッシュはモデルごとに別物なので、リトライのたびにキャッシュを書き直す割高な処理が走る。
7月24日の "default" モードは、この面倒を API の中に畳み込む。fallbacks に文字列 "default" を渡し、server-side-fallback-2026-07-01 ベータヘッダを添えるだけでいい。
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: server-side-fallback-2026-07-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-fable-5",
"max_tokens": 1024,
"fallbacks": "default",
"messages": [{"role": "user", "content": "Hello, Claude"}]
}'
response = client.beta.messages.create(
model="claude-fable-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
fallbacks="default",
betas=["server-side-fallback-2026-07-01"],
)
print(response.model) # 実際に答えたモデルが入る
主モデルが断ると、API は同じリクエストを、その拒否カテゴリに対して Anthropic が推奨する別モデルへその場で投げ直す。呼び出しは 1 回、返るレスポンスも 1 つ。窓口で断られたのではなく、答えてよい担当者のところまで連れて行かれる感覚に近い。どのモデルが答えたかはトップレベルの model フィールドに出て、content の先頭には引き継ぎ地点を示す fallback ブロック({"type": "fallback", "from": {...}, "to": {...}})が挿さる。フォールバックが走ったこと自体は usage.iterations に fallback_message エントリとして残るので、stop_reason != "refusal" と合わせて「フォールバックで救われた応答」を検知できる。
推奨先を自前で決め打ちしたいなら、fallbacks に最大 3 つまでモデルの配列を渡す明示指定もある。許可される宛先はモデルごとに決まっていて(Fable 5 なら Opus 4.8 と Opus 5)、ベータヘッダ付きで Models API の allowed_fallback_models として引ける。
「性能が下がる」フォールバックは、たぶん設計どおり
面白いのは、Anthropic が安全策をモデルごと・カテゴリごとに、そのモデルの能力に合わせて個別に張っている点だ。ドキュメントは「カテゴリによっては、フラグが立ったリクエストはより能力の低いモデルへ回されるか、拒否される」と明記する。強いモデルほど危険な依頼に応えられてしまうので厳しく縛り、あえて非力なモデルへ流す。フォールバック=格上げではなく、リスクに応じた振り分けだと理解すると腑に落ちる。"default" モードはこの推奨マッピングをサーバ側で保持してくれるので、推奨が変わっても呼び出し側のコードは触らずに済む。カテゴリによっては推奨先が無く、その場合は拒否がそのまま残る。
課金は「実際に答えたモデルのぶんだけ」。出力前に断られた試行は usage.iterations に記録こそ残るが請求されない(ただしレート制限には数える)。回し直しでキャッシュを書き直す割高分は fallback credit が相殺する仕組みで、"default" モードや SDK ミドルウェアを使えば自動で効く。さらに一度フォールバックした会話は約 1 時間・組織スコープで「貼り付く(sticky routing)」ので、毎ターン確実に断られる主モデルへ無駄に投げ続けることもない。
トリガーになるのは安全分類器による拒否だけで、レート制限・過負荷・サーバエラーはそのまま呼び出し側に返る点も実務では重要だ。フォールバックはあくまで「断られたとき」の話であって、障害時の万能リトライではない。
どう受け止めるか
この機能の本質は、Anthropic が自社分類器の誤検知を認めたうえで、閾値を緩めるのではなくルーティングで解く道を選んだところにある。安全性を「拒否か許可か」の二値ではなく、モデル選択の問題として扱う設計だ。バッチ API では使えない、宛先はホワイトリスト内に限られる、といった制約はあるが、正当なセキュリティ調査やライフサイエンス系の用途で Fable 5 / Opus 5 を使うなら、まず入れておいて損はない。
そして機能の有無に関わらず、拒否が HTTP 200 で届くという一点は今すぐ覚えておく価値がある。エラー監視の死角で、あなたのユーザーが黙って空の返事を受け取っているかもしれない。まずは stop_reason == "refusal" を独立したメトリクスとして吐かせるところから始めるのが実務的だ。