为什么需要成本控制?
调用大模型 API 按 token 计费,失控的调用可能在一夜之间产生数千元账单。常见失控场景包括:用户构造超长输入(token 炸弹)、代码 bug 导致循环调用、爬虫批量刷接口、以及模型返回超长响应等。成本控制不是可选项,而是生产系统的基础设施。
Token 预算设计
在系统架构层设计多维度的 token 预算,形成嵌套的限制体系:
| 维度 | 示例限制 | 目的 |
|---|---|---|
| 单次请求输入 | ≤ 4,000 tokens | 防止 token 炸弹攻击 |
| 单次请求输出 | ≤ 2,000 tokens | 控制单次成本上限 |
| 单用户/小时 | ≤ 50,000 tokens | 防止单用户过度消费 |
| 单用户/天 | ≤ 200,000 tokens | 日配额管理 |
| 全局/小时 | ≤ 2,000,000 tokens | 系统级成本上限 |
| 全局/月 | ≤ 50,000,000 tokens | 月度预算控制 |
限流策略实现
策略一:令牌桶算法(Token Bucket)
允许短时突发流量,同时保证长期速率不超标。适合用户交互场景。
import time
import threading
from dataclasses import dataclass, field
@dataclass
class TokenBucket:
"""令牌桶限流器(线程安全)"""
capacity: int # 桶容量(最大突发 token 数)
refill_rate: float # 每秒补充的 token 数
_tokens: float = field(init=False)
_last_refill: float = field(init=False)
_lock: threading.Lock = field(init=False, default_factory=threading.Lock)
def __post_init__(self):
self._tokens = float(self.capacity)
self._last_refill = time.monotonic()
def _refill(self):
"""补充令牌"""
now = time.monotonic()
elapsed = now - self._last_refill
new_tokens = elapsed * self.refill_rate
self._tokens = min(self.capacity, self._tokens + new_tokens)
self._last_refill = now
def consume(self, tokens: int) -> bool:
"""尝试消费 tokens,成功返回 True,限流返回 False"""
with self._lock:
self._refill()
if self._tokens >= tokens:
self._tokens -= tokens
return True
return False
@property
def available(self) -> float:
with self._lock:
self._refill()
return self._tokens
策略二:滑动窗口计数(Sliding Window)
精确统计时间窗口内的消费量,无突发容忍,适合严格的配额控制。
import time
from collections import deque
import redis # 生产环境推荐用 Redis 做分布式限流
class SlidingWindowRateLimiter:
"""基于 Redis 的滑动窗口限流(生产级)"""
def __init__(self, redis_client, window_seconds: int, max_tokens: int):
self.redis = redis_client
self.window = window_seconds
self.max_tokens = max_tokens
def check_and_consume(self, user_id: str, token_count: int) -> dict:
"""
检查并消费 token 配额
返回: {"allowed": bool, "used": int, "remaining": int, "reset_in": int}
"""
key = f"ratelimit:{user_id}:{self.window}s"
now = time.time()
window_start = now - self.window
pipe = self.redis.pipeline()
# 移除窗口外的记录
pipe.zremrangebyscore(key, 0, window_start)
# 获取当前窗口消费量
pipe.zrange(key, 0, -1, withscores=True)
# 设置过期
pipe.expire(key, self.window + 1)
_, records, _ = pipe.execute()
current_usage = sum(score for _, score in records)
if current_usage + token_count > self.max_tokens:
return {
"allowed": False,
"used": int(current_usage),
"remaining": max(0, self.max_tokens - int(current_usage)),
"reset_in": self.window,
"error": f"超出速率限制:已用 {int(current_usage)}/{self.max_tokens} tokens"
}
# 记录本次消费
self.redis.zadd(key, {f"{now}:{token_count}": now}, score=token_count)
return {
"allowed": True,
"used": int(current_usage + token_count),
"remaining": self.max_tokens - int(current_usage + token_count),
"reset_in": self.window,
}
降级方案:超预算切小模型
当用户接近配额上限时,不直接拒绝服务,而是降级到更便宜的小模型,保证基本可用性:
from enum import Enum
class ModelTier(Enum):
PREMIUM = "gpt-4o" # 高质量,贵
STANDARD = "gpt-4o-mini" # 均衡
ECONOMY = "gpt-3.5-turbo" # 最便宜
LOCAL = "qwen2.5-7b-local" # 本地模型,免费
# 模型定价(每千 token 美元,输入价格)
MODEL_PRICES = {
ModelTier.PREMIUM: 0.005,
ModelTier.STANDARD: 0.00015,
ModelTier.ECONOMY: 0.0005,
ModelTier.LOCAL: 0.0,
}
class AdaptiveModelSelector:
"""根据配额余量自适应选择模型"""
TIER_THRESHOLDS = [
(0.5, ModelTier.PREMIUM), # 剩余 > 50%:用最好模型
(0.2, ModelTier.STANDARD), # 剩余 20-50%:降级到标准
(0.05, ModelTier.ECONOMY), # 剩余 5-20%:再降级
(0.0, ModelTier.LOCAL), # 剩余 < 5%:切本地模型
]
def select_model(self, quota_remaining_ratio: float,
task_priority: str = "normal") -> ModelTier:
"""
根据剩余配额比例选择模型
quota_remaining_ratio: 0.0 ~ 1.0
task_priority: "high" | "normal" | "low"
"""
if task_priority == "high":
# 高优先级任务不降级
return ModelTier.PREMIUM
for threshold, tier in self.TIER_THRESHOLDS:
if quota_remaining_ratio > threshold:
return tier
return ModelTier.LOCAL
def estimate_cost(self, model: ModelTier, input_tokens: int,
output_tokens: int) -> float:
"""预估请求成本(美元)"""
price = MODEL_PRICES[model]
return (input_tokens + output_tokens) / 1000 * price
成本监控与告警
import smtplib
from datetime import datetime
class CostMonitor:
"""成本监控与告警"""
def __init__(self, daily_budget_usd: float, alert_threshold: float = 0.8):
self.daily_budget = daily_budget_usd
self.alert_threshold = alert_threshold # 达到预算的80%时告警
self.daily_cost = 0.0
self.cost_history = [] # [(timestamp, cost, model, user_id)]
def record_usage(self, cost: float, model: str, user_id: str):
"""记录一次调用的成本"""
self.daily_cost += cost
self.cost_history.append((datetime.now(), cost, model, user_id))
# 检查是否需要告警
usage_ratio = self.daily_cost / self.daily_budget
if usage_ratio >= self.alert_threshold:
self._send_alert(usage_ratio)
# 硬上限:超过100%直接禁用
if usage_ratio >= 1.0:
self._emergency_stop()
def _send_alert(self, usage_ratio: float):
"""发送成本告警(示例用 print,生产环境换成真实告警渠道)"""
print(f"🚨 成本告警:今日已消耗预算的 {usage_ratio*100:.1f}%")
print(f" 已花费: ${self.daily_cost:.4f} / 预算: ${self.daily_budget:.2f}")
# 生产环境:发送到 Slack/钉钉/邮件/PagerDuty
def _emergency_stop(self):
"""超出预算硬停"""
print("🛑 紧急停止:今日 AI 调用预算已耗尽,服务已暂停。")
raise RuntimeError("Daily AI cost budget exceeded")
def get_top_consumers(self, n: int = 5) -> list:
"""获取消费最多的用户"""
from collections import defaultdict
user_costs = defaultdict(float)
for _, cost, _, user_id in self.cost_history:
user_costs[user_id] += cost
return sorted(user_costs.items(), key=lambda x: x[1], reverse=True)[:n]
# 完整使用示例
monitor = CostMonitor(daily_budget_usd=10.0, alert_threshold=0.8)
selector = AdaptiveModelSelector()
def ai_call_with_cost_control(user_id: str, prompt: str, priority: str = "normal"):
"""带成本控制的 AI 调用封装"""
# 1. 获取用户配额使用情况(简化示例)
quota_ratio = 0.6 # 实际从 Redis 读取
# 2. 选择合适模型
model = selector.select_model(quota_ratio, priority)
# 3. 预估 token 数(tiktoken 精确计算)
input_tokens = len(prompt.split()) * 1.3 # 粗估
# 4. 执行调用(此处省略实际API调用)
output_tokens = 500 # 假设
# 5. 记录成本
cost = selector.estimate_cost(model, int(input_tokens), output_tokens)
monitor.record_usage(cost, model.value, user_id)
print(f"✅ 调用完成 | 用户: {user_id} | 模型: {model.value} | 成本: ${cost:.5f}")
成本优化最佳实践
- 缓存高频重复请求:相同或相似的查询命中缓存,成本降低 30-60%
- 精确估算 token 数:用 tiktoken 库精确计算,避免超过上下文窗口造成错误
- 流式输出 + 提前截断:用 streaming 模式,达到足够长度后主动停止生成
- Prompt 压缩:去除冗余说明,保留核心指令,减少 input token
- 批量处理:非实时场景合并多个小请求,减少调用次数
- 分级模型路由:简单任务路由到小模型,复杂任务才用大模型
小结
速率限制和成本控制是 AI 应用从 demo 走向生产的必经关卡。核心三要素:多维预算设计(请求/用户/全局)、自适应降级(配额紧张时切便宜模型)、实时监控告警(达到预算阈值前预警)。把成本控制逻辑做成可配置的基础设施,而不是硬编码在业务代码里。