跳转到内容
API 入口

Cookbook

错误重试示例

错误重试示例讲清 OpenAI-compatible API 的 429、5xx、网络超时、指数退避、随机抖动、幂等性、日志脱敏和上线检查。

预计阅读
6 分钟
最近更新
2026/07/06
维护方式
人工编辑

错误重试不是“失败了就再来一次”。它是一套生产稳定性策略:只重试短期限流、 网络抖动和临时服务端错误;遇到鉴权、权限、参数、模型名或额度问题时停下来 修配置。

这篇示例适合正在接入 OpenAI-compatible API、SDK、内部 AI 工具、批处理脚本 或 Codex 自动化任务的开发者。示例基于 OfApp.cn 的兼容入口说明写法,具体模 型、能力、额度和价格请以 OfApp.cn 控制台或官方文档为准。

项目 内容
适合任务 API 稳定性、429 退避、5xx 重试、网络超时、批处理失败队列
不适合任务 修复 401、403、404、模型名错误、参数错误、明确 quota 问题
阅读时间 约 6 分钟
更新时间 2026-07-02

错误重试是客户端在请求失败后,按有限次数、有限时间和明确条件重新发起请求 的机制。好的重试策略不会追求“请求一定成功”,而是避免临时故障影响用户,同 时不把一次故障放大成更多失败请求。

在 OpenAI-compatible API 场景里,重试策略通常要回答五个问题:

问题 推荐判断
这个错误是否临时 429、408、409、5xx、网络连接失败更可能是临时错误
请求是否可以安全重放 纯生成、只读检索更容易重试;写数据库、发消息、扣费任务要谨慎
等多久再试 使用指数退避和随机抖动,避免所有客户端同一时间重发
最多试几次 通常 2 到 3 次,批处理可进入延迟队列
失败后怎么查 记录状态码、错误类型、请求 ID、重试次数和脱敏上下文
记录错误和请求 ID分类状态429 / 5xx判断幂等避免重复副作用退避等待指数 + 抖动达到上限失败队列或人工处理不要记录 Key 或完整用户资料无法确认安全重放时,不自动重试停止继续轰炸接口
错误类型 是否重试 建议
429 Rate Limit 适合有限重试 降并发、缩短输入、加入指数退避和随机抖动
408 Request Timeout 适合有限重试 缩短单次请求时间,设置客户端 timeout
409 Conflict 可有限重试 短暂等待后重试,仍失败则记录上下文
500 / 502 / 503 / 504 适合有限重试 记录请求 ID,退避后再试
APIConnectionError 适合有限重试 检查网络、代理、DNS、公司网关和超时

OpenAI 官方限流指南建议使用随机指数退避处理 rate limit,因为失败请求也会 计入每分钟限制,连续快速重发不会解决问题,只会更快消耗可用窗口。

错误类型 为什么不该盲目重试 应该怎么做
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 和模型名请从控制台或官方文档获 取。

Terminal window
export OPENAI_BASE_URL="https://api.ofapp.cn/v1"
export OPENAI_API_KEY="sk-xxxx"
export OPENAI_MODEL="以控制台可用模型为准"

服务端代码里应把 Base URL、Key、模型名、timeout、重试次数和日志级别都做 成配置项,不要写死在业务函数中。

下面示例展示一个轻量重试外壳。它只处理 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 版本同样只重试临时错误,并在日志里保留脱敏诊断字段。

import os
import random
import time
from 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 默认重试,也建议显式设置 timeoutmax_retries,并在业务 日志里记录失败类型。默认行为可以帮你处理一部分临时错误,但不能替代并发控 制、失败队列和权限排查。

场景 建议重试次数 建议等待
用户实时请求 1 到 2 次 300ms 到 2s,失败后提示用户
后台批处理 2 到 3 次 1s 到 8s,失败项进入队列
长文本或大上下文 1 到 2 次 降低输入长度后再尝试
流式输出 0 到 1 次 保存已接收内容,让用户决定是否重试
quota 或权限错误 0 次 停止任务,回到控制台复核

退避时间不宜无限增长。用户请求要保护体验,批处理任务要保护系统吞吐。超过 上限后,应进入失败队列、告警、人工复核或稍后再跑,而不是持续占用线程。

“幂等”指同一个任务重试多次,不会产生重复副作用。文本生成请求本身通常可 以重试,但围绕它的业务动作未必安全。

任务 重试风险 处理方式
生成草稿 可能出现两版内容 保存任务 ID,让用户选择保留哪一版
写入数据库 可能重复写入 使用唯一任务键或 upsert
发送邮件 可能重复发送 先记录发送状态,再决定是否补发
扣费或发券 可能重复执行 不自动重试,改为人工或事务状态机
批量处理文档 可能重复消耗额度 记录输入 hash、状态和输出路径

在 Codex、工作流工具或自动化脚本里,建议把“API 调用成功”和“业务动作完成” 分成两个可观察步骤。API 成功但写库失败,和 API 失败但业务未执行,是两种不 同的恢复路径。

重试日志要帮助定位问题,同时避免泄露敏感信息。

应记录 不应记录
状态码、错误类型、请求 ID API Key、Cookie、完整 Authorization 头
attempt、delay、maxAttempts 用户完整原文、身份证、手机号、私密文件
模型名、端点、超时配置 完整响应体里的敏感数据
输入 token 或字符数摘要 未脱敏的数据库记录
是否进入失败队列 生产环境密钥和真实账单信息

推荐把重试日志写成结构化事件,例如 openai_retryopenai_retry_exhaustedopenai_request_failed。这样可以在日志平台里按状态码、模型、任务类型 和服务版本聚合,而不是只看到一串无法检索的报错文本。

检查项 合格标准
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 和示例请求。具体模型、能力、额度和价格请以控制台或官方文档为准。

不一定。短期限流可以做有限退避重试;如果错误体或控制台提示 quota、账号、 项目或权限限制,应停止自动重试,先处理限制来源。

用户实时请求通常 1 到 2 次,后台批处理通常 2 到 3 次。超过上限后进入失败 队列或人工处理,并保留请求 ID 和脱敏上下文。

网络超时和 APIConnectionError 怎么处理?

Section titled “网络超时和 APIConnectionError 怎么处理?”

先设置合理 timeout,再对连接失败做有限重试。若公司代理、DNS、网关或服务 器网络持续异常,重试只能缓解短暂抖动,不能替代网络排查。

流式请求中断可以自动重试吗?

Section titled “流式请求中断可以自动重试吗?”

可以做一次有限重试,但不要假设可以从中间无缝续传。应保存已接收内容,明确 告诉用户内容可能不完整,并让用户选择重新生成或继续处理。

会。失败请求、重复输入和多次生成都可能增加资源消耗。生产系统应限制重试次 数,缩短过长输入,并记录哪些任务被重复执行。

OfApp.cn 的重试次数应该怎么设?

Section titled “OfApp.cn 的重试次数应该怎么设?”

本文只给通用工程建议。实际次数要结合业务延迟、并发、模型、额度和控制台限 制决定;涉及 OfApp.cn 的具体限制,请以 OfApp.cn 控制台或官方文档为准。

错误重试的核心不是“多试几次”,而是只对临时错误做有限、可观测、可停止的 重试。429、5xx、408、409 和网络连接失败可以退避重试;401、403、404、参数 错误、模型名错误和明确 quota 问题应回到配置、权限和控制台排查。上线前把 重试次数、timeout、幂等性、失败队列和日志脱敏都写清楚,API 调用才不会在 故障时放大问题。