Claude Media
Claude APIストリーミングのエラー復旧がv4.6で変わった

Claude APIストリーミングのエラー復旧がv4.6で変わった

ストリーミング中断からの復旧手順が、Claude 4.6以降でassistantメッセージからuserメッセージ方式に変わりました。

ストリームが途中で切れたら、続きを送って再開する

ストリーミングの応答がネットワーク障害やタイムアウトで途切れたとき、受け取れた分を捨てずに続きを生成させる手順がドキュメントに定義されています。再開の目的はClaude4.5以前も4.6以降も同じです。違うのは、途中経過をリクエストのどこに置くかです。

4.5以前は途中経過をassistantメッセージとして積み、4.6以降はuserメッセージに入れて「続きから書いて」と指示します。旧方式を4.6以降へ送ると、劣化した応答が返るのではなく、リクエスト自体が400で拒否されます。

4.6以降にassistantメッセージ方式を送ると400になる

Claude4.6以降のモデルでは、最後のassistantターンに部分的な文章を置く「プリフィル」が使えなくなりました。公式のプロンプトガイドによると、Claude Mythos Previewも対象で、プリフィルしたassistantメッセージを含むリクエストは400エラーを返します。4.5以前のモデルは引き続きプリフィルに対応しています。会話の途中にassistantメッセージを入れる使い方は、4.6以降でも影響を受けません。禁止されるのは、最後のターンに置いて続きを書かせる使い方だけです。

復旧ロジックを4.5時代のまま持ち越した実装は、障害時にだけ通る経路で初めてこの400に当たります。通常の応答では動いているため、モデルIDを4.6以降へ上げた後の検証で見落としやすい箇所です。400は invalid_request_error にあたるので、SDKの例外クラスとエラー形式の対応表で自分のSDKの例外名を押さえておくと、ログから原因を追えます。

再開してよい中断と、してはいけない中断

ストリーミング中の失敗は、HTTPステータスで見分けられる通常のエラーとは扱いが違います。APIが200を返した後にエラーが起きるため、通常のエラー処理の仕組みには乗りません。そのかわりSSEの error イベントが流れてきます。

event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}

overloaded_error は、非ストリーミングならHTTP529にあたるエラーです。イベントを返さないまま接続が落ちる場合もあります。アイドルな接続を一定時間で切るネットワークがあり、大きな max_tokens を非ストリーミングで送ると影響を受けやすくなります。緩和策はストリーミングへの切り替えか、TCPソケットのキープアライブ設定です。公式SDKは、非ストリーミングリクエストが10分を超えそうな場合に検証を行い、キープアライブのソケットオプションも設定します。

再開できるかどうかは、エラーの種類より「完成したテキストをどこまで受け取れたか」で決まります。error イベントが非ストリーミングのどのステータスにあたるかで、再開の前にやることが変わります。

ステータスエラー種別再開の前に確認すること
429エラー種別rate_limit_error再開の前に確認すること組織のレート制限か、利用枠の月間上限か。上限の場合は retry-after ヘッダーが付かず、アクセスが戻るまで失敗し続ける
500エラー種別api_error再開の前に確認することAnthropic側の内部エラー。指数バックオフでの再試行が公式の案内
504エラー種別timeout_error再開の前に確認すること処理がタイムアウトした。長い処理にはストリーミングかBatches APIが案内されている
529エラー種別overloaded_error再開の前に確認すること全ユーザーに及ぶ高トラフィック。リトライ対象

支出上限に絡むエラーは、再開しても通りません。組織やワークスペースに自分で設定した支出上限へ達すると、APIは400を返します。Claude Codeワークスペースの上限だけは429になることがあります。使用量が急増した組織では、まれにアクセラレーション制限で429が出るので、トラフィックは段階的に増やして一定の使い方を保つよう案内されています。再開ループを回す前に、上限が原因でないかを確かめてください。

公式SDKは、接続エラー・レート制限・5xxを既定で2回、指数バックオフ付きで自動リトライし、retry-after があればそれに従います。回数は max_retries(TypeScriptでは maxRetries)で変えられます。この自動リトライは通常のエラーへの対処であり、200の後に切れたストリームの続きを組み立てる処理は含みません。エラーコード別の設計はClaude APIのエラーハンドリング設計にまとめています。

切れたストリームは、イベントの並びで見分ける

完走したストリームは決まった順序で流れます。message_start で空の content を持つ Message が届き、続いてコンテンツブロックごとに content_block_start、1つ以上の content_block_delta、content_block_stop が並びます。最後に message_delta が1回以上と、締めの message_stop です。各ブロックの index は、完成後の content 配列の位置に対応します。

この並びを物差しにすると、受け取れた範囲を判定できます。content_block_stop まで届いたブロックは完成済みで、message_stop が来ていなければ応答全体は未完です。ping イベントはいつ混ざってもよく、中身を持たないので読み飛ばします。未知のイベント型が将来増えることがあるため、知らない型で処理を落とさない実装が前提になります。

障害の調査用には、request-id を残します。すべてのAPI応答にこのヘッダーが付き、サポートへ問い合わせるときにこの値が要ります。ストリームを開いた時点で保存しておくと、切断後の問い合わせに使えます。PythonとTypeScriptのSDKでは、トップレベルの応答オブジェクトの _request_id から読めます。

再開の手順は4.5系も4.6系も3ステップ

手順

ストリーミング再開の3ステップ

  1. 1

    受信済みの内容を保存する

    エラーの前に受け取れた内容を保持します。SDKのメッセージ蓄積とエラー処理の機能を使うよう案内されています。

  2. 2

    継続リクエストを組み立てる

    ここだけモデル世代で分岐します。具体的な形は次の比較のとおりです。

  3. 3

    ストリーミングを再開する

    新しいストリームを開き、中断箇所の続きを受け取ります。

Claude4.5以前とClaude4.6以降で、継続リクエストの形が違う

くらべる

途中経過の積み方

assistantメッセージ

Claude4.5以前

受け取った途中経過を、assistantメッセージとして messages の末尾に追加します。モデルは自分の発言の続きとして生成します。

userメッセージ

Claude4.6以降

途中経過と「続きから書いて」という指示を、userメッセージに入れます。

まず4.5以前の形です。例に使う claude-sonnet-4-5 は、2026年9月30日に非推奨となり、退役予定日は2026年11月30日です。後継は claude-sonnet-5-5 で、ここでは4.5以前の形を示すために使っています。

messages = [
    {"role": "user", "content": "長い技術解説をお願いします"},
    {"role": "assistant", "content": partial_response_text},  # 途中経過をそのまま積む
]
 
response = client.messages.create(
    model="claude-sonnet-4-5",  # 4.5以前の例(非推奨)
    max_tokens=1024,
    messages=messages,
)

4.6以降では、途中経過をuserメッセージの文面に埋め込みます。

continuation_prompt = (
    f"Your previous response was interrupted and ended with "
    f"[{partial_response_text}]. Continue from where you left off."
)
 
messages = [
    {"role": "user", "content": "長い技術解説をお願いします"},
    {"role": "user", "content": continuation_prompt},
]
 
response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    messages=messages,
)

TypeScriptでも組み立て方は同じです。

const continuationPrompt =
  `Your previous response was interrupted and ended with ` +
  `[${partialResponseText}]. Continue from where you left off.`;
 
const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "長い技術解説をお願いします" },
    { role: "user", content: continuationPrompt },
  ],
});

Messages APIは、連続するuserターンを1つのターンにまとめます。そのため上の2通のuserメッセージは、実際には1通のuserターンとして届きます。公式の移行ガイドは、継続指示の文面を Your previous response was interrupted and ended with [previous_response]. Continue from where you left off. としています。

なぜassistantメッセージ方式をやめたのか

理由は、プリフィルの廃止にあります。公式のプロンプトガイドは、モデルの知能と指示追従が上がり、プリフィルを要する用途の大半はもう不要になったと説明しています。継続の用途については、続きの指示をuserメッセージに移し、中断した応答の最後の部分を含める移行先が示されています。

もう一つ、移行ガイドの但し書きがあります。エラー処理や不完全な応答の処理で、UX上の不利益がなければ、リクエストをそのまま再実行する選択肢も挙がっています。ユーザーに途中まで表示していない、バッチ処理の中の1件、といった場面では、継続ロジックを書かずに再実行するほうが実装が軽く済みます。画面に文字を流している最中の中断で、続きから出したいときに継続リクエストの出番があります。

使い方別に見る、改修が要る範囲

利用形態影響度対応
自前でストリーミング復旧を組んでいる影響度明確な影響あり対応モデルIDで継続リクエストの形を分岐する
4.5系と4.6系を同時に運用している影響度条件次第対応ルーティング層で復旧ロジックを世代別に切り替える
SDKの自動リトライだけに頼っている影響度明確な影響あり対応200の後に起きたエラーは通常のエラー処理に乗らない
切断時は再実行するだけ影響度影響なし対応プリフィルを送らないので400にならない

自前の復旧ロジックを持つチームは、モデルIDの分岐を入れずに4.6以降へ移ると、切断時の経路だけが400で失敗します。

thinkingとtool_useのブロックは部分復旧できない

公式のベストプラクティスによると、復旧できるのは text ブロックだけです。tool_use ブロックと拡張思考(extended thinking)のブロックは、部分的な状態から再開できません。ブロックが完成したかどうかは、content_block_stop が届いたかで判定できます。ツール呼び出しの途中で切れたら、その tool_use ブロックごと破棄し、直前に完了した text ブロックから再開します。input_json_delta の差分をマージして再送する処理は書かず、ブロックごと破棄する設計にします。

stop_reason が refusal で、継続ではなくリセットが必要なケースはClaude APIのrefusal stop_reasonを検出してリセットする方法で扱っています。

継続をまたぐ使用量は自分で積算する

継続リクエストの messages には、元の指示、途中経過、継続指示文が入ります。message_delta イベントの usage は累積値なので、継続を何度かまたいだ合計は、自分で積算してください。4.5以前のassistantメッセージ方式には、応答全体を再処理せずに済む利点があると説明されています。

長い出力を何度も再開するなら、中断の頻度を下げる設計も選択肢です。長時間のリクエストにはストリーミングかMessage Batches APIが案内されており、Batches APIは結果をポーリングで受け取れるため、途切れない接続を必要としません。

復旧ロジックを本番前に試す

障害を待たずに、継続ロジックは手元で検証できます。ストリームを途中で強制切断するテストダブルを用意し、text ブロックの途中で止めて、次のリクエストが正しく組み立てられるかを見ます。

手順

切断テストで見る3点

  1. 1

    モデルIDの分岐が発火するか

    4.5系ではassistant、4.6系ではuserの継続になっているかを確かめます。

  2. 2

    tool_useの途中で切ったとき

    破棄と再開の処理が実際に動いているかを見ます。

  3. 3

    継続後の文体が変わっていないか

    自動テストにしにくいので、モデルID・切断位置・継続後の冒頭数十文字をログに残し、目視で確認できる形にします。

SDKを使わず生のSSEを自前でパースする実装では、content_block_stop を受け取る前に接続が切れる場合も想定します。途中のデルタを完成扱いにすると、文の途中で欠けた状態で次のリクエストに積まれます。

Agent SDK側のストリーミング設定はAgent SDKのストリーミング出力を有効にするで扱っています。

まとめ

モデルIDを4.6以降へ上げる変更には、切断テストを回帰項目として一緒に入れる、という判断が残ります。通常の応答では動く経路なので、テストを足さない限り、障害が起きる日まで400は表に出ません。

この記事を共有:XはてブLinkedIn