限流与速率限制

📑 目录

在 AI Agent 编程工具的日常使用中,限流(Rate Limiting) 是开发者最常遇到的"拦路虎"之一。Claude Code 作为 Anthropic 官方推出的命令行 AI 编程助手,其内部构建了一套极为精密的限流与速率限制系统。本文将深入源码,解析 Claude Code 如何处理 API 配额、模拟限流场景、实现智能重试以及模型降级策略,帮助读者理解一个生产级 AI 工具如何在资源受限的边界内保持优雅的用户体验。

一、限流架构概览

Claude Code 的限流系统分布在三个核心文件中:

  • src/services/claudeAiLimits.ts(约 16KB):限流状态的核心逻辑,负责解析 API 响应头、计算配额状态、触发早期预警。
  • src/services/mockRateLimits.ts(约 29KB):内部测试用的 mock 限流系统,支持 20 多种限流场景的模拟。
  • src/services/rateLimitMessages.ts:限流消息的集中管理,统一生成错误提示和警告文案。
flowchart TD
    A[API 响应] --> B[processRateLimitHeaders]
    B --> C{是否启用 mock?}
    C -->|是| D[applyMockHeaders]
    C -->|否| E[原始 Headers]
    D --> F[computeNewLimitsFromHeaders]
    E --> F
    F --> G[extractQuotaStatusFromHeaders]
    G --> H[emitStatusChange]
    H --> I[UI 渲染层]
    H --> J[Retry 决策层]

上图展示了限流数据从 API 响应到 UI 展示的完整链路。值得注意的是,Claude Code 不在客户端做任何封号判断,所有限制均通过服务端下发的 anthropic-ratelimit-unified-* 响应头传递,客户端只是忠实执行(src/services/claudeAiLimits.ts,第 1~15 行)。这种设计将策略决策完全交由服务端,确保客户端逻辑简洁且不会过时。

二、速率限制类型

Anthropic 的 API 采用多层级的统一限流器(Unified Rate Limiter),Claude Code 在其基础上封装了五种核心限流类型:

2.1 五种核心限流窗口

// src/services/claudeAiLimits.ts,第 27~36 行
type QuotaStatus = 'allowed' | 'allowed_warning' | 'rejected'

type RateLimitType =
  | 'five_hour'          // 5 小时滑动窗口(会话级限制)
  | 'seven_day'          // 7 天滚动窗口(周级限制)
  | 'seven_day_opus'     // Opus 模型专用的 7 天限制
  | 'seven_day_sonnet'   // Sonnet 模型专用的 7 天限制
  | 'overage'            // 超额使用限制(付费后仍有上限)

每种限流类型对应不同的用户行为和业务场景:

限流类型时间窗口触发场景重置策略
five_hour5 小时滑动窗口单一会话内过度使用从首次请求开始计时
seven_day7 天滚动窗口周度总量超标自然滚动重置
seven_day_opus7 天(Opus 专属)Opus 模型消耗过快与周窗口同步
seven_day_sonnet7 天(Sonnet 专属)Sonnet 模型消耗过快与周窗口同步
overage按配置超额额度耗尽按组织账单周期

2.2 响应头解析机制

每次 API 调用后,Claude Code 从响应头中提取限流状态(src/services/claudeAiLimits.ts,第 376~436 行):

function computeNewLimitsFromHeaders(headers: globalThis.Headers): ClaudeAILimits {
  // 核心状态:allowed / allowed_warning / rejected
  const status =
    (headers.get('anthropic-ratelimit-unified-status') as QuotaStatus) || 'allowed'

  // 重置时间(Unix 秒)
  const resetsAtHeader = headers.get('anthropic-ratelimit-unified-reset')
  const resetsAt = resetsAtHeader ? Number(resetsAtHeader) : undefined

  // 命中的限制类型
  const rateLimitType = headers.get(
    'anthropic-ratelimit-unified-representative-claim',
  ) as RateLimitType | null

  // 超额使用状态
  const overageStatus = headers.get(
    'anthropic-ratelimit-unified-overage-status',
  ) as QuotaStatus | null

  // 超额被禁用的原因(12 种可能)
  const overageDisabledReason = headers.get(
    'anthropic-ratelimit-unified-overage-disabled-reason',
  ) as OverageDisabledReason | null

  // ...
}

status === 'rejected' 时,所有后续 API 调用会被立即阻止,用户必须等到 resetsAt 时间戳之后才能恢复使用。

2.3 超额禁用原因枚举

Anthropic 定义了 12 种超额使用被禁用的原因(src/services/claudeAiLimits.ts,第 107~120 行):

export type OverageDisabledReason =
  | 'overage_not_provisioned'        // 未开通超额
  | 'org_level_disabled'             // 组织级别禁用
  | 'org_level_disabled_until'       // 组织级别临时禁用
  | 'out_of_credits'                 // 余额不足
  | 'seat_tier_level_disabled'       // 席位等级禁用
  | 'member_level_disabled'          // 个人账户禁用
  | 'seat_tier_zero_credit_limit'    // 席位等级信用额度为零
  | 'group_zero_credit_limit'        // 组信用额度为零
  | 'member_zero_credit_limit'       // 个人信用额度为零
  | 'org_service_level_disabled'     // 组织服务级别禁用
  | 'org_service_zero_credit_limit'  // 组织服务信用额度为零
  | 'no_limits_configured'           // 未配置限制
  | 'unknown'                        // 未知原因

这种精细化的原因分类让 Claude Code 能够向用户展示准确的引导信息,例如余额不足时提示充值,席位等级禁用时引导联系管理员。

三、Mock 限流系统

对于内部测试和演示场景,Claude Code 提供了一套完整的 Mock 限流系统,仅在 USER_TYPE === 'ant'(Anthropic 内部员工)时激活。

3.1 Mock 场景覆盖

src/services/mockRateLimits.ts 定义了 21 种 mock 场景(第 62~82 行):

export type MockScenario =
  | 'normal'
  | 'session-limit-reached'
  | 'approaching-weekly-limit'
  | 'weekly-limit-reached'
  | 'overage-active'
  | 'overage-warning'
  | 'overage-exhausted'
  | 'out-of-credits'
  | 'org-zero-credit-limit'
  | 'org-spend-cap-hit'
  | 'member-zero-credit-limit'
  | 'seat-tier-zero-credit-limit'
  | 'opus-limit'
  | 'opus-warning'
  | 'sonnet-limit'
  | 'sonnet-warning'
  | 'fast-mode-limit'
  | 'fast-mode-short-limit'
  | 'extra-usage-required'
  | 'clear'

外部构建(external build)中,这些函数被编译为无操作(no-op)的空实现(src/services/mockRateLimits.ts,第 84~128 行),确保生产环境不会意外触发模拟逻辑:

export function setMockHeader(
  _key: MockHeaderKey,
  _value: string | undefined,
): void {}

export function getMockHeaders(): MockHeaders | null {
  return null
}

export function shouldProcessMockLimits(): boolean {
  return false
}

3.2 Mock 的 facade 隔离

生产代码通过 rateLimitMocking.ts 与 mock 系统解耦(src/services/rateLimitMocking.ts,第 1~27 行):

/**
 * Facade for rate limit header processing
 * This isolates mock logic from production code
 */

export function processRateLimitHeaders(
  headers: globalThis.Headers,
): globalThis.Headers {
  // Only apply mocks for Ant employees using /mock-limits command
  if (shouldProcessMockLimits()) {
    return applyMockHeaders(headers)
  }
  return headers
}

这种 Facade 模式 的设计非常精妙:生产代码始终调用 processRateLimitHeaders(),无需关心底层是真实限流还是 mock 限流。Mock 逻辑被完全隔离在可选模块中,外部构建甚至可以将整个 mock 模块 tree-shake 掉。

3.3 开发调试命令

内部员工可通过 /mock-limits 命令快速切换限流场景,用于验证 UI 提示、重试逻辑和降级策略的正确性。这是大型工程中**可测试性(Testability)**设计的典范——不依赖外部 API 状态,即可在本地复现生产环境的边界条件。

四、早期预警系统

Claude Code 最具工程智慧的设计之一,是在用户真正被限流之前就发出警告。系统通过两种方式实现早期预警:

4.1 服务端 surpassed-threshold 头

当 API 响应包含 anthropic-ratelimit-unified-{claim}-surpassed-threshold 头时,客户端直接展示对应警告(src/services/claudeAiLimits.ts,第 255~294 行)。

4.2 客户端时间相对预警(Fallback)

当服务端未发送阈值头时,Claude Code 会自行计算用户是否在"燃烧"配额(src/services/claudeAiLimits.ts,第 53~70 行):

const EARLY_WARNING_CONFIGS: EarlyWarningConfig[] = [
  {
    rateLimitType: 'five_hour',
    windowSeconds: 5 * 60 * 60,
    thresholds: [{ utilization: 0.9, timePct: 0.72 }],
  },
  {
    rateLimitType: 'seven_day',
    windowSeconds: 7 * 24 * 60 * 60,
    thresholds: [
      { utilization: 0.75, timePct: 0.6 },
      { utilization: 0.5, timePct: 0.35 },
      { utilization: 0.25, timePct: 0.15 },
    ],
  },
]

预警逻辑的核心是使用率-时间比src/services/claudeAiLimits.ts,第 98~103 行):

function computeTimeProgress(resetsAt: number, windowSeconds: number): number {
  const nowSeconds = Date.now() / 1000
  const windowStart = resetsAt - windowSeconds
  const elapsed = nowSeconds - windowStart
  return Math.max(0, Math.min(1, elapsed / windowSeconds))
}

例如,对于 7 天窗口:如果用户在时间只过去了 35% 的情况下就消耗了 50% 的配额,系统会立即发出警告。这种速度感知的预警比单纯的阈值判断更加智能,因为它考虑到了用户的消耗速率。

五、重试机制

当 API 返回错误时,Claude Code 的重试系统展现了极高的工程成熟度。核心实现在 src/services/api/withRetry.ts 中。

5.1 重试架构

sequenceDiagram
    participant U as 用户
    participant Q as QueryEngine
    participant R as withRetry
    participant A as Anthropic API

    U->>Q: 发送请求
    Q->>R: withRetry(operation)
    loop 最多 maxRetries+1 次
        R->>A: API 调用
        alt 成功
            A-->>R: 返回结果
            R-->>Q: yield 结果
        else 429/529 错误
            A-->>R: 返回错误
            R->>R: 计算退避延迟
            R-->>Q: yield SystemAPIErrorMessage
            Q-->>U: 显示"正在重试..."
            R->>R: sleep(delayMs)
        else 不可重试错误
            A-->>R: 返回错误
            R-->>Q: throw CannotRetryError
            Q-->>U: 显示错误
        end
    end

5.2 指数退避与抖动

重试延迟采用指数退避 + 随机抖动策略(src/services/api/withRetry.ts,第 530~548 行):

export function getRetryDelay(
  attempt: number,
  retryAfterHeader?: string | null,
  maxDelayMs = 32000,
): number {
  if (retryAfterHeader) {
    const seconds = parseInt(retryAfterHeader, 10)
    if (!isNaN(seconds)) {
      return seconds * 1000
    }
  }

  const baseDelay = Math.min(
    BASE_DELAY_MS * Math.pow(2, attempt - 1),
    maxDelayMs,
  )
  const jitter = Math.random() * 0.25 * baseDelay
  return baseDelay + jitter
}

参数设计:

  • BASE_DELAY_MS = 500:基础延迟 500ms
  • maxDelayMs = 32000:最大延迟 32 秒
  • jitter = 25%:随机抖动避免惊群效应

如果 API 返回 Retry-After 头,则优先使用服务端指定的时间。

5.3 最大重试次数

默认最大重试次数为 10 次,但可通过环境变量覆盖(src/services/api/withRetry.ts,第 52~53 行、第 789~794 行):

const DEFAULT_MAX_RETRIES = 10

export function getDefaultMaxRetries(): number {
  if (process.env.CLAUDE_CODE_MAX_RETRIES) {
    return parseInt(process.env.CLAUDE_CODE_MAX_RETRIES, 10)
  }
  return DEFAULT_MAX_RETRIES
}

5.4 错误分类与决策矩阵

shouldRetry() 函数实现了精细化的错误分类(src/services/api/withRetry.ts,第 696~787 行):

function shouldRetry(error: APIError): boolean {
  // Never retry mock errors
  if (isMockRateLimitError(error)) {
    return false
  }

  // Persistent mode: 429/529 always retryable
  if (isPersistentRetryEnabled() && isTransientCapacityError(error)) {
    return true
  }

  // CCR mode: 401/403 are transient blips
  if (
    isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
    (error.status === 401 || error.status === 403)
  ) {
    return true
  }

  // Check x-should-retry header
  const shouldRetryHeader = error.headers?.get('x-should-retry')
  if (shouldRetryHeader === 'true') {
    return true
  }

  // Retry on connection errors
  if (error instanceof APIConnectionError) {
    return true
  }

  // 429 rate limits: retry for non-subscribers or enterprise
  if (error.status === 429) {
    return !isClaudeAISubscriber() || isEnterpriseSubscriber()
  }

  // 401: clear cache and retry
  if (error.status === 401) {
    clearApiKeyHelperCache()
    return true
  }

  // 5xx: retry server errors
  if (error.status && error.status >= 500) return true

  return false
}

关键决策逻辑:

  • Mock 错误:永不重试,避免测试死循环
  • 429 限流Claude.ai 订阅用户(Pro/Max)不重试,因为窗口重置时间通常很长;企业用户和 API Key 用户则重试
  • 529 过载:区分前台/后台请求,前台请求重试(用户正在等待),后台请求直接丢弃(避免级联放大)
  • 401 认证错误:清除缓存后重试,触发 OAuth Token 刷新

5.5 持久化重试模式(Persistent Retry)

对于无人值守会话(unattended sessions),Claude Code 提供了一种特殊的持久化重试模式(src/services/api/withRetry.ts,第 91~104 行):

const PERSISTENT_MAX_BACKOFF_MS = 5 * 60 * 1000   // 5 分钟
const PERSISTENT_RESET_CAP_MS = 6 * 60 * 60 * 1000 // 6 小时
const HEARTBEAT_INTERVAL_MS = 30_000               // 30 秒

在此模式下,429/529 错误会无限重试,退避上限为 5 分钟,且每次长等待期间会定期 yield 心跳消息,防止宿主环境将空闲会话标记为死亡。这对于 CI/CD 集成或长时间后台任务至关重要。

六、降级策略

当资源受限时,Claude Code 不会简单粗暴地报错退出,而是实施多层降级策略,尽可能保持核心功能可用。

6.1 模型降级:Opus → Sonnet → Haiku

当连续遭遇 529 过载错误(默认阈值 3 次)时,系统会触发模型降级(src/services/api/withRetry.ts,第 326~364 行):

if (is529Error(error)) {
  consecutive529Errors++
  if (consecutive529Errors >= MAX_529_RETRIES) {
    // Check if fallback model is specified
    if (options.fallbackModel) {
      logEvent('tengu_api_opus_fallback_triggered', {
        original_model: options.model,
        fallback_model: options.fallbackModel,
      })

      // Throw special error to indicate fallback was triggered
      throw new FallbackTriggeredError(
        options.model,
        options.fallbackModel,
      )
    }
  }
}

降级链路通常遵循:

Claude Opus(最强推理)
    ↓ 连续 3 次 529
Claude Sonnet(均衡性能)
    ↓ 继续受限
Claude Haiku(最快最轻)

FallbackTriggeredError 被上游 QueryEngine 捕获后,会以降级后的模型重新发起请求,用户几乎无感知。

6.2 Fast Mode 降级

Fast Mode 是 Claude Code 的一项加速功能,但在限流时会自动降级为标准速度(src/services/api/withRetry.ts,第 261~305 行):

if (wasFastModeActive && error instanceof APIError &&
    (error.status === 429 || is529Error(error))) {
  const retryAfterMs = getRetryAfterMs(error)
  if (retryAfterMs !== null && retryAfterMs < SHORT_RETRY_THRESHOLD_MS) {
    // 短等待:保持 Fast Mode 以保留 Prompt Cache
    await sleep(retryAfterMs, options.signal, { abortError })
    continue
  }
  // 长等待:进入 Cooldown,切换到标准速度
  const cooldownMs = Math.max(
    retryAfterMs ?? DEFAULT_FAST_MODE_FALLBACK_HOLD_MS,
    MIN_COOLDOWN_MS,
  )
  triggerFastModeCooldown(Date.now() + cooldownMs, cooldownReason)
  retryContext.fastMode = false
  continue
}

这里有两个关键设计:

  • 短等待(<20秒):保持 Fast Mode 活跃,因为重新启用需要重建 Prompt Cache,成本较高
  • 长等待(≥20秒):主动降级到标准速度,避免在等待期间占用 Fast Mode 配额

6.3 功能降级与提示引导

当配额完全耗尽时,Claude Code 通过 rateLimitMessages.ts 向用户展示精准的状态信息和操作引导(src/services/rateLimitMessages.ts,第 45~104 行):

export function getRateLimitMessage(
  limits: ClaudeAILimits,
  model: string,
): RateLimitMessage | null {
  // 超额使用场景
  if (limits.isUsingOverage) {
    if (limits.overageStatus === 'allowed_warning') {
      return {
        message: "You're close to your extra usage spending limit",
        severity: 'warning',
      }
    }
    return null
  }

  // 被拒绝状态
  if (limits.status === 'rejected') {
    return { message: getLimitReachedText(limits, model), severity: 'error' }
  }

  // 预警状态(利用率 > 70% 才显示)
  if (limits.status === 'allowed_warning') {
    const WARNING_THRESHOLD = 0.7
    if (limits.utilization !== undefined && limits.utilization < WARNING_THRESHOLD) {
      return null
    }
    const text = getEarlyWarningText(limits)
    if (text) {
      return { message: text, severity: 'warning' }
    }
  }

  return null
}

消息文案会根据订阅类型动态调整 upsell 引导(src/services/rateLimitMessages.ts,第 261~297 行):

  • Pro/Max 用户:提示 /upgrade to keep using Claude Code
  • Team/Enterprise 用户:提示 /extra-usage to request more
  • 已启用超额的用户:静默无缝切换,不打扰用户

6.4 预飞检查(Pre-flight Check)

每次会话启动时,Claude Code 会发送一个极小的探测请求来预检配额状态(src/services/claudeAiLimits.ts,第 199~218 行):

async function makeTestQuery() {
  const model = getSmallFastModel()  // 使用最便宜的 Haiku 模型
  const anthropic = await getAnthropicClient({
    maxRetries: 0,
    model,
    source: 'quota_check',
  })
  return anthropic.beta.messages.create({
    model,
    max_tokens: 1,  // 只请求 1 个 token
    messages: [{ role: 'user', content: 'quota' }],
  }).asResponse()
}

这个设计体现了极致的成本意识:用最便宜的模型 + 最少的 token 完成探测,只为获取响应头中的配额信息,而不消耗宝贵的会话配额。

七、限流消息渲染

限流消息的最终呈现由 RateLimitMessage.tsx 组件负责,而文案统一由 rateLimitMessages.ts 生成。这种文案与渲染分离的架构确保了:

  1. 文案可集中管理,避免碎片化
  2. UI 组件只负责展示,不涉及业务逻辑
  3. 便于国际化(虽然 Claude Code 目前主要支持英文)

错误消息前缀被提取为常量数组(src/services/rateLimitMessages.ts,第 21~27 行):

export const RATE_LIMIT_ERROR_PREFIXES = [
  "You've hit your",
  "You've used",
  "You're now using extra usage",
  "You're close to",
  "You're out of extra usage",
] as const

配合 isRateLimitErrorMessage() 工具函数,下游组件可以快速判断一条消息是否属于限流错误,而无需脆弱的字符串匹配。

八、工程启示

Claude Code 的限流系统为我们设计生产级 AI Agent 提供了以下宝贵经验:

1. 服务端主导策略
所有限流判断由服务端响应头驱动,客户端零策略硬编码。这使得限流规则可以动态调整,无需发版即可生效。

2. 分层防御体系
从早期预警 → 软限制(warning)→ 硬限制(rejected)→ 超额切换 → 模型降级,形成完整的防御梯度,而非简单粗暴地"一刀切"。

3. 可测试性设计
Mock 限流系统让开发者和 QA 可以在本地复现所有生产限流场景,无需等待真实配额耗尽。Facade 隔离确保了 mock 代码不会污染生产逻辑。

4. 用户感知的优雅降级
每次降级都伴随清晰的状态提示和操作引导(/upgrade/extra-usage),将负面体验转化为商业转化机会。

5. 成本极致优化
预飞检查使用 Haiku + 1 token,重试时区分前台/后台请求,Fast Mode 降级保留 Prompt Cache——每一处细节都体现了对 API 成本的精打细算。

结语

Claude Code 的限流系统不是一个简单的"收到 429 就报错"的粗糙实现,而是一套融合了状态机、预测模型、重试策略、降级链路和商业逻辑的精密工程体系。对于正在构建 AI Agent 应用的开发者而言,这套系统提供了极佳的参考范本:如何在资源受限的现实世界中,既保护后端服务,又最大化用户体验。

理解这些机制后,当你下次在终端里看到 "You’ve used 75% of your weekly limit" 的提示时,你会知道在这行文字背后,是数百行精心设计的代码在为你的编程体验保驾护航。