把一次调用写"结实":哪些错该重试,哪些重试是白烧钱
四类错误的真实响应 + 一张抄进项目的判定表
ap0.3 你给调用装了重试。但重试有个反直觉的地方:对一半的错误,重试是纯浪费——浪费时间、浪费额度、还把日志刷满,让真正的问题更难找。
这一节把错误分类讲透:亲手制造四类典型错误,看它们真实的响应长什么样,然后给你一张能直接抄进项目的判定表。
医生看急诊要先分诊:失血要立刻手术,感冒回家躺三天,骨折得先拍片。全都当成"再等等看"或全都推进手术室,都是灾难。错误处理同理:先分类,再决定要不要重试。
四类错误的真实长相(实测)
跑本节代码,故意制造四种错误,真实响应:
① 正常请求
状态: 200 | 0.62s | 好呀!今天心情
② 认证失败(key 写错)
状态: 401 | 0.2s
{"error":{"message":"Authentication Fails, Your api key: ****0000 is invalid",
"type":"authentication_error"...}}
判定: ❌ 别重试——重试一万次也不会对
③ 参数错误(模型名不存在)
状态: 400 | 0.12s
{"error":{"message":"The supported API model names are ... but you passed deepseek-not-exist"...}}
判定: ❌ 别重试——请求本身有毛病
④ 参数错误(max_tokens=-5)
状态: 400 | 0.1s
{"error":{"message":"...max_tokens: invalid value: integer `-5`, expected u32..."}}
判定: ❌ 别重试
⑤ 超时(把 timeout 压到 0.2s)
状态: timeout | 0.32s | The read operation timed out
判定: ✅ 该重试——服务可能好好的,只是这次没赶上
注意 ②③④ 的耗时:0.1~0.2 秒就返回了。它们是"服务清清楚楚告诉你:你的请求有问题"。你重试三次,只是把这句话听三遍。
判定表(抄进你的项目)
| 类别 | 典型 | 怎么办 |
|---|---|---|
| 该重试 | 超时、连接错误、5xx 服务端错误、429 限流 | 指数退避重试(429 必须退避,见 ap1.3) |
| 别重试 | 400 参数错、401 认证失败、402 余额不足、404 模型不存在 | 立刻失败,记日志,让人来修 |
| 慎重试 | 任何"重复执行有副作用"的操作:扣费、发消息、写库 | 先做幂等(带唯一请求 id),再谈重试 |
第三行最容易出人命:第一次其实成功了,只是响应超时——你一重试,就扣了两次费、发了两条消息。工程上的解法叫幂等:每个请求带一个唯一 id,服务端认 id 去重;做不到就至少在你这边记录"这个 id 已发起过"。
升级版调用函数
把 ap0.3 那个函数升级成会分诊的版本(节选,完整版在代码里):
NO_RETRY = {400, 401, 402, 403, 404, 422} # 请求本身的问题,重试无意义
def ask(msg, timeout=30, retries=3):
for i in range(retries):
try:
return _post(msg, timeout)
except HTTPError as e:
if e.code in NO_RETRY:
raise # 立刻失败,别浪费
if e.code == 429: # 限流:退避后重试
time.sleep((2 ** i) + random.uniform(0, 0.3)); continue
if 500 <= e.code < 600: # 服务端错:退避后重试
time.sleep((2 ** i) + random.uniform(0, 0.3)); continue
raise
except (TimeoutError, URLError): # 网络类:重试的主战场
time.sleep((2 ** i) + random.uniform(0, 0.3))
raise RuntimeError("重试耗尽")
🔧 动手做:给你的调用函数装上分诊(12 分钟)
- 跑本节代码
error_taxonomy.py,亲眼看四类错误的响应(故意把 key 改错、把模型名写错,看服务怎么回你)。 - 把上面的分诊逻辑合进你 ap0.3 存的
llm.py。 - 验证分诊生效:把模型名改成不存在的,跑一次——它应该 0.1 秒就失败,而不是傻等三轮重试(耗时就是最好的证据)。
- 再验证另一边:把超时压到 0.1 秒,确认它老老实实重试了三次才放弃。
- 想一想你毕业方向里哪个操作是"重复执行有副作用"的(扣额度?发通知?写订单?),记下来——AP7 做额度系统时会用到。
✅ 做到这里你应该有:一个会分诊的调用函数,以及两次亲手验证(该快的快、该重试的重试)。
为什么:没分诊的重试是"看起来很稳,实际在浪费"——线上排障时你会看到大量重复的 401 日志,却找不到真正的故障点。分诊让失败快速暴露、让抖动被悄悄兜住,这是可观测性的第一步。
线上最痛的时刻:用户说"报错了",你翻日志只看到 Exception: request failed。每次失败至少记四样:状态码/异常类型、耗时、第几次重试、请求的唯一 id。有了这四样,你能在一分钟内判断"是我们的问题还是服务商的问题";没有,你只能猜。ap1.6 做成本仪表时,会把这套日志一起建起来。
这是我的大模型调用函数:【粘贴】。请按生产标准审:1) 错误分诊是否完整(哪些码该重试/不该重试/需要幂等);2) 超时设置(连接 vs 读取)是否合理;3) 失败日志缺哪些关键字段;4) 有没有「重复执行会产生副作用」的风险点。逐条给改法。
分诊三句话:网络类和 5xx/429 重试(配退避)、4xx 立刻失败、有副作用的先幂等再谈重试;失败日志记全四样。
下一节换个战场——体验:同一个回答,用户是等 4 秒看到全部,还是 0.5 秒就看到第一个字?实测差 8 倍。
🔎 来源与核验· 2 条,点开核对
