请解释带参数装饰器的三层嵌套结构,并实现一个支持指数退避+抖动的 LLM 自动重试装饰器。
你问的三层嵌套,其实拆开来看,就是“配置→装饰→包装”这三次传递。我用一个实战场景来讲:给调用大模型接口的函数做自动重试,并且要指数退避+随机抖动。
🧱 为什么必须是三层¶
-
第一层(最外层):接收你传的配置,比如
max_retries=3, base_delay=1, max_delay=60,这叫“参数工厂”。它返回真正的装饰器。 -
第二层(中间层):标准的
decorator(func),接收被装饰的函数。 -
第三层(最内层):
wrapper(*args, **kwargs),实际执行函数、加重试逻辑的地方。
如果只有两层,你只能拿到 func,没办法在定义时传入可配置的参数。三层本质上是 工厂函数(配置) → 装饰器 → 新函数 这条链。
🧪 指数退避 + 抖动的重试装饰器实现¶
下面是可运行的代码,我嵌入了图标来标出每层职责。
import time
import random
import functools
from collections.abc import Callable
from typing import Type, Tuple
# 🏭 第一层:参数工厂,接收重试策略配置
def llm_retry(
max_retries: int = 3,
base_delay: float = 1.0, # 秒
max_delay: float = 60.0, # 秒
jitter: bool = True,
retry_on: Tuple[Type[BaseException], ...] = (Exception,)
):
"""
返回一个装饰器,用于自动重试可能失败的 LLM 调用。
"""
# 🎯 第二层:真正的装饰器
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
# ⚙️ 第三层:包装函数,加入重试逻辑
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except retry_on as e:
last_exception = e
if attempt == max_retries:
raise # 重试耗尽,抛出最后一次异常
# 📈 指数退避:base_delay * 2^attempt
sleep_time = base_delay * (2 ** attempt)
# 🎲 可选抖动:在 0 到 sleep_time 之间取随机值
if jitter:
sleep_time = random.uniform(0, sleep_time)
# 🛑 封顶,防止无限等待
sleep_time = min(sleep_time, max_delay)
print(f"⏳ 第{attempt+1}次重试,等待 {sleep_time:.2f}s "
f"(异常: {type(e).__name__})")
time.sleep(sleep_time)
# 理论上不会执行到这里
raise last_exception
return wrapper
return decorator
🧠 使用示例¶
import openai
@llm_retry(max_retries=5, base_delay=1, max_delay=30, jitter=True,
retry_on=(openai.RateLimitError, openai.APIConnectionError))
def ask_gpt(prompt: str) -> str:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
如果 API 触发限流或网络错误,会自动进入退避:第一次等 ~1s,第二次 ~2s,第三次 ~4s… 加上随机抖动,能有效避免惊群效应,保护接口端。