2

AIエージェント Rate Limiting設計ガイド

AIエージェント Rate Limiting設計ガイド

この記事の結論

AIエージェントのAPIコスト暴走を防ぐ。トークンバジェット・バックオフ・Redis制限の実装。

「なんか今月のAPI費用が先月の3倍になってる…」

AIエージェントを本番導入した直後、こんな報告が上がってくることは珍しくない。原因を調べると、エラーループに陥ったエージェントが同じAPIを数百回呼び出していた——という話はリアルに起きている。

従来のRate Limitingは「1分間に100リクエスト以下」といったリクエスト数ベースの制御だった。しかしAIエージェントは1回のリクエストで50トークンのものもあれば10,000トークンのものもあり、均一なカウントは実態を反映しない。Gartnerは「2026年にAPIトラフィック増加の30%以上がAI・LLMツールから来る」と予測しており、APIゲートウェイの設計思想が根本から変わりつつある。

この記事では、AIエージェント特有の暴走パターンを整理し、トークンバジェット・リクエスト制限・バックオフ戦略をコード付きで解説する。

なぜ従来のRate Limitingが壊れるのか

まずエージェント特有の3つの問題を押さえておきたい。

1つ目は「コストの非均一性」だ。50トークンのプロンプトと10,000トークンのプロンプトは、リクエストカウント上は同じ「1」だが、コストは200倍異なる。

2つ目は「バースト性」。自律エージェントはツール呼び出しを10〜20回連鎖させることがある。人間のAPIアクセスとは桁違いのバースト性を持つ。

3つ目は「ループ障害」。エラーハンドリングを誤ると、エージェントが同じAPIを無限に叩き続ける。人間であれば途中で気づくが、エージェントは止まらない。

以下に、リクエストベースとトークンベースの比較を示す。

比較項目 リクエストベース トークンベース
カウント単位 HTTPリクエスト数 消費トークン総数
コスト反映 反映されない 実態に即している
バースト検知 大規模バーストのみ 小規模でも検知可
モデル違いの扱い 区別なし コスト重み付け可
適した用途 均一コストREST API LLM API全般

AIエージェントの設計全体については、AIエージェント構築完全ガイドで体系的に整理しているので、基礎から確認したい方はそちらも参照してほしい。

すぐ実装できるトークンバジェット設計

エージェント全体に「1回の実行で使えるトークン上限」を設けるのが最も効果的な第一手だ。以下のコードはPython + OpenAI SDK向けのシンプルなトークンバジェット管理クラスになる。


# 動作環境: Python 3.11+, openai>=1.30.0
# pip install openai tiktoken
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。

import tiktoken
from openai import OpenAI
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class TokenBudget:
    """エージェント実行ごとのトークン予算管理"""
    max_total: int = 50_000          # 1実行あたりの上限
    max_per_call: int = 8_000        # 1APIコールあたりの上限
    input_used: int = 0
    output_used: int = 0
    call_count: int = 0
    _warnings: list = field(default_factory=list)

    @property
    def total_used(self) -> int:
        return self.input_used + self.output_used

    @property
    def remaining(self) -> int:
        return self.max_total - self.total_used

    def check_budget(self, estimated_tokens: int) -> bool:
        """実行前に予算残高を確認"""
        if estimated_tokens > self.remaining:
            self._warnings.append(
                f"Budget exceeded: need {estimated_tokens}, remaining {self.remaining}"
            )
            return False
        if estimated_tokens > self.max_per_call:
            self._warnings.append(
                f"Single call too large: {estimated_tokens} > {self.max_per_call}"
            )
            return False
        return True

    def record_usage(self, usage):
        """APIレスポンスから使用量を記録"""
        self.input_used += usage.prompt_tokens
        self.output_used += usage.completion_tokens
        self.call_count += 1

client = OpenAI()
enc = tiktoken.encoding_for_model("gpt-4o")

def agent_call_with_budget(
    messages: list,
    budget: TokenBudget,
    model: str = "gpt-4o-mini"  # コスト最適化: シンプルタスクは mini を使用
) -> Optional[str]:
    """バジェット管理付きAPIコール"""
    # 事前トークン推定
    estimated = sum(len(enc.encode(m["content"])) for m in messages)

    if not budget.check_budget(estimated):
        raise ValueError(f"Token budget exhausted: {budget._warnings[-1]}")

    response = client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=min(2000, budget.remaining)  # 残り予算を上限に
    )

    budget.record_usage(response.usage)
    return response.choices[0].message.content

動作環境: Python 3.11+, openai>=1.30.0, tiktoken>=0.7.0

ポイントは3点。まず実行前に推定トークン数をチェックして超過を早期検知すること。次に1コールあたりの上限(max_per_call)と合計上限(max_total)を分けて管理すること。そして max_tokens を残予算に連動させることで、想定外の大量出力を防止する。

指数バックオフとジッター:ループ防止の実装

エラーループ対策には「指数バックオフ(Exponential Backoff)+ジッター(Jitter)」が定石だ。同時に複数のエージェントが再試行するとスパイクが生じるため、ランダム揺らぎ(ジッター)を加える必要がある。


# 動作環境: Python 3.11+, openai>=1.30.0
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。

import time
import random
import logging
from functools import wraps

logger = logging.getLogger(__name__)

def exponential_backoff(
    max_retries: int = 5,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    jitter: bool = True
):
    """指数バックオフ+ジッターデコレータ"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries:
                        logger.error(f"Max retries exceeded: {e}")
                        raise

                    # 指数バックオフ計算
                    delay = min(base_delay * (2 ** attempt), max_delay)

                    # ジッター追加(同時実行時のスパイク防止)
                    if jitter:
                        delay *= (0.5 + random.random() * 0.5)

                    logger.warning(
                        f"Attempt {attempt + 1}/{max_retries} failed: {e}. "
                        f"Retrying in {delay:.1f}s"
                    )
                    time.sleep(delay)
        return wrapper
    return decorator

@exponential_backoff(max_retries=5, base_delay=1.0, jitter=True)
def safe_llm_call(messages: list) -> str:
    """バックオフ付きAPIコール"""
    # ここに実際のAPIコール
    pass

ポイント: base_delay * (2 ** attempt) で指数的に待機時間を増やしつつ、random.random() でジッターを加える。5回リトライで最大60秒の待機キャップを設けることで、無限ループを防止できる。

APIゲートウェイレベルのRate Limiting設計

アプリケーション側だけでなく、APIゲートウェイレベルでのRate Limitingも重要だ。Zuplo等のAI対応ゲートウェイでは、トークン消費量を追跡してリクエストを制御できる。


# 動作環境: Python 3.11+
# Redis使用(pip install redis)
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。

import redis
import time
from typing import Tuple

class TokenRateLimiter:
    """Redisを使ったスライディングウィンドウ型トークンRate Limiter"""

    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client

    def check_and_consume(
        self,
        user_id: str,
        tokens_to_consume: int,
        window_seconds: int = 3600,   # 1時間ウィンドウ
        hourly_limit: int = 100_000,  # 1時間あたり10万トークン
        monthly_limit: int = 5_000_000  # 月500万トークン
    ) -> Tuple[bool, dict]:
        """
        トークン消費前に残高確認し、OK なら消費記録する
        Returns: (allowed, status_dict)
        """
        now = int(time.time())
        hour_key = f"tokens:{user_id}:hour:{now // window_seconds}"
        month_key = f"tokens:{user_id}:month:{now // (3600 * 24 * 30)}"

        pipe = self.redis.pipeline()
        pipe.incrby(hour_key, tokens_to_consume)
        pipe.expire(hour_key, window_seconds * 2)
        pipe.incrby(month_key, tokens_to_consume)
        pipe.expire(month_key, 3600 * 24 * 31)
        hourly_used, _, monthly_used, _ = pipe.execute()

        # 超過チェック(先に加算してからチェックするため、加算分を引く)
        hourly_current = hourly_used - tokens_to_consume
        monthly_current = monthly_used - tokens_to_consume

        if hourly_current + tokens_to_consume > hourly_limit:
            # ロールバック
            self.redis.decrby(hour_key, tokens_to_consume)
            self.redis.decrby(month_key, tokens_to_consume)
            return False, {
                "error": "hourly_limit_exceeded",
                "used": hourly_current,
                "limit": hourly_limit,
                "reset_in": window_seconds - (now % window_seconds)
            }

        return True, {
            "hourly_used": hourly_used,
            "hourly_limit": hourly_limit,
            "monthly_used": monthly_used,
            "monthly_limit": monthly_limit
        }

スライディングウィンドウ方式を採用することで、固定ウィンドウの「境界突破」(ウィンドウ切り替え直後に大量リクエストを送る攻撃)を防げる。

モデルルーティングによるコスト最適化

Rate Limitingと組み合わせて効果的なのが、タスク複雑度に応じたモデルルーティングだ。単純なクエリにGPT-4oを使い続けるのは明らかな過剰投資になる。

タスク種別 推奨モデル コスト目安(入力100万トークン) 備考
分類・ルーティング GPT-4o mini / Haiku $0.15〜0.25 シンプルな判断に最適
RAG・要約 GPT-4o / Claude Sonnet $2.50〜3.00 品質とコストのバランス
複雑な推論 o3 / Claude Opus $10〜15 本当に複雑な問題のみ
コード生成 Claude Sonnet / GPT-4o $2.50〜3.00 精度重要タスク

※価格は2026年4月時点の公式サイト参照値。変動するため最新は各社公式を確認。

実際に検証されている事例として、80%のシンプルクエリをGPT-3.5系、20%の複雑クエリをGPT-4系にルーティングすることで月額コストを$54,000から$12,000以下に削減したケースが報告されている(Moltbook-AI.com, 2026)。ただし自社のユースケースで実測することが前提だ。

【要注意】よくある失敗パターンと回避策

Rate Limiting設計で繰り返される失敗がある。

失敗1: リトライに上限を設けない

❌ APIエラーが出たら無限再試行
⭕ max_retries=5 + 指数バックオフ + タイムアウト設定

なぜ重要か: 無限ループで月の予算を数時間で使い切るケースが実際にある。

失敗2: 入力トークンだけ見て出力を無視する

❌ プロンプトトークン数だけを制限
⭕ prompt_tokens + completion_tokens の合計で管理

なぜ重要か: 出力トークンは入力の3〜10倍コストになるモデルが多い。長文レスポンスで予算超過しやすい。

失敗3: エージェント間でバジェットを共有しない

❌ 各エージェントが独自のRate Limitを持つ
⭕ ユーザーIDまたはプロジェクトIDで共有バジェットを管理

なぜ重要か: マルチエージェント構成では複数エージェントが同時に動いてバジェットを食いつくすことがある。

失敗4: 本番環境でいきなり厳しい制限をかける

❌ 本番で100トークン/秒制限→エージェントが頻繁に詰まる
⭕ まず測定→適切な上限を設定→段階的に引き締める

なぜ重要か: 制限が厳しすぎると正常なフローがブロックされる。まずモニタリングで実際の使用量を把握することが先決だ。

モニタリングとアラートの設定

Rate Limitingを入れた後は、実際の消費状況を監視する仕組みが必要になる。コスト管理・オブザーバビリティの実装については、AIエージェントのオブザーバビリティ設計(OpenTelemetry)も合わせて参照してほしい。

最低限設定すべきアラート指標は以下の3つだ。

  • 1時間あたりトークン消費が前日比300%超:ループ障害の早期検知
  • 429エラー(Rate Limit超過)が1分間に10回超:制限設定の見直しシグナル
  • 月次予算の80%到達:予算アラートで事前対処

参考・出典

まとめ:今日から始める3つのアクション

  1. 今日やること:既存エージェントのAPIコール数と月間トークン消費量を計測する。何も計測していない場合は、まずここから始める。
  2. 今週中:TokenBudgetクラスを実装して、1実行あたりの上限(max_total)を設定する。指数バックオフも同時に入れる。
  3. 今月中:Redisを使ったゲートウェイレベルのRate Limiterを本番に展開し、モニタリングアラートを設定する。

あわせて読みたい:


この記事はAIgent Lab編集部がお届けしました。AIエージェント導入のご相談は Uravationお問い合わせフォーム からお気軽にどうぞ。

Need help moving from reading to rollout?

この記事を読んで導入イメージが固まってきた方へ

Uravationでは、AIエージェントの要件整理、PoC設計、社内導入、研修まで一気通貫で支援しています。

この記事をシェア

X Facebook LINE

※ 本記事の情報は2026年4月時点のものです。サービスの料金・仕様は変更される可能性があります。最新情報は各サービスの公式サイトをご確認ください。

関連記事