error: llm api error: an error occurred during streaming というメッセージが表示された場合、その原因はほぼ次の6つのいずれかです:接続が200を返した後にSSEイベントとして送信されるプロバイダーの途中障害、クライアントまたはゲートウェイ側の読み取りタイムアウト、生成途中の429レート制限、SSEパーサーを破壊する不正な形式または予期しないチャンク、ネットワーク接続の切断、またはストリームが開いた後に初めて発生する認証・クォータの問題。この順序で確認してください — ほとんどのストリーミング障害は最初の3つに該当します。
さらに読み進める前に、発生している状況と原因を照合してください:
| 観察される状況 | 参照先 |
|---|---|
| ストリームが開始し、いくつかのトークンが到着した後、HTTPステータスに変化なく中断する | 原因1 |
| ストリームが10秒以上停止し、新しいトークンが到着せず、クライアントがタイムアウトを発生させる | 原因2 |
| トラフィックの急増時やリクエストのバースト直後にエラーが頻発する | 原因3 |
例外がネットワーク終了ではなく KeyError、JSONDecodeError、またはパース失敗を示す |
原因4 |
エラーが汎用的(APIConnectionError、ECONNRESET)で、安定したネットワークでも不定期に発生する |
原因5 |
| キーのローテーション後、支出制限に達した後、または環境を切り替えた後の最初のリクエストでエラーが発生する | 原因6 |
以下の完全な診断チェックリストでは、各行に推定される原因を明記して繰り返します。
重要なポイント
- ストリーミングエラーは、HTTPステータスが既に200を返した 後 にSSE
errorイベントとして到着する可能性があるため、ステータスコードのみのリトライロジックでは検出できません。 - AnthropicのMessages APIは、ストリーム途中の過負荷を、新しいHTTP 529としてではなく、ストリーム本体内の
event: errorと"type": "overloaded_error"で報告します。 - OpenRouterは、プロバイダー全体で同じパターンを文書化しています:最初のトークンが出荷されると、障害は最上位の
errorフィールドとfinish_reason: "error"を持つchat.completion.chunkとして到着します。 - すべてのストリームチャンクが成功形であると想定するクライアントライブラリは、プロバイダーがストリーム途中で構造化されたエラーペイロードを送信した場合、誤解を招くエラー(実際の原因ではなく
APIConnectionError)でクラッシュします。 - ほとんどの修正方法はプロバイダーに関係なく同じです:ストリーム本文内のエラーイベントを解析し、接続タイムアウトとは別のトークンレベルのストールタイムアウトを設定し、HTTPステータスだけでなくエラーの種類に基づいて指数バックオフを使用します。
診断チェックリスト:最初に根本原因を見つける
コードを変更する前に、これを実行してください。各行は、観察できる症状を修正するセクションにマッピングします。
| 観察される状況 | 推定される原因 | 参照先 |
|---|---|---|
| ストリームが開始し、いくつかのトークンが到着した後、HTTPステータスに変化なく中断する | プロバイダーの途中障害(過負荷、コンテンツフィルター、プロバイダークラッシュ) | 原因1 |
| ストリームが10秒以上停止し、新しいトークンが到着せず、クライアントがタイムアウトを発生させる | 読み取りタイムアウトまたはアイドルストリームの停止 | 原因2 |
| トラフィックの急増時やリクエストのバースト直後にエラ—が頻発する | ストリ—ム途中のレ—ト制限 | 原因3 |
例外がネットワーク終了ではなく KeyError、JSONDecodeError、またはパ—ス失敗を示す |
不正な形式または予期しないチャンク | 原因4 |
エラ—が汎用的(APIConnectionError、ECONNRESET)で、安定したネットワークでも不定期に発生する |
接続断またはマスクされたプロバイダ—エラ— | 原因5 および 原因4 |
| キーのローテーション後、支出制限に達した後、または環境を切り替えた後の最初のリクエストでエラーが発生する | 認証またはクォータ障害 | 原因6 |
ログに汎用的な例外名のみが表示され、上流のメッセージがない場合、それ自体が症状です — 汎用的なラッパーが本当の原因を隠す理由については、原因4 と 原因5 を参照してください。
原因1: プロバイダーの途中障害(過負荷、コンテンツフィルター、プロバイダークラッシュ)
これは最も多くの人を混乱させる原因です。リクエストが成功したように見えるからです。ストリーミング応答が開始されると、HTTPステータスコードとヘッダーは既にクライアントにコミットされています。その後、プロバイダーが障害(容量枯渇、内部エラー、部分出力後のコンテンツフィルター作動、モデルプロセスのクラッシュ)に遭遇すると、HTTPステータスをエラーコードに変更できません。障害はストリーム自体の中で特別なイベントとして伝送される必要があります。
AnthropicのMessages APIはこれを直接文書化しています:APIは「イベントストリームで[エラ—]を送信する可能性があリ」、ストリーム途中で過負荷状態が発生した場合の正確な例を示しています:
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}
これは多くのリトライロジックにおける実際のギャップです。コードが response.status_code のみを検査する場合、障害が発生する前に既に200を確認しているため、リトライは決してトリガーされません。この正確なパターンを説明するサードパーティのインシデントレポートは、簡潔に述べています:「HTTPステータスコードに基づくリトライロジックは、ステータスが既に200であったため決してトリガーされません。結果は、通知なしに切り詰められた応答です」—そして、ステータスコードのみに依存するのではなく、ストリーム本文内のエラーイベントを解析することを推奨しています。
OpenRouterのドキュメントは、1つのベンダーだけでなく、ルーティング先のプロバイダー全体で同じ構造上の問題を説明しています。最初のトークンが書き込まれると、「HTTP 200 OK ステータスとヘッダーは既にコミットされています — 変更できません」そのため、プロバイダーの障害は「SSEイベントとして帯域内で到着する必要があリます」。このクラスのエラ—について文書化された原因は次の通リです:
- プロバイダ—の切断 — 部分的な出力後に上流の接続が切れる(ネットワ—ク問題、プロバイダ—のクラッシュ、ロ—ドバランサ—のタイムアウト)
- プロバイダータイムアウト — 生成途中でモデルが応答を停止し、読取り期限が切れる
- 生成中のトークン制限に到達 — モデルが出力中に
max_tokensまたはコンテクストウィンドウを埋める - 出力コンテンツフイルタ— — 一部が既にストリ—ムされた後、モデレ—ションシステムが生成されたテキストをフラグする
- プロバイダ—の過負荷 — ストリ—ム開始後に上流がレ—ト制限または容量エラ—を返す
OpenRouterのストリーム途中のエラ—ペイロ—ドは、正常に見えるチャンク内にエラ—を運び、finish_reason がストリ—ムが異常終了したことを示します:
{"id":"gen-abc123","object":"chat.completion.chunk","created":1234567890,"model":"openai/gpt-4o","provider":"OpenAI","error":{"code":429,"message":"Rate limit exceeded","metadata":{"error_type":"rate_limit_exceeded"}},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}
これが原因であることを確認する方法: 失敗したリクエストの生のSSEストリ—ム(解析されたテキスト出力だけでな<)をログします。終了イベントの前のストリ—ム内のどこかで error イベントまたは最上位の error フィールドを持つチャンクが見つかれば、これが原因です。
修正方法: finish_reason: "error" および event: error ペイロードを、空のコンテンツを持つ成功した完了としてではなく、リトライ可能な失敗として扱います。HTTPステータスではなく、ペイロード内の特定のエラータイプに基づいてバックオフでリトライします。HTTPステータスは既に200を読み取っているためです。
原因2: 読み取りタイムアウトまたはアイドルストリームの停止
ストールはハードエラーとは異なります:接続は開いたままですが、新しいトークンが長期間到着しません。ほとんどのHTTPクライアントは、2つのタイムアウト概念 — 総リクエストタイムアウトと読み取り(アイドル)タイムアウト — を混同しています。長いストリームは、大きな応答の場合、正当に短い総タイムアウトを超える可能性がありますが、本当に停止したストリームは、アイドル読み取りチェックが存在しない場合、総タイムアウトウィンドウ内に十分収まる可能性があります。
OpenAI Python SDK は、デフォルトの総リクエストタイムアウトを10分に設定しています:「デフォルトでは、リクエストは10分後にタイムアウトします。これは timeout オプションで設定でき、float または httpx.Timeout オブジェクトを受け入れます… タイムアウト時には APITimeoutError がスローされます。」また、特定の障害を自動的にリトライします:「接続エラー(ネットワーク接続の問題など)、408 Request Timeout、409 Conflict、429 Rate Limit、および >=500 の内部エラーはすべてデフォルトでリトライされ」、2回、max_retries で設定可能です。
確認方法: 最後に受信したトークンとエラーの間のギャップを測定します。設定されたタイムアウト値に一致する固定期間は、プロバイダー側の障害ではなくタイムアウトを示しています。
修正方法: 1つではなく2つのタイムアウトを設定します — リクエストライフサイクル用の接続/総タイムアウトと、N秒以内にチャンクが到着しない場合に発動する個別のアイドル読み取りタイムアウトです。これにより、「モデルが遅い」と「ストリームが死んだ」を区別できます。チャット完了の場合、15〜30秒が妥当なアイドル読み取りタイムアウトです。モデルに最初のトークンまでの長い無言の「思考」フェーズがある場合は、これを引き上げてください。
原因3: ストリーム途中のレート制限(429)
レート制限は通常、リクエストが開始される前に拒否します。ただし、一部のゲートウェイはトークンごとまたはウィンドウごとに制限を適用するため、リクエストがストリーミングを開始し、生成途中で予算を超えると遮断される可能性があります — 原因1 と同じ帯域内エラー形状で、初期応答ステータスとしてではなく、ストリーム内に 429 コードが到着します。
確認方法: 障害がトラフィックのバースト時や、一貫した1分あたりのリクエスト数のしきい値でクラスタリングしているかどうかを確認します。Retry-After ヘッダー、またはエラーペイロード内の同等のフィールドがそれを確認します。
修正方法: Retry-After が存在する場合はそれを尊重し、リトライ前に同時実行数を減らし、指数バックオフを行います。頻繁なヒットは、より積極的にリトライするのではなく、並列ストリーム数を減らすかアカウントティアをアップグレードするシグナルです。
原因4: 不正な形式または予期しないチャンクがパーサーを破壊する
すべての「ストリ—ミングエラ—」がモデルの責任ではありませン。一部は完全にクライアント側のものです:SSEパーサ—がすべてのチャンクに固定された形状があると想定しており、異なる形状のペイロ—ド(プロバイダ—からの正当なエラ—ペイロ—ドを含む)がその想定を破り、無関係に見える例外をスローします。
文書化されたケース:LiteLLMの ollama_chat ストリーミングハンドラーは、すべてのチャンクに message フィールドが含まれていると想定していました。Ollama が代わりに {"error": "error parsing tool call: ..."} のような構造化されたエラ—を返した際、ハンドラ—は無防備の chunk["message"] ルックアップを行い、KeyError: 'message' を発生成しました。より広範な例外ハンドラ—がそれを捕え、APIConnectionError として再スローしました — 実際にはモデルのツールコール出力内のJSON解析障害であったものに対する、ネットワーク的なエラ—です。バグ報告書はその代価について明確です:それをデバッグしていたエンジニヤは、「実際には不正な形式のツールコールJSONであったものに対して reason=timeout / LLM request timed out をログに記録し」、問題ではなかったネットワークおよびインラストラクチャの原因を追跡するのに「誤診断に2日間を費やしました」。
一般的なパターン:プロバイダーが解析コードの予期しない形状でエラーを送信し、低レベルの例が発生し(KeyError、TypeError、JSONデコードエラ—)、包括的な except ブロックがそれを本当の原因を消去する汎用的な接続またはストリーミングエラーでラップします。
確認方法: 例外ラッピングコードの前に生のチャンクをログします。生のペイロ—ドに実際のメッセ—ジを持つ error フィ—ルドがある場合、クライアントは現在表示されている汎用的なエラ—に至る途中で有用な情報を破棄しています。
修正方法: message や delta などの期待されるフィールドにアクセスする前に error キーを確認し、明示的に分岐します。すべての障害を1つの汎用的な例外タイプにまとめるのではなく、再スロー時に上流のエラーメッセージとステータスコードを保持します。
原因5: ネットワーク接続の切断
時には原因は本当にネットワークです:プロキシまたはロ—ドバランサ—がアイドル期間後に長寿接続を閉じるか、クライアントのネットワークがリクエスト途中で変更されます。これは原因2 に類似していますが、修正方法が異なります — 接続自体が消えているのであり、自分のタイムアウトウィンドウ内で単にアイドル状態にあるだけではありませン。
確認方法: アプリケ—ション·レベルのタイムアウト例外ではなく、接続リセット·スタイルのエラ—(ECONNRESET、Broken pipe、Connection reset by peer)を探し、障害が既知の中間者のアイドル接続タイムアウト(多くのロードバランサーはデフォルトで60秒の非アクティブ)と相関しているかどうかを確認します。
修正方法: プロキシまたはロードバランサーを制御できる場合は、そのアイドル接続タイムアウトを予想される最大ストリーム期間よりも上げるか、キープアライブpingを追加します。ドロップが制御外の場合は、リトライ可能として扱います — ほとんどのストリーミングAPIは部分的なストリームの再開をサポートしていないため、リトライは生成を最初からやり直すことを意味します。
原因6: 認証またはクォータ障害がストリーム途中で顕在化する
一部のゲートウェイは、認証と課金を遅延検証します — 接続が開き、最初のチャンクを生成中に認証または残高チェックが行われ、その障害が、リクエスト時にクリーンな401/402としてではなく、ストリーミングエラーとして報告されます。
確認方法: エラーが断続的ではなく、特定のキーからのすべてのリクエストで発生しているかどうか、またキーのローテーション、課金イベント、または環境変更(プロダクションのベースURLに対する開発キー、またはその逆)の直後に開始されたかどうかを確認します。
修正方法: キーの形式(Authorization: Bearer <key>、生のキーではない)を確認し、キーがアクティブであることを確認し、リクエストレベルのデバッグとは別にアカウント残高を確認します。プロンプトやモデルに関係なく、すべてのリクエストが同一に失敗する場合は、前述の5つの原因ではなく、これを示しています。
6つすべてを処理するリトライおよびバックオフパターン
単一のリトライ戦略で、HTTPステータスだけでなくストリーム本文をチェックする場合、原因1 から 原因5 までをカバーできます:
import time
import openai
def stream_with_recovery(client, **kwargs):
max_attempts = 3
for attempt in range(max_attempts):
try:
collected = ""
stream = client.chat.completions.create(stream=True, **kwargs)
for chunk in stream:
choice = chunk.choices[0] if chunk.choices else None
if choice and getattr(choice, "finish_reason", None) == "error":
raise RuntimeError(f"mid-stream error: {chunk}")
if choice and choice.delta.content:
collected += choice.delta.content
return collected
except (openai.APITimeoutError, openai.APIConnectionError, openai.RateLimitError) as e:
if attempt == max_attempts - 1:
raise
time.sleep(2 ** attempt)
return collected
この例は、base_url="https://api.novita.ai/openai" を設定することで、Novita AI のOpenAI互換のチャット完リクエストエンドポイント に対しても動作する Open AI互換のクライアント形状を使用しています。上記のコード以外に重要な点が3つあります:
- 原因1 に従い、正常な完了を想定する前に、チヤンク内容に帯内エラ—がないか確認します。
- 特にレ—ト制限や過負荷エラ—の場合、即時リトライではなく指数バックオフ(
2 ** attempt、上限付き)を使用します。 - 将来のデバッグが原因4 の2日間の誤診断を繰り返さないように、独自の例外タイプでラップする前に、生の上流エラーメッセージをログに記録します。
特定のプロバイダーやモデルが他のものより頻繁に失敗する場合、そのリクエストクラスを別のモデルにルーティングすることは、必要になる前に用意しておく価値のある緩和策です — インシデント中に即興で行うのではなく、そのフォールバックポリシーを定義する方法については、マルチプロバイダーLLMサービスを定義されたアップタイム目標で運用する を参照してください。
結論
このエラ—の診断は、推測ではなく消去法のプロセスです:上部のチェックリストを使用して症状を6つの原因の1つに一致させ、各セクションが指摘する特定のシグナル(帯内 error イベント、タイムアウトに一致するストール期間、バースト相関の429、パーサーがチョークした生のペイロード、接続リセット文字列、または1つのキーからのすべてのリクエストで繰り返される失敗)でそれを確認します。原因1から3が最も一般的であり、原因1、3、および5は同じ根本的な修正を共有しています:ストリームが開始されたらHTTPステータスコードを信頼するのをやめ、代わりにストリーム自体が報告する内容に基づいてバックオフでリトライすることです。
上記のパターンを使用してリトライロジックを一度修正すると、最初の5つの原因が同時に解決されます。原因6は例外です — 無効なキーや残高不足をリトライで修正することはできないため、すべてのリクエストで同一の失敗が発生する場合は、ネットワーク問題ではなく構成チェックとして扱います。
よくある質問
LLM API リクエストが正常に動作することもあれば、ストリーミングエラーで失敗することもあるのはなぜですか?
断続的な障害は、構成の問題ではなく、ストリーム途中の原因を示しています — 不正なキーや間違ったエンドポイントなどの構成エラーは毎回失敗します。両方ともトラフィックに依存するため、最初にプロバイダーの過負荷とレート制限を確認してください。
ストリーミングエラーはタイムアウトと同じですか?
常に同じとは限りません。タイムアウトは、設定されたウィンドウ内に応答が到着しなかったことを意味します。ストリーム途中のエラーは、応答が開始され、いくつかのトークンが到着し、その後ストリーム内に障害イベントが送信されたことを意味します。エラ—処理は両者を区別する必要があリます。なぜなら、修正方法が異なるからです。
実際の問題が別のものだったのに、エラーメッセージが「connection error」と表示されるのはなぜですか?
多くのクライアントライブラリは、チャンクがパーサーの期待する形状と一致しない場合に、予期しない例外を汎用的な接続エラータイプでラップします — 原因4 では、JSON解析エラーが本当の原因が見つかるまで2日間接続タイムアウトとして報告された文書化されたケースを参照してください。
ストリーム途中のエラー後、最初からやり直さずにストリームを再開できますか?
ほとんどのOpenAI互換およびAnthropicスタイルのストリーミングAPIは、障害発生時点からの部分的なストリームの再開をサポートしていませン。完全なリクエストをリトライし、部分的な出力を追記するのではなく破棄して、重複コンテンツを避けてくダさい。
LLM API コールには常にストリミングを使用すべきですか?
ストリミングは、1つの大きなリクエストがタイムアウトするのを回しますが、こノガイド全体でカバ—されている部分的な出力障害モ—ドを導入します。部分的な出力を表示する必要がない短い応答の場合、ストリーミングなしのリクエストの方がエラ—処理が簡単です。
ストリーム応答の finish_reason: error はどういう意味ですか?
これは、一部のゲートウェイが途中で失敗したストリームの最後のチャンクに付加する終端信号であり、stop や length などの通常の値とは異なります。リクエストのHTTPステータスが200であったとしても、失敗した生成として扱います。
推奨記事
- 低コストと高アップタイムを実現する最適なマルチプロバイダーLLMサービスとは? — 複数のプロバイダー間でLLMトラフィックを実行するチーム向けのSLO設計、プロバイダー健全性モニタリング、およびインシデントプレイブック。
- OpenAI Python SDK:インストール、セットアップ、および実践的な統合 — 上記のコ—ド例で使用されているSDのクライアント構成、ストリ—ミング、リトラ、およびタイムアウトオプション。
- レート制限とは何か?AIサ—ビスのための実践ガイド — レ—ト制限がどのように実施され、その周りにリトライ動作を設計する方法。
