ブログに戻る
コラム

LLM APIのレートリミット(429)とは?原因と対処法を解説

FastMetal

LLM APIを本番で使い始めると、ほぼ確実に一度は出会うのが HTTP 429 エラーです。429は「Too Many Requests」を意味する状態コードで、**レートリミット(rate limit)**に引っかかったときに返されます。ただし、LLM APIの429には「送りすぎ」以外の原因もあり、原因を取り違えると、リトライしても永久に成功しないコードを書いてしまいます。

この記事では、レートリミットの仕組みと、429が意味する2つの状況の見分け方、そして正しい対処パターンを解説します。OpenAI互換APIを前提に、コード例はOpenAI SDKで示します。

レートリミットとは

レートリミットとは、一定時間内にAPIキーごとに受け付けるリクエスト量の上限です。LLM APIでは、主に次の2種類の軸で設定されます。

種類意味引っかかりやすい状況
RPM(Requests Per Minute)1分あたりのリクエスト数短いリクエストを大量に並列送信したとき
TPM(Tokens Per Minute)1分あたりの処理トークン数長いプロンプトや長文出力を連続で送ったとき

上限を超えたリクエストは処理されず、429で即座に返されます。なぜこうした制限があるかというと、LLMの推論はGPUという共有資源を使うため、一部の利用者が無制限に送ると、他の全員の応答が遅くなるからです。プロバイダーにとってレートリミットは、共有インフラの品質を守るための仕組みです。

具体的な上限値はプロバイダー・モデル・契約によって異なります。数字を前提にコードを書くのではなく、429が返ることを前提に設計するのが基本です。

429が意味する2つの状況

ここがLLM API特有の注意点です。同じ429でも、意味がまったく違う2つのケースがあります。

1. 本当のレートリミット(送りすぎ)

RPMやTPMの上限を一時的に超えた状態です。時間が経てば枠は回復するので、少し待って再送すれば成功します。リトライが正しい対処です。

2. 残高切れ(プリペイド型ゲートウェイの場合)

FastMetalのようなプリペイド型のAPIでは、APIキーごとにチャージした残高が上限になります。残高を使い切ると、リクエストは HTTP 429 で拒否され、レスポンスボディに budget_exceeded が含まれます

この場合、いくら待っても状況は変わりません。残高をチャージしない限り成功しないので、リトライしてはいけません。単純な「429なら待って再送」のループは、残高切れのキーに対して無意味なリトライを繰り返すだけになります。

つまり、ステータスコードだけで判断せず、レスポンスボディを見て原因を分ける必要があります。残高が少なくなると事前にアラートメールが届くので、あわせて活用してください(残高アラートメールの解説)。

正しい対処パターン

指数バックオフ+ジッター

本当のレートリミットに対しては、待ち時間を1秒、2秒、4秒…と倍々に伸ばしながら再送する指数バックオフが定石です。さらに、複数のクライアントが同時に再送して再び衝突しないよう、待ち時間に乱数(ジッター)を加えます。レスポンスに Retry-After ヘッダーがあれば、その秒数を優先します。

リトライ回数には必ず上限を設け、超えたらエラーとして呼び出し元に返します。

残高切れはリトライしない

budget_exceeded を検出したら、即座に例外として表面化させ、チャージを促す・管理者に通知する、といった処理に切り替えます。

同時実行数を制限する

429を「受けてから対処する」より、そもそも出さないほうが効率的です。セマフォで同時実行数を絞れば、並列度を制御しつつ上限内に収められます。バッチ処理なら、一斉送信ではなくキューに積んで順に処理するのが安全です。

1リクエストのトークンを減らす

TPM系の制限は、リクエスト数ではなくトークン量で決まります。プロンプトの冗長な部分を削り、出力長を max_tokens で制限するだけで、同じRPMでも処理できる件数が増えます(トークンの基礎)。

Pythonでの実装例

OpenAI SDKの base_url をFastMetalに向け、429を原因別に処理する例です。

import random
import time
import openai
from openai import OpenAI

client = OpenAI(
    api_key="<FASTMETAL_API_KEY>",
    base_url="https://api.fastmetal.ai/v1",
)

def chat(messages, max_retries=5):
    for attempt in range(max_retries):
        try:
            return client.chat.completions.create(
                model="anthropic-claude-haiku-4-5",
                messages=messages,
                max_tokens=300,
            )
        except openai.RateLimitError as e:
            if "budget_exceeded" in str(e):
                raise RuntimeError("残高切れです。チャージしてください") from e
            wait = min(2 ** attempt + random.uniform(0, 1), 30)
            time.sleep(wait)
    raise RuntimeError("レートリミットのリトライ上限に達しました")

resp = chat([{"role": "user", "content": "こんにちは"}])
print(resp.choices[0].message.content)

ポイントは、RateLimitError を捕まえたあとにメッセージの中身で分岐している点です。budget_exceeded ならその場で例外を投げ、それ以外は指数バックオフで再送します。並列で呼ぶ場合は、この関数を threading.Semaphoreasyncio.Semaphore で包んで同時実行数を絞ってください。

なお、FastMetalの無料モデル(モデルIDが -free で終わるもの)には、キーごとに独自のレートリミットが設定されています。無料モデルで429が出た場合も、同じバックオフの考え方で対処できます。

よくある質問

Q. 429が出たら、とにかく待って再送すればいいですか? 本当のレートリミットならそれで正解ですが、プリペイド型のAPIでは残高切れも429で返ります。レスポンスボディに budget_exceeded が含まれていればリトライは無意味なので、チャージを促す処理に切り替えてください。

Q. レートリミットの具体的な上限値はどこで確認できますか? プロバイダーやモデルごとに異なり、変更されることもあります。数値に依存した設計ではなく、429が返る前提でバックオフと同時実行制限を組み込んでおくのが安全です。

Q. リトライは何回まで行うべきですか? 一般的には3〜5回程度で上限を設け、超えたら呼び出し元にエラーを返します。無制限にリトライすると、障害時に処理が滞留し、原因の切り分けも難しくなります。

まとめ

レートリミットは共有インフラを守るための仕組みで、429は「送りすぎ」のサインです。ただしプリペイド型のLLM APIでは、同じ429が「残高切れ」を意味することもあるため、レスポンスボディで原因を見分け、前者は指数バックオフで再送、後者はリトライせずチャージへ誘導する、という分岐が欠かせません。同時実行数の制限とトークン削減で、429そのものを減らす設計も忘れずに。

FastMetalはプリペイド型OpenAI互換APIなので、上のコードがそのまま使えます。Chat Completions APIの基本とあわせて、ドキュメントから最初のリクエストを送ってみてください。モデルごとの単価は料金ページで確認できます。

最新のAIモデルを今すぐ試す

最新のAIモデルはFastMetalのAPIキー1つで利用できます。ブラウザですぐに試す、またはOpenAI SDKからそのまま呼び出せます。