大模型 API 调用的超时与重试策略从简单重试到自适应退避一、默认重试带来的连锁故障一个 AI 辅助写作应用对 OpenAI API 的调用配置了失败后重试 3 次。当 OpenAI 服务出现间歇性故障时大量用户的请求排队重试。原本 1 次调用变成了 4 次调用1 次原始 3 次重试QPS 瞬间翻了 4 倍。这不仅没有提高成功率反而因请求堆积触发了 OpenAI 的 Rate Limit导致更多本可成功的调用被拒绝。这就是重试风暴——不加控制的重试在服务抖动时将负载放大加速服务恶化。大模型 API 的重试策略需要额外的考量API 计费基于 Token一次无效的重试也在消耗配额和费用。二、重试策略的三层递进从基础到高级建立三层重试机制graph TB REQ[API 调用请求] -- RETRY{判断错误类型} RETRY --|4xx 客户端错误| NORETRY[不重试br/直接返回错误] RETRY --|429 Rate Limit| WAIT[等待 Retry-After] RETRY --|5xx 服务端错误| BACKOFF[指数退避重试] RETRY --|网络超时| BACKOFF WAIT --|等待结束| REQ BACKOFF -- CHECK{重试次数 3?} CHECK --|是| DELAY[延时: base × 2^attempt jitter] CHECK --|否| FALLBACK[降级备用模型/缓存/兜底] DELAY -- REQ核心原则4xx 错误不重试——请求本身有问题认证失败、参数非法重试不会改变结果429 Rate Limit 遵循服务端的 Retry-After 头指令而非使用固定的退避时间5xx 和网络超时才使用指数退避退避中加入随机抖动防止惊群效应。三、自适应退避的工程实现// 自适应重试策略——根据错误类型和 API 状态动态调整 // 设计意图不盲目重试区分错误类型、尊重服务端指令 interface RetryConfig { maxRetries: number; baseDelayMs: number; maxDelayMs: number; // 退避倍数 backoffMultiplier: number; // 是否启用自适应根据近期成功率动态调整 adaptive: boolean; } interface RetryState { // 滑动窗口内的成功/失败统计 recentSuccesses: number; recentFailures: number; windowSize: number; } class AdaptiveRetryClient { private state: RetryState { recentSuccesses: 0, recentFailures: 0, windowSize: 100, }; private config: RetryConfig { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 30000, backoffMultiplier: 2, adaptive: true, }; async callT( apiCall: () PromiseT, context: { model: string; endpoint: string }, ): PromiseT { let lastError: Error | null null; for (let attempt 0; attempt this.config.maxRetries; attempt) { try { const result await apiCall(); // 成功——更新滑动窗口统计 this.recordSuccess(); return result; } catch (error) { lastError error as Error; const statusCode this.extractStatusCode(error); // 不可重试的错误类型 if (this.isNonRetryable(statusCode, error as Error)) { throw error; } // Rate Limit——使用服务端指定的等待时间 if (statusCode 429) { const retryAfter this.extractRetryAfter(error); if (retryAfter) { // 服务端指定的等待时间优先 await this.sleep(retryAfter * 1000); continue; // 不计入重试次数 } } // 最后一次尝试——不再重试 if (attempt this.config.maxRetries) { this.recordFailure(); break; } // 计算退避延迟 const delay this.calculateDelay(attempt); console.warn( API 调用失败 (第 ${attempt 1}/${this.config.maxRetries 1} 次) ${delay}ms 后重试。错误: ${lastError.message} ); await this.sleep(delay); } } this.recordFailure(); throw new Error( API 调用在 ${this.config.maxRetries 1} 次尝试后仍失败: ${lastError?.message} ); } private isNonRetryable( statusCode: number | null, error: Error, ): boolean { // 4xx 客户端错误429 除外 if (statusCode statusCode 400 statusCode 500 statusCode ! 429) { return true; } // 认证错误不重试 if (error.message.includes(authentication) || error.message.includes(unauthorized)) { return true; } // 上下文长度超限——缩短后可能可以重试 if (error.message.includes(context_length_exceeded)) { return true; // 这种错误靠重试无法解决 } return false; } private calculateDelay(attempt: number): number { // 指数退避baseDelay × multiplier^attempt let delay this.config.baseDelayMs * Math.pow( this.config.backoffMultiplier, attempt, ); // 自适应调整——近期失败率高时增加退避时间 if (this.config.adaptive) { const failureRate this.getFailureRate(); if (failureRate 0.3) { // 失败率超过 30%退避时间翻倍 delay * 2; } } // 随机抖动——±25%防止惊群效应 const jitter delay * (0.5 Math.random() * 0.5); // 不超过最大延迟 return Math.min(jitter, this.config.maxDelayMs); } private getFailureRate(): number { const total this.state.recentSuccesses this.state.recentFailures; if (total 0) return 0; return this.state.recentFailures / total; } private recordSuccess(): void { // 滑动窗口内的计数更新 this.state.recentSuccesses; if (this.state.recentSuccesses this.state.recentFailures this.state.windowSize) { // 窗口溢出——减半以保持计数在范围内 this.state.recentSuccesses Math.floor(this.state.recentSuccesses / 2); this.state.recentFailures Math.floor(this.state.recentFailures / 2); } } private recordFailure(): void { this.state.recentFailures; } private extractStatusCode(error: unknown): number | null { if (error typeof error object status in error) { return (error as { status: number }).status; } return null; } private extractRetryAfter(error: unknown): number | null { // 从响应头或错误体中提取 Retry-After if (error typeof error object headers in error) { const headers (error as { headers: Recordstring, string }).headers; if (headers?.[retry-after]) { return parseInt(headers[retry-after], 10); } } return null; } private sleep(ms: number): Promisevoid { return new Promise(resolve setTimeout(resolve, ms)); } }四个关键设计错误分类——4xx 和认证错误不重试429 使用服务端 Retry-After只有 5xx/超时才走退避自适应退避——失败率超过 30% 时自动增加退避时间避免重试风暴随机抖动——退避延迟不固定防止所有重试在同一时刻集中发起滑动窗口统计——不累积历史计数只关注近期成功率。四、超时与重试的协同设计总超时 vs 单次超时。如果单次调用超时 30 秒重试 3 次的理论总时长为 120 秒。用户不可能等待 2 分钟。需要设置总超时时间如 45 秒在总时长耗尽时即使还有重试次数也不再重试。流式响应的超时策略。流式 API 的超时不能简单用30 秒无响应因为 Token 间的间隔可能从 50ms 到 5 秒不等。应使用累计空闲时间而非单次间隔来判定超时。Token 浪费的计量。每次重试都可能是对已经消耗的 Token 的重复计费。应在日志中单独记录重试消耗的 Token用于成本优化分析。五、总结大模型 API 调用的重试策略错误分类决定是否重试——4xx/认证错误不重试429 遵循 Retry-After5xx/超时走退避自适应退避——根据近期失败率动态调整退避时间总超时保护——防止无限重试导致的用户体验恶化。落地建议实现错误分类函数覆盖 OpenAI/Anthropic 等主流 API 的错误码设置单次超时 30 秒 总超时 60 秒的双重保护日志中单独记录重试次数和重试 Token 消耗监控重试率——如果 10%排查是调用方问题还是 API 服务不稳定。