AI推論サービスゲートウェイ実践:LLM APIゲートウェイ、モデルルーティングとレート制限

技术架构

概要

  • AI推論ゲートウェイはLLMサービスの「玄関」です:ルーティング、レート制限、デグレード、オブザーバビリティの4つのコア機能が不可欠
  • モデルルーティングの3つの戦略:コスト優先、レイテンシ優先、品質優先、ビジネスシナリオに応じて選択
  • レート制限は単なる不正利用防止ではありません:Token Rate Limitingはリクエスト頻度制限よりも精密で、単一ユーザーによるコンテキストウィンドウの独占を防ぎます
  • フォールバック機構はSLAの最後の砦です:プライマリモデルタイムアウト→セカンダリモデル引き継ぎ→キャッシュフォールバック→グレースフルデグラデーション
  • 本記事ではゲートウェイアーキテクチャからGo実装までの完全なソリューションを提供します(K8sデプロイとPrometheus監視含む)

目次


なぜLLMサービスに専用ゲートウェイが必要か

従来のAPIゲートウェイの3つの課題

課題 説明 影響
Token非認識 リクエスト頻度で制限、Token消費を認識しない 長いコンテキストリクエストでGPUを独占される
モデルルーティングなし リクエスト特徴に応じたモデルルーティングが不可能 小さな質問に大きなモデルを使用しコストを浪費
ストリーミング非対応 SSEストリーミングレスポンスの制限/デグレードロジックが異なる ストリーミングリクエストのタイムアウト後にグレースフルデグラデーション不可

AI推論ゲートウェイ vs 従来のAPIゲートウェイ

項目 従来のAPIゲートウェイ AI推論ゲートウェイ
レート制限の次元 リクエスト頻度 Token消費量 + リクエスト頻度
ルーティング戦略 URLパス モデル能力 + コスト + レイテンシ
レスポンスモード リクエスト-レスポンス SSEストリーミング + リクエスト-レスポンス
デグレード戦略 エラーを返す セカンダリモデルにフォールバック
オブザーバビリティ QPS/レイテンシ Tokenスループット/初回Tokenレイテンシ/コンテキスト長

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

┌──────────────────────────────────────────────────────────────┐
│              AI推論ゲートウェイアーキテクチャ                    │
│                                                                │
│  ┌──────────┐                                                │
│  │ クライアント│                                                │
│  └────┬─────┘                                                │
│       │                                                       │
│  ┌────▼──────────────────────────────────────────────────┐  │
│  │              AI Inference Gateway                      │  │
│  │                                                        │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │  │
│  │  │ 認証     │ │ レート   │ │ ルーティング│ │ デグレード│ │  │
│  │  │ API Key  │ │ Token RL │ │ モデル   │ │Fallback │ │  │
│  │  │ OAuth2.0 │ │ 優先キュー│ │ 負荷分散 │ │ キャッシュ│ │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └─────────┘ │  │
│  │                                                        │  │
│  │  ┌──────────────────────────────────────────────────┐ │  │
│  │  │              オブザーバビリティ                     │ │  │
│  │  │ Prometheus + OpenTelemetry + 構造化ログ            │ │  │
│  │  └──────────────────────────────────────────────────┘ │  │
│  └──────────────────────────────────────────────────────┘  │
│       │           │           │                              │
│  ┌────▼─────┐ ┌───▼──────┐ ┌─▼────────┐                   │
│  │ vLLM     │ │ TGI      │ │ SGLang   │                   │
│  │ Qwen-7B  │ │ Llama-70B│ │ Qwen-7B  │                   │
│  └──────────┘ └──────────┘ └──────────┘                   │
└──────────────────────────────────────────────────────────────┘

モデルルーティング:3つの戦略と実装

ルーティング戦略比較

戦略 ルーティングロジック コスト レイテンシ 品質 適用シナリオ
コスト優先 最小モデルに優先ルーティング 最低 社内ツール
レイテンシ優先 最速モデルに優先ルーティング 最低 リアルタイムチャット
品質優先 最強モデルに優先ルーティング 最高 最高 専門シナリオ

Goルーティング実装

package gateway

import (
	"context"
	"math"
	"sync"
	"time"
)

type ModelEndpoint struct {
	Name         string
	URL          string
	ModelID      string
	MaxTokens    int
	CostPerToken float64
	AvgLatency   time.Duration
	CurrentLoad  float64
	Capabilities []string
}

type RoutingStrategy string

const (
	CostFirst     RoutingStrategy = "cost_first"
	LatencyFirst  RoutingStrategy = "latency_first"
	QualityFirst  RoutingStrategy = "quality_first"
)

type ModelRouter struct {
	endpoints []*ModelEndpoint
	strategy  RoutingStrategy
	mu        sync.RWMutex
}

func NewModelRouter(strategy RoutingStrategy) *ModelRouter {
	return &ModelRouter{strategy: strategy}
}

func (r *ModelRouter) Register(endpoint *ModelEndpoint) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.endpoints = append(r.endpoints, endpoint)
}

func (r *ModelRouter) Route(ctx context.Context, req *InferenceRequest) (*ModelEndpoint, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	candidates := r.filterByCapability(req)
	if len(candidates) == 0 {
		return nil, ErrNoAvailableModel
	}

	switch r.strategy {
	case CostFirst:
		return r.routeByCost(candidates), nil
	case LatencyFirst:
		return r.routeByLatency(candidates), nil
	case QualityFirst:
		return r.routeByQuality(candidates), nil
	default:
		return r.routeByLatency(candidates), nil
	}
}

func (r *ModelRouter) routeByCost(candidates []*ModelEndpoint) *ModelEndpoint {
	best := candidates[0]
	for _, ep := range candidates[1:] {
		if ep.CostPerToken < best.CostPerToken && ep.CurrentLoad < 0.9 {
			best = ep
		}
	}
	return best
}

func (r *ModelRouter) routeByLatency(candidates []*ModelEndpoint) *ModelEndpoint {
	best := candidates[0]
	for _, ep := range candidates[1:] {
		effectiveLatency := float64(ep.AvgLatency) / (1.0 - ep.CurrentLoad + 0.01)
		bestLatency := float64(best.AvgLatency) / (1.0 - best.CurrentLoad + 0.01)
		if effectiveLatency < bestLatency {
			best = ep
		}
	}
	return best
}

func (r *ModelRouter) routeByQuality(candidates []*ModelEndpoint) *ModelEndpoint {
	best := candidates[0]
	for _, ep := range candidates[1:] {
		if ep.MaxTokens > best.MaxTokens && ep.CurrentLoad < 0.9 {
			best = ep
		}
	}
	return best
}

func (r *ModelRouter) filterByCapability(req *InferenceRequest) []*ModelEndpoint {
	var filtered []*ModelEndpoint
	for _, ep := range r.endpoints {
		if ep.CurrentLoad >= 0.95 {
			continue
		}
		if req.MaxTokens > 0 && ep.MaxTokens < req.MaxTokens {
			continue
		}
		filtered = append(filtered, ep)
	}
	return filtered
}

Token Rate Limiting:精密なレート制限

Token制限 vs リクエスト制限

制限の次元 メリット デメリット 適用シナリオ
リクエスト頻度 実装が簡単 Token消費を認識しない 低精度レート制限
Token/分 GPU消費を精密に制御 Token数の推定が必要 本番推奨
同時リクエスト数 GPU同時実行を制御 コンテキスト長を認識しない 単純なシナリオ

Go Token制限実装

package gateway

import (
	"context"
	"sync"
	"time"
)

type TokenBucket struct {
	mu          sync.Mutex
	tokens      float64
	maxTokens   float64
	refillRate  float64
	lastRefill  time.Time
}

func NewTokenBucket(maxTokens, refillRate float64) *TokenBucket {
	return &TokenBucket{
		tokens:     maxTokens,
		maxTokens:  maxTokens,
		refillRate: refillRate,
		lastRefill: time.Now(),
	}
}

func (tb *TokenBucket) Allow(estimatedTokens int) bool {
	tb.mu.Lock()
	defer tb.mu.Unlock()

	now := time.Now()
	elapsed := now.Sub(tb.lastRefill).Seconds()
	tb.tokens = math.Min(tb.maxTokens, tb.tokens+elapsed*tb.refillRate)
	tb.lastRefill = now

	if tb.tokens >= float64(estimatedTokens) {
		tb.tokens -= float64(estimatedTokens)
		return true
	}
	return false
}

type TokenRateLimiter struct {
	buckets map[string]*TokenBucket
	mu      sync.RWMutex
}

func NewTokenRateLimiter() *TokenRateLimiter {
	return &TokenRateLimiter{buckets: make(map[string]*TokenBucket)}
}

func (rl *TokenRateLimiter) RegisterUser(userID string, maxTokens, refillRate float64) {
	rl.mu.Lock()
	defer rl.mu.Unlock()
	rl.buckets[userID] = NewTokenBucket(maxTokens, refillRate)
}

func (rl *TokenRateLimiter) Allow(userID string, estimatedTokens int) bool {
	rl.mu.RLock()
	bucket, ok := rl.buckets[userID]
	rl.mu.RUnlock()

	if !ok {
		return false
	}
	return bucket.Allow(estimatedTokens)
}

フォールバックとデグレード:SLAの最後の砦

4段階デグレード戦略

┌──────────────────────────────────────────────────────────┐
│              4段階デグレード戦略                              │
│                                                            │
│  Level 0: 正常サービス                                     │
│  ┌──────────────────────────────────────────┐             │
│  │ プライマリモデル(Qwen-72B) → 50ms, 最高品質│             │
│  └──────────────────────────────────────────┘             │
│                    ↓ タイムアウト/過負荷                      │
│  Level 1: セカンダリモデル引き継ぎ                           │
│  ┌──────────────────────────────────────────┐             │
│  │ セカンダリモデル(Qwen-7B) → 20ms, 品質低下│             │
│  └──────────────────────────────────────────┘             │
│                    ↓ セカンダリも過負荷                       │
│  Level 2: キャッシュフォールバック                           │
│  ┌──────────────────────────────────────────┐             │
│  │ セマンティックキャッシュヒット → 5ms, キャッシュ品質│     │
│  └──────────────────────────────────────────┘             │
│                    ↓ キャッシュミス                          │
│  Level 3: グレースフルデグラデーション                       │
│  ┌──────────────────────────────────────────┐             │
│  │ プリセット返信 + リトライプロンプトを返す   │             │
│  └──────────────────────────────────────────┘             │
└──────────────────────────────────────────────────────────┘

Go フォールバック実装

type FallbackChain struct {
	primary    *ModelEndpoint
	secondary  *ModelEndpoint
	cache      SemanticCache
	timeout    time.Duration
}

func (fc *FallbackChain) Generate(ctx context.Context, req *InferenceRequest) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, fc.timeout)
	defer cancel()

	result, err := fc.callModel(ctx, fc.primary, req)
	if err == nil {
		return result, nil
	}

	log.Warn("primary model failed, falling back", "error", err)

	if fc.secondary != nil {
		result, err = fc.callModel(ctx, fc.secondary, req)
		if err == nil {
			return result, nil
		}
		log.Warn("secondary model failed", "error", err)
	}

	if fc.cache != nil {
		cached, ok := fc.cache.Get(req.Prompt)
		if ok {
			log.Info("cache hit on fallback")
			return cached, nil
		}
	}

	return "サービスは一時的に利用できません。後でもう一度お試しください。", ErrServiceUnavailable
}

Goゲートウェイ実装とK8sデプロイ

K8s Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ai-inference-gateway
  namespace: ai-inference
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ai-inference-gateway
  template:
    spec:
      containers:
        - name: gateway
          image: myregistry/ai-inference-gateway:v1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "2"
              memory: 2Gi
            limits:
              cpu: "4"
              memory: 4Gi
          env:
            - name: PRIMARY_MODEL_URL
              value: "http://vllm-qwen72b:8000/v1"
            - name: SECONDARY_MODEL_URL
              value: "http://vllm-qwen7b:8000/v1"
            - name: REDIS_URL
              value: "redis://redis:6379"
            - name: ROUTING_STRATEGY
              value: "latency_first"
            - name: TOKEN_RATE_LIMIT
              value: "100000"
---
apiVersion: v1
kind: Service
metadata:
  name: ai-gateway-svc
  namespace: ai-inference
spec:
  selector:
    app: ai-inference-gateway
  ports:
    - port: 8080
      targetPort: 8080
  type: ClusterIP

Prometheus監視

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: ai-gateway-alerts
  namespace: ai-inference
spec:
  groups:
    - name: ai-gateway
      rules:
        - alert: HighFallbackRate
          expr: rate(gateway_fallback_total[5m]) / rate(gateway_requests_total[5m]) > 0.1
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "AIゲートウェイのフォールバック率が高すぎます"
        - alert: TokenRateLimitExceeded
          expr: rate(gateway_rate_limit_exceeded_total[5m]) > 10
          for: 2m
          labels:
            severity: warning
          annotations:
            summary: "Token制限が頻繁にトリガーされています"

まとめと関連記事

AI推論ゲートウェイはLLMサービスの「玄関」であり、4つのコア機能(ルーティング、レート制限、デグレード、オブザーバビリティ)はすべて不可欠です。Token Rate Limitingはリクエスト頻度制限よりも精密で、4段階のフォールバックがSLAの最後の砦を守ります。

設計の要点まとめ

  1. AI推論ゲートウェイはToken認識が必須、従来のAPIゲートウェイでは不十分
  2. 3つのルーティング戦略:コスト優先/レイテンシ優先/品質優先、シナリオに応じて選択
  3. Token Rate Limitingは本番レート制限の標準
  4. 4段階フォールバック:プライマリモデル→セカンダリモデル→キャッシュ→グレースフルデグラデーション
  5. Go実装+K8sデプロイ+Prometheus監視が本番の標準構成

関連記事

権威ある参考資料

ブラウザローカルツールを無料で試す →

#AI推理网关#LLM API网关#模型路由策略#推理服务治理#AI网关限流#2026