GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-03-26上級

Gemini API AIゲートウェイ設計パターン — レート制限・フォールバック・コスト追跡を統合するプロキシサーバー構築ガイド

Gemini APIを本番運用するためのAIゲートウェイ(プロキシサーバー)を設計・実装する上級ガイド。レート制限、自動フォールバック、トークンコスト追跡、複数モデルルーティングを一元管理するアーキテクチャを解説します。

gemini-api279gatewayproxyrate-limiting4cost-optimization21production105architecture10python103advanced12

取り組みの背景 — なぜ AIゲートウェイが必要なのか

Gemini API を1つのアプリケーションから呼び出すだけなら、SDKを直接使えば十分です。しかし本番環境では、複数のサービスが同時にAPIを呼び出し、レート制限に引っかかり、コストが予測不能に膨らむ問題が避けられません。

AIゲートウェイ(APIプロキシ)は、アプリケーションとGemini APIの間に立つ中間レイヤーです。すべてのAPIリクエストを一箇所で管理することで、以下の課題を解決します。

  • レート制限の一元管理: 複数サービスからのリクエストを集約し、429エラーを事前に防止
  • 自動フォールバック: Gemini 2.5 Pro がダウンした場合に Flash へ自動切り替え
  • トークンコスト追跡: リクエストごとのトークン使用量とコストをリアルタイムに記録
  • モデルルーティング: リクエストの複雑さに応じて Pro / Flash を自動選択

AIゲートウェイのアーキテクチャ設計

全体構成

AIゲートウェイは4つの主要コンポーネントで構成されます。

┌─────────────┐     ┌──────────────────────────────────────┐     ┌─────────────┐
│  Service A  │────▶│          AI Gateway (Proxy)           │────▶│ Gemini 2.5  │
│  Service B  │────▶│  ┌────────┐ ┌──────┐ ┌───────────┐  │────▶│    Pro       │
│  Service C  │────▶│  │Rate    │ │Router│ │Cost       │  │────▶│ Gemini 2.5  │
│  Service D  │────▶│  │Limiter │ │      │ │Tracker    │  │     │    Flash    │
│             │     │  └────────┘ └──────┘ └───────────┘  │     └─────────────┘
└─────────────┘     └──────────────────────────────────────┘

設計原則

ゲートウェイを設計する際の重要な原則は3つあります。

透過性(Transparency): クライアント側はゲートウェイの存在を意識せず、通常のGemini APIと同じインターフェースで呼び出せること。

耐障害性(Resilience): 1つのモデルやリージョンが障害を起こしても、自動的に代替パスへフォールバックし、クライアントにエラーを返さないこと。

可観測性(Observability): すべてのリクエストのレイテンシ、トークン数、コスト、エラー率をリアルタイムに計測・記録すること。

レート制限の実装 — トークンバケットアルゴリズム

Gemini API のレート制限は RPM(Requests Per Minute)と TPM(Tokens Per Minute)の2軸で管理されています。ゲートウェイ側でこれを先回りして制御することで、429エラーの発生を防ぎます。

import time
import asyncio
from dataclasses import dataclass, field
from typing import Optional
 
@dataclass
class TokenBucket:
    """トークンバケットによるレート制限"""
    capacity: float          # バケットの最大容量
    refill_rate: float       # 1秒あたりの補充量
    tokens: float = field(init=False)
    last_refill: float = field(init=False)
    _lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False)
 
    def __post_init__(self):
        self.tokens = self.capacity
        self.last_refill = time.monotonic()
 
    async def acquire(self, count: float = 1.0, timeout: float = 30.0) -> bool:
        """トークンを取得する。取得できるまで待機(タイムアウトあり)"""
        deadline = time.monotonic() + timeout
        async with self._lock:
            while True:
                self._refill()
                if self.tokens >= count:
                    self.tokens -= count
                    return True
                # 次のトークン補充まで待機
                wait_time = (count - self.tokens) / self.refill_rate
                if time.monotonic() + wait_time > deadline:
                    return False  # タイムアウト
                await asyncio.sleep(min(wait_time, 0.1))
 
    def _refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now
 
class RateLimiter:
    """RPM + TPM の二重レート制限"""
 
    def __init__(self, rpm: int = 360, tpm: int = 4_000_000):
        # RPM: 1分あたりのリクエスト数
        self.request_bucket = TokenBucket(
            capacity=rpm,
            refill_rate=rpm / 60.0
        )
        # TPM: 1分あたりのトークン数
        self.token_bucket = TokenBucket(
            capacity=tpm,
            refill_rate=tpm / 60.0
        )
 
    async def acquire_request(self, estimated_tokens: int = 1000) -> bool:
        """リクエスト送信前にレート制限を確認"""
        req_ok = await self.request_bucket.acquire(1.0)
        tok_ok = await self.token_bucket.acquire(float(estimated_tokens))
        return req_ok and tok_ok
 
    async def report_actual_tokens(self, actual_tokens: int, estimated_tokens: int):
        """実際のトークン数で補正(推定より少なければ返却)"""
        diff = estimated_tokens - actual_tokens
        if diff > 0:
            self.token_bucket.tokens = min(
                self.token_bucket.capacity,
                self.token_bucket.tokens + diff
            )

ポイントは 推定トークン数で先に確保し、実測値で補正する パターンです。これにより、リクエスト送信前に枠を確保しつつ、実際の使用量が少なかった場合はバケットに返却してスループットを最大化できます。

自動フォールバック — サーキットブレーカーパターン

特定のモデルで連続してエラーが発生した場合、自動的に代替モデルへ切り替えるサーキットブレーカーを実装します。

import enum
from collections import deque
 
class CircuitState(enum.Enum):
    CLOSED = "closed"        # 正常(リクエストを通す)
    OPEN = "open"            # 遮断(リクエストをブロック)
    HALF_OPEN = "half_open"  # 試行中(1件だけ通す)
 
@dataclass
class CircuitBreaker:
    """モデル単位のサーキットブレーカー"""
    failure_threshold: int = 5       # この回数失敗したらOPENに
    recovery_timeout: float = 60.0   # OPEN後、再試行までの秒数
    success_threshold: int = 2       # HALF_OPENで成功したらCLOSEDに
 
    state: CircuitState = field(default=CircuitState.CLOSED, init=False)
    failures: int = field(default=0, init=False)
    successes_in_half_open: int = field(default=0, init=False)
    last_failure_time: float = field(default=0.0, init=False)
 
    def can_execute(self) -> bool:
        if self.state == CircuitState.CLOSED:
            return True
        if self.state == CircuitState.OPEN:
            # recovery_timeout 経過後に HALF_OPEN へ
            if time.monotonic() - self.last_failure_time >= self.recovery_timeout:
                self.state = CircuitState.HALF_OPEN
                self.successes_in_half_open = 0
                return True
            return False
        # HALF_OPEN: 1件ずつ試行
        return True
 
    def record_success(self):
        if self.state == CircuitState.HALF_OPEN:
            self.successes_in_half_open += 1
            if self.successes_in_half_open >= self.success_threshold:
                self.state = CircuitState.CLOSED
                self.failures = 0
        else:
            self.failures = 0
 
    def record_failure(self):
        self.failures += 1
        self.last_failure_time = time.monotonic()
        if self.failures >= self.failure_threshold:
            self.state = CircuitState.OPEN

サーキットブレーカーには3つの状態があります。CLOSED(正常稼働)でエラーが閾値を超えるとOPEN(遮断)に遷移し、一定時間後にHALF_OPEN(試行)で1件ずつリクエストを通して回復を確認します。

モデルルーティング — リクエストの複雑さに応じた自動選択

すべてのリクエストに Gemini 2.5 Pro を使うのはコストの無駄です。入力の複雑さを判定して、単純なタスクには Flash、複雑なタスクには Pro を自動ルーティングします。

@dataclass
class ModelConfig:
    name: str
    model_id: str
    cost_per_1k_input: float   # USD per 1K input tokens
    cost_per_1k_output: float  # USD per 1K output tokens
    priority: int              # 低いほど優先
 
# Gemini モデル定義(2026年3月時点の料金)
MODELS = {
    "flash": ModelConfig(
        name="Gemini 2.5 Flash",
        model_id="gemini-2.5-flash-preview-05-20",
        cost_per_1k_input=0.00015,
        cost_per_1k_output=0.0006,
        priority=1,
    ),
    "pro": ModelConfig(
        name="Gemini 2.5 Pro",
        model_id="gemini-2.5-pro-preview-05-06",
        cost_per_1k_input=0.00125,
        cost_per_1k_output=0.005,
        priority=2,
    ),
}
 
class ModelRouter:
    """リクエストの複雑さに基づくモデル自動選択"""
 
    # 複雑なタスクを示すキーワード
    COMPLEX_INDICATORS = [
        "analyze", "compare", "evaluate", "design",
        "architecture", "optimize", "debug", "refactor",
        "分析", "比較", "評価", "設計", "最適化", "デバッグ",
    ]
 
    def select_model(
        self,
        prompt: str,
        force_model: Optional[str] = None,
        max_output_tokens: int = 1024,
    ) -> ModelConfig:
        # 明示的なモデル指定がある場合
        if force_model and force_model in MODELS:
            return MODELS[force_model]
 
        complexity_score = self._estimate_complexity(prompt, max_output_tokens)
 
        # スコア 0.6 以上なら Pro、それ未満なら Flash
        if complexity_score >= 0.6:
            return MODELS["pro"]
        return MODELS["flash"]
 
    def _estimate_complexity(self, prompt: str, max_output_tokens: int) -> float:
        score = 0.0
        prompt_lower = prompt.lower()
 
        # キーワードマッチ
        keyword_hits = sum(
            1 for kw in self.COMPLEX_INDICATORS if kw in prompt_lower
        )
        score += min(keyword_hits * 0.15, 0.45)
 
        # 入力長(長いプロンプトは複雑な傾向)
        if len(prompt) > 3000:
            score += 0.2
        elif len(prompt) > 1000:
            score += 0.1
 
        # 出力トークン数が多い場合
        if max_output_tokens > 4096:
            score += 0.2
 
        return min(score, 1.0)

実運用ではキーワードマッチだけでなく、過去のリクエスト履歴からクライアントごとの傾向を学習し、ルーティング精度を向上させることも可能です。

トークンコスト追跡 — リアルタイムダッシュボード対応

ゲートウェイを通過するすべてのリクエストのコストをリアルタイムに記録します。

import json
import datetime
from collections import defaultdict
 
@dataclass
class UsageRecord:
    timestamp: str
    client_id: str
    model: str
    input_tokens: int
    output_tokens: int
    cost_usd: float
    latency_ms: float
    status: str  # "success" | "error" | "fallback"
 
class CostTracker:
    """トークンコストのリアルタイム追跡"""
 
    def __init__(self):
        self.records: list[UsageRecord] = []
        self.daily_totals: dict[str, float] = defaultdict(float)  # date -> USD
        self.client_totals: dict[str, float] = defaultdict(float)  # client -> USD
 
    def record(
        self,
        client_id: str,
        model_config: ModelConfig,
        input_tokens: int,
        output_tokens: int,
        latency_ms: float,
        status: str = "success",
    ) -> UsageRecord:
        cost = (
            (input_tokens / 1000) * model_config.cost_per_1k_input
            + (output_tokens / 1000) * model_config.cost_per_1k_output
        )
        now = datetime.datetime.now(datetime.timezone.utc)
        record = UsageRecord(
            timestamp=now.isoformat(),
            client_id=client_id,
            model=model_config.name,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            cost_usd=round(cost, 6),
            latency_ms=round(latency_ms, 2),
            status=status,
        )
        self.records.append(record)
        self.daily_totals[now.strftime("%Y-%m-%d")] += cost
        self.client_totals[client_id] += cost
        return record
 
    def get_daily_summary(self, date: Optional[str] = None) -> dict:
        target = date or datetime.datetime.now().strftime("%Y-%m-%d")
        day_records = [r for r in self.records if r.timestamp.startswith(target)]
        return {
            "date": target,
            "total_requests": len(day_records),
            "total_cost_usd": round(sum(r.cost_usd for r in day_records), 4),
            "total_input_tokens": sum(r.input_tokens for r in day_records),
            "total_output_tokens": sum(r.output_tokens for r in day_records),
            "avg_latency_ms": round(
                sum(r.latency_ms for r in day_records) / max(len(day_records), 1), 2
            ),
            "error_rate": round(
                sum(1 for r in day_records if r.status == "error")
                / max(len(day_records), 1),
                4,
            ),
        }

CostTracker は、各リクエストのコストをモデルの料金体系に基づいて自動計算し、日次・クライアント別の集計を即座に返せます。Prometheus や Grafana と連携すれば、リアルタイムダッシュボードの構築も容易です。

ゲートウェイ統合 — すべてを組み合わせる

4つのコンポーネントを統合した AIGateway クラスの実装です。

import google.generativeai as genai
 
class AIGateway:
    """Gemini API AIゲートウェイ(統合プロキシ)"""
 
    def __init__(self, api_key: str, rpm: int = 360, tpm: int = 4_000_000):
        genai.configure(api_key=api_key)
        self.router = ModelRouter()
        self.cost_tracker = CostTracker()
        self.rate_limiters: dict[str, RateLimiter] = {
            "flash": RateLimiter(rpm=rpm, tpm=tpm),
            "pro": RateLimiter(rpm=rpm // 2, tpm=tpm // 2),
        }
        self.circuit_breakers: dict[str, CircuitBreaker] = {
            "flash": CircuitBreaker(),
            "pro": CircuitBreaker(),
        }
 
    async def generate(
        self,
        prompt: str,
        client_id: str = "default",
        force_model: Optional[str] = None,
        max_output_tokens: int = 1024,
        temperature: float = 0.7,
    ) -> dict:
        """ゲートウェイ経由でGemini APIを呼び出す"""
        # 1. モデルルーティング
        model_config = self.router.select_model(prompt, force_model, max_output_tokens)
        model_key = "pro" if "Pro" in model_config.name else "flash"
 
        # 2. サーキットブレーカー確認 → フォールバック
        if not self.circuit_breakers[model_key].can_execute():
            fallback_key = "flash" if model_key == "pro" else "pro"
            if self.circuit_breakers[fallback_key].can_execute():
                model_config = MODELS[fallback_key]
                model_key = fallback_key
            else:
                raise Exception("All models are in circuit-open state")
 
        # 3. レート制限チェック
        estimated_tokens = len(prompt) // 4 + max_output_tokens
        if not await self.rate_limiters[model_key].acquire_request(estimated_tokens):
            raise Exception(f"Rate limit exceeded for {model_config.name}")
 
        # 4. API呼び出し
        start_time = time.monotonic()
        try:
            model = genai.GenerativeModel(model_config.model_id)
            response = model.generate_content(
                prompt,
                generation_config=genai.types.GenerationConfig(
                    max_output_tokens=max_output_tokens,
                    temperature=temperature,
                ),
            )
            elapsed_ms = (time.monotonic() - start_time) * 1000
 
            # 5. 成功記録
            input_tokens = response.usage_metadata.prompt_token_count
            output_tokens = response.usage_metadata.candidates_token_count
 
            self.circuit_breakers[model_key].record_success()
            await self.rate_limiters[model_key].report_actual_tokens(
                input_tokens + output_tokens, estimated_tokens
            )
 
            record = self.cost_tracker.record(
                client_id=client_id,
                model_config=model_config,
                input_tokens=input_tokens,
                output_tokens=output_tokens,
                latency_ms=elapsed_ms,
            )
 
            return {
                "text": response.text,
                "model": model_config.name,
                "usage": {
                    "input_tokens": input_tokens,
                    "output_tokens": output_tokens,
                    "cost_usd": record.cost_usd,
                },
                "latency_ms": elapsed_ms,
            }
 
        except Exception as e:
            elapsed_ms = (time.monotonic() - start_time) * 1000
            self.circuit_breakers[model_key].record_failure()
 
            # フォールバック試行
            fallback_key = "flash" if model_key == "pro" else "pro"
            if self.circuit_breakers[fallback_key].can_execute():
                return await self._fallback_generate(
                    prompt, fallback_key, client_id, max_output_tokens, temperature
                )
            raise
 
    async def _fallback_generate(
        self, prompt, model_key, client_id, max_output_tokens, temperature
    ):
        """フォールバック先のモデルで再試行"""
        model_config = MODELS[model_key]
        start_time = time.monotonic()
 
        model = genai.GenerativeModel(model_config.model_id)
        response = model.generate_content(
            prompt,
            generation_config=genai.types.GenerationConfig(
                max_output_tokens=max_output_tokens,
                temperature=temperature,
            ),
        )
        elapsed_ms = (time.monotonic() - start_time) * 1000
 
        input_tokens = response.usage_metadata.prompt_token_count
        output_tokens = response.usage_metadata.candidates_token_count
 
        record = self.cost_tracker.record(
            client_id=client_id,
            model_config=model_config,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            latency_ms=elapsed_ms,
            status="fallback",
        )
 
        return {
            "text": response.text,
            "model": f"{model_config.name} (fallback)",
            "usage": {
                "input_tokens": input_tokens,
                "output_tokens": output_tokens,
                "cost_usd": record.cost_usd,
            },
            "latency_ms": elapsed_ms,
        }

実際の使用例を示します。

import asyncio
 
# 期待する出力:
# Model: Gemini 2.5 Flash (単純なリクエストはFlashにルーティング)
# Cost: $0.000XXX
# Daily Summary: {'date': '2026-03-26', 'total_requests': 1, ...}
 
async def main():
    gateway = AIGateway(api_key="YOUR_GEMINI_API_KEY")
 
    # 単純なリクエスト → Flash にルーティング
    result = await gateway.generate(
        prompt="Pythonのリスト内包表記について簡潔に説明してください",
        client_id="service-a",
    )
    print(f"Model: {result['model']}")
    print(f"Cost: ${result['usage']['cost_usd']}")
 
    # 複雑なリクエスト → Pro にルーティング
    result = await gateway.generate(
        prompt="以下のマイクロサービスアーキテクチャを分析し、ボトルネックを特定して最適化案を設計してください...",
        client_id="service-b",
    )
    print(f"Model: {result['model']}")
 
    # 日次サマリー
    print(gateway.cost_tracker.get_daily_summary())
 
asyncio.run(main())

本番デプロイのベストプラクティス

環境変数とシークレット管理

APIキーやモデルのエンドポイントは環境変数で管理し、コードにハードコードしない点が肝心です。

import os
 
GATEWAY_CONFIG = {
    "api_key": os.environ["GEMINI_API_KEY"],
    "rpm": int(os.environ.get("GATEWAY_RPM", "360")),
    "tpm": int(os.environ.get("GATEWAY_TPM", "4000000")),
    "log_level": os.environ.get("GATEWAY_LOG_LEVEL", "INFO"),
}

ヘルスチェックエンドポイント

ゲートウェイ自体の健全性を外部から確認できるヘルスチェックも必須です。サーキットブレーカーの状態、レート制限の残量、直近のエラー率を返すエンドポイントを設けましょう。

ログとアラート

すべてのリクエストを構造化ログ(JSON)で出力し、エラー率が閾値を超えた場合にSlackやPagerDutyへ通知する仕組みを組み込みます。CostTracker の日次コストが予算上限に達した場合のアラートも重要です。

まとめ

AIゲートウェイは、Gemini APIを本番運用する上で避けて通れないインフラレイヤーです。本記事で解説した4つのコンポーネント — レート制限(トークンバケット)、自動フォールバック(サーキットブレーカー)、モデルルーティング、コスト追跡 — を組み合わせることで、信頼性とコスト効率を両立した運用基盤を構築できます。

まずは小規模なプロジェクトでゲートウェイを導入し、トラフィックの増加に合わせてスケーリングしていくのが現実的なアプローチです。コスト追跡のデータが蓄積されれば、ルーティングの最適化やプロンプトの改善にも活用でき、継続的な改善サイクルが回り始めます。

本記事のテーマをさらに深く

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-07-02
Gemma 4 ローカル推論と Gemini API の使い分けで月額¥32,000を¥9,000台にした — ハイブリッドルーターの本番設計
月額¥32,000のGemini APIコストを¥9,000台まで下げたハイブリッド推論構成の記録です。ルーティング設計・Python実装・本番の落とし穴に加え、2026年7月のGemma 4 API提供開始を踏まえた構成見直しの指針もまとめました。
API / SDK2026-04-07
Gemini API セマンティックルーター実装ノート ― Flash と Pro を賢く振り分けて API 費用を抑える
Gemini API でクエリ内容を自動判定し最適モデルへ振り分けるセマンティックルーターの実装ノート。Flash と Pro を 2 段階で賢くディスパッチして API 費用を抑える設計パターン、Python・TypeScript の動くコード、運用で気づいた 7 つの実装インサイトをまとめました。
API / SDK2026-05-05
Gemini API のキャッシュ戦略で月3万円を6,000円にした:Context Caching・Implicit Caching の本番設計
Gemini API のContext CachingとImplicit Cachingを正しく設計することでAPI費用を80%削減した実践ガイド。キャッシュ選定の判断基準、動作確認済み実装コード、本番でのトラブルシュートまで体系的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →