大模型API工程化实战:限流、缓存、降级与成本优化完整指南
从单体API调用到企业级LLM网关,本文系统讲解大模型API工程化的六大核心模块:限流策略、语义缓存、故障降级、成本控制、重试机制与可观测性,附完整Python代码示例。
为什么需要LLM API工程化?
很多团队接入大模型的方式非常简单——就是一个 requests.post() 调用。但当业务量上来后,各种问题接踵而至:
- 成本失控:一个月API账单从几千涨到几十万,钱花在哪了说不清
- 稳定性差:第三方API偶尔超时或限流,整个业务就挂了
- 性能瓶颈:高并发下响应慢,用户体验差
- 难以排障:出了问题不知道是网络、模型还是代码的问题
- 安全隐患:API密钥泄漏、恶意调用、数据越权
LLM工程化就是用后端工程的方法论来解决这些问题,让AI应用从"能用"变成"可靠、高效、可控"。
本文将带你从零搭建一个生产级的LLM API网关,涵盖六大核心能力。
LLM工程化能力矩阵
┌──────────────────────────────────────────────────────────┐
│ LLM API 工程化能力矩阵 │
├──────────┬──────────┬──────────┬─────────────────────────┤
│ 限流保护 │ 语义缓存 │ 故障降级 │ 成本控制 │
│ 令牌桶算法 │ 精确匹配 │ 熔断机制 │ Token计数 │
│ 滑动窗口 │ 向量相似 │ 降级模型 │ 路由优化 │
│ 用户级限流 │ LRU淘汰 │ 本地回退 │ 缓存命中 │
├──────────┼──────────┼──────────┼─────────────────────────┤
│ 重试机制 │ 可观测性 │ 安全控制 │ 模型路由 │
│ 指数退避 │ 请求日志 │ 鉴权认证 │ 智能路由 │
│ 幂等性 │ 指标监控 │ 内容过滤 │ 负载均衡 │
│ 超时控制 │ 链路追踪 │ 数据脱敏 │ 故障转移 │
└──────────┴──────────┴──────────┴─────────────────────────┘
一、限流保护:防止成本失控
1.1 多级限流策略
生产环境需要多层次的限流:
| 限流层级 | 作用 | 典型配置 |
|---|---|---|
| 全局限流 | 控制总体成本 | 10万次/天 |
| 用户级限流 | 防止单用户滥用 | 100次/分钟 |
| IP级限流 | 防止恶意攻击 | 50次/分钟 |
| 接口级限流 | 保护特定高成本接口 | 10次/分钟 |
1.2 令牌桶算法实现
import time
from threading import Lock
from collections import defaultdict
from typing import Dict
class TokenBucket:
"""令牌桶限流器"""
def __init__(self, rate: float, capacity: int):
"""
rate: 每秒生成令牌数
capacity: 桶容量(最大突发请求数)
"""
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.last_time = time.time()
self.lock = Lock()
def acquire(self, tokens: int = 1) -> bool:
"""获取令牌,返回是否成功"""
with self.lock:
now = time.time()
# 计算新增令牌
elapsed = now - self.last_time
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.rate
)
self.last_time = now
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
def get_wait_time(self, tokens: int = 1) -> float:
"""获取需要等待的时间(秒)"""
with self.lock:
if self.tokens >= tokens:
return 0
deficit = tokens - self.tokens
return deficit / self.rate
class MultiLevelRateLimiter:
"""多级限流器"""
def __init__(self):
# 全局限流:1000请求/分钟
self.global_limiter = TokenBucket(rate=1000/60, capacity=200)
# 用户级限流
self.user_limiters: Dict[str, TokenBucket] = {}
self.user_default_rate = 100/60 # 100次/分钟
self.user_default_capacity = 30
self.lock = Lock()
def _get_user_limiter(self, user_id: str) -> TokenBucket:
if user_id not in self.user_limiters:
with self.lock:
if user_id not in self.user_limiters:
self.user_limiters[user_id] = TokenBucket(
rate=self.user_default_rate,
capacity=self.user_default_capacity
)
return self.user_limiters[user_id]
def check(self, user_id: str, tokens: int = 1) -> dict:
"""
检查是否允许请求
返回: {allowed: bool, reason: str, wait_time: float}
"""
# 全局检查
if not self.global_limiter.acquire(tokens):
return {
'allowed': False,
'reason': 'global_rate_limit',
'wait_time': self.global_limiter.get_wait_time(tokens)
}
# 用户级检查
user_limiter = self._get_user_limiter(user_id)
if not user_limiter.acquire(tokens):
return {
'allowed': False,
'reason': 'user_rate_limit',
'wait_time': user_limiter.get_wait_time(tokens)
}
return {'allowed': True, 'reason': '', 'wait_time': 0}
# 使用示例
if __name__ == "__main__":
limiter = MultiLevelRateLimiter()
# 模拟用户请求
for i in range(5):
result = limiter.check("user_001")
status = "✅ 允许" if result['allowed'] else "❌ 限流"
print(f"请求 {i+1}: {status} - {result['reason']}")
二、语义缓存:大幅降低成本与延迟
2.1 缓存策略对比
| 缓存类型 | 命中率 | 实现难度 | 适用场景 |
|---|---|---|---|
| 精确匹配缓存 | 低(5-15%) | 简单 | 完全重复的查询 |
| 语义缓存 | 高(30-60%) | 中等 | 相似问题频繁出现的场景 |
| 生成结果缓存 | 中 | 高 | 固定模板的生成任务 |
2.2 语义缓存实现
import hashlib
import json
import time
from typing import Optional, Tuple, List
from dataclasses import dataclass
@dataclass
class CacheEntry:
key: str
query: str
response: str
model: str
created_at: float
access_count: int
embedding: List[float] = None
class LRUCache:
"""LRU缓存实现"""
def __init__(self, max_size: int = 1000):
self.max_size = max_size
self.cache = {} # key -> CacheEntry
self.access_order = [] # 按访问顺序,最新在末尾
self.lock = None # 生产环境加锁
def get(self, key: str) -> Optional[CacheEntry]:
if key in self.cache:
# 移到末尾(最新访问)
self.access_order.remove(key)
self.access_order.append(key)
entry = self.cache[key]
entry.access_count += 1
return entry
return None
def put(self, key: str, entry: CacheEntry):
if key in self.cache:
self.access_order.remove(key)
elif len(self.cache) >= self.max_size:
# 淘汰最旧的
oldest_key = self.access_order.pop(0)
del self.cache[oldest_key]
self.cache[key] = entry
self.access_order.append(key)
def size(self) -> int:
return len(self.cache)
class SemanticCache:
"""语义缓存 - 结合精确匹配和向量相似匹配"""
def __init__(self, exact_cache_size: int = 5000,
semantic_threshold: float = 0.95):
self.exact_cache = LRUCache(max_size=exact_cache_size)
self.semantic_threshold = semantic_threshold
# 生产环境应使用向量数据库(FAISS/Milvus/Qdrant)
self.vector_entries = [] # 简化:内存向量列表
def _exact_key(self, query: str, model: str) -> str:
"""生成精确匹配的key"""
content = f"{model}:{query.strip().lower()}"
return hashlib.md5(content.encode()).hexdigest()
def _simple_embedding(self, text: str) -> List[float]:
"""
简化版文本向量(生产环境应使用Embedding API)
实际使用:openai.embeddings.create 或 text-embedding-3-small
"""
# 这里用字符级统计模拟,实际项目请用真实embedding模型
text = text.lower()
vec = [0.0] * 128
for i, char in enumerate(text):
vec[ord(char) % 128] += 1.0
# 归一化
norm = sum(x*x for x in vec) ** 0.5
if norm > 0:
vec = [x/norm for x in vec]
return vec
def _cosine_similarity(self, v1: List[float], v2: List[float]) -> float:
"""余弦相似度"""
return sum(a*b for a, b in zip(v1, v2))
def lookup(self, query: str, model: str) -> Optional[str]:
"""
查找缓存
返回: 缓存的回复,或None
"""
# 1. 先尝试精确匹配
key = self._exact_key(query, model)
entry = self.exact_cache.get(key)
if entry:
return entry.response
# 2. 语义匹配(简化版,生产环境用向量数据库)
query_emb = self._simple_embedding(query)
best_score = 0
best_response = None
for entry in self.vector_entries:
if entry.model != model:
continue
score = self._cosine_similarity(query_emb, entry.embedding)
if score > best_score and score >= self.semantic_threshold:
best_score = score
best_response = entry.response
return best_response
def store(self, query: str, model: str, response: str):
"""存入缓存"""
key = self._exact_key(query, model)
embedding = self._simple_embedding(query)
entry = CacheEntry(
key=key,
query=query,
response=response,
model=model,
created_at=time.time(),
access_count=1,
embedding=embedding
)
self.exact_cache.put(key, entry)
self.vector_entries.append(entry)
# 限制向量条目数量
if len(self.vector_entries) > 2000:
self.vector_entries = self.vector_entries[-1500:]
# 使用示例
if __name__ == "__main__":
cache = SemanticCache()
# 存入缓存
cache.store(
query="Python如何实现快速排序?",
model="gpt-4o",
response="快速排序的Python实现如下:\n\ndef quick_sort(arr):\n if len(arr) <= 1:\n return arr\n pivot = arr[len(arr)//2]\n left = [x for x in arr if x < pivot]\n middle = [x for x in arr if x == pivot]\n right = [x for x in arr if x > pivot]\n return quick_sort(left) + middle + quick_sort(right)"
)
# 精确匹配查询
result = cache.lookup("Python如何实现快速排序?", "gpt-4o")
print(f"精确匹配: {'命中' if result else '未命中'}")
# 相似查询
result2 = cache.lookup("python快速排序怎么写?", "gpt-4o")
print(f"语义匹配: {'命中' if result2 else '未命中'}")
语义缓存的效果:在客服、FAQ类场景中,语义缓存可以达到40-60%的命中率,意味着: - 成本降低40-60% - 延迟从秒级降到毫秒级 - 第三方API故障时仍能服务部分请求
三、重试与容错机制
3.1 指数退避重试
import time
import random
import asyncio
from typing import Callable, Any, Optional
from functools import wraps
def retry_with_backoff(
max_retries: int = 3,
base_delay: float = 1.0,
max_delay: float = 30.0,
backoff_factor: float = 2.0,
jitter: bool = True,
retry_on_exceptions: tuple = (Exception,)
):
"""
指数退避重试装饰器
Args:
max_retries: 最大重试次数
base_delay: 初始延迟(秒)
max_delay: 最大延迟(秒)
backoff_factor: 退避因子
jitter: 是否添加抖动
retry_on_exceptions: 需要重试的异常类型
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except retry_on_exceptions as e:
last_exception = e
if attempt == max_retries:
raise
# 计算延迟
delay = min(base_delay * (backoff_factor ** attempt), max_delay)
if jitter:
delay = delay * (0.5 + random.random() * 0.5)
time.sleep(delay)
raise last_exception
return wrapper
return decorator
# 异步版本
def async_retry_with_backoff(
max_retries: int = 3,
base_delay: float = 1.0,
max_delay: float = 30.0,
backoff_factor: float = 2.0,
jitter: bool = True,
retry_on_exceptions: tuple = (Exception,)
):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries + 1):
try:
return await func(*args, **kwargs)
except retry_on_exceptions as e:
last_exception = e
if attempt == max_retries:
raise
delay = min(base_delay * (backoff_factor ** attempt), max_delay)
if jitter:
delay = delay * (0.5 + random.random() * 0.5)
await asyncio.sleep(delay)
raise last_exception
return wrapper
return decorator
3.2 熔断器模式
import time
from enum import Enum
from threading import Lock
class CircuitState(Enum):
CLOSED = "closed" # 正常状态
OPEN = "open" # 熔断状态
HALF_OPEN = "half_open" # 半开状态(探测恢复)
class CircuitBreaker:
"""熔断器实现"""
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: float = 30.0,
half_open_max_calls: int = 3,
failure_window: float = 60.0
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_max_calls = half_open_max_calls
self.failure_window = failure_window
self.state = CircuitState.CLOSED
self.failure_times = []
self.half_open_calls = 0
self.last_failure_time = 0
self.lock = Lock()
def _is_window_expired(self) -> bool:
"""检查熔断窗口是否过期"""
return time.time() - self.last_failure_time > self.recovery_timeout
def _record_failure(self):
"""记录失败"""
now = time.time()
self.failure_times.append(now)
self.last_failure_time = now
# 清理窗口外的失败记录
self.failure_times = [
t for t in self.failure_times
if now - t < self.failure_window
]
def can_execute(self) -> bool:
"""判断是否可以执行请求"""
with self.lock:
if self.state == CircuitState.CLOSED:
return True
if self.state == CircuitState.OPEN:
if self._is_window_expired():
# 进入半开状态
self.state = CircuitState.HALF_OPEN
self.half_open_calls = 0
return True
return False
if self.state == CircuitState.HALF_OPEN:
return self.half_open_calls < self.half_open_max_calls
return False
def record_success(self):
"""记录成功"""
with self.lock:
if self.state == CircuitState.HALF_OPEN:
# 半开状态下成功,恢复正常
self.state = CircuitState.CLOSED
self.failure_times.clear()
self.half_open_calls = 0
elif self.state == CircuitState.CLOSED:
# 正常状态下成功,可以适当清理旧的失败记录
pass
def record_failure(self):
"""记录失败"""
with self.lock:
self._record_failure()
if self.state == CircuitState.HALF_OPEN:
# 半开状态下失败,重新熔断
self.state = CircuitState.OPEN
self.half_open_calls = 0
elif self.state == CircuitState.CLOSED:
# 检查是否达到熔断阈值
if len(self.failure_times) >= self.failure_threshold:
self.state = CircuitState.OPEN
def get_state(self) -> str:
return self.state.value
# 使用示例
if __name__ == "__main__":
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=5.0)
print("初始状态:", breaker.get_state())
# 模拟连续失败
for i in range(4):
if breaker.can_execute():
print(f"请求 {i+1}: 执行 -> 失败")
breaker.record_failure()
else:
print(f"请求 {i+1}: 被熔断")
print("当前状态:", breaker.get_state())
print(f"等待 {breaker.recovery_timeout} 秒后恢复...")
四、成本精细化管理
4.1 Token计数与成本追踪
import tiktoken # pip install tiktoken
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class CostRecord:
request_id: str
user_id: str
model: str
prompt_tokens: int
completion_tokens: int
total_tokens: int
cost: float
timestamp: float
class CostManager:
"""LLM成本管理器"""
# 2026年7月主流模型定价(美元/1K tokens)
MODEL_PRICING = {
'gpt-4o': {'input': 0.005, 'output': 0.015},
'gpt-4o-mini': {'input': 0.00015, 'output': 0.0006},
'claude-3-5-sonnet': {'input': 0.003, 'output': 0.015},
'claude-3-opus': {'input': 0.015, 'output': 0.075},
'deepseek-v3': {'input': 0.00014, 'output': 0.00028},
'qwen2-72b': {'input': 0.0008, 'output': 0.002},
}
def __init__(self):
self.encoders = {} # 缓存encoder
self.records: List[CostRecord] = []
self.user_daily_cost: Dict[str, float] = {}
self.total_cost = 0.0
def _get_encoder(self, model: str):
"""获取token编码器"""
if model not in self.encoders:
try:
self.encoders[model] = tiktoken.encoding_for_model(model)
except:
# 默认使用cl100k_base
self.encoders[model] = tiktoken.get_encoding("cl100k_base")
return self.encoders[model]
def count_tokens(self, text: str, model: str = 'gpt-4o') -> int:
"""计算文本的token数量"""
encoder = self._get_encoder(model)
return len(encoder.encode(text))
def count_messages_tokens(self, messages: List[Dict],
model: str = 'gpt-4o') -> int:
"""计算对话消息的token数量"""
encoder = _get_encoder(model) if False else None # 简化
# 使用tiktoken的计算方式
try:
enc = tiktoken.encoding_for_model(model)
except:
enc = tiktoken.get_encoding("cl100k_base")
tokens_per_message = 4 # 每条消息的基础开销
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(enc.encode(str(value)))
num_tokens += 3 # 回复的基础开销
return num_tokens
def calculate_cost(self, prompt_tokens: int,
completion_tokens: int, model: str) -> float:
"""计算请求成本"""
pricing = self.MODEL_PRICING.get(model,
{'input': 0.01, 'output': 0.03})
cost = (
prompt_tokens / 1000 * pricing['input'] +
completion_tokens / 1000 * pricing['output']
)
return round(cost, 6)
def record_cost(self, request_id: str, user_id: str, model: str,
prompt_tokens: int, completion_tokens: int):
"""记录成本"""
cost = self.calculate_cost(prompt_tokens, completion_tokens, model)
record = CostRecord(
request_id=request_id,
user_id=user_id,
model=model,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
cost=cost,
timestamp=datetime.now().timestamp()
)
self.records.append(record)
self.total_cost += cost
# 用户日成本统计
today = datetime.now().strftime('%Y-%m-%d')
key = f"{user_id}:{today}"
self.user_daily_cost[key] = self.user_daily_cost.get(key, 0) + cost
return cost
def get_user_daily_cost(self, user_id: str) -> float:
"""获取用户今日成本"""
today = datetime.now().strftime('%Y-%m-%d')
key = f"{user_id}:{today}"
return self.user_daily_cost.get(key, 0)
def get_cost_summary(self) -> Dict:
"""获取成本统计摘要"""
model_costs = {}
user_costs = {}
for record in self.records:
model_costs[record.model] = model_costs.get(record.model, 0) + record.cost
user_costs[record.user_id] = user_costs.get(record.user_id, 0) + record.cost
return {
'total_cost': round(self.total_cost, 4),
'total_requests': len(self.records),
'total_tokens': sum(r.total_tokens for r in self.records),
'avg_cost_per_request': round(
self.total_cost / len(self.records), 6
) if self.records else 0,
'by_model': {k: round(v, 4) for k, v in model_costs.items()},
'top_users': sorted(
user_costs.items(), key=lambda x: x[1], reverse=True
)[:10]
}
4.2 成本优化策略
| 策略 | 节省比例 | 实现难度 | 对效果影响 |
|---|---|---|---|
| 语义缓存 | 30-60% | 中 | 低(缓存命中时无影响) |
| 模型路由 | 20-50% | 中 | 可控(简单任务用小模型) |
| Prompt精简 | 10-30% | 低 | 低 |
| 输出长度限制 | 5-20% | 低 | 低 |
| 批量请求 | 10-25% | 中 | 无 |
| 流式输出 | - | 低 | 提升体验 |
五、模型智能路由
5.1 基于复杂度的路由
from typing import Dict, Tuple, List
import re
class ModelRouter:
"""智能模型路由器"""
def __init__(self):
self.models = {
'fast': {
'name': 'gpt-4o-mini',
'cost_per_1k_input': 0.00015,
'speed': 'fast',
'quality': 'good'
},
'balanced': {
'name': 'gpt-4o',
'cost_per_1k_input': 0.005,
'speed': 'medium',
'quality': 'excellent'
},
'premium': {
'name': 'claude-3-opus',
'cost_per_1k_input': 0.015,
'speed': 'slow',
'quality': 'best'
}
}
def _assess_complexity(self, query: str, context: str = '') -> str:
"""
评估任务复杂度
返回: simple, medium, complex
"""
text = query + ' ' + context
score = 0
# 长度指标
if len(text) > 2000:
score += 2
elif len(text) > 500:
score += 1
# 关键词指标
complex_keywords = [
'分析', '优化', '设计', '架构', '策略', '方案',
'对比', '评估', '预测', '推理', '代码审查',
'debug', '优化', '重构', '复杂', '系统'
]
for kw in complex_keywords:
if kw in text:
score += 0.5
# 代码相关
if re.search(r'```|def |function |class ', text):
score += 1
# 数学/逻辑
if re.search(r'数学|证明|推导|公式|算法', text):
score += 1
if score >= 3:
return 'complex'
elif score >= 1:
return 'medium'
else:
return 'simple'
def route(self, query: str, context: str = '',
preference: str = 'balanced') -> Tuple[str, Dict]:
"""
选择合适的模型
返回: (模型名, 路由信息)
"""
complexity = self._assess_complexity(query, context)
# 基于复杂度选择
model_tier = {
'simple': 'fast',
'medium': 'balanced',
'complex': 'premium'
}[complexity]
# 用户偏好调整
if preference == 'cost':
if complexity == 'complex':
model_tier = 'balanced' # 复杂任务也用平衡模型
else:
model_tier = 'fast'
elif preference == 'quality':
model_tier = 'premium'
model = self.models[model_tier]
return model['name'], {
'complexity': complexity,
'model_tier': model_tier,
'preference': preference,
'estimated_cost': model['cost_per_1k_input']
}
# 使用示例
if __name__ == "__main__":
router = ModelRouter()
test_queries = [
("今天天气怎么样?", "simple"),
("帮我写一个Python爬虫", "medium"),
("设计一个高并发的微服务架构,需要考虑缓存、消息队列、数据库分片等", "complex"),
]
for query, expected in test_queries:
model, info = router.route(query)
print(f"[{expected}] {query[:30]}... -> {model} (复杂度: {info['complexity']})")
六、可观测性:监控与日志
6.1 请求追踪与指标
import time
import uuid
from typing import Dict, Any, Optional
from contextlib import contextmanager
from dataclasses import dataclass, field
@dataclass
class LLMRequestMetrics:
request_id: str
model: str
user_id: str
prompt_tokens: int = 0
completion_tokens: int = 0
latency_ms: float = 0
status: str = "success" # success, error, rate_limited, cached
error_type: str = ""
cache_hit: bool = False
cost: float = 0.0
metadata: Dict = field(default_factory=dict)
class LLMObserver:
"""LLM可观测性管理器"""
def __init__(self):
self.metrics_history = []
self.error_count = 0
self.total_requests = 0
self.cached_requests = 0
self.total_latency = 0.0
@contextmanager
def trace(self, model: str, user_id: str,
metadata: Dict = None):
"""
请求追踪上下文管理器
使用方式:
with observer.trace(model, user_id) as metrics:
response = call_llm(...)
metrics.prompt_tokens = ...
"""
request_id = str(uuid.uuid4())
metrics = LLMRequestMetrics(
request_id=request_id,
model=model,
user_id=user_id,
metadata=metadata or {}
)
start_time = time.time()
try:
yield metrics
except Exception as e:
metrics.status = "error"
metrics.error_type = type(e).__name__
self.error_count += 1
raise
finally:
metrics.latency_ms = (time.time() - start_time) * 1000
self.total_requests += 1
self.total_latency += metrics.latency_ms
if metrics.cache_hit:
self.cached_requests += 1
self.metrics_history.append(metrics)
# 生产环境:上报到监控系统(Prometheus/Datadog等)
self._report_metrics(metrics)
def _report_metrics(self, metrics: LLMRequestMetrics):
"""上报指标(生产环境实现)"""
# 生产环境中上报到监控系统
pass
def get_dashboard_stats(self) -> Dict:
"""获取统计面板数据"""
if self.total_requests == 0:
return {'total_requests': 0}
avg_latency = self.total_latency / self.total_requests
cache_hit_rate = self.cached_requests / self.total_requests * 100
error_rate = self.error_count / self.total_requests * 100
return {
'total_requests': self.total_requests,
'avg_latency_ms': round(avg_latency, 2),
'cache_hit_rate': f"{cache_hit_rate:.1f}%",
'error_rate': f"{error_rate:.2f}%",
'cached_requests': self.cached_requests,
'errors': self.error_count,
'total_cost': round(sum(m.cost for m in self.metrics_history), 4)
}
# 使用示例
if __name__ == "__main__":
observer = LLMObserver()
# 模拟请求
for i in range(3):
try:
with observer.trace("gpt-4o", "user_001") as metrics:
# 模拟API调用
time.sleep(0.1)
metrics.prompt_tokens = 150
metrics.completion_tokens = 250
metrics.cost = 0.0045
if i == 1:
metrics.cache_hit = True
except Exception as e:
print(f"请求失败: {e}")
stats = observer.get_dashboard_stats()
print("=== 监控面板 ===")
for k, v in stats.items():
print(f" {k}: {v}")
七、完整LLM网关集成
将以上所有模块组合成一个完整的LLM网关:
from typing import Dict, List, Optional
import time
import uuid
class LLMGateway:
"""
生产级LLM API网关
集成:限流 + 缓存 + 重试 + 熔断 + 成本管理 + 可观测性 + 模型路由
"""
def __init__(self):
self.rate_limiter = MultiLevelRateLimiter()
self.cache = SemanticCache()
self.circuit_breaker = CircuitBreaker(
failure_threshold=10,
recovery_timeout=60.0
)
self.cost_manager = CostManager()
self.observer = LLMObserver()
self.router = ModelRouter()
def chat(self, user_id: str, messages: List[Dict],
model: Optional[str] = None,
preference: str = 'balanced',
use_cache: bool = True,
max_tokens: int = 1000) -> Dict:
"""统一聊天接口"""
request_id = str(uuid.uuid4())
with self.observer.trace(model or "auto", user_id,
{"request_id": request_id}) as metrics:
# 1. 限流检查
rate_result = self.rate_limiter.check(user_id)
if not rate_result['allowed']:
metrics.status = "rate_limited"
return {
'success': False,
'error': 'rate_limit_exceeded',
'message': f"请求过于频繁,请等待 {rate_result['wait_time']:.1f} 秒",
'request_id': request_id
}
# 2. 模型路由
query = messages[-1].get('content', '') if messages else ''
context = ' '.join([m.get('content', '') for m in messages[:-1]])
if model is None:
model, route_info = self.router.route(query, context, preference)
metrics.metadata['route_info'] = route_info
metrics.model = model
# 3. 缓存查询
if use_cache:
cached_response = self.cache.lookup(query, model)
if cached_response:
metrics.cache_hit = True
metrics.prompt_tokens = 0
metrics.completion_tokens = 0
return {
'success': True,
'content': cached_response,
'model': model,
'from_cache': True,
'request_id': request_id,
'latency_ms': metrics.latency_ms
}
# 4. 熔断检查
if not self.circuit_breaker.can_execute():
metrics.status = "circuit_open"
return {
'success': False,
'error': 'circuit_breaker_open',
'message': '服务暂时不可用,请稍后再试',
'request_id': request_id
}
# 5. 调用LLM API(带重试)
try:
response = self._call_llm_with_retry(model, messages, max_tokens)
# 记录成功
self.circuit_breaker.record_success()
# 成本记录
prompt_tokens = response.get('usage', {}).get('prompt_tokens', 0)
completion_tokens = response.get('usage', {}).get('completion_tokens', 0)
cost = self.cost_manager.record_cost(
request_id, user_id, model,
prompt_tokens, completion_tokens
)
metrics.prompt_tokens = prompt_tokens
metrics.completion_tokens = completion_tokens
metrics.cost = cost
# 存入缓存
if use_cache:
self.cache.store(query, model, response['content'])
return {
'success': True,
'content': response['content'],
'model': model,
'usage': response.get('usage', {}),
'cost': cost,
'from_cache': False,
'request_id': request_id,
'latency_ms': metrics.latency_ms
}
except Exception as e:
self.circuit_breaker.record_failure()
metrics.status = "error"
metrics.error_type = type(e).__name__
raise
@retry_with_backoff(
max_retries=3,
base_delay=1.0,
retry_on_exceptions=(TimeoutError, ConnectionError)
)
def _call_llm_with_retry(self, model: str, messages: List[Dict],
max_tokens: int) -> Dict:
"""调用LLM API(带重试)"""
# 实际项目中调用OpenAI/Anthropic等API
# 这里是模拟
return {
'content': f"这是 {model} 的回复(模拟)",
'usage': {
'prompt_tokens': 150,
'completion_tokens': 200,
'total_tokens': 350
}
}
常见问题 FAQ
Q1: 小团队有必要搞这么复杂的网关吗?
不一定。建议按阶段来: - MVP阶段:直接调用API + 简单日志 - 增长阶段:加缓存 + 成本统计 + 基础限流 - 规模化阶段:完整网关 + 熔断 + 模型路由
不要过早优化,但要为未来扩展留好接口。
Q2: 语义缓存的准确率如何保证?不会给出错误答案吗?
语义缓存有两个关键参数: - 相似度阈值:设得越高越准确,但命中率越低。建议从0.95开始调 - 缓存有效期:对于时效性强的内容,设置较短的TTL
另外,建议对高风险场景(如医疗、法律)关闭语义缓存,只使用精确匹配。
Q3: 如何选择合适的模型路由策略?
常见的路由策略有: 1. 基于关键词:简单直接,但覆盖不全 2. 基于分类器:用小模型先做任务分类,准确率较高 3. 基于用户分层:付费用户用好模型,免费用户用小模型 4. 基于A/B测试:先尝试小模型,效果不好再升级
建议从简单的开始,根据数据逐步优化。
Q4: 成本优化效果最好的手段是什么?
按ROI排序: 1. 语义缓存(命中率高的话效果最显著) 2. 模型路由(简单任务用便宜模型) 3. Prompt优化(减少不必要的上下文) 4. 输出长度限制(限制max_tokens)
很多团队光靠缓存就能省一半成本。
Q5: 熔断器的阈值怎么设比较合理?
建议起步配置: - 失败阈值:5-10次 - 时间窗口:1-5分钟 - 恢复时间:30-60秒
然后根据实际错误率和用户体验调整。错误率太高就降低阈值,太敏感就提高阈值。
总结
LLM API工程化是AI应用从玩具到产品的必经之路。本文覆盖了六大核心模块:
- 限流保护 — 防止成本失控和服务过载
- 语义缓存 — 降低成本、提升响应速度
- 重试与熔断 — 保证系统稳定性
- 成本管理 — 精细化追踪和优化AI开销
- 模型路由 — 在效果和成本间找到最优解
- 可观测性 — 让系统状态透明可追踪
这些能力不需要一次性全部实现,但建议从一开始就预留好架构,随着业务增长逐步完善。毕竟,一个不可控的AI应用,效果再好也不敢大规模上线。