LLM API 调用重试怎么写:哪些错误该重试,哪些别碰

做过 LLM 服务的人都写过这段代码:调用失败,for 循环重试三次。看起来很稳,但生产事故里有一半跟这三行循环有关。LLM API 的失败行为和传统 HTTP API 不一样,重试策略得按它的特性来设计。

专属插画
LLM API 调用重试怎么写:哪些错误该重试,哪些别碰

LLM API 调用重试怎么写:哪些错误该重试,哪些别碰

做过 LLM 服务的人都写过这段代码:调用失败,for 循环重试三次。看起来很稳,但生产事故里有一半跟这三行循环有关。LLM API 的失败行为和传统 HTTP API 不一样,重试策略得按它的特性来设计。

先把错误分类

LLM API 常见的失败形态大致五类:

- **429(限流)**:触发 RPM 或 token 配额上限,响应头通常带 Retry-After。该重试,直接按头的值等。

- **5xx 和网络超时**:上游模型服务抖动。该重试,但要计入熔断统计。

- **流式传输中途卡死**:连接半断,客户端没有抛错,只是长时间收不到新 token。必须设空闲超时(比如 20 秒无数据即断开),超时就按失败处理。

- **400(参数错误、上下文超限)**:不要重试。输入没变,第 101 次调用和第 1 次一样错。它暴露的是要立刻修的问题:输入超长、JSON 畸形、模型名写错。

- **内容审核拒绝**:部分服务返回特定错误码。重试不会让它通过,只会多烧钱。

判断标准一句话:**只重试状态可能变化的错误**。400 和审核拒绝是确定性的,重试每次都是确定的失败。

退避怎么算

别用固定 1 秒间隔。20 个请求同时撞限流,固定间隔意味着 20 个再一起撞。用指数退避加随机抖动:等待时间 = 1s × 2^n × rand(0.5~1.5),上限 30 秒。429 带 Retry-After 时优先用服务端给的值,不要自己算。

重试次数一般 3~4 次封顶,再加一个总超时(比如整个任务最多等 60 秒)。前三次都失败,大概率是服务端问题,第四次盲打没有意义。总超时的作用是给上游定合同:同步接口到点必须返回 503,或者转异步任务让用户来轮询,不能无限挂着。

重试的隐性成本:重复计费

这是 LLM 特有的坑:**请求实际执行了,但响应没回来,重跑一次会不会被收两次钱?** 典型场景:请求已成功、模型生成完毕、最后一个网络环节断了,客户端拿到的是超时。重试时前一次调用往往已经计费。

对策分两档。小请求(几百 token 以内)损失可忽略,不用处理。大任务(长文生成、批处理)用幂等键:请求带同一个 key,服务端对重复 key 直接返回已有结果而不是重算。部分 API 原生支持(如请求头 Idempotency-Key);服务不支持的话,自己存请求指纹(输入哈希 + 参数哈希)到 Redis,TTL 半小时,重试前先查有没有同指纹的结果。

熔断和降级

调用多个模型服务时,加个熔断器:滑动窗口(比如 1 分钟)错误率超过 50% 就断开,新请求直接走降级路径,每 30~60 秒放一个试探请求检查恢复。用 pybreaker 或 Resilience4j,自己写也就十行代码。

流式接口实用的降级顺序:重试失败 → 切备用小模型并在结果里标注 → 仍失败返回缓存的保守答案(标注缓存来源)→ 仍失败明确告诉用户服务繁忙并返回 503。不要让调用方无限等一个不存在的流。

最后一个容易被忽略的细节:统计"次"而不是"请求"。某接口成功率 90%,失败全被静默重试救了,监控会显示 100%。真出故障时你分不清正常抖动和服务整体挂了。日志里保留 attempt 次数,监控里分"首次成功率"和"最终成功率"两条线,平时看起来差不多,出事时这条差值就是你最需要的报警信号。

留言区

欢迎分享你的想法!

发表留言

0/500

加载留言中…