API重试机制与指数退避策略
·
OpenAI API 重试机制怎么写?指数退避、最大重试次数与错误分类
调用大模型 API 时,网络抖动、服务端临时过载、超时等问题随时可能发生。如果没有重试机制,一次偶发错误就会导致用户体验断裂。
这篇文章把重试策略从简单到生产级讲清楚,帮你写出稳健的 API 调用代码。
为什么需要重试
API 调用失败分两类:
| 类型 | 错误码 | 是否应该重试 |
|---|---|---|
| 临时性错误 | 429(限流)、500/502/503(服务端错误)、超时 | 应该重试 |
| 永久性错误 | 400(参数错误)、401(认证失败)、404(模型不存在) | 不应重试 |
重试的核心原则:只重试临时性错误,不重试永久性错误。对 400 错误重试 10 次也不会成功,反而浪费时间和配额。
基础重试实现
最简单的重试
import time
from openai import OpenAI, APITimeoutError, APIConnectionError, RateLimitError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL",
)
def chat_with_retry(messages, model="YOUR_MODEL", max_retries=3):
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages,
)
return response
except (APITimeoutError, APIConnectionError, RateLimitError) as e:
print(f"第 {attempt + 1} 次失败: {e}")
if attempt < max_retries - 1:
time.sleep(2) # 固定等待 2 秒
else:
raise
response = chat_with_retry([
{"role": "user", "content": "你好"}
])
print(response.choices[0].message.content)
问题很明显:固定等待 2 秒不够灵活。如果服务端需要 10 秒才能恢复,2 秒后重试还是会失败。
指数退避策略
指数退避(Exponential Backoff)是最经典的重试策略:每次重试的等待时间翻倍。
import time
import random
from openai import OpenAI, APITimeoutError, APIConnectionError, RateLimitError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL",
)
def exponential_backoff_retry(messages, model="YOUR_MODEL", max_retries=5, base_delay=1):
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages,
)
return response
except (APITimeoutError, APIConnectionError, RateLimitError) as e:
if attempt < max_retries - 1:
# 指数退避 + 随机抖动
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"第 {attempt + 1} 次失败,{delay:.1f} 秒后重试...")
time.sleep(delay)
else:
raise
response = exponential_backoff_retry([
{"role": "user", "content": "你好"}
])
等待时间序列:1s → 2s → 4s → 8s → 16s
为什么要加随机抖动? 如果多个客户端同时失败并同时重试,会造成"重试风暴",把服务端再次打垮。随机抖动让重试请求分散开。
使用 tenacity 库
生产环境推荐使用 tenacity 库,它提供了更优雅的重试装饰器:
pip install tenacity
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from openai import OpenAI, APITimeoutError, APIConnectionError, RateLimitError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL",
)
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=1, max=60),
retry=retry_if_exception_type((APITimeoutError, APIConnectionError, RateLimitError)),
reraise=True,
)
def chat(messages, model="YOUR_MODEL"):
response = client.chat.completions.create(
model=model,
messages=messages,
)
return response
response = chat([
{"role": "user", "content": "你好"}
])
| 参数 | 说明 |
|---|---|
stop_after_attempt(5) | 最多重试 5 次 |
wait_exponential(multiplier=1, min=1, max=60) | 指数退避,最短 1 秒,最长 60 秒 |
retry_if_exception_type(...) | 只对特定异常重试 |
reraise=True | 重试耗尽后抛出原始异常 |
读取 Retry-After 头
429 响应通常会带 Retry-After 头,告诉你服务端建议的等待时间:
import time
from openai import OpenAI, RateLimitError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL",
)
def chat_with_retry_after(messages, model="YOUR_MODEL", max_retries=5):
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages,
)
return response
except RateLimitError as e:
# 尝试读取 Retry-After 头
retry_after = getattr(e, 'retry_after', None)
if retry_after:
delay = float(retry_after)
print(f"服务端建议等待 {delay} 秒")
else:
delay = 2 ** attempt
print(f"使用指数退避,等待 {delay} 秒")
if attempt < max_retries - 1:
time.sleep(delay)
else:
raise
except Exception as e:
if attempt < max_retries - 1:
delay = 2 ** attempt
time.sleep(delay)
else:
raise
流式输出的重试
流式输出的重试更复杂,因为可能在传输中途断开:
import time
from openai import OpenAI, APITimeoutError, APIConnectionError, RateLimitError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL",
)
def stream_with_retry(messages, model="YOUR_MODEL", max_retries=3):
for attempt in range(max_retries):
try:
stream = client.chat.completions.create(
model=model,
messages=messages,
stream=True,
)
collected = ""
for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
collected += content
print(content, end="", flush=True)
return collected
except (APITimeoutError, APIConnectionError) as e:
print(f"\n[中断] 第 {attempt + 1} 次失败: {e}")
if attempt < max_retries - 1:
delay = 2 ** attempt
print(f"[重试] {delay} 秒后继续...")
time.sleep(delay)
else:
raise
result = stream_with_retry([
{"role": "user", "content": "写一篇 500 字的文章"}
])
带熔断的重试
如果服务端持续不可用,不断重试没有意义。熔断器(Circuit Breaker)可以在连续失败后暂停请求:
import time
from openai import OpenAI
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.failure_count = 0
self.last_failure_time = 0
self.state = "closed" # closed, open, half-open
def call(self, func, *args, **kwargs):
if self.state == "open":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "half-open"
print("[熔断器] 进入半开状态,尝试恢复")
else:
raise Exception("[熔断器] 服务不可用,请稍后重试")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
def _on_success(self):
self.failure_count = 0
self.state = "closed"
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "open"
print(f"[熔断器] 连续失败 {self.failure_count} 次,进入熔断状态")
# 使用
breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=60)
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
def make_request():
return client.chat.completions.create(
model="YOUR_MODEL",
messages=[{"role": "user", "content": "你好"}],
)
try:
response = breaker.call(make_request)
print(response.choices[0].message.content)
except Exception as e:
print(f"请求失败: {e}")
| 状态 | 说明 |
|---|---|
| closed | 正常状态,请求正常通过 |
| open | 熔断状态,直接拒绝请求 |
| half-open | 半开状态,允许少量请求试探恢复 |
重试策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定间隔 | 简单 | 可能造成重试风暴 | 内部测试 |
| 指数退避 | 逐步加大间隔 | 可能等待过久 | 通用场景 |
| 指数退避+抖动 | 避免重试风暴 | 稍复杂 | 生产环境推荐 |
| Retry-After | 尊重服务端建议 | 依赖服务端返回 | 429 限流场景 |
| 熔断器 | 避免无效重试 | 需要额外状态管理 | 高可用场景 |
快速排错表
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 重试后仍然失败 | 永久性错误被重试 | 只对 429/5xx/超时重试 |
| 重试等待太久 | 指数退避上限太高 | 设置 max 上限(如 60 秒) |
| 多个客户端同时重试 | 没有随机抖动 | 添加 jitter |
| 服务端持续不可用 | 没有熔断机制 | 引入 Circuit Breaker |
配置检查清单
| 检查项 | 建议值 |
|---|---|
| 最大重试次数 | 3-5 次 |
| 退避基数 | 1 秒 |
| 最大等待时间 | 60 秒 |
| 是否添加抖动 | 是 |
| 是否读取 Retry-After | 是 |
| 是否引入熔断 | 高可用场景必须 |
重试机制是 API 调用的基本保障。指数退避 + 随机抖动 + Retry-After + 熔断器,这四个组合起来就能覆盖绝大多数生产场景。
更多推荐



所有评论(0)