每次失败都裸抛异常?聊聊错误重试与降级该在哪一层做
先看三个常见的失败做法
很多独立开发者和小团队在接入 LLM API 时,对“请求失败”这件事的处理,基本上停留在教科书水平。以下三种做法,你在自己的代码库里大概率能找到至少一种。
做法一:完全不处理,靠用户帮你发现故障。
resp = client.chat.completions.create(...)
print(resp.choices[0].message.content)代码跑通了,demo 演示没问题,上线第一周就翻车:网络抖动一次,接口抛个超时异常,整个功能直接白屏。用户截图发过来,你才知道出事了。
做法二:try-except 一把梭,捕获之后打个日志就完事。
try:
resp = client.chat.completions.create(...)
except Exception:
logger.error("请求失败")比起做法一好了一点,但用户体验没有任何变化——该失败还是失败。日志里堆满了 error,你既不会重试,也不知道该不该重试。
做法三:无脑 for 循环重试三次。
for i in range(3):
try:
resp = client.chat.completions.create(...)
break
except Exception:
continue这是看起来最“勤奋”的做法,也是隐患最大的一种。问题在于:不是所有错误都值得重试。
- 401 鉴权失败、400 参数错误:这是确定性失败,重试一万次结果一样,纯烧时间。
- 429 限流:重试要退避(backoff),立刻重发只会火上浇油。
- 500 类服务端错误:可以重试,但要有间隔。
- 网络超时:可以重试,但要设上限。
无脑重试还有个更隐蔽的代价:如果每次重试都重新发送完整上下文,Token 消耗会成倍增加。这一点在我们之前讨论 Token 预算的文章里已经提过,这里不再展开。
正确路径:分清错误类型,再决定“重试”还是“降级”
一个成熟的做法只需要回答两个问题:
- 这个错误是暂时性的吗? 是 → 带退避地重试;否 → 直接进入降级。
- 重试用尽之后怎么办? 返回一个可用的兜底结果,而不是抛异常给用户。
在动手之前,你需要一个能提供稳定服务的 API 入口。这里以 ThisToken.AI 为例:
- 访问 ThisToken.AI,注册一个账号;
- 在控制台的 API Keys 页面创建一个 Key,妥善保存(不要硬编码进代码,用环境变量);
- 记下接入地址:
https://api.thistoken.ai/v1。
费用方面,以官网价格页为准,这里不引用具体数字。
可直接运行的示例代码
下面的 Python 示例实现了:指数退避重试 + 按错误类型区分处理 + 降级兜底。使用官方 openai SDK,兼容模式接入。
import os
import time
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get("THISTOKEN_API_KEY"),
base_url="https://api.thistoken.ai/v1",
)
MAX_RETRIES = 3
BASE_DELAY = 1.0 # 秒
def classify_error(exc) -> str:
"""把异常粗分为三类:no_retry / retry_now / retry_later"""
status = getattr(exc, "status_code", None)
if status is None:
return "retry_later" # 网络超时、连接错误
if status in (400, 401, 403, 404):
return "no_retry" # 参数/鉴权问题,重试无意义
if status == 429:
return "retry_later" # 限流,需要等待
if status >= 500:
return "retry_now" # 服务端错误,可快速重试
return "no_retry"
def chat_with_fallback(prompt: str, fallback: str = "服务暂时不可用,请稍后再试。"):
delay = BASE_DELAY
last_exc = None
for attempt in range(1, MAX_RETRIES + 1):
try:
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
timeout=30,
)
return resp.choices[0].message.content
except Exception as exc:
last_exc = exc
kind = classify_error(exc)
if kind == "no_retry":
print(f"[第{attempt}次] 确定性失败,不重试: {exc}")
break
if attempt == MAX_RETRIES:
break
if kind == "retry_later":
time.sleep(delay)
delay *= 2 # 指数退避:1s → 2s → 4s
else:
time.sleep(0.5) # 服务端错误,短暂等待即可
# 全部重试用尽 → 降级,而不是把异常抛给用户
print(f"[降级] 重试用尽: {last_exc}")
return fallback
if __name__ == "__main__":
result = chat_with_fallback("用一句话解释什么是指数退避。")
print(result)运行方式:
export THISTOKEN_API_KEY="你的Key"
pip install openai
python retry_demo.py几个值得注意的细节:
classify_error是核心。 它把“要不要重试”这个决策从模糊的直觉变成了显式规则。你可以按自己的业务再细分。- 指数退避而不是固定间隔。 遇到限流时,固定间隔的重试近似于持续攻击。
- 降级结果要“可用”。 上面返回的是一句提示文案;在你的业务里,降级可以是缓存的上次结果、规则引擎的兜底输出,或者一个明确的“稍后重试”入口——但绝不该是一个未捕获的堆栈。
降级策略的三个层次
重试用尽只是起点,降级做得好不好,直接决定故障期间用户看到什么:
- 文案降级:返回一句友好的提示(示例代码的做法,最低门槛)。
- 缓存降级:把最近一次成功的结果缓存下来,故障时返回旧数据并标注“数据更新于 X 分钟前”。对内容生成类场景非常实用。
- 链路降级:主模型不可用时切换到备用模型。如果你的接入层支持多模型,可以在
chat_with_fallback外再套一层模型选择逻辑。
写在最后
错误处理不是上线前的“收尾工作”,而是接入 API 的第一块地基。先把这段代码跑通,再根据自己业务的错误分布慢慢调整分类规则和退避参数——比起事后救火,这点前期投入非常划算。
如果你还没有账号,可以先去 https://api.thistoken.ai/register 注册,创建一个 API Key,把上面的示例跑起来,亲眼看看重试和降级在你自己的网络环境里是怎么工作的。
---
不想折腾多家供应商的接入差异?在 https://api.thistoken.ai/register 注册,用一个 base_url 调用所有模型。