AI 工程实践

AI API 报错怎么办?400、401、429、超时与流式中断排障指南

按请求链路定位 AI API 常见错误,区分限流与额度问题,建立超时、有限重试、日志脱敏和流式中断处理的实用方案。

琥珀色诊断棱镜修复断开的数据通路

AI API 出错时,连续点击“重试”很容易让问题变得更复杂:凭证错误不会被重试修复,限流可能被更多请求放大,而超时后的再次发送可能生成第二份结果。

有效的排障顺序是先确定请求在哪一层失败,再判断原因是否暂时,最后决定是否重试。本文面向 HTTP AI API,使用兼容 Chat Completions 的文本请求说明方法;具体错误字段和限制规则以服务商文档为准。

一、先收集一份最小证据

出现错误时,先记录发生时间和时区、目标主机与路径、模型 ID、HTTP 状态码、脱敏后的错误类型、请求耗时以及请求 ID(如果服务提供)。不要记录完整 Authorization 请求头。

把请求体缩减为一句普通文本,暂时移除历史对话、工具定义、图片和非必要参数。如果最小请求成功,就逐项恢复原请求;如果仍然失败,优先检查地址、权限和链路。

下面是一份适合工单的记录模板:

发生时间:2026-10-12 14:30,UTC+08:00
接口:POST /v1/chat/completions
模型:填写实际模型 ID
状态:429
错误类型:填写脱敏后的 type/code
耗时:填写本次测量值
请求 ID:有则填写,没有则注明未返回
是否最小请求也失败:是 / 否
最近变更:模型、参数、网络或依赖是否改变

这些值是记录格式示例,不是某个平台的故障数据。分享时还要检查主机信息、输入片段和错误内容是否涉及内部业务。

二、按状态码缩小范围

现象 优先检查 通常应采取的动作
400 或 422 请求字段、类型、上下文和模型参数 修改请求,再测试
401 密钥是否有效、认证格式是否正确 修复凭证或认证配置
403 账户或资源权限、访问规则 确认权限与服务规则
404 URL 路径、模型或资源是否存在 检查路径和资源标识
429 限流、额度或用量限制 先识别错误原因,再决定处理
500、502、503、504 服务链路或网关异常 结合业务允许有限重试
无 HTTP 状态码 DNS、TLS、代理、连接和超时 从网络层定位

这是排查入口,而不是所有服务通用的错误语义。相同状态码可能对应不同原因,服务商也可能返回自定义状态。OpenAI 官方的 错误码说明可作为直连接口的参考,不能据此推断第三方所有错误含义。

400:先减参数,别先换网络

常见问题包括 messages 格式错误、字段类型不对、模型不支持某个参数,或者上下文过长。恢复到 model 加一条 user 消息的最小请求,确认成功后逐个增加字段。若加入某个参数后立即失败,查看该模型的支持范围。

401 和 403:认证与权限分开看

401 排查密钥是否进入当前进程、是否复制了多余空格,以及请求头是否为服务要求的认证格式。403 则应进一步看模型权限、账户策略和访问规则。不要将更换 IP 或更换密钥当作没有依据的默认操作。

404:看完整路径,也看错误体

/v1/v1/chat/completions 是典型的路径重复。另一种情况是路径正确,但模型 ID 不存在或资源不可访问。错误体如果返回 HTML,也可能来自网页服务器或网关,而不是模型 API。

三、429 为什么不能统一重试?

429 可能是请求频率或 token 用量超过当前限额,也可能与额度、支出或其他账户限制有关。前者通常需要降低速率并等待;后者需要处理账户状态,反复重发不能解决。

不要只判断错误描述中有没有英文单词 quota。优先使用文档定义的 type 或 code,并检查账户信息。无法分类时停止自动重试,把脱敏信息交给维护人员确认。

对于明确可重试的限流,优先遵循 Retry-After,然后结合指数退避和随机抖动。该头可能是等待秒数,也可能是 HTTP 日期。服务没有提供该头时,再使用本地等待策略。OpenAI 的 限流指南说明了速率控制与退避的基本思路。

退避只能缓解短暂问题。如果每次任务都会触发限流,应减少并发、缩小输出预算或排队执行,而不是不断提高重试次数。

四、超时不等于服务端没有执行

连接超时、读取超时和整个任务的截止时间是不同概念。客户端没有按时收到结果,并不能证明上游没有开始或完成生成。

因此,给纯文本生成增加重试时也要考虑额外用量;给图片生成、文件处理或带工具执行的任务增加重试时,更要考虑重复结果和副作用。除非服务明确提供并保证相应幂等机制,否则不能把自定义请求 ID 当作防止重复执行的凭证。

应用可以生成自己的任务 ID,用于关联日志和阻止重复点击。它只能帮助控制应用侧行为,不能自动取消已经发送到上游的任务。

五、一个有限重试的 Python 模板

下面示例使用 requests,只演示非流式、无工具副作用的文本生成。需要 Python 3.10 或更新版本,安装依赖并配置 AI_BASE_URL、AI_API_KEY、AI_MODEL。

python -m pip install requests

代码默认不自动重试 429,因为兼容服务的错误码无法统一判断;确认服务文档后,可把明确的瞬时限流 code 添加到集合中。对可重试的 HTTP 错误最多发送三次。连接与读取异常直接抛出,让调用者决定是否恢复。

import os
import random
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

import requests

# 仅填写服务文档明确说明可以等待重试的 code。
RETRYABLE_429_CODES = set()
RETRYABLE_STATUS = {500, 502, 503, 504}
MAX_ATTEMPTS = 3
MAX_INLINE_WAIT = 30.0

def retry_after_seconds(value):
    if not value:
        return None
    try:
        return max(0.0, float(value))
    except ValueError:
        try:
            target = parsedate_to_datetime(value)
            if target.tzinfo is None:
                target = target.replace(tzinfo=timezone.utc)
            return max(0.0, (target - datetime.now(timezone.utc)).total_seconds())
        except (ValueError, TypeError, OverflowError):
            return None

def get_error_code(response):
    try:
        body = response.json()
    except ValueError:
        return None
    if not isinstance(body, dict):
        return None
    error = body.get("error")
    return error.get("code") if isinstance(error, dict) else None

def generate_text():
    url = os.environ["AI_BASE_URL"].rstrip("/") + "/chat/completions"
    headers = {
        "Authorization": "Bearer " + os.environ["AI_API_KEY"],
        "Content-Type": "application/json",
    }
    payload = {
        "model": os.environ["AI_MODEL"],
        "messages": [{"role": "user", "content": "用一句话解释缓存。"}],
    }
    with requests.Session() as session:
        for attempt in range(MAX_ATTEMPTS):
            with session.post(
                url, headers=headers, json=payload, timeout=(5, 30)
            ) as response:
                if response.ok:
                    body = response.json()
                    return body["choices"][0]["message"]["content"]

                code = get_error_code(response)
                retryable = response.status_code in RETRYABLE_STATUS
                if response.status_code == 429:
                    retryable = (
                        isinstance(code, str) and code in RETRYABLE_429_CODES
                    )
                if not retryable or attempt == MAX_ATTEMPTS - 1:
                    response.raise_for_status()

                delay = retry_after_seconds(response.headers.get("Retry-After"))
                if delay is None:
                    delay = random.uniform(0.5, min(8.0, 2.0 ** (attempt + 1)))
                if delay > MAX_INLINE_WAIT:
                    raise RuntimeError(
                        f"服务要求至少等待 {delay:.1f} 秒,请转为延迟任务"
                    )
            time.sleep(delay)
    raise RuntimeError("重试流程未取得结果")

if __name__ == "__main__":
    print(generate_text())

timeout=(5, 30) 分别设置连接和读取等待,单位为秒。它不是严格的整项任务总时限;生产系统应在任务层增加截止时间或队列超时。服务要求的等待时间过长时,示例退出而不是把等待时间强行截短,这样不会提前重发。

这个模板还需要业务侧验证返回文本和输出格式。HTTP 成功只说明请求层成功,不保证模型完成了你的业务目标。

六、避免两层重试叠加

如果 SDK 自己重试,外层任务也重试,总发送次数会相乘。例如两层各允许最多三次尝试,最坏情况下可能产生九次发送。应明确由哪一层负责重试,并记录总尝试次数。

使用 OpenAI Python SDK 时,可以显式设置 max_retries=0,交给业务统一处理,或直接使用 SDK 的有限重试并关闭外层重复机制。具体行为应依据安装版本与 官方 SDK 文档确认。

七、流式输出断了,应该怎么办?

流式连接可能先返回成功状态,之后在读取事件时断开,因此只监控 HTTP 状态码会漏掉故障。

应用需要分别标记“收到首个片段”“流正常结束”和“读取中断”。如果用户已经看到半段回答,重试时应明确开始一次新生成,不能把新结果无提示地接在旧文本后。断线后的续传能力必须由服务协议提供,不能自行假定存在。

八、把稳定性变成可观察的行为

至少记录成功率、各类失败数量、每次尝试耗时、重试次数,以及流式请求的首片段等待与完整结束率。失败也要纳入统计,不能只统计成功请求然后得出“接口很快”的结论。

处理能力不足时,用有界队列和有限并发;服务连续异常时暂缓新请求并提示用户稍后再试。日志只保存排障必要的信息,敏感输入默认不记录,确需样本时再经过脱敏与权限控制。

下一次报错,可以按“最小请求 → 状态与错误类型 → 地址/权限/限流/链路 → 有限恢复”的顺序处理。目标是让每一次失败都能解释,而不是让重试隐藏问题。