Claude API の refusal フォールバック 使い方

Claude.ai エンジニアリング 7分で読めます

Claude Opus 5 や Claude Fable 5 のような安全性の保護が強いモデルでは、安全性分類器がリクエストを辞退することがあります。このとき返ってくるのはエラーではなく HTTP 200 のレスポンスで、stop_reasonrefusal になります。本記事は、その見分け方と、辞退されたリクエストを別のモデルで自動的に処理し直す fallbacks パラメータの使い方を日本語で整理した非公式の解説です。

refusal は例外ではなく200応答

まず押さえるべきは、辞退が例外として飛んでこないことです。HTTP のステータスコードは 200 で、レスポンスオブジェクトも正常に返ります。違うのは stop_reason の値だけです。

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    messages=[...],
)

if response.stop_reason == "refusal":
    handle_refusal()
else:
    print(response.content[0].text)

ここで一番よくある壊れ方が、response.content[0] を無条件に読むコードです。出力が始まる前に辞退された場合、content空の配列になります。添字アクセスがそのまま例外になるため、stop_reason を先に見る形に直してください。

辞退には2つのタイミングがあります。

あわせて stop_details というオブジェクトが付くことがあります。category にどの方針に触れたか(cyberbioreasoning_extractionfrontier_llm など。null のこともあります)、explanation に説明が入ります。ただし分岐は stop_reason で行ってくださいstop_details は参考情報で、辞退時でも null になり得ますし、explanation が必ず入る保証もありません。

もう1点。分類器によるブロックと、モデル自身が答えを断った通常の拒否は、どちらも同じ stop_reason: "refusal" で表れます。どちらなのかは stop_details.category で判断します。これは「別のモデルで再試行して意味があるか」の判断に直結します。

fallbacks パラメータで自動的に切り替える

fallbacks は、辞退されたリクエストをサーバー側で別のモデルに回して、その結果を返す仕組みです。クライアント側でリトライを書かずに済み、往復も1回で終わります。

指定の仕方は2通りあります。新しく推奨されているのは "default" という文字列を渡す形です。

POST /v1/messages
anthropic-beta: server-side-fallback-2026-07-01

{
  "model": "claude-opus-5",
  "fallbacks": "default",
  "max_tokens": 1024,
  "messages": [{"role": "user", "content": "..."}]
}

"default" モードは、辞退の理由(カテゴリ)に応じて Anthropic が推奨する代替モデルへ自動で振り分けます。たとえば cyber カテゴリの辞退は Claude Opus 4.8 に回ります。

もう1つは、代替モデルを自分で列挙する配列形式です。

response = client.beta.messages.create(
    model="claude-fable-5",
    max_tokens=16000,
    betas=["server-side-fallback-2026-06-01"],
    fallbacks=[{"model": "claude-opus-4-8"}],
    messages=[{"role": "user", "content": "..."}],
)

ベータヘッダーが形式ごとに違う点に注意してください。配列形式は server-side-fallback-2026-06-01"default" 形式は server-side-fallback-2026-07-01 です。片方のヘッダーにもう片方の形式を組み合わせると 400 になります。日付が新しいほうが上位互換、という関係ではありません。

どちらを選ぶべきかというと、基本は "default" です。理由は2つあります。第一に、代替モデルごとに搭載している分類器が違うため、適切な代替先は「なぜ辞退されたか」によって変わります。第二に、モデルを固定して書いておくと、そのモデルが将来非推奨になったときに自分で移行作業が必要になります。"default" はどちらの問題も持ち込みません。

なお fallbacks が発動するのは方針による辞退のときだけです。レート制限、過負荷、サーバーエラーはそのまま返り、フォールバックは起きません。

どのモデルが答えたかを見分ける

フォールバックが起きたかどうかは、レスポンスの2か所から読み取ります。

1つ目は content のなかの fallback ブロックです。モデルが切り替わった地点ごとに1つ入ります。

for block in response.content:
    if block.type == "fallback":
        print(f"{block.from_.model} が辞退し、{block.to.model} が続行しました")

2つ目は usage.iterations のなかの fallback_message エントリです。「結局どのモデルが答えたか」を知りたいときは、こちらを見てください。

fallback_ran = any(
    entry.type == "fallback_message"
    for entry in (response.usage.iterations or [])
)
if fallback_ran and response.stop_reason != "refusal":
    print(f"{response.model} が応答しました")

ブロックではなく usage.iterations を推す理由は、スティッキールーティングにあります。一度フォールバックが起きた会話は、その後およそ1時間、非ストリーミングのリクエストが最初から代替モデルで処理されるようになります(ベストエフォートです)。このとき辞退そのものが起きていないので、fallback ブロックは付きません。ブロックだけを見ていると「フォールバックしていない」と誤読します。

また、代替モデルもまた辞退することがありますfallback_message の有無だけでなく、最終的な stop_reason も併せて確認してください。最終レスポンスの stop_reasonrefusal なら、連鎖の全員が辞退したという意味です。トップレベルの model フィールドには、実際にそのメッセージを生成したモデルの名前が入ります。

課金とレート制限の扱い

課金は試行ごとに分かれて記録されます。正となるのは usage.iterations で、トップレベルの usage は「返ってきたメッセージを生成した試行」のぶんだけを表します。

プロンプトキャッシュについても補足があります。キャッシュはモデルごとに分かれているため、素朴に別モデルへ投げ直すと冷えたキャッシュへの書き込みを払い直すことになります。サーバー側 fallbacks では、この分の再計算(クレジット方式の値付け直し)が自動で適用されます。

レート制限は、その試行を実行したモデルの枠を消費します。ここに運用上の落とし穴があります。代替モデル側がレート制限や過負荷に掛かっていると、フォールバックの試行自体が行われず、元の辞退がそのまま返ります。このとき stop_details.recommended_model に「直接投げ直すとよいモデル」が入ります(あくまでヒントで、保証ではありません。推奨が無いときは null です)。

したがって、代替モデル側のレート制限枠も、想定される辞退の量に見合うサイズにしておく必要があります。フォールバックを設定しただけでは、代替先が詰まっている状況は救えません。

ストリーミングと後続ターンの注意点

ストリーミングの場合、フォールバックは同じストリーム上で行われ、すでに受け取った内容が無効になることはありません。

もう1つ、ストリーミングではスティッキールーティングが参照されません(初期リリース時点)。つまりストリームでは fallback ブロックの有無が完全な判定材料になります。非ストリーミングとは判定の仕方が変わる点に注意してください。

会話を続けるとき、途中でフォールバックが起きたターンを次のリクエストに含める場合は、一部のブロックを落とす必要があります。具体的には、最後の fallback ブロックよりにある thinkingredacted_thinkingtool_use の各ブロックと、対応する server_tool_result が無い server_tool_use ブロック、その他の未知のモデル内部ブロックです。text ブロック、対になっているサーバーツールのブロック、境界より後のものはそのまま返して構いません。fallback ブロック自体は無視される監査用の目印なので、残しても消しても動きます。

最後に提供面の制限です。サーバー側 fallbacksClaude API のみで使えます。Batches API では拒否され、Amazon Bedrock、Google Vertex AI、Microsoft Foundry では利用できません。これらの環境では、SDK が提供するクライアント側のミドルウェア(BetaRefusalFallbackMiddlewareBetaFallbackState)を使ってください。

本記事は Anthropic の公式仕様に基づく非公式の日本語解説です。ベータ機能のため、フラグ名や仕様は変更される可能性があります。実装前に公式ドキュメントで最新の内容をご確認ください。