429は429じゃない――LLMプロキシで学んだエラー分類の設計パターン

LLM APIを運用していると、429 Too Many Requestsに何度も遭遇する。「レートリミットに達した」というHTTPステータスコードだけを見れば、一律に「待ってから再試行すればいい」と思うかもしれない。

しかし実際には、同じ429の中に、90秒で回復する一時的スロットリングと、人間が課金しないと解決しない残高不足が同居している。この違いを無視して一律に「プロバイダ枯渇」と扱うと、一時的なスロットリングで7日間ブロックするような過剰反応が起きる。

問題:429 = 枯渇という二元論

私たちが運用しているLLMプロキシ(Cloudflare Workers上で稼働)は、複数プロバイダのクオータ枯渇時に自動フォールバックする仕組みだ。当初の実装では、429を受け取ると一律に「そのプロバイダは枯渇した」とみなし、プロバイダのリセットスケジュール(週次または日次)までKVでブロックしていた。

これが過剰だった。例えば、あるLLM APIサービスが一時的な負荷でレートリミットエラー(エラーコード1302)を返しただけで、プロバイダ全体が最長7日間ブロックされていた。90秒で回復すべきものを、週次リセットまで待つ必要はない。

解決策:4つのティアによるエラー分類

エラーコードとメッセージを解析し、429を4つのティアに分類した:

  • transient(1302, 1305):一時的なレートリミット・サービス過負荷。90秒のクールダウン。KVを汚染しない。
  • time_windowed(1308, 1310):5時間使用上限・週次/月次上限到達。エラーメッセージからリセット時刻をパースしてTTLを設定。
  • manual_action(1113, 1309):残高不足・パッケージ期限切れ。7日のTTL。人間のアクション(課金・更新)が必要。
  • default_quota:分類不能なエラー。プロバイダの固定スケジュールにフォールバック。

中国語エラーメッセージのパース問題

time_windowedティアの実装で直面した意外な壁:LLM APIサービスのエラーメッセージが中国語でリセット時刻を通知することがある。

已达到 5 小时的使用限额。您的限额将在 2026-05-09 20:42:25 重置。

一方で、英語メッセージではプレースホルダーを使う:

You have reached the 5-hour usage limit. Your quota will reset at next_flush_time.

英語メッセージのnext_flush_timeは実際のタイムスタンプではなくプレースホルダーであり、パースできない。中国語メッセージには具体的な日時が埋め込まれている。どちらの形式も処理できるように実装した——英語ならプレースホルダー検出でfallback、中国語ならタイムスタンプ抽出でTTL計算。

この問題は他のプロジェクトでも報告されている。英語ベースのエラー分類器が中国語エラーメッセージを捕捉できないケースが散見される。多言語エラーメッセージを扱うAPIプロキシでは、言語の違いによるパース漏れを明示的に設計に組み込む必要がある。

設計原則の整理

この経験から、LLM APIプロキシのエラー処理における3つの原則を整理した:

1. HTTPステータスコードは事象を名指しするが、原因を名指ししない

429は「リクエストが多すぎる」という事象を伝える。しかし、その原因が一時的なスロットリングか、恒常的な上限到達か、残高不足かは、エラーメッセージの深層にしか書かれていない。ステータスコードだけを見て一律に処理すると、過剰なブロックや不要な待機が発生する。

2. 不確かな情報よりも、情報がないことを正直に伝える

この原則は別の改善でも実践した。プロキシが各モデルのコンテキストウィンドウを固定値130kで返していたが、多くのモデルで実態と合っていない。クライアント側が「まだ余裕がある」と誤認し、コンテキスト長超過でAPIエラーを起こすリスクがあった。

固定値を廃止し、元プロバイダから情報が返らない場合は「情報なし」を貫通して返す方針に切り替えた。不確かな値をでっち上げることで生じる誤認リスクは、値がないことによる不便よりも危険だ。

3. リセット時刻が分かるなら、推測で待つ必要はない

time_windowedティアの本質はここにある。エラーメッセージに「2026-05-09 20:42:25にリセットされる」と書かれているなら、その時刻まで待てばいい。一律に「週次リセットまでブロック」するのは、情報を無視した推測に基づく待機だ。

エラーメッセージからリセット時刻をパースしてTTLを計算する——これが「分かることを使う」設計だ。分からない場合(transientやmanual_action)には、ティアに応じた合理的なフォールバック値を設定する。

KV値形式の改善

従来のKV値は単なるタイムスタンプ(raw timestamp)だった。改善後はJSON形式に変更し、分類カテゴリ・エラーコード・リセット時刻を含む:

// 従来
"1700000000"

// 改善後
{
  "category": "time_windowed",
  "error_code": "1308",
  "reset_at": "2026-05-09T20:42:25+08:00"
}

これにより、/statusエンドポイントでの診断性が大幅に向上した。「このプロバイダが枯渇している」だけでなく、「1308コードで5時間上限に達し、20:42にリセットされる」という具体的な状況が読み取れる。後方互換性も確保——raw timestampの古い値も正常に処理される。

まとめ

LLM APIの429エラーは、表面上は1つのステータスコードだが、内部には複数の意味が層をなしている。これを4つのティアに分類することで:

  • 一時的スロットリングを90秒で回復させ、不要な長期ブロックを排除
  • リセット時刻が分かる場合は推測で待たず、正確なTTLを設定
  • 人間のアクションが必要なエラーを7日TTLで隔離し、自動回復の期待を排除

「429は429じゃない」——この認識が、プロキシ運用の過剰反応を合理的な分類に変える。

この記事は、Cloudflare Workers上のLLMプロキシ運用で得られた知見を再構成したもの。プロキシは複数プロバイダのクオータ枯渇時に自動フォールバックする仕組みで、エラー分類はその信頼性を担保する基盤的な設計要素だ。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール