429 不是意外,是常态:重新思考 AI 助手的容错设计

429 不是意外,是常态:重新思考 AI 助手的容错设计

凌晨 2 点,你的 Clawdbot 停止响应。

日志里全是这个:

429 rate_limit_error: This request would exceed your account's rate limit

429,HTTP 协议里的"你太快了,歇会儿"。

但你的 AI 助手不会"歇会儿",它会一直重试,一直报错,直到你手动介入。

这不是 AI 助手,这是需要保姆的智障机器人。

为什么 429 是必然的?

大模型 API 的限流策略比你想象的严格:

Anthropic Claude 限制(企业外用户)

RPM (Requests Per Minute): ~50
TPM (Tokens Per Minute): ~100k
并发请求: 5-10

OpenAI 限制(个人账户)

RPM: 10-60 (根据套餐)
TPM: 40k-150k
日总量: 可能有隐藏上限

当你把 Clawdbot 接到 3 个 WhatsApp 群、5 个 Telegram 频道、10 个私聊时,消息可能同时涌入。

每条消息都触发一次 API 调用。

429 不是异常,是你触碰到了系统设计的边界。

最原始的「重试」逻辑有多糟

很多人的第一反应是加重试:

async function callAPI(prompt) {
  for (let i = 0; i < 5; i++) {
    try {
      return await claude.chat(prompt);
    } catch (error) {
      if (error.status === 429) {
        await sleep(60000); // 等 1 分钟
        continue;
      }
      throw error;
    }
  }
  throw new Error('重试 5 次全部失败');
}

这套逻辑的问题:

问题1:线性等待浪费时间

假设限流窗口是 1 分钟,你已经触发限流。

  • 第1次重试:等 60 秒 → 仍然 429(因为窗口还没过)
  • 第2次重试:等 60 秒 → 仍然 429
  • 第3次重试:等 60 秒 → 成功

总耗时:180 秒,但实际上 API 在第 65 秒时就恢复了。

问题2:惊群效应

5 个请求同时触发 429:

请求A: 429 → 等60秒
请求B: 429 → 等60秒
请求C: 429 → 等60秒
...

60秒后,5个请求同时苏醒 → 同时重试 → 又是 429

这叫"惊群",会让限流时间无限延长。

问题3:成本无限

如果是因为"配额用完"(比如月度总量耗尽),重试根本没用。

但代码会傻傻地重试 5 次,每次等 60 秒,最后还是失败。

浪费 5 分钟 + 用户体验极差。

我认为应该这样设计

策略1:区分错误类型

不是所有错误都适合重试:

enum ErrorStrategy {
  RETRY,      // 网络抖动、偶发超时
  FALLBACK,   // 429、模型不可用
  ABORT       // 认证失败、参数错误
}

function classifyError(error: APIError): ErrorStrategy {
  switch (error.status) {
    case 429:
      return error.type === 'rate_limit' 
        ? ErrorStrategy.FALLBACK   // 立刻切换模型
        : ErrorStrategy.RETRY;     // 偶发限流,重试
    
    case 503:
      return ErrorStrategy.RETRY;  // 服务暂时不可用
    
    case 401:
    case 403:
      return ErrorStrategy.ABORT;  // 认证问题,重试无意义
    
    case 400:
      return ErrorStrategy.ABORT;  // 参数错误,不应该重试
    
    default:
      return ErrorStrategy.RETRY;
  }
}

策略2:指数退避

不要固定等 60 秒,使用指数增长的等待时间:

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // 指数退避:1s, 2s, 4s, 8s...
      const delay = Math.min(1000 * Math.pow(2, i), 30000);
      
      // 加入随机抖动,避免惊群
      const jitter = Math.random() * 1000;
      
      await sleep(delay + jitter);
    }
  }
}

策略3:快速 Fallback

当检测到 429 时,不应该重试,应该立刻切换备用模型:

async function callWithFallback(
  prompt: string, 
  models: string[]
): Promise<string> {
  for (const model of models) {
    try {
      return await callAPI(prompt, model);
    } catch (error) {
      if (error.status === 429) {
        console.log(`${model} 限流,切换到下一个模型`);
        continue;  // 立刻尝试下一个,不等待
      }
      throw error;
    }
  }
  throw new Error('所有模型都不可用');
}

// 使用
const result = await callWithFallback('你好', [
  'anthropic/claude-opus-4.5',
  'openai/gpt-4-turbo',
  'deepseek/deepseek-r1'
]);

策略4:熔断器

如果某个模型连续失败 N 次,停止尝试一段时间:

class CircuitBreaker {
  private failures = new Map<string, number>();
  private openUntil = new Map<string, number>();
  
  async call<T>(model: string, fn: () => Promise<T>): Promise<T> {
    // 检查熔断状态
    if (this.isOpen(model)) {
      throw new Error(`${model} 已熔断,${this.getResetTime(model)}秒后恢复`);
    }
    
    try {
      const result = await fn();
      this.recordSuccess(model);
      return result;
    } catch (error) {
      this.recordFailure(model);
      
      // 连续失败 5 次,熔断 10 分钟
      if (this.failures.get(model) >= 5) {
        this.openUntil.set(model, Date.now() + 600000);
      }
      
      throw error;
    }
  }
  
  private isOpen(model: string): boolean {
    const until = this.openUntil.get(model);
    if (!until) return false;
    
    if (Date.now() > until) {
      this.openUntil.delete(model);
      this.failures.delete(model);
      return false;
    }
    
    return true;
  }
}

更激进的想法:放弃"完美响应"

传统软件的思维是:要么成功,要么报错

但 AI 助手可以有第三种状态:降级服务

降级策略

当所有高质量模型都 429 时,不应该直接报错"服务不可用",而是:

降级1:使用缓存响应

if (isRateLimited()) {
  const cached = await getCachedResponse(prompt);
  if (cached) {
    return `[使用缓存] ${cached}`;
  }
}

适合重复性问题("今天天气怎么样"可能每天都有人问)。

降级2:简化任务

原始请求: "分析这份 50 页的财报,找出 3 个风险点"
降级请求: "总结这份财报的主要内容"

降低任务复杂度,使用更便宜的模型(Haiku 而不是 Opus)。

降级3:延迟处理

即时响应: "当前请求量较大,您的任务已加入队列,预计 5 分钟后完成。"

后台执行: 等限流解除后处理,处理完主动推送结果。

这需要一个"任务队列"系统。

结论

429 错误是大模型时代的"日常"。

传统的"重试解决一切"思维不再适用。

真正稳定的 AI 助手应该:

  1. 预判风险:监控 API usage,在接近限流前就降低频率
  2. 快速切换:429 出现后立刻 fallback,不浪费时间
  3. 熔断保护:连续失败后停止尝试,避免雪崩
  4. 降级服务:在无法提供完美服务时,给用户"次优方案"

这不是技术问题,是设计哲学的转变:

从"零容错"到"优雅降级"

承认系统会失败,提前设计失败时的 Plan B,才是成熟的工程思维。


技术参考

← 返回博客列表