AI推論サービスゲートウェイ実践:LLM APIゲートウェイ、モデルルーティングとレート制限
技术架构
概要
- AI推論ゲートウェイはLLMサービスの「玄関」です:ルーティング、レート制限、デグレード、オブザーバビリティの4つのコア機能が不可欠
- モデルルーティングの3つの戦略:コスト優先、レイテンシ優先、品質優先、ビジネスシナリオに応じて選択
- レート制限は単なる不正利用防止ではありません:Token Rate Limitingはリクエスト頻度制限よりも精密で、単一ユーザーによるコンテキストウィンドウの独占を防ぎます
- フォールバック機構はSLAの最後の砦です:プライマリモデルタイムアウト→セカンダリモデル引き継ぎ→キャッシュフォールバック→グレースフルデグラデーション
- 本記事ではゲートウェイアーキテクチャからGo実装までの完全なソリューションを提供します(K8sデプロイとPrometheus監視含む)
目次
- なぜLLMサービスに専用ゲートウェイが必要か
- AI推論ゲートウェイアーキテクチャ設計
- モデルルーティング:3つの戦略と実装
- Token Rate Limiting:精密なレート制限
- フォールバックとデグレード:SLAの最後の砦
- Goゲートウェイ実装とK8sデプロイ
- まとめと関連記事
なぜ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の最後の砦を守ります。
設計の要点まとめ:
- AI推論ゲートウェイはToken認識が必須、従来のAPIゲートウェイでは不十分
- 3つのルーティング戦略:コスト優先/レイテンシ優先/品質優先、シナリオに応じて選択
- Token Rate Limitingは本番レート制限の標準
- 4段階フォールバック:プライマリモデル→セカンダリモデル→キャッシュ→グレースフルデグラデーション
- Go実装+K8sデプロイ+Prometheus監視が本番の標準構成
関連記事:
- クラウドネイティブAIデプロイガイド — AI推論サービスのK8sデプロイ
- 大規模モデル推論高速化ベンチマーク — ゲートウェイバックエンドの推論エンジン選定
- K8s Gateway API移行ガイド — K8sトラフィック管理の進化
権威ある参考資料:
ブラウザローカルツールを無料で試す →
#AI推理网关#LLM API网关#模型路由策略#推理服务治理#AI网关限流#2026