Cookbook
错误重试示例
错误重试示例讲清 OpenAI-compatible API 的 429、5xx、网络超时、指数退避、随机抖动、幂等性、日志脱敏和上线检查。
错误重试不是“失败了就再来一次”。它是一套生产稳定性策略:只重试短期限流、 网络抖动和临时服务端错误;遇到鉴权、权限、参数、模型名或额度问题时停下来 修配置。
这篇示例适合正在接入 OpenAI-compatible API、SDK、内部 AI 工具、批处理脚本 或 Codex 自动化任务的开发者。示例基于 OfApp.cn 的兼容入口说明写法,具体模 型、能力、额度和价格请以 OfApp.cn 控制台或官方文档为准。
| 项目 | 内容 |
|---|---|
| 适合任务 | API 稳定性、429 退避、5xx 重试、网络超时、批处理失败队列 |
| 不适合任务 | 修复 401、403、404、模型名错误、参数错误、明确 quota 问题 |
| 阅读时间 | 约 6 分钟 |
| 更新时间 | 2026-07-02 |
错误重试是什么
Section titled “错误重试是什么”错误重试是客户端在请求失败后,按有限次数、有限时间和明确条件重新发起请求 的机制。好的重试策略不会追求“请求一定成功”,而是避免临时故障影响用户,同 时不把一次故障放大成更多失败请求。
在 OpenAI-compatible API 场景里,重试策略通常要回答五个问题:
| 问题 | 推荐判断 |
|---|---|
| 这个错误是否临时 | 429、408、409、5xx、网络连接失败更可能是临时错误 |
| 请求是否可以安全重放 | 纯生成、只读检索更容易重试;写数据库、发消息、扣费任务要谨慎 |
| 等多久再试 | 使用指数退避和随机抖动,避免所有客户端同一时间重发 |
| 最多试几次 | 通常 2 到 3 次,批处理可进入延迟队列 |
| 失败后怎么查 | 记录状态码、错误类型、请求 ID、重试次数和脱敏上下文 |
重试决策流程图
Section titled “重试决策流程图”哪些错误适合重试
Section titled “哪些错误适合重试”| 错误类型 | 是否重试 | 建议 |
|---|---|---|
| 429 Rate Limit | 适合有限重试 | 降并发、缩短输入、加入指数退避和随机抖动 |
| 408 Request Timeout | 适合有限重试 | 缩短单次请求时间,设置客户端 timeout |
| 409 Conflict | 可有限重试 | 短暂等待后重试,仍失败则记录上下文 |
| 500 / 502 / 503 / 504 | 适合有限重试 | 记录请求 ID,退避后再试 |
| APIConnectionError | 适合有限重试 | 检查网络、代理、DNS、公司网关和超时 |
OpenAI 官方限流指南建议使用随机指数退避处理 rate limit,因为失败请求也会 计入每分钟限制,连续快速重发不会解决问题,只会更快消耗可用窗口。
哪些错误不该重试
Section titled “哪些错误不该重试”| 错误类型 | 为什么不该盲目重试 | 应该怎么做 |
|---|---|---|
| 401 Unauthorized | Key、请求头或鉴权方式错误 | 检查环境变量、Bearer 头和 Key 来源 |
| 403 Forbidden | 权限、项目、模型或策略限制 | 复核控制台和官方文档 |
| 404 Not Found | 路径、模型名或端点错误 | 检查 Base URL、路径和模型名 |
| 400 Bad Request | 参数结构、类型或上下文错误 | 打印脱敏请求摘要,修请求体 |
| insufficient quota | 额度、项目或账号限制 | 停止重试,回到控制台复核 |
如果错误信息明确指向 quota、账单、项目限制、模型不可用或参数非法,继续重 试通常只会增加延迟、日志噪音和排查成本。
示例使用通用 OpenAI-compatible 配置。OfApp.cn 的文档入口显示兼容 Base URL
可以使用 https://api.ofapp.cn/v1,真实 Key 和模型名请从控制台或官方文档获
取。
export OPENAI_BASE_URL="https://api.ofapp.cn/v1"export OPENAI_API_KEY="sk-xxxx"export OPENAI_MODEL="以控制台可用模型为准"服务端代码里应把 Base URL、Key、模型名、timeout、重试次数和日志级别都做 成配置项,不要写死在业务函数中。
JavaScript:有限指数退避
Section titled “JavaScript:有限指数退避”下面示例展示一个轻量重试外壳。它只处理 408、409、429、5xx 和连接类错误, 并在每次重试前加入随机抖动。
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL, timeout: 30_000, maxRetries: 0,});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function retryAfterMs(error) { const header = error.headers?.["retry-after"] ?? error.headers?.get?.("retry-after"); const seconds = Number(header); return Number.isFinite(seconds) ? seconds * 1000 : null;}
function isRetryable(error) { const status = error.status; return ( status === 408 || status === 409 || status === 429 || status >= 500 || error.name === "APIConnectionError" );}
async function withRetry(run, options = {}) { const maxAttempts = options.maxAttempts ?? 3; const baseDelayMs = options.baseDelayMs ?? 600;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { return await run(); } catch (error) { const canRetry = isRetryable(error) && attempt < maxAttempts; if (!canRetry) throw error;
const serverDelay = retryAfterMs(error); const jitter = Math.floor(Math.random() * 250); const fallbackDelay = baseDelayMs * 2 ** (attempt - 1) + jitter; const delay = serverDelay ?? fallbackDelay;
console.warn("openai_retry", { status: error.status, name: error.name, attempt, delay, requestId: error.request_id, });
await sleep(delay); } }}
const completion = await withRetry(() => client.chat.completions.create({ model: process.env.OPENAI_MODEL, messages: [{ role: "user", content: "生成一句测试文本。" }], }),);
console.log(completion.choices[0]?.message?.content);这里把 SDK 自带重试关掉,是为了展示自定义策略。实际项目可以选择 SDK 默认 重试、统一封装重试,或两者择一;不要让 SDK、网关、任务队列和业务代码叠加 出不可控的多层重试。
Python:有限指数退避
Section titled “Python:有限指数退避”Python 版本同样只重试临时错误,并在日志里保留脱敏诊断字段。
import osimport randomimport timefrom openai import APIConnectionError, APIStatusError, OpenAI, RateLimitError
client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"], timeout=30.0, max_retries=0,)
def retry_after_seconds(error): response = getattr(error, "response", None) if response is None: return None value = response.headers.get("retry-after") try: return float(value) except (TypeError, ValueError): return None
def is_retryable_status(error): status = getattr(error, "status_code", None) return status in (408, 409, 429) or (status is not None and status >= 500)
def with_retry(run, max_attempts=3, base_delay=0.6): for attempt in range(1, max_attempts + 1): try: return run() except RateLimitError as error: caught_error = error retryable = True except APIConnectionError as error: caught_error = error retryable = True except APIStatusError as error: caught_error = error retryable = is_retryable_status(error)
if not retryable or attempt == max_attempts: raise caught_error
server_delay = retry_after_seconds(caught_error) fallback_delay = base_delay * (2 ** (attempt - 1)) + random.random() * 0.25 delay = server_delay if server_delay is not None else fallback_delay
print( { "event": "openai_retry", "status": getattr(caught_error, "status_code", None), "attempt": attempt, "delay": round(delay, 2), "request_id": getattr(caught_error, "request_id", None), } ) time.sleep(delay)
result = with_retry( lambda: client.chat.completions.create( model=os.environ["OPENAI_MODEL"], messages=[{"role": "user", "content": "生成一句测试文本。"}], ))
print(result.choices[0].message.content)如果你使用 SDK 默认重试,也建议显式设置 timeout 和 max_retries,并在业务
日志里记录失败类型。默认行为可以帮你处理一部分临时错误,但不能替代并发控
制、失败队列和权限排查。
退避参数怎么选
Section titled “退避参数怎么选”| 场景 | 建议重试次数 | 建议等待 |
|---|---|---|
| 用户实时请求 | 1 到 2 次 | 300ms 到 2s,失败后提示用户 |
| 后台批处理 | 2 到 3 次 | 1s 到 8s,失败项进入队列 |
| 长文本或大上下文 | 1 到 2 次 | 降低输入长度后再尝试 |
| 流式输出 | 0 到 1 次 | 保存已接收内容,让用户决定是否重试 |
| quota 或权限错误 | 0 次 | 停止任务,回到控制台复核 |
退避时间不宜无限增长。用户请求要保护体验,批处理任务要保护系统吞吐。超过 上限后,应进入失败队列、告警、人工复核或稍后再跑,而不是持续占用线程。
幂等性怎么处理
Section titled “幂等性怎么处理”“幂等”指同一个任务重试多次,不会产生重复副作用。文本生成请求本身通常可 以重试,但围绕它的业务动作未必安全。
| 任务 | 重试风险 | 处理方式 |
|---|---|---|
| 生成草稿 | 可能出现两版内容 | 保存任务 ID,让用户选择保留哪一版 |
| 写入数据库 | 可能重复写入 | 使用唯一任务键或 upsert |
| 发送邮件 | 可能重复发送 | 先记录发送状态,再决定是否补发 |
| 扣费或发券 | 可能重复执行 | 不自动重试,改为人工或事务状态机 |
| 批量处理文档 | 可能重复消耗额度 | 记录输入 hash、状态和输出路径 |
在 Codex、工作流工具或自动化脚本里,建议把“API 调用成功”和“业务动作完成” 分成两个可观察步骤。API 成功但写库失败,和 API 失败但业务未执行,是两种不 同的恢复路径。
日志和可观测性
Section titled “日志和可观测性”重试日志要帮助定位问题,同时避免泄露敏感信息。
| 应记录 | 不应记录 |
|---|---|
| 状态码、错误类型、请求 ID | API Key、Cookie、完整 Authorization 头 |
| attempt、delay、maxAttempts | 用户完整原文、身份证、手机号、私密文件 |
| 模型名、端点、超时配置 | 完整响应体里的敏感数据 |
| 输入 token 或字符数摘要 | 未脱敏的数据库记录 |
| 是否进入失败队列 | 生产环境密钥和真实账单信息 |
推荐把重试日志写成结构化事件,例如 openai_retry、openai_retry_exhausted
和 openai_request_failed。这样可以在日志平台里按状态码、模型、任务类型
和服务版本聚合,而不是只看到一串无法检索的报错文本。
上线前检查清单
Section titled “上线前检查清单”| 检查项 | 合格标准 |
|---|---|
| Base URL | OfApp.cn 示例使用 https://api.ofapp.cn/v1,实际以官方文档为准 |
| Key 管理 | 服务端读取环境变量,不进入前端包和客户端日志 |
| 错误分类 | 401、403、404、400、quota 不盲目重试 |
| 可重试范围 | 408、409、429、5xx、连接错误有限重试 |
| 退避策略 | 指数退避加随机抖动,尊重可用的 Retry-After |
| 上限 | 用户请求 1 到 2 次,后台任务 2 到 3 次 |
| 幂等性 | 有任务 ID、输入 hash、唯一键或状态机 |
| 日志 | 保留 request id、attempt、delay,所有敏感字段脱敏 |
| 熔断 | 连续失败后暂停批量任务或进入失败队列 |
| 错误 | 后果 | 修复 |
|---|---|---|
| 无限重试 | 放大限流、延迟和成本 | 设置最大次数和失败队列 |
| 对 401 重试 | 同一鉴权错误反复出现 | 修 Key、请求头和项目权限 |
| 忽略并发 | 所有 worker 同时重发 | 加队列、令牌桶或并发上限 |
| 没有随机抖动 | 客户端在同一秒重发 | 在退避时间里加入 jitter |
| 不记录 request id | 无法定位一次失败 | 在日志中保存脱敏请求 ID |
| 非幂等任务自动重试 | 造成重复发送、重复写入 | 设计唯一键、状态机或人工确认 |
- OpenAI API 错误码指南:说明常见错误类型,并给出 RateLimitError、APIConnectionError 和 APIStatusError 的处理方式。
- OpenAI Rate limits 指南:建议对限流使用随机指数退避,并提醒失败请求仍会计入限制窗口。
- OfApp.cn API 直转文档:说明 OfApp.cn 的 OpenAI 兼容接入入口、Key 和示例请求。具体模型、能力、额度和价格请以控制台或官方文档为准。
429 一定要重试吗?
Section titled “429 一定要重试吗?”不一定。短期限流可以做有限退避重试;如果错误体或控制台提示 quota、账号、 项目或权限限制,应停止自动重试,先处理限制来源。
5xx 可以重试几次?
Section titled “5xx 可以重试几次?”用户实时请求通常 1 到 2 次,后台批处理通常 2 到 3 次。超过上限后进入失败 队列或人工处理,并保留请求 ID 和脱敏上下文。
网络超时和 APIConnectionError 怎么处理?
Section titled “网络超时和 APIConnectionError 怎么处理?”先设置合理 timeout,再对连接失败做有限重试。若公司代理、DNS、网关或服务 器网络持续异常,重试只能缓解短暂抖动,不能替代网络排查。
流式请求中断可以自动重试吗?
Section titled “流式请求中断可以自动重试吗?”可以做一次有限重试,但不要假设可以从中间无缝续传。应保存已接收内容,明确 告诉用户内容可能不完整,并让用户选择重新生成或继续处理。
重试会增加成本吗?
Section titled “重试会增加成本吗?”会。失败请求、重复输入和多次生成都可能增加资源消耗。生产系统应限制重试次 数,缩短过长输入,并记录哪些任务被重复执行。
OfApp.cn 的重试次数应该怎么设?
Section titled “OfApp.cn 的重试次数应该怎么设?”本文只给通用工程建议。实际次数要结合业务延迟、并发、模型、额度和控制台限 制决定;涉及 OfApp.cn 的具体限制,请以 OfApp.cn 控制台或官方文档为准。
- 429 Rate Limit:区分限流、并发、quota 和重试放大。
- 500 Server Error:处理服务端临时错误和请求 ID。
- streaming interrupted:处理流式中断、保存已生成内容和提示重试。
- invalid API key:排查 Key、请求头和环境变量。
- insufficient quota:识别额度、项目和账号限制。
- OpenAI-compatible API 快速调用:验证 Base URL、Key、模型名和最小请求。
- JavaScript SDK 示例:在 Node.js 服务端接入兼容接口。
- Python SDK 示例:在 Python 脚本和服务端接入兼容接口。
错误重试的核心不是“多试几次”,而是只对临时错误做有限、可观测、可停止的 重试。429、5xx、408、409 和网络连接失败可以退避重试;401、403、404、参数 错误、模型名错误和明确 quota 问题应回到配置、权限和控制台排查。上线前把 重试次数、timeout、幂等性、失败队列和日志脱敏都写清楚,API 调用才不会在 故障时放大问题。