OpenAI Python SDK 超时和重试:为 CheapTokens 设置它们
在 OpenAI Python SDK 中设置 timeout、max_retries 和 max_tokens,捕获正确的错误,并防止卡住的请求运行 10 分钟。

本页内容
你的脚本调用了模型,却什么也没返回,十分钟后它仍在等待。这就是 OpenAI Python SDK 的默认行为:10 分钟超时和 2 次重试。要修改它,请将 timeout=30.0 和 max_retries=2 传给 OpenAI(...),用 client. 按调用覆盖它们,并用 max_tokens 限制回答长度。
SDK 的 README(2026 年 10 月 27 日查阅)记录了所有这些内容。下面将其应用于 CheapTokens 端点 https:/,该端点使用 OpenAI Chat Completions 格式。该设置的详细信息见使用 CheapTokens 发出你的第一个 API 请求和文档。
什么都不设置时 SDK 会做什么#
有两个默认值很重要。超时是 10 分钟。重试是 2 次,带有短暂的指数退避,适用于连接错误以及 408、409、429 和 5xx 响应,并且仅在请求体可以安全重发时才会重试。超时的请求也会被重试。
因此每次尝试都有自己的超时,最坏情况大致是超时乘以尝试次数,再加上它们之间的暂停:
- 默认:600 × 3 = 1800 秒,半小时
timeout=30.0,max_retries=2:30 × 3 = 90 秒
假设一个 Web 端点必须在 30 秒内响应。使用默认值时,它可能挂起半小时,你的代码才会看到错误。在继续阅读之前,先在另一个设置上试试。
猜数字
一个客户端的 timeout=45.0 且 max_retries=3。在抛出异常之前,最坏情况下的等待时间是多少秒(不计尝试之间的暂停)?
你的猜测
0400
显示答案
答案: 180
45 ×(3 次重试 + 1 次首次尝试)= 180 秒;尝试之间的暂停还会在此基础上增加一点。
在客户端上设置 timeout 和 max_retries#
最简单的修复方法是在创建客户端时一次性设置两者:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
timeout=30.0, # seconds per attempt
max_retries=2, # 0 turns retries off
)
resp = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Flash-0731",
messages=[{"role": "user", "content": "Summarize this ticket in two sentences: ..."}],
max_tokens=800,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)max_retries 必须是非负整数。timeout 接受浮点数,或者如果你想要分别限制连接和读取,可以接受 httpx Timeout 对象。当前主分支的 README 为此导入 httpx2,旧版本使用 httpx,所以运行 pip show openai 并检查你的安装使用哪一个。普通浮点数不需要导入,两种方式都适用。

使用 CheapTokens 文档中的完整模型 id,这里是 deepseek-:不同提供商的 id 不同。该模型每次回答最多接受 32,768 个输出 token,并具有 380K 上下文,这就是上面 max_tokens 那一行有意存在的原因。下面会进一步说明,模型相关内容见便宜的 DeepSeek。
为单个请求覆盖它#
一个客户端,不同的任务。聊天回复应该快速放弃,夜间摘要可以等待。与其创建第二个客户端,不如为这次调用派生一个:
quick = client.with_options(timeout=5.0, max_retries=0)
resp = quick.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Flash-0731",
messages=[{"role": "user", "content": "Reply with one word: ok"}],
max_tokens=5,
)with_options 返回带有新值的副本,并保持 client 不变。当你自己的代码已经重试时,设置 max_retries=0 是正确的做法,例如重新入队任务的作业队列。否则两层会相乘:3 次 SDK 尝试嵌套在 3 次队列尝试中,一个任务会产生 9 个请求。
捕获真正重要的错误#
重试耗尽后,SDK 会抛出异常。每个错误类都继承自 openai.APIError,失败的请求带有 request_id,这就是你要记录的内容:
import openai
def ask(prompt: str) -> str | None:
try:
r = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Flash-0731",
messages=[{"role": "user", "content": prompt}],
max_tokens=800,
)
return r.choices[0].message.content
except openai.APITimeoutError:
print("timed out after retries")
except openai.RateLimitError as e:
print("429, slow down:", e.request_id)
except openai.APIConnectionError:
print("cannot reach the API")
except openai.APIStatusError as e:
print("HTTP", e.status_code, e.request_id)
return None顺序是从窄到宽。APIStatusError 涵盖任何 4xx 或 5xx 响应,所以如果它放在最前面,就会吞掉 RateLimitError。超时也是同样的思路:它位于一般连接错误之上。在批处理脚本中,return None 正是关键:记录提示词,继续处理下一个,最后重新运行失败的项。
当超时触发且重试用尽时,异常看起来是这样的:

关于 429 情况的详细信息,包括尝试之间的等待,请参阅来自 AI API 的 429 Too Many Requests。
限制 max_tokens,让长回答无法失控#
超时只是停止等待。它不会让模型写得更少。没有 max_tokens 时,具有 32,768 token 回答限制的模型可以自由地写很长的回复,而长回复就是慢回复。设置一个适合任务的限制:工单摘要用 800,是/否用 5。
代价是回答可能被截断。检查 resp.:length 表示在模型完成之前就达到了上限。为什么 AI 回答会在句子中间停止?对此进行了说明。如果你改为流式传输回答,第一批词会提前到达,等待感觉更短,请参阅Python 中的流式响应。
重试对账单有什么影响#
重试就是一次新请求。如果某次尝试已被处理,它就是一次带有自己 token 的独立请求。假设一份工单摘要输入 1,500 token、输出 400 token:按今天的价格,一次请求花费 $0.000063,完整的价格表列出了其他模型。价格按模型固定,没有高峰时段,所以凌晨 3 点重试和中午重试花费相同。
把滑块移到你自己的批次上:
成本计算器
一批 1,000 份工单摘要
每次请求的输入 token 数: 1,500 · 每次请求的输出 token 数: 400 · 每日请求数: 1,000
| 模型 | 每天 | 每月 |
|---|---|---|
| GLM-5.3-Flash$0.02$0.04 | $0.046 | $1.38 |
| DeepSeek-V4-Flash$0.015$0.10 | $0.0625 | $1.88 |
| MiniMax-M2.7$0.05$0.15 | $0.135 | $4.05 |
表格显示的是一轮。如果批次的十分之一需要第二次尝试,就加上十分之一。当失败很少时,重试是便宜的保险;当你针对一个持续失败的端点设置 max_retries=10 时,它就是一种昂贵的习惯。
发布之前#
清单
超时和重试,已为生产做好准备



