AI API网关与大模型路由完全教程

教程简介

本教程全面讲解AI API网关与大模型路由的核心架构与实战部署,涵盖LiteLLM/OpenRouter/Portkey等主流网关对比、多模型统一路由策略、负载均衡与故障转移、API Key管理与安全、速率限制与配额控制、请求/响应日志与审计、成本追踪与预算告警、缓存策略(语义缓存/精确缓存)、模型Fallback链、企业级多租户部署、与Kong/Nginx集成等核心内容,帮助开发者构建高可用的LLM API网关。

AI API网关与大模型路由完全教程

引言

随着大语言模型(LLM)生态的爆发式增长,企业面临一个现实挑战:如何同时管理OpenAI、Anthropic、Google、开源模型等多个LLM服务,并在它们之间实现智能路由、成本控制和高可用保障?答案就是——AI API网关

本教程将从架构设计到实战部署,全面讲解AI API网关与大模型路由的核心技术,帮助你构建一个企业级的LLM统一接入层。


1. 什么是AI API网关

1.1 核心概念

AI API网关是位于客户端与多个LLM后端之间的中间层服务,它承担以下职责:

  • 统一路由:将请求路由到合适的模型(如根据任务类型、成本、延迟)
  • 负载均衡:在多个同模型实例间分配请求
  • 故障转移:当主模型不可用时自动切换到备用模型
  • API Key管理:统一管理多个LLM提供商的密钥
  • 速率限制:控制每个用户/团队的请求频率
  • 成本追踪:监控和预警各模型的Token消耗与费用
  • 日志审计:记录所有请求/响应,便于合规和调试
  • 缓存:缓存重复请求的响应,降低成本和延迟

1.2 架构总览

┌─────────────┐     ┌──────────────────────┐     ┌─────────────────┐
│   Client    │────▶│   AI API Gateway     │────▶│  OpenAI API     │
│  (App/Bot)  │     │                      │     │  Claude API     │
└─────────────┘     │  - 路由策略          │     │  Gemini API     │
                    │  - 负载均衡          │     │  本地模型(Ollama)│
                    │  - 故障转移          │     │  ...            │
                    │  - 认证/鉴权         │     └─────────────────┘
                    │  - 日志/监控          │
                    │  - 缓存              │
                    │  - 成本控制          │
                    └──────────────────────┘

2. 主流AI API网关对比

2.1 LiteLLM

LiteLLM 是目前最流行的开源LLM代理网关,支持100+模型提供商,提供统一的OpenAI兼容接口。

核心特性:

  • 支持100+模型(OpenAI、Anthropic、Google、Cohere、HuggingFace、Ollama等)
  • OpenAI兼容API格式
  • 内置负载均衡和故障转移
  • 细粒度的预算和速率限制
  • 完善的日志和回调系统
  • 支持团队/虚拟Key管理

安装与基础配置:

pip install litellm[proxy]

基础代理配置文件 litellm_config.yaml

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
      api_base: https://api.openai.com/v1

  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY_2
      api_base: https://api.openai.com/v1

  - model_name: claude-sonnet
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY

  - model_name: local-llama
    litellm_params:
      model: ollama/llama3.1
      api_base: http://localhost:11434

router_settings:
  routing_strategy: "simple-shuffle"  # 负载均衡策略
  num_retries: 3                      # 重试次数
  timeout: 60                         # 超时时间(秒)
  allowed_fails: 2                    # 允许失败次数
  cooldown_time: 30                   # 冷却时间(秒)

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  database_url: os.environ/DATABASE_URL  # PostgreSQL用于持久化

启动LiteLLM Proxy:

litellm --config litellm_config.yaml --port 4000

客户端调用:

import openai

# 使用与OpenAI完全兼容的接口
client = openai.OpenAI(
    api_key="your-litellm-key",
    base_url="http://localhost:4000/v1"
)

response = client.chat.completions.create(
    model="gpt-4o",  # 或 "claude-sonnet"、"local-llama"
    messages=[
        {"role": "user", "content": "用Python实现快速排序"}
    ]
)
print(response.choices[0].message.content)

2.2 OpenRouter

OpenRouter 是一个商业化的LLM路由服务,无需自建基础设施,提供统一API访问数百个模型。

核心特性:

  • 访问200+模型,无需分别注册
  • 自动模型路由(根据任务选择最佳模型)
  • 按Token计费,价格透明
  • 支持模型回退链
  • 隐私模式(不存储请求数据)

使用方式:

import openai

client = openai.OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="sk-or-your-openrouter-key",
)

# 直接指定模型,格式为 provider/model
response = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    messages=[
        {"role": "user", "content": "解释什么是Transformer架构"}
    ],
    extra_body={
        "route": "fallback",  # 启用回退路由
        "models": [
            "anthropic/claude-3.5-sonnet",
            "openai/gpt-4o",
            "google/gemini-pro-1.5"
        ]
    }
)

2.3 Portkey

Portkey 是一个面向企业的AI网关平台,提供可观测性、可靠性和安全性。

核心特性:

  • 多模型自动回退
  • 语义缓存(基于嵌入的相似度缓存)
  • 完整的请求追踪和可观测性
  • 细粒度的Guardrails(输入/输出安全护栏)
  • A/B测试和金丝雀发布

安装与使用:

pip install portkey-ai
from portkey_ai import Portkey

# 初始化客户端
portkey = Portkey(
    api_key="your-portkey-api-key",
    virtual_key="openai-xxx"  # 虚拟Key,隐藏真实API Key
)

# 基础调用
response = portkey.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "什么是RAG?"}]
)

# 配置自动回退
fallback_config = {
    "strategy": {"mode": "fallback"},
    "targets": [
        {"virtual_key": "openai-key", "model": "gpt-4o"},
        {"virtual_key": "anthropic-key", "model": "claude-3-5-sonnet-20241022"},
        {"virtual_key": "google-key", "model": "gemini-pro"},
    ]
}

response = portkey.with_config(fallback_config).chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}]
)

2.4 综合对比表

特性 LiteLLM OpenRouter Portkey
部署方式 自托管/云 云服务 云/自托管
模型数量 100+ 200+ 50+
开源 ✅ Apache 2.0 部分开源
语义缓存
成本追踪
多租户
适合场景 自建网关 快速接入 企业级

3. 多模型统一路由策略

3.1 基于任务类型的路由

不同任务适合不同的模型。我们可以根据请求中的任务标签进行智能路由:

from litellm import Router
import litellm

# 定义模型路由配置
model_list = [
    {
        "model_name": "coding-task",
        "litellm_params": {
            "model": "anthropic/claude-3-5-sonnet-20241022",
            "api_key": "your-key"
        }
    },
    {
        "model_name": "chat-task",
        "litellm_params": {
            "model": "openai/gpt-4o-mini",
            "api_key": "your-key"
        }
    },
    {
        "model_name": "creative-task",
        "litellm_params": {
            "model": "openai/gpt-4o",
            "api_key": "your-key"
        }
    }
]

router = Router(model_list=model_list)

# 根据任务类型路由
TASK_MODEL_MAP = {
    "code_generation": "coding-task",
    "code_review": "coding-task",
    "casual_chat": "chat-task",
    "summarization": "chat-task",
    "creative_writing": "creative-task",
    "translation": "creative-task",
}

async def route_by_task(task_type: str, messages: list):
    model_name = TASK_MODEL_MAP.get(task_type, "chat-task")
    response = await router.acompletion(
        model=model_name,
        messages=messages
    )
    return response

3.2 基于成本的路由

当预算有限时,可以设计基于成本的路由策略:

from litellm import Router

model_list = [
    {
        "model_name": "cost-optimized",
        "litellm_params": {
            "model": "openai/gpt-4o-mini",    # 低成本
            "api_key": "your-key"
        }
    },
    {
        "model_name": "cost-optimized",
        "litellm_params": {
            "model": "anthropic/claude-3-5-sonnet-20241022",  # 高性能
            "api_key": "your-key"
        }
    }
]

router = Router(
    model_list=model_list,
    routing_strategy="cost-based-routing"  # 基于成本的路由
)

# 默认使用低成本模型
response = await router.acompletion(
    model="cost-optimized",
    messages=[{"role": "user", "content": "简单问候"}]
)

3.3 基于延迟的路由

对实时性要求高的场景,可以基于历史延迟数据选择最快的模型:

router = Router(
    model_list=model_list,
    routing_strategy="latency-based-routing",  # 基于延迟的路由
    latency_tracking_cache_ttl: 300  # 缓存延迟数据5分钟
)

3.4 自定义路由函数

对于更复杂的路由需求,可以编写自定义路由逻辑:

import time
from litellm import Router

router = Router(model_list=model_list)

async def smart_router(
    messages: list,
    user_tier: str = "free",
    priority: str = "balanced"
):
    """智能路由:综合考虑用户等级、优先级和时间因素"""
    
    hour = time.localtime().tm_hour
    
    # 免费用户在高峰期使用轻量模型
    if user_tier == "free" and 9 <= hour <= 18:
        model = "chat-task"  # gpt-4o-mini
    # 付费用户始终使用高级模型
    elif user_tier == "premium":
        if priority == "quality":
            model = "creative-task"  # gpt-4o
        elif priority == "speed":
            model = "coding-task"    # claude
        else:
            model = "creative-task"
    else:
        model = "chat-task"
    
    response = await router.acompletion(
        model=model,
        messages=messages,
        user=user_tier
    )
    return response

4. 负载均衡与故障转移

4.1 LiteLLM负载均衡策略

LiteLLM内置多种负载均衡策略:

# litellm_config.yaml
router_settings:
  # 策略选项:
  # - simple-shuffle: 随机分配
  # - least-busy: 最少繁忙实例
  # - latency-based-routing: 基于延迟
  # - cost-based-routing: 基于成本
  # - usage-based-routing: 基于使用量
  routing_strategy: "least-busy"
  
  # 健康检查
  allowed_fails: 2           # 连续失败次数后标记为不健康
  cooldown_time: 30          # 不健康模型的冷却时间(秒)
  retry_after: 5             # 重试等待时间
  
  # 超时设置
  timeout: 60
  max_parallel_requests: 100 # 最大并发请求

4.2 手动实现故障转移

import asyncio
from typing import Optional

class LLMFailover:
    """自定义LLM故障转移管理器"""
    
    def __init__(self):
        self.providers = [
            {"name": "openai", "model": "gpt-4o", "priority": 1, "healthy": True},
            {"name": "anthropic", "model": "claude-3-5-sonnet", "priority": 2, "healthy": True},
            {"name": "google", "model": "gemini-pro", "priority": 3, "healthy": True},
        ]
        self.failure_counts = {}
    
    async def call_with_failover(self, messages: list, **kwargs) -> Optional[dict]:
        """按优先级调用,失败时自动切换到下一个"""
        sorted_providers = sorted(
            [p for p in self.providers if p["healthy"]],
            key=lambda x: x["priority"]
        )
        
        for provider in sorted_providers:
            try:
                response = await self._call_provider(provider, messages, **kwargs)
                # 成功,重置失败计数
                self.failure_counts[provider["name"]] = 0
                return response
            except Exception as e:
                print(f"[{provider['name']}] 调用失败: {e}")
                count = self.failure_counts.get(provider["name"], 0) + 1
                self.failure_counts[provider["name"]] = count
                
                if count >= 3:
                    provider["healthy"] = False
                    print(f"[{provider['name']}] 标记为不健康,进入冷却")
                    asyncio.create_task(self._cooldown(provider))
        
        raise Exception("所有Provider均不可用")
    
    async def _call_provider(self, provider: dict, messages: list, **kwargs):
        """调用具体的Provider"""
        import openai
        client = openai.AsyncOpenAI(api_key=f"your-{provider['name']}-key")
        response = await client.chat.completions.create(
            model=provider["model"],
            messages=messages,
            **kwargs
        )
        return response
    
    async def _cooldown(self, provider: dict):
        """冷却后恢复健康状态"""
        await asyncio.sleep(60)
        provider["healthy"] = True
        self.failure_counts[provider["name"]] = 0
        print(f"[{provider['name']}] 恢复健康状态")

# 使用示例
failover = LLMFailover()
result = await failover.call_with_failover(
    messages=[{"role": "user", "content": "Hello!"}]
)

5. API Key管理与安全

5.1 虚拟Key管理

不要直接暴露真实API Key给客户端,使用虚拟Key进行映射:

import hashlib
import secrets
import sqlite3
from datetime import datetime

class VirtualKeyManager:
    """虚拟Key管理器"""
    
    def __init__(self, db_path: str = "keys.db"):
        self.db = sqlite3.connect(db_path)
        self._init_db()
    
    def _init_db(self):
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS virtual_keys (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                virtual_key TEXT UNIQUE NOT NULL,
                real_key_name TEXT NOT NULL,
                team TEXT DEFAULT 'default',
                budget_limit REAL DEFAULT 100.0,
                budget_used REAL DEFAULT 0.0,
                rate_limit_per_min INTEGER DEFAULT 60,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                is_active BOOLEAN DEFAULT 1
            )
        """)
        self.db.commit()
    
    def create_key(self, team: str, real_key_name: str, 
                   budget_limit: float = 100.0,
                   rate_limit: int = 60) -> str:
        """创建虚拟Key"""
        virtual_key = f"sk-{secrets.token_hex(32)}"
        self.db.execute(
            """INSERT INTO virtual_keys 
               (virtual_key, real_key_name, team, budget_limit, rate_limit_per_min)
               VALUES (?, ?, ?, ?, ?)""",
            (virtual_key, real_key_name, team, budget_limit, rate_limit)
        )
        self.db.commit()
        return virtual_key
    
    def validate_key(self, virtual_key: str) -> dict | None:
        """验证虚拟Key并返回配置"""
        cursor = self.db.execute(
            """SELECT real_key_name, team, budget_limit, budget_used, 
                      rate_limit_per_min, is_active
               FROM virtual_keys WHERE virtual_key = ?""",
            (virtual_key,)
        )
        row = cursor.fetchone()
        if not row or not row[5]:
            return None
        return {
            "real_key_name": row[0],
            "team": row[1],
            "budget_limit": row[2],
            "budget_used": row[3],
            "rate_limit": row[4]
        }
    
    def update_usage(self, virtual_key: str, cost: float):
        """更新使用量"""
        self.db.execute(
            "UPDATE virtual_keys SET budget_used = budget_used + ? WHERE virtual_key = ?",
            (cost, virtual_key)
        )
        self.db.commit()

# 使用示例
manager = VirtualKeyManager()

# 为团队创建Key
key = manager.create_key(
    team="engineering",
    real_key_name="OPENAI_API_KEY",
    budget_limit=500.0,
    rate_limit=120
)
print(f"Virtual Key: {key}")

# 验证Key
config = manager.validate_key(key)
if config:
    print(f"Team: {config['team']}, Budget: ${config['budget_used']}/{config['budget_limit']}")

5.2 密钥轮转

import os
from datetime import datetime, timedelta

class KeyRotator:
    """API Key自动轮转管理"""
    
    def __init__(self):
        self.keys = {}  # provider -> list of keys
        self.current_index = {}
    
    def add_key(self, provider: str, key: str, expires_at: datetime = None):
        if provider not in self.keys:
            self.keys[provider] = []
            self.current_index[provider] = 0
        self.keys[provider].append({
            "key": key,
            "expires_at": expires_at,
            "created_at": datetime.now()
        })
    
    def get_current_key(self, provider: str) -> str:
        """获取当前有效的Key,自动轮转过期Key"""
        if provider not in self.keys:
            raise ValueError(f"No keys for provider: {provider}")
        
        keys = self.keys[provider]
        idx = self.current_index[provider]
        
        # 检查当前Key是否过期
        current = keys[idx]
        if current["expires_at"] and datetime.now() > current["expires_at"]:
            idx = (idx + 1) % len(keys)
            self.current_index[provider] = idx
            print(f"[KeyRotator] Rotated to key index {idx} for {provider}")
        
        return keys[idx]["key"]

# 使用
rotator = KeyRotator()
rotator.add_key("openai", "sk-key-1", expires_at=datetime.now() + timedelta(days=30))
rotator.add_key("openai", "sk-key-2", expires_at=datetime.now() + timedelta(days=60))

6. 速率限制与配额控制

6.1 基于Redis的速率限制

import time
import redis

class RateLimiter:
    """基于Redis的滑动窗口速率限制器"""
    
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url)
    
    def is_allowed(
        self,
        key: str,
        max_requests: int,
        window_seconds: int
    ) -> tuple[bool, dict]:
        """检查请求是否在速率限制内"""
        now = time.time()
        window_start = now - window_seconds
        pipe = self.redis.pipeline()
        
        # 清除窗口外的记录
        pipe.zremrangebyscore(key, 0, window_start)
        # 添加当前请求
        pipe.zadd(key, {str(now): now})
        # 统计窗口内的请求数
        pipe.zcard(key)
        # 设置Key过期
        pipe.expire(key, window_seconds)
        
        results = pipe.execute()
        request_count = results[2]
        
        allowed = request_count <= max_requests
        remaining = max(0, max_requests - request_count)
        
        return allowed, {
            "limit": max_requests,
            "remaining": remaining,
            "reset_at": int(now + window_seconds)
        }

# 使用示例
limiter = RateLimiter()

# 检查用户速率(每分钟60次)
allowed, info = limiter.is_allowed(
    key="rate:user:123",
    max_requests=60,
    window_seconds=60
)

if not allowed:
    print(f"速率限制!重置时间: {info['reset_at']}")
else:
    print(f"允许请求,剩余: {info['remaining']}")

6.2 Token配额控制

class TokenQuotaManager:
    """Token配额管理器"""
    
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url)
    
    def check_quota(
        self,
        user_id: str,
        estimated_tokens: int,
        daily_limit: int,
        monthly_limit: int
    ) -> tuple[bool, str]:
        """检查用户Token配额"""
        today = time.strftime("%Y-%m-%d")
        month = time.strftime("%Y-%m")
        
        daily_key = f"quota:{user_id}:daily:{today}"
        monthly_key = f"quota:{user_id}:monthly:{month}"
        
        daily_used = int(self.redis.get(daily_key) or 0)
        monthly_used = int(self.redis.get(monthly_key) or 0)
        
        if daily_used + estimated_tokens > daily_limit:
            return False, f"日配额不足: 已用{daily_used}, 限额{daily_limit}"
        
        if monthly_used + estimated_tokens > monthly_limit:
            return False, f"月配额不足: 已用{monthly_used}, 限额{monthly_limit}"
        
        return True, "OK"
    
    def record_usage(self, user_id: str, tokens_used: int):
        """记录Token使用量"""
        today = time.strftime("%Y-%m-%d")
        month = time.strftime("%Y-%m")
        
        daily_key = f"quota:{user_id}:daily:{today}"
        monthly_key = f"quota:{user_id}:monthly:{month}"
        
        pipe = self.redis.pipeline()
        pipe.incrby(daily_key, tokens_used)
        pipe.expire(daily_key, 86400 * 2)  # 保留2天
        pipe.incrby(monthly_key, tokens_used)
        pipe.expire(monthly_key, 86400 * 35)  # 保留35天
        pipe.execute()

7. 请求/响应日志与审计

7.1 结构化日志系统

import json
import time
import uuid
from datetime import datetime
from dataclasses import dataclass, asdict
from typing import Optional

@dataclass
class RequestLog:
    request_id: str
    timestamp: str
    user_id: str
    team: str
    model: str
    provider: str
    messages_count: int
    total_tokens: int
    prompt_tokens: int
    completion_tokens: int
    cost_usd: float
    latency_ms: float
    status: str  # success, error, timeout
    error_message: Optional[str] = None
    
    def to_json(self) -> str:
        return json.dumps(asdict(self), ensure_ascii=False)

class AuditLogger:
    """审计日志管理器"""
    
    def __init__(self, log_file: str = "llm_audit.jsonl"):
        self.log_file = log_file
    
    def log_request(self, log: RequestLog):
        """记录请求日志"""
        with open(self.log_file, "a") as f:
            f.write(log.to_json() + "\n")
    
    def get_user_costs(
        self, user_id: str, start_date: str, end_date: str
    ) -> dict:
        """统计用户成本"""
        total_cost = 0.0
        total_tokens = 0
        request_count = 0
        
        with open(self.log_file, "r") as f:
            for line in f:
                entry = json.loads(line)
                if (entry["user_id"] == user_id and 
                    start_date <= entry["timestamp"][:10] <= end_date):
                    total_cost += entry["cost_usd"]
                    total_tokens += entry["total_tokens"]
                    request_count += 1
        
        return {
            "total_cost": round(total_cost, 4),
            "total_tokens": total_tokens,
            "request_count": request_count
        }

# 使用示例
logger = AuditLogger()

log = RequestLog(
    request_id=str(uuid.uuid4()),
    timestamp=datetime.now().isoformat(),
    user_id="user_123",
    team="engineering",
    model="gpt-4o",
    provider="openai",
    messages_count=3,
    total_tokens=1500,
    prompt_tokens=1000,
    completion_tokens=500,
    cost_usd=0.015,
    latency_ms=2300,
    status="success"
)
logger.log_request(log)

8. 成本追踪与预算告警

8.1 实时成本监控

import asyncio
from datetime import datetime, timedelta

class CostMonitor:
    """成本监控与告警"""
    
    # 各模型的每1K Token价格(输入/输出)
    MODEL_PRICING = {
        "gpt-4o": {"input": 0.0025, "output": 0.01},
        "gpt-4o-mini": {"input": 0.00015, "output": 0.0006},
        "claude-3-5-sonnet": {"input": 0.003, "output": 0.015},
        "claude-3-haiku": {"input": 0.00025, "output": 0.00125},
        "gemini-pro": {"input": 0.000125, "output": 0.000375},
    }
    
    def __init__(self, alert_callback=None):
        self.daily_costs = {}  # team -> cost
        self.monthly_costs = {}
        self.alerts_sent = set()
        self.alert_callback = alert_callback
    
    def calculate_cost(
        self, model: str, prompt_tokens: int, completion_tokens: int
    ) -> float:
        """计算单次请求成本"""
        pricing = self.MODEL_PRICING.get(model)
        if not pricing:
            return 0.0
        return (
            (prompt_tokens / 1000) * pricing["input"] +
            (completion_tokens / 1000) * pricing["output"]
        )
    
    def record_cost(self, team: str, cost: float):
        """记录成本并检查告警"""
        today = datetime.now().strftime("%Y-%m-%d")
        month = datetime.now().strftime("%Y-%m")
        
        daily_key = f"{team}:{today}"
        monthly_key = f"{team}:{month}"
        
        self.daily_costs[daily_key] = self.daily_costs.get(daily_key, 0) + cost
        self.monthly_costs[monthly_key] = self.monthly_costs.get(monthly_key, 0) + cost
        
        # 检查告警阈值
        self._check_alerts(team)
    
    def _check_alerts(self, team: str):
        """检查预算告警"""
        today = datetime.now().strftime("%Y-%m-%d")
        month = datetime.now().strftime("%Y-%m")
        
        daily_cost = self.daily_costs.get(f"{team}:{today}", 0)
        monthly_cost = self.monthly_costs.get(f"{team}:{month}", 0)
        
        # 日预算告警
        if daily_cost > 50 and f"{team}:daily:50" not in self.alerts_sent:
            self._send_alert(team, "daily", daily_cost, 50)
            self.alerts_sent.add(f"{team}:daily:50")
        
        if daily_cost > 100 and f"{team}:daily:100" not in self.alerts_sent:
            self._send_alert(team, "daily", daily_cost, 100)
            self.alerts_sent.add(f"{team}:daily:100")
        
        # 月预算告警
        if monthly_cost > 1000 and f"{team}:monthly:1000" not in self.alerts_sent:
            self._send_alert(team, "monthly", monthly_cost, 1000)
            self.alerts_sent.add(f"{team}:monthly:1000")
    
    def _send_alert(self, team: str, period: str, current: float, threshold: float):
        """发送告警"""
        message = (
            f"⚠️ 预算告警\n"
            f"团队: {team}\n"
            f"周期: {period}\n"
            f"当前: ${current:.2f}\n"
            f"阈值: ${threshold:.2f}"
        )
        print(message)
        if self.alert_callback:
            self.alert_callback(message)
    
    def get_report(self, team: str) -> dict:
        """获取成本报告"""
        today = datetime.now().strftime("%Y-%m-%d")
        month = datetime.now().strftime("%Y-%m")
        
        return {
            "daily_cost": round(self.daily_costs.get(f"{team}:{today}", 0), 4),
            "monthly_cost": round(self.monthly_costs.get(f"{team}:{month}", 0), 4),
            "daily_budget": 100,
            "monthly_budget": 5000
        }

9. 缓存策略

9.1 精确缓存

import hashlib
import json
import redis

class ExactCache:
    """精确匹配缓存"""
    
    def __init__(self, redis_url: str = "redis://localhost:6379", ttl: int = 3600):
        self.redis = redis.from_url(redis_url)
        self.ttl = ttl
    
    def _generate_key(self, model: str, messages: list) -> str:
        """生成缓存Key"""
        content = json.dumps({
            "model": model,
            "messages": messages
        }, sort_keys=True)
        return f"llm_cache:{hashlib.sha256(content.encode()).hexdigest()}"
    
    def get(self, model: str, messages: list) -> dict | None:
        """查询缓存"""
        key = self._generate_key(model, messages)
        cached = self.redis.get(key)
        if cached:
            return json.loads(cached)
        return None
    
    def set(self, model: str, messages: list, response: dict):
        """设置缓存"""
        key = self._generate_key(model, messages)
        self.redis.setex(key, self.ttl, json.dumps(response))
    
    def get_stats(self) -> dict:
        """获取缓存统计"""
        keys = self.redis.keys("llm_cache:*")
        return {
            "total_entries": len(keys),
            "memory_used": sum(self.redis.memory_usage(k) or 0 for k in keys[:100])
        }

9.2 语义缓存

import numpy as np
from typing import Optional

class SemanticCache:
    """基于嵌入向量的语义缓存"""
    
    def __init__(self, similarity_threshold: float = 0.95):
        self.cache = []  # list of (embedding, response)
        self.similarity_threshold = similarity_threshold
    
    async def get_embedding(self, text: str) -> list[float]:
        """获取文本嵌入向量"""
        # 这里可以使用OpenAI、Cohere等嵌入模型
        import openai
        client = openai.AsyncOpenAI()
        response = await client.embeddings.create(
            model="text-embedding-3-small",
            input=text
        )
        return response.data[0].embedding
    
    def cosine_similarity(self, a: list, b: list) -> float:
        """计算余弦相似度"""
        a, b = np.array(a), np.array(b)
        return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
    
    async def get(self, query: str) -> Optional[dict]:
        """语义查询缓存"""
        query_embedding = await self.get_embedding(query)
        
        best_match = None
        best_similarity = 0
        
        for embedding, response in self.cache:
            sim = self.cosine_similarity(query_embedding, embedding)
            if sim > best_similarity:
                best_similarity = sim
                best_match = response
        
        if best_similarity >= self.similarity_threshold:
            print(f"语义缓存命中!相似度: {best_similarity:.4f}")
            return best_match
        
        return None
    
    async def set(self, query: str, response: dict):
        """添加到语义缓存"""
        embedding = await self.get_embedding(query)
        self.cache.append((embedding, response))
        
        # 限制缓存大小
        if len(self.cache) > 10000:
            self.cache = self.cache[-5000:]

10. 企业级多租户部署

10.1 多租户架构设计

from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.security import HTTPBearer
import jwt

app = FastAPI()
security = HTTPBearer()

# 租户配置
TENANT_CONFIG = {
    "tenant_a": {
        "team_id": "team_a",
        "models": ["gpt-4o", "gpt-4o-mini"],
        "daily_budget": 100,
        "rate_limit": 60,
        "priority": "high"
    },
    "tenant_b": {
        "team_id": "team_b",
        "models": ["gpt-4o-mini", "claude-haiku"],
        "daily_budget": 50,
        "rate_limit": 30,
        "priority": "normal"
    }
}

async def get_tenant(request: Request, token = Depends(security)):
    """从JWT Token中解析租户信息"""
    try:
        payload = jwt.decode(
            token.credentials,
            "your-secret-key",
            algorithms=["HS256"]
        )
        tenant_id = payload.get("tenant_id")
        if tenant_id not in TENANT_CONFIG:
            raise HTTPException(status_code=403, detail="Unknown tenant")
        return {"tenant_id": tenant_id, **TENANT_CONFIG[tenant_id]}
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/v1/chat/completions")
async def chat_completions(request: Request, tenant = Depends(get_tenant)):
    """多租户聊天接口"""
    body = await request.json()
    model = body.get("model")
    
    # 检查模型权限
    if model not in tenant["models"]:
        raise HTTPException(
            status_code=403,
            detail=f"Tenant {tenant['tenant_id']} cannot access model {model}"
        )
    
    # 检查预算和速率限制(使用前面的RateLimiter和CostMonitor)
    # ... 
    
    # 转发请求到实际的LLM
    # ...
    
    return {"status": "ok", "tenant": tenant["tenant_id"]}

11. 与Kong/Nginx集成

11.1 Nginx反向代理配置

upstream litellm_backend {
    server 127.0.0.1:4000;
    # 多实例负载均衡
    server 127.0.0.1:4001;
    server 127.0.0.1:4002;
}

server {
    listen 443 ssl;
    server_name llm-api.yourcompany.com;
    
    ssl_certificate /etc/ssl/certs/your-cert.pem;
    ssl_certificate_key /etc/ssl/private/your-key.pem;
    
    # 请求体大小限制(大上下文窗口需要)
    client_max_body_size 50m;
    
    # 速率限制
    limit_req_zone $binary_remote_addr zone=llm_rate:10m rate=60r/m;
    
    location /v1/ {
        limit_req zone=llm_rate burst=20 nodelay;
        
        proxy_pass http://litellm_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置(LLM响应可能较慢)
        proxy_read_timeout 120s;
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        
        # 流式响应支持
        proxy_buffering off;
        proxy_cache off;
    }
}

11.2 Kong Gateway插件配置

# Kong服务配置
_format_version: "3.0"
services:
  - name: llm-gateway
    url: http://litellm:4000
    routes:
      - name: llm-route
        paths:
          - /v1
    plugins:
      - name: rate-limiting
        config:
          minute: 60
          policy: redis
          redis_host: redis
      - name: key-auth
        config:
          key_names:
            - api_key
      - name: http-log
        config:
          http_endpoint: http://log-collector:8080/logs

12. 完整实战:构建生产级LLM网关

12.1 项目结构

llm-gateway/
├── config/
│   ├── litellm_config.yaml
│   └── nginx.conf
├── src/
│   ├── __init__.py
│   ├── router.py          # 路由逻辑
│   ├── auth.py            # 认证鉴权
│   ├── rate_limiter.py    # 速率限制
│   ├── cache.py           # 缓存层
│   ├── logger.py          # 日志系统
│   ├── cost_monitor.py    # 成本监控
│   └── main.py            # FastAPI应用
├── tests/
├── docker-compose.yaml
├── Dockerfile
└── requirements.txt

12.2 Docker Compose部署

version: "3.8"

services:
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    ports:
      - "4000:4000"
    volumes:
      - ./config/litellm_config.yaml:/app/config.yaml
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    depends_on:
      - redis
      - postgres

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  postgres:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=litellm
      - POSTGRES_USER=litellm
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./config/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./certs:/etc/ssl/certs
    depends_on:
      - litellm

volumes:
  redis_data:
  postgres_data:

13. 最佳实践总结

  1. 始终使用虚拟Key:不要将真实API Key暴露给客户端
  2. 实施多层缓存:精确缓存 + 语义缓存可显著降低成本
  3. 设置合理的超时:LLM响应时间长,需要适当放宽超时
  4. 监控成本:实时追踪Token消耗,设置预算告警
  5. 故障转移必备:至少配置2个备用模型
  6. 日志要全:记录每次请求的完整上下文,便于排查问题
  7. 流式支持:生产环境务必支持SSE流式响应
  8. 定期轮转Key:定期更换API Key,降低泄露风险
  9. 隔离租户:多租户场景下严格隔离资源和配额
  10. 压测先行:上线前进行充分的压力测试

总结

AI API网关是企业级LLM应用的核心基础设施。通过本教程,你已经掌握了从开源工具选型(LiteLLM、OpenRouter、Portkey)到自建网关的完整技术栈,包括路由策略、负载均衡、安全管控、成本监控和缓存优化等关键能力。

选择合适的方案取决于你的具体需求:

  • 快速验证:直接使用OpenRouter
  • 灵活自建:部署LiteLLM
  • 企业级需求:Portkey + 自定义中间件

无论选择哪种方案,核心原则不变:统一路由、智能降级、全面监控、安全第一

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录