技术指南
AI API 故障分诊:从连接失败到 401、429 与 5xx
按传输、认证、权限、资源、限流和暂时故障分诊 AI API 错误,并建立不会泄密或放大重试的恢复路径。
先按一次请求实际停在哪一层分诊:没有 HTTP 响应时查执行位置、DNS、连接、TLS 与代理;收到 4xx 时读错误体并修正认证、权限、资源或配额;只有被官方定义为暂时性的 429、5xx 或连接错误才进入受限重试。换出口、换密钥和无限重试不能互相替代。
先用响应边界决定下一步
| 观察结果 | 已经知道 | 下一步 |
|---|---|---|
| 解析失败、连接拒绝或超时 | 尚未取得目标 API 的 HTTP 响应 | 确认命令在哪个主机/容器运行,再查 DNS、路由、代理、TLS 与客户端超时 |
| 401 | 请求到达了能返回 HTTP 的服务边界 | 核对密钥是否存在、是否被撤销,以及认证头和项目/组织是否正确 |
| 403 | 服务识别了请求但拒绝访问 | 读供应商错误代码,核对权限、项目、区域、策略或资源范围 |
| 404 | 路径或资源没有匹配 | 核对 API 版本、端点、模型和资源 ID;不要直接判定网络中断 |
| 429 | 某个限额或余额条件被触发 | 先分辨速率、余额、支出或使用上限,再决定等待、降速还是修改账户设置 |
| 5xx 或供应商过载代码 | 服务或中间网关未完成请求 | 保存 request ID,按官方语义有限重试并检查状态页 |
固定一个最小请求并保存脱敏证据
复现时保持同一运行环境、端点、模型、请求体和超时,只改变一个变量。先确认应用、远程主机、容器和 CI runner 中究竟哪个进程发出请求;浏览器可用不能证明另一个执行环境的 DNS、代理和证书链相同。
umask 077
work=$(mktemp -d)
# 只在受控终端使用自己的环境变量;不要把展开后的命令或文件贴进工单。
curl --silent --show-error \
--connect-timeout 10 --max-time 60 \
--dump-header "$work/headers.txt" \
--output "$work/body.json" \
--write-out '%{http_code} %{time_connect} %{time_appconnect} %{time_total}\n' \
--header "Authorization: Bearer $OPENAI_API_KEY" \
https://api.openai.com/v1/models
# 仅提取允许共享的字段;先人工检查 body.json 是否包含业务数据。
awk 'BEGIN{IGNORECASE=1} /^x-request-id:|^retry-after:|^content-type:/' \
"$work/headers.txt"如果连 HTTP 状态都没有,保存 curl 的退出码和阶段耗时,不要立刻换 Key。若得到响应,优先读取结构化错误字段和响应头;HTML 登录页或企业网关页面说明响应可能来自中间设备,不能按目标 API 的状态码语义解释。
401、403 与 404 不共享同一个修复动作
OpenAI 当前文档把 401 细分为无效认证、错误 Key、组织成员资格和 IP allowlist 等原因;403 明确包含不支持地区。Anthropic 区分 authentication、permission 与 not_found;Gemini 也分别给出 authentication、permission_denied、not_found 和 model_not_found。状态码只是入口,最终动作应由供应商错误代码、消息和当前项目配置决定。
- 401:确认进程确实读取到预期变量,但不要输出完整值;核对认证头、Key 状态和所属项目。
- 403:核对该 Key 对目标资源、模型和工作区的权限,以及组织政策与供应商支持范围。
- 404:核对 API 版本、路径、模型别名和资源归属;资源 ID 往往不能跨项目复用。
- 400/413:回到当前 API schema、请求大小和字段约束,不要通过重试掩盖确定性输入错误。
先分类 429,再决定是否重试
429 不是统一的“等几秒”。OpenAI 当前错误表把请求速率、预付余额、组织/项目支出上限和组织使用上限分开;只有请求速率类适合按 Retry-After 或速率指南降速,余额和限额类必须先修正账户条件。Anthropic 的 429 表示速率限制并可能带 Retry-After;Gemini 还区分 rate_limit_exceeded 与 quota_exceeded。
| 429 类型 | 处理 | 不要做 |
|---|---|---|
| 瞬时请求/令牌速率 | 尊重 Retry-After,降低并发,使用带抖动的指数退避 | 所有 worker 同时立即重试 |
| 余额、支出或使用上限 | 核对错误代码与控制台设置,恢复条件满足后再发请求 | 把确定性拒绝当瞬时拥塞 |
| 日配额或项目配额 | 等待重置或申请调整,并为用户提供排队/降级 | 轮换同一项目下的 Key 规避项目级限制 |
5xx、超时和过载需要有上限的恢复策略
OpenAI 对 500 与 503 建议短暂等待后重试;Anthropic 除 5xx 外还使用 529 表示临时过载,并说明官方 SDK 默认会对连接错误、429 和 5xx 做有限退避;Gemini 建议只对 408、429 和 5xx 等暂时错误使用指数退避,不重试 400 或 403。先了解 SDK 已有行为,避免应用层再套一层造成重试放大。
- 为每次尝试设置连接和总超时,给整个用户操作设置总预算。
- 指数退避加入随机抖动,尊重 Retry-After,并限制次数与总时长。
- 记录每次尝试的供应商、模型、错误代码、request ID 和耗时,不记录请求秘密。
- 对会产生外部副作用的操作使用供应商支持的幂等机制;无法证明幂等时不要自动重放。
- 超过预算后明确失败、排队或降级;持续异常再查官方状态页并携 request ID 升级。
把供应商差异留在适配层
| 供应商 | 应保留的诊断字段 | 容易误判的差异 |
|---|---|---|
| OpenAI | HTTP 状态、error.code/type、x-request-id、Retry-After | 429 可能是速率,也可能是余额、支出或使用上限 |
| Anthropic | HTTP 状态、error.type、request-id、Retry-After | 临时过载使用 529;流式响应可能在 200 之后发送错误事件 |
| Gemini | HTTP 状态、结构化 code/message、配额上下文 | 429 可区分速率与配额;400/403 通常应修请求或权限而非重试 |
不要把三家的状态码压成一条无条件映射。适配层应把原始状态、供应商错误代码和 request ID 原样保留,再转换成产品内部的认证失败、权限失败、资源错误、限流、配额、暂时故障或未知故障。
把一次排障收敛成可复用运行手册
- 确认实际发请求的主机、容器、进程和发布版本。
- 用同一最小请求复现,保存脱敏状态、错误代码、request ID 与阶段耗时。
- 没有 HTTP 响应时排查 DNS、连接、TLS、代理和超时;不要先轮换 Key。
- 401/403/404/400 按供应商错误字段修正认证、权限、路径、资源或请求体。
- 429 先区分速率与余额/支出/配额;仅对可恢复类型降速或等待。
- 暂时错误按 Retry-After 和带抖动退避有限重试,检查 SDK 是否已经自动重试。
- 修复后用同一请求和同一执行环境复测,并确认日志、指标和用户提示都已恢复。
- 记录根因、触发条件和停止重试的门槛;密钥曾泄露时完成撤销、轮换和日志清理。