如何设计 Claude API 调用的重试与降级策略
在生产环境里接入 Claude API,最麻烦的通常不是“接口能不能调通”,而是“接口出问题时,系统会怎么表现”。如果只是简单写个 try catch,失败后重试 3 次,看起来好像更稳了,但实际可能带来更高延迟、重复消耗 token、触发更严重的限流,甚至在 Agent 场景里重复执行工具调用。

所以,一套真正可靠的 Claude API 调用方案,不能只盯着“失败后重发”。更合理的做法是,把 API 重试机制、API 降级策略、熔断、限流、幂等、流式响应处理和监控 放在一起考虑。这样系统在出错时才不会手忙脚乱,也能尽量把影响控制在可接受范围内。
为什么 Claude API 不能简单地“失败就重试”
Claude API 和普通 HTTP API 不太一样,主要体现在几个方面:
第一,生成结果并不完全幂等。同样的 prompt 请求多次,模型返回的内容可能不一样,这和普通查询接口不太一样。
第二,每次请求都有成本。一次重试可能就意味着额外的 token 消耗,尤其是长上下文、长输出任务,成本会更明显。
另外,长文本和流式响应更容易中断。比如 Claude Code、Agent 执行、文档总结这类场景,请求链路长、返回时间久,中间任何一层超时都可能导致连接断开。
还有一点容易被忽略:错误类型很多,处理方式差异很大。429、529、timeout、context length exceeded 看起来都是“失败”,但背后的原因完全不同,不能用同一套重试逻辑处理。
更严重的是,多层重试会放大故障。客户端重试一次,SDK 再重试一次,网关也重试,中转层和队列消费者也各自重试,最后很容易形成重试风暴,把本来只是短暂抖动的问题放大成系统性故障。
因此,Claude API 的稳定性设计原则不是“失败就重试”,而是:
先判断错误类型,再决定要不要重试;如果重试仍然失败,再进入降级,而不是一直等、一直试。
Claude API 常见失败类型:先分类,再决定是否重试
设计 API 重试机制时,第一步最好先做一张错误决策表。这样后续代码实现和排障都会清晰很多。
错误类型常见原因是否自动重试推荐处理400 Bad Request参数错误、messages 格式错误、max_tokens 不合理否修正请求参数401 UnauthorizedAPI key 无效或缺失否检查密钥配置403 Forbidden账户权限不足、模型无权限否检查权限与模型可用性404 / model not found模型名错误、模型不可用通常否修正模型映射,必要时切备用模型context length exceeded上下文过长否压缩上下文、分块、摘要408 / timeout网络抖动、读取超时是,有限重试指数退避重试429 Rate LimitRPM、TPM、并发或组织额度限制是,但必须退避降低并发、排队、延长退避500服务端异常是短退避重试,失败后降级502 / 503上游暂不可用、网关异常是短退避重试,必要时切上游529 overloaded上游过载是,但等待更久长退避、熔断、切备用模型SSE 建连失败流式连接未建立是用户通常无感知重试流式输出中断已返回部分内容后断开谨慎保存 partial output,提示继续生成工具调用后失败工具已产生副作用通常否查询执行状态,避免重复执行
这张表的重点其实很简单:大多数 4xx 不应该重试,429 和 5xx 才是重试重点;上下文过长也不是网络问题,重试没有意义。
Claude API 重试机制设计:次数、退避、抖动与超时
不要使用无限重试
无限重试听起来“很努力”,但在生产系统里往往很危险。它会占满连接池,让队列越堆越多,还会进一步放大上游故障。
比较稳妥的方式,是给每类请求设置两个边界:
最大重试次数:比如 2~3 次;
最大总耗时:比如在线请求控制在 30~60 秒内,后台任务可以适当放长。
像在线聊天、网页问答这类用户正在等结果的场景,重试次数应该少一些,优先保证响应速度。对于后台批处理、离线文档总结,则可以通过队列做更长周期的重跑,不必让用户一直卡在前台等。
推荐使用指数退避 + jitter
固定间隔重试很容易出问题。比如大量请求同时失败,然后又在 1 秒后同时重试,结果上游压力更大,故障反而被放大。
更推荐使用指数退避加随机抖动:
delay = min(max_delay, initial_delay * 2^retry_count) + random_jitter
常见默认配置可以参考下面这个表:
错误最大重试次数初始等待最大等待timeout2500ms5s42931s15s500/502/5032500ms8s529 overloaded32s20s
其中 jitter 可以用 full jitter,也就是在 0 ~ 当前退避上限 之间随机取一个值。这样做的好处很明显:避免所有请求在同一时间点重新打到上游。
timeout 应该分层设置
没有 timeout 的重试机制,其实并不可靠。因为一次请求如果长时间挂住,再叠加重试,很快就会把并发资源耗光。
实际设计时,建议至少区分这些 timeout:
connect timeout:连接建立超时;
read timeout:读取响应超时;
total timeout:整个请求生命周期的超时;
first token timeout:流式响应首 token 的等待时间;
streaming idle timeout:流式输出过程中允许的最长空闲时间;
用户等待超时:前端或业务层能接受的最大等待时间。
尤其是 streaming 场景,网关、反向代理和客户端的 idle timeout 都要设置得足够长。否则模型还在正常生成,但连接已经被中间层断开了,用户看到的就是“生成失败”。
哪些 Claude API 请求不应该自动重试
有些错误看起来可以再试一次,但实际上不适合做透明自动重试。比如:
400 参数错误;
401 / 403 认证或权限错误;
模型名称错误;
context length exceeded;
用户主动取消请求;
流式响应已经输出大量内容;
Agent 工具调用已经执行;
数据库写入、支付、发券等业务副作用已经发生;
请求已经发出但响应丢失,而且无法确认上游是否完成。
这里尤其要注意一点:LLM 文本生成不是严格幂等的。自动重试可能让用户看到两个不同版本的答案,也可能平白增加 token 成本。对于有副作用的业务流程,更不能简单重放整个请求。
Claude API 降级策略:不只是换一个模型
很多人一提到 API 降级,就想到“主模型失败了换备用模型”。这当然是一种方式,但生产环境里的降级远不止这一层。
模型降级
比较常见的模型降级路径是:
Claude Opus → Claude Sonnet → Claude Haiku
也可以按稳定性来切换:
最新模型 → 更稳定的旧模型
复杂推理、代码审查、长文档分析这类任务,可以优先使用能力更强的模型;而分类、摘要、翻译、简单客服这些任务,降级到成本更低、速度更快的模型,通常也能达到不错的效果。
能力降级
当模型或上游压力比较大时,可以降低单次请求的复杂度。比如:
减少
max_tokens;缩短 system prompt;
禁用 tools / function calling;
关闭复杂 JSON schema 校验;
从“完整报告”降级为“要点摘要”;
从多轮深度推理降级为单轮回答。
这类降级的思路是:先保证用户能拿到一个可用结果,再考虑结果是否足够完整。
上下文降级
Claude API 很适合处理长上下文任务,但上下文越长,延迟、成本和失败率往往都会上升。所以在必要时,可以对上下文做降级:
全量上下文 → 摘要上下文;
最近 20 轮对话 → 最近 5 轮对话;
原始长文档 → 分块摘要;
全文输入 → RAG 检索片段;
历史消息 → 压缩后的 memory。
换句话说,不一定每次都要把所有信息塞给模型。只保留最关键的上下文,很多时候反而更稳。
交互降级
当实时生成不可用时,也不一定要直接报错。可以根据业务场景换一种交互方式:
实时返回 → 异步任务;
自动处理 → 人工审核;
完整答案 → 简短答案;
生成失败 → 返回可解释错误和“重新生成”按钮;
Claude Code 场景 → 暂停执行并提示用户确认。
对用户来说,清楚知道发生了什么、接下来能做什么,比看到一个冷冰冰的失败提示要好得多。
按业务场景选择不同策略
场景重试策略降级策略注意事项在线聊天最多 1~2 次快速重试短回答、低成本模型优先保证响应速度后台批处理可多次重试延迟执行、队列重跑必须有任务幂等Claude Code谨慎重试切备用模型、提示用户注意长连接和工具调用文档总结可重试分块摘要、压缩上下文防止上下文超限Agent 工具调用工具前可重试,工具后谨慎人工确认、暂停流程防止副作用重复RAG 问答可重试生成返回检索摘要保留引用来源
不同场景的关注点并不一样。在线业务更看重延迟,后台任务更看重最终成功率,而 Agent 场景最需要关注的是幂等和副作用控制。策略不能一套用到底,要结合业务特点来定。
流式响应 streaming 的特殊处理
Claude API 在聊天、Claude Code、Agent、长文本生成里经常会使用流式响应。streaming 的重试逻辑不能直接照搬普通请求,因为它可能已经把一部分内容返回给用户了。
SSE 建连前失败
如果请求还没有建立连接,也没有任何内容返回,这种情况通常可以自动重试。用户一般感知不到,体验影响也比较小。
首 token 前失败
如果连接已经建立,但首 token 还没返回,也可以做有限重试,并记录 first token latency。这类失败通常仍然可以看成“用户还没看到输出”,处理空间比较大。
输出过程中断
一旦已经返回了部分内容,就不建议透明重试整个请求。否则可能出现重复输出、上下文不一致,甚至用户看到两段风格不同的答案。
更稳妥的做法是:
保存
partial_output;前端提示“生成中断,是否继续”;
续写时把已生成内容放进上下文;
在 prompt 中明确要求“从上文中断处继续,不要重复”;
对工具调用记录
tool_call_id和执行状态。
这样虽然实现稍微复杂一点,但能明显减少重复生成和副作用问题。
熔断与限流:避免一个故障拖垮整个系统
重试只能处理短暂失败,解决不了持续故障。如果某个模型持续出现 5xx、529,或者 timeout 比例明显升高,就应该引入熔断。
比如可以设置这样的规则:
如果 claude-sonnet 在最近 1 分钟内 5xx/529 错误率 > 30%,且请求数 > 20:
- 对该模型打开熔断 60 秒;
- 新请求路由到备用模型;
- 每 10 秒放行 1 个探测请求;
- 探测成功率恢复后关闭熔断。
熔断维度建议至少覆盖这些方向:
按模型熔断;
按上游熔断;
按租户熔断;
按错误类型熔断。
限流同样重要,而且不能只看请求数。Claude API 这类模型接口通常要同时考虑 RPM、TPM 和并发数。遇到 429 之后,不要马上继续重试,而是应该降低并发、进入队列,或者在必要时快速失败。
对于高并发系统,还建议设置 retry budget,也就是限制重试流量占正常流量的比例。否则重试请求太多,反而会挤压正常请求。
幂等性与重复请求控制
Claude API 的文本生成本身不保证严格幂等,所以业务层最好自己设计重复请求控制。
一个常见做法是,为每个生成任务生成业务幂等键:
idempotency_key = hash(user_id + conversation_id + message_id + normalized_prompt + model)
实际落地时,可以考虑这些做法:
保存
request_id、message_id和任务状态;请求前先查询是否已经有完成结果;
重试时复用同一个业务任务,而不是创建新任务;
后台任务使用状态机,比如 pending、running、succeeded、failed、cancelled;
Agent 工具调用要把“模型生成”和“工具执行”拆开处理;
如果工具执行成功但模型响应失败,不应重放整个对话。
简单说,模型可以重试,但业务动作不能随便重复执行。尤其是写数据库、发消息、下单、扣费这类操作,更要谨慎。
Claude API 重试与降级的 TypeScript 示例
下面是一个简化版示例,主要展示错误分类、指数退避、timeout 和模型 fallback。真实项目里,通常还需要结合官方 SDK 或自己的 HTTP 客户端封装。
const fallbackModels = [
"claude-sonnet",
"claude-haiku"
];
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function shouldRetry(status?: number, errorType?: string) {
if (!status && errorType === "timeout") return true;
if ([408, 429, 500, 502, 503, 529].includes(status || 0)) return true;
return false;
}
function shouldFallback(status?: number, errorType?: string) {
if ([500, 502, 503, 529].includes(status || 0)) return true;
if (errorType === "model_unavailable") return true;
return false;
}
function backoffDelay(attempt: number, base = 500, max = 8000) {
const cap = Math.min(max, base * Math.pow(2, attempt));
return Math.floor(Math.random() * cap); // full jitter
}
async function callWithTimeout(url: string, body: any, timeoutMs: number) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify(body),
signal: controller.signal
});
if (!res.ok) {
const err: any = new Error(`HTTP ${res.status}`);
err.status = res.status;
throw err;
}
return await res.json();
} catch (e: any) {
if (e.name === "AbortError") {
e.errorType = "timeout";
}
throw e;
} finally {
clearTimeout(timer);
}
}
async function callClaudeWithRetry(prompt: string) {
const maxRetries = 2;
const timeoutMs = 30000;
for (const model of fallbackModels) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await callWithTimeout("https://api.example.com/messages", {
model,
messages: [{ role: "user", content: prompt }],
max_tokens: 1024
}, timeoutMs);
} catch (e: any) {
const status = e.status;
const errorType = e.errorType;
console.log({
model,
status,
errorType,
attempt,
is_fallback: model !== fallbackModels[0]
});
if (!shouldRetry(status, errorType)) throw e;
if (attempt === maxRetries) break;
await sleep(backoffDelay(attempt));
}
}
}
throw new Error("Claude API request failed after retry and fallback");
}
这段代码只是演示主流程。到了生产环境,还需要补充日志、指标、熔断状态、请求幂等键,以及 streaming 场景下的中断处理。
监控与告警:如何判断策略是否真的有效
没有监控的 API 重试机制,其实是不可控的。你可能以为系统更稳定了,但实际上只是把失败隐藏在更长的延迟和更高的成本里。
至少应该记录这些指标:
总请求量;
成功率、失败率;
P50 / P95 / P99 延迟;
首 token 延迟;
429 比例;
5xx 比例;
529 比例;
timeout 比例;
平均重试次数;
重试成功率;
重试后仍失败比例;
熔断次数;
降级触发次数;
fallback 模型命中率;
流式中断率;
token 消耗与重试成本;
用户可见错误率。
日志字段也要尽量完整,方便后续排查:
request_id
user_id / tenant_id
model
fallback_model
upstream
status_code
error_type
retry_count
retry_delays
is_fallback
fallback_reason
latency_ms
first_token_latency_ms
input_tokens
output_tokens
estimated_cost
streaming
partial_output
circuit_breaker_state
告警规则可以这样设计:
5 分钟内 529 比例 > 10%:触发模型熔断
5 分钟内 429 比例 > 5%:降低并发并延长退避
降级率 > 20%:通知运维排查上游
重试后失败率 > 30%:暂停自动重试,改为快速失败
这些指标不只是为了“看起来专业”,而是为了判断策略是否真的有效:重试有没有提高成功率,降级有没有保护用户体验,成本有没有失控。
推荐默认配置:从个人项目到生产环境
个人项目
max_retries = 2
initial_delay = 500ms
max_delay = 5s
timeout = 30s
fallback = sonnet → haiku
这种配置适合低并发、用户量较小的应用。重点不是做得多复杂,而是避免无限重试,并在失败时给用户一个清楚的提示。
小团队生产环境
max_retries = 2~3
jitter = true
total_timeout = 45s
按错误类型重试
启用模型 fallback
记录 retry_count 和 fallback_reason
到了小团队生产环境,建议补上基础监控、请求日志、限流和简单熔断。这样即使出问题,也能比较快定位原因。
企业高并发环境
客户端少重试
网关统一重试与熔断
按模型 / 上游 / 租户限流
使用队列削峰
配置 retry budget
建立 SLA 指标与告警
如果使用第三方 Claude API 兼容接入平台,或者自建网关,还需要关注它是否支持多线路、限流、日志、故障切换、企业充值、开票和基础技术协助等能力。当然,具体能力要以服务方最新说明为准,调用方自己也仍然要保留容错策略,不能把稳定性完全寄托在外部平台上。
总结:Claude API 重试与降级的设计原则
设计 Claude API 调用的重试与降级策略,可以记住这几条原则:
第一,先分类错误,再决定是否重试。400、401、403、上下文超限这类问题,通常重试也没用。
第二,不要无限重试。每个请求都应该有最大次数和总时间预算。
第三,对 429、5xx、529 使用指数退避 + jitter。这样可以减少重试风暴,避免把上游压得更严重。
另外,降级不只是换模型。它还包括能力降级、上下文降级、交互降级、成本控制和上游切换。
同时,流式响应和工具调用必须特殊处理。如果内容已经输出,或者工具已经执行,就不要盲目重放整个请求。
最后,没有监控的重试与降级都不可靠。重试次数、降级原因、延迟、错误率和 token 成本,都应该被记录下来。
真正可靠的 Claude API 调用体系,不是“失败后多试几次”这么简单,而是在错误分类、重试预算、降级路径、幂等控制和监控告警之间形成闭环。只有这样,系统在面对抖动、限流和上游故障时,才能既稳住用户体验,也控制住成本和风险。
作者提示含AI生成内容。
