Claude Haiku 5.5のstop_reason refusalは呼び出し側で受ける
Claude Haiku 5.5は安全性の分類器でリクエストを断ることがあり、サーバー側フォールバックもありません。拒否の4カテゴリ、課金、クライアント側の再送の組み方をまとめます。
Claude Haiku 5.5には、リクエストを断る安全性の分類器が入っています。断られると stop_reason: "refusal" の成功レスポンスが返ります。ここまでは上位モデルと同じですが、Haiku 5.5には決定的な違いが1つあります。サーバー側フォールバックがありません。別モデルへの再送は、すべて呼び出し側のコードで書くことになります。
Haiku 4.5から載せ替えるコードは、この点で最も事故が起きやすい箇所です。4.5には分類器による拒否がなく、拒否を想定した分岐が元のコードにありません。
Haiku 5.5の拒否はどんな形で返るか
分類器が止めたリクエストは、エラーではなく通常のレスポンスです。HTTPステータスは200で、stop_reason が "refusal" になります。stop_details.category に、どの方針領域が引っかかったかが入ります。ストリーミング中に拒否を検出してリセットする一般的な手順は、refusal stop_reasonの検出とリセットが扱っています。この記事は、Haiku 5.5固有の事情に絞ります。
Haiku 5.5のプロンプトガイドに載っているカテゴリは4つです。
| category | 止める対象 |
|---|---|
cyber | 止める対象マルウェアやエクスプロイトの開発など、サイバー攻撃に転用されうる要求 |
frontier_llm | 止める対象競合するAIモデルの開発を助けうる要求 |
bio | 止める対象危険な実験手法など、生物学的な危害につながりうる要求 |
general_harms | 止める対象上の3つ以外の、利用ポリシー上の領域 |
cyber と general_harms は、無害な作業でも該当することがあると書かれています。ソースコードの脆弱性を見つける作業は許可されている一方、二重用途になりやすい高リスクのセキュリティ作業は許可されていません。正当なセキュリティ業務が cyber で止まる組織には、Cyber Verification Programへの申請という道があります。bio で止まる生命科学の業務には、Life Sciences Verification Programがあります。
分類器の拒否はHaiku 4.5から移ってきた人にとって新しい挙動です。移行チェックリストでは10番目の項目として、stop_reason: "refusal" の処理が挙がっています。
空のcontentを前提にしたコードが落ちる
拒否レスポンスの例では、content が空の配列で、output_tokens も0です。出力が始まる前に止まると、読むべきテキストブロックが存在しません。
Haiku 5.5には、もう1つ似た落とし穴があります。適応的思考が既定でオンなので、通常の応答でも先頭が thinking ブロックになることがあります。移行ガイドは、先頭ブロックを答えとして読むコードは type でブロックを選ぶように直すよう求めています。拒否の処理を足すときは、この修正も一緒に済ませると手戻りがありません。
def first_text(res):
# 拒否や思考ブロックの先頭配置でも落ちない取り出し方
for block in res.content:
if block.type == "text":
return block.text
return None同じリクエストを再送しても通らない
Haiku 5.5が断ったリクエストを、そのままHaiku 5.5へ送り直しても、たいてい同じ拒否が返ります。ネットワークエラーのリトライとは扱いが違います。指数バックオフで待っても、結果は変わりません。
リトライ処理が stop_reason を見ずに「応答が空だったから再試行」と動くコードは、拒否のたびに同じリクエストを送り続けます。拒否も課金や入力トークンの消費を伴うことがあるので、ここは分けて書く必要があります。
拒否は課金されるのか
課金のルールは、カテゴリと発生のタイミングで決まります。出力が始まる前の拒否は、bio、frontier_llm、reasoning_extraction のときだけ課金されます。それ以外のカテゴリと、category が null の拒否は、出力前なら課金されません。どちらの場合も、リクエストはレート制限には数えられます。
出力の途中で止まった拒否は別です。入力トークンと、すでにストリームで流れた出力が、通常の料金で課金されます。
課金対象のカテゴリ表は、Haiku 5.5専用ではなく、拒否を返すモデル全体に共通の表です。Haiku 5.5のプロンプトガイドが挙げるカテゴリは4つで、reasoning_extraction は入っていません。ただし拒否の一般ページはHaiku 5.5も対象に含めてこの表を載せています。分岐は4カテゴリ決め打ちにせず、未知の値でも落ちない形にしておきます。
再送の選択肢は2つしか残らない
拒否時に別モデルへ回す方法は、通常は3つあります。サーバー側フォールバック、SDKのミドルウェア、自前の再送です。Haiku 5.5で使えるのは、後ろの2つだけです。
Haiku 5.5で使える再送の方法
サーバー側フォールバック
fallbacks パラメータで、拒否されたリクエストをAPIが別モデルへ回す仕組みです。Haiku 5.5には対応していません。fallbacks: "default" を付けても拒否のままで、フォールバック先のモデルを列挙すると400エラーが返ります。
SDKミドルウェアか自前の再送
クライアントの側で拒否を検出し、別モデルへ送り直します。どちらもフォールバックのクレジットは付きません。
どちらを選ぶかは、動かす場所で決まります。Anthropic SDKを使っているなら、ミドルウェアをクライアントに1回設定すれば、client.beta.messages を通る呼び出しで拒否のたびに自動で再送されます。生のHTTPで呼んでいる、または再送の条件を細かく決めたいなら、自前で書きます。サーバー側フォールバックの設定そのものは、サーバーサイドフォールバックの設定手順にあります。そちらは対応モデルが主題で、Haiku 5.5は対象外です。
フォールバックのクレジットが付かない
サーバー側フォールバックやミドルウェアでは、再送先のプロンプトキャッシュ書き込みコストを払い戻すクレジットが使われます。キャッシュを二重に作る分を埋め合わせる仕組みです。Haiku 5.5の拒否にはこのクレジットが付きません。したがって、拒否の後に別モデルへ再送すると、フォールバック先のキャッシュ書き込みを満額で払います。
長いシステムプロンプトにキャッシュを効かせている構成ほど、この差が出ます。拒否率が高い用途では、再送のたびにキャッシュを作り直すコストが積み上がります。クレジットの仕組みと二重課金の避け方は、fallback_credit_tokenの解説で扱っています。
自前で書く再送の最小形
自前の再送は、3つの動作に分けられます。拒否を検出する、別モデルに送り直す、会話の以降のターンでもそのモデルを使い続ける。次のコードは、その骨格を示す例です(公式の手順に沿った形で、モデル名は例です)。
import anthropic
client = anthropic.Anthropic()
PRIMARY = "claude-haiku-5-5"
FALLBACK = "claude-opus-4-8" # 自分で検証済みのモデルに置き換える
def ask(messages, model=PRIMARY):
res = client.messages.create(
model=model, max_tokens=2048, messages=messages
)
if res.stop_reason == "refusal" and model == PRIMARY:
# 同じモデルへの再送は別の拒否になりやすい
return ask(messages, model=FALLBACK)
return res実運用では、この骨格にいくつか足します。
- 再送の上限は、1ターンではなく1リクエスト単位で数える。エージェントとサブエージェントを併用すると、1ターンで拒否が複数回出ることがある
- フォールバック先も拒否したら、そこで止める。ループさせない
- 途中で止まった出力は不完全なものとして捨て、再送先の出力で置き換える
- 会話が続くなら、以降のターンも再送先のモデルを使う。Haiku 5.5に戻すと、同じ話題でまた拒否される
思考ブロックは残して送る
Haiku 5.5が拒否した後の再送は、クレジットを使わない側のケースに当たります。会話履歴に入っている過去の thinking ブロックは、残しても、入力トークン節約のために取り除いてもかまいません。
ただし、Claude Opus 5.5とClaude Sonnet 5.5は、Claude APIとGoogle Cloudでは、Haiku 5.5の思考ブロックを読めます。再送先がこの2つなら、ブロックは残しておくほうが文脈を保てます。読めない再送先では、APIがブロックを課金せずに捨てます。
Message Batchesの拒否は成功として返る
Batch APIでは、拒否されたアイテムが result.type: "succeeded" で返り、中身の stop_reason が "refusal" になります。結果の集計でエラー件数だけを見ていると、拒否は素通りします。この点はBatch APIの拒否の落とし穴で詳しく扱っています。
バッチでは、サーバー側フォールバックの fallbacks を付けたアイテムがそもそもエラー結果になります。Haiku 5.5は元からフォールバックを持たないので、運用の流れは変わりません。
- 結果から
stop_reasonが"refusal"のアイテムを集める - 別モデルで受けられる形に直す。多ターンの履歴の思考ブロックは、残しても取り除いてもよい
- 新しいバッチか単発のリクエストとして、別モデルに送り直す
バッチの拒否にはフォールバックのクレジットが発行されないため、stop_details に fallback_credit_token は入りません。
監視に拒否を1つの指標として載せる
拒否はHTTP 200で返るので、エラー率や5xxの監視には映りません。公式の注意書きは、拒否1件につき1イベントを出すことを勧めています。Haiku 5.5では別モデルで答えた応答の数を数える仕組み(サーバー側フォールバックの fallback_message)が使えないため、自前の再送の成功件数を別のイベントとして出し、拒否数との差を見るのが現実的です。
分岐の判定にも注意があります。条件には stop_reason か stop_details.type を使います。category と explanation は、名前付きの分類に当てはまらない拒否で null になるので、そこを条件にすると取りこぼします。また explanation の文面は安定が保証されないため、画面に出すだけにとどめ、解析には使いません。
Claude Codeで haiku を指定している場合の拒否は、OpenTelemetryの api_refusal イベントで数えられます。設定はClaude CodeのOTelでapi_refusalを数える手順にあります。haiku エイリアスがHaiku 5.5を指すようになった経緯は、Claude Code v2.1.293のリリースノートにまとまっています。
再送先を決める前に拒否の中身を集計する
Haiku 5.5の拒否は、無害な作業でも cyber や general_harms に当たることがあります。再送先を決める前に、直近の拒否件数とカテゴリの内訳を一度集計しておくと、判断の根拠が手元に残ります。cyber や bio が大半なら、正当な業務を対象にした検証プログラムへの申請が候補になります。
もう1つはコストの見積もりです。再送先は、Haiku 5.5と同じ価格帯のモデルとは限りません。拒否率が低くても、再送先の単価とキャッシュ書き込みの満額課金が重なるので、Haikuを選んだ理由だった低コストがどこまで残るかを計算しておきます。
Haiku 5.5の全体像と、Claude Codeで担う役割はClaude Haikuの解説記事にあります。
まとめ
Haiku 5.5の拒否対応は、Haiku 4.5から載せ替える際の移行項目の1つです。要点は3つあります。拒否はHTTP 200で返り、content が空のことがある。サーバー側フォールバックもクレジットも使えないので、再送は呼び出し側が書く。再送先のキャッシュ書き込みは満額課金になる。
拒否の分岐は、先頭ブロックを type で選ぶ修正と同じタイミングで入れると手戻りが減ります。拒否の監視はエラー率とは別の指標として最初から用意しておくと、本番で初めて気づく事態を避けられます。