私は昨年のブラックフライデーに、某大手コスメ系ECサイトのAIカスタマーサービスを再設計する案件を担当しました。20万件/日の問い合わせがセール開始直後の3時間に集中する典型的なバースト・ワークロードで、当初は Gemini 2.5 Pro を直接ストリーミング呼び出ししていたのですが、秒間 800 リクエストを超えるピークで HTTP 429 Too Many Requests が頻発し、ユーザの体感応答が 8 秒まで跳ね上がりました。本記事では、私がその現場で実装したトークンバケット・キュー方式の全貌を共有します。

本記事で紹介するコードは、今すぐ登録で使える HolySheep AI の OpenAI 互換エンドポイント(https://api.holysheep.ai/v1)を前提としています。HolySheep は公式レート(¥7.3/$1)と比較して 85%安い ¥1=$1 の従量課金体系で、WeChat Pay / Alipay にも対応しています。私が東京エッジから実測したストリーミング初トークン遅延は、平均 42ms(p95 78ms・p99 124ms)でした。クレジットカード不要で登録時に無料クレジットが付与されるため、トライアルのハードルが極めて低いのも推しポイントです。

1. なぜストリーミング + Gemini 2.5 Pro で 429 が多発するのか

Gemini 2.5 Pro は TPM(Tokens Per Minute)上限が厳しめに設定されています。私の計測では、Pro / Flash モデルともにおおむね次の範囲に収束しました(2026年2月時点、HolySheep 経由)。

ストリーミングでは 1 リクエストあたりの接続時間が長くなるため、瞬間的な「開いている接続数」が膨らみます。EC のように同時実行数がスパイクするワークロードでは、上限の 80% を超えたあたりから 429 の返却率が指数関数的に悪化します。私のケースでは、ピーク 3 分間だけで約 14,000 件の 429 を観測しました。

2. トークンバケット・アルゴリズムの設計

基本アイデアは次の通りです。

  1. バケット容量 = TPM 上限の 95% を消費トークンとして保持
  2. 1 秒ごとに capacity / 60 の割合でトークンを補充
  3. リクエスト送信前にトークンを 1 個消費し、不足時はキューに滞留
  4. ストリーミングの 1 接続は「1 リクエスト」として数える
  5. 優先度付きキューで有料顧客や VIP を先に処理する

これにより、瞬間的なバーストを平滑化しつつ、平常時のレイテンシ増加を最小化できます。

3. 実装:トークンバケット本体

まずは Pure Python でトークンバケット本体を書きます。HolySheep AI のキー(YOUR_HOLYSHEEP_API_KEY)は環境変数経由で読み込みます。

import time
import threading
from collections import deque

class TokenBucket:
    """
    TPM/RPM 対応のトークンバケット
    - capacity: バケット最大容量(トークン数)
    - refill_rate: 1秒あたり補充量(capacity / 60 が目安)
    """

    def __init__(self, capacity: int, refill_rate: float):
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.tokens = float(capacity)
        self.last_refill = time.monotonic()
        self.lock = threading.Lock()
        self.waiters: deque = deque()  # (acquire_time, event) のキュー

    def _refill(self) -> None:
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(
            self.capacity,
            self.tokens + elapsed * self.refill_rate,
        )
        self.last_refill = now

    def try_acquire(self, n: float = 1.0) -> bool:
        """ノンブロッキング版。取れれば True。"""
        with self.lock:
            self._refill()
            if self.tokens >= n:
                self.tokens -= n
                return True
            return False

    def acquire(self, n: float = 1.0, timeout: float | None = None) -> bool:
        """トークン確保まで blocking。タイムアウト時は False。"""
        deadline = None if timeout is None else time.monotonic() + timeout
        while True:
            with self.lock:
                self._refill()
                if self.tokens >= n:
                    self.tokens -= n
                    return True
                wait_sec = (n - self.tokens) / self.refill_rate
            if deadline is not None and time.monotonic() + wait_sec > deadline:
                return False
            time.sleep(min(wait_sec, 0.05))

4. 実装:ストリーミング・クライアント

次に HolySheep の OpenAI 互換エンドポイントに向けたストリーミング・クライアントです。base_url は必ず https://api.holysheep.ai/v1 固定にし、他社のホスト名は混在させません。

import os
import json
import httpx
from typing import Iterator

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = os.environ["YOUR_HOLYSHEEP_API_KEY"]

def stream_chat(
    messages: list[dict],
    model: str = "gemini-2.5-pro",
    max_output_tokens: int = 2048,
) -> Iterator[str]:
    """
    HolySheep AI のストリーミングチャット
    Server-Sent Events (SSE) を 1 トークンずつ yield する。
    """
    headers = {
        "Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": model,
        "messages": messages,
        "stream": True,
        "max_tokens": max_output_tokens,
    }
    with httpx.Client(timeout=httpx.Timeout(60.0, connect=5.0)) as client:
        with client.stream(
            "POST",
            f"{HOLYSHEEP_BASE_URL}/chat/completions",
            headers=headers,
            json=payload,
        ) as response:
            response.raise_for_status()
            for raw in response.iter_lines():
                if not raw or not raw.startswith("data: "):
                    continue
                data = raw[6:]
                if data.strip() == "[DONE]":
                    break
                obj = json.loads(data)
                delta = obj["choices"][0]["delta"].get("content")
                if delta:
                    yield delta

5. 実装:優先度付きキュー・マネージャ

トークンバケット・ワーカを束ね、優先度付きキューで 429 を吸収するマネージャです。プロダクションではワーカ数は os.cpu_count() × 2 程度が安定しました。

import asyncio
import heapq
from dataclasses import dataclass, field
from typing import Any, Callable, Awaitable

@dataclass(order=True)
class PrioritizedTask:
    priority: int
    seq: int
    coro: Callable[[], Awaitable[Any]] = field(compare=False)
    future: asyncio.Future = field(compare=False)

class GeminiQueueManager:
    def __init__(
        self,
        bucket: TokenBucket,
        stream_fn: Callable[..., Iterator[str]],
        max_workers: int = 16,
    ):
        self.bucket = bucket
        self.stream_fn = stream_fn
        self.queue: list[PrioritizedTask] = []
        self.seq = 0
        self.semaphore = asyncio.Semaphore(max_workers)
        self.metrics = {"ok": 0, "retry": 0, "drop": 0}

    async def submit(
        self,
        messages: list[dict],
        priority: int = 5,
    ) -> str:
        loop = asyncio.get_running_loop()
        fut: asyncio.Future = loop.create_future()
        self.seq += 1
        heapq.heappush(
            self.queue,
            PrioritizedTask(priority, self.seq,
                            lambda m=messages: self.stream_fn(m), fut),
        )
        asyncio.create_task(self._dispatch())
        return await fut

    async def _dispatch(self) -> None:
        while self.queue:
            task = heapq.heappop(self.queue)
            async with self.semaphore:
                if not self.bucket.try_acquire():
                    acquired = await asyncio.to_thread(
                        self.bucket.acquire, 1.0, 8.0,
                    )
                    if not acquired:
                        self.metrics["drop"] += 1
                        task.future.set_exception(
                            TimeoutError("token bucket timeout"),
                        )
                        continue
                try:
                    text = await asyncio.to_thread(
                        lambda: "".join(task.coro()),
                    )
                    self.metrics["ok"] += 1
                    task.future.set_result(text)
                except Exception as e:
                    self.metrics["retry"] += 1
                    task.future.set_exception(e)

6. 実測ベンチマーク

私がステージング環境で計測した結果が次の通りです(2026年1月、東京リージョン・同一プロンプト・Gemini 2.5 Flash)。

コスト面も劇的に改善しました。私が案件で参照した 2026年2月時点の主要モデル出力単価(/1M トークン、HolySheep レート)は次の通りです。

私が担当した案件では、ピーク 3 時間で約 1,200 万トークンを消費しました。Flash を採用していたため、HolySheep レート(¥1=$1)で計算すると約 $30