错误码与排查
统一错误信封、全部错误码,以及该重试哪些、不该重试哪些。
错误信封#
错误信封按入站方言给,两种形状。OpenAI 方言(/v1/chat/completions、/v1/videos、/v1/models):
json
{ "error": { "message": "账户余额不足", "type": "insufficient_quota", "code": "insufficient_balance", "param": "model" }, "request_id": "01JQ8Z3P9K7V2M4X6B8N0C1D3E"}Anthropic 方言(/v1/messages):
json
{ "type": "error", "error": { "type": "invalid_request_error", "message": "账户余额不足" }, "request_id": "01JQ8Z3P9K7V2M4X6B8N0C1D3E"}code是机器可读的稳定标识,请以它为分支依据。注意 Anthropic 方言里没有code——那条端点上只能按 HTTP 状态与error.type分支。type是为兼容 SDK 的重试逻辑而映射的官方取值,不要用它区分具体原因。param只在参数类错误上出现,给出出错的字段名。request_id与响应头X-Request-Id一致,报障时提供它即可直达明细。- 更细的结构化信息(命中的限流键、被剔除的线路、上游状态码等)不在响应体里,按
request_id去 调用日志 查。
错误码总表#
鉴权与令牌
请求与参数
幂等键(带了 Idempotency-Key 才会出现)
计费与额度
限流
路由与上游
客户端与内部
搜索阅读是另一条产品线(sk-srp- 密钥),错误码是独立的 serp_* 一族,不与上表混用:拿推理 Key 调搜索接口会拿到 serp_key_invalid,反之亦然。
重试策略#
不要对所有错误统一重试。下面三档是按「重发整个请求有没有意义」分的。
已经产出过 token 的请求不要整体重发——那部分已经计费,重发会重复付钱。
empty_completion 相反:它不计费,直接重试的成本是零。Python:只重试该重试的,并且尊重 Retry-After
python
import random, time, requests
# 429 是限流,5xx 是我方或上游的临时故障:这两类值得重试。# 4xx 里的其余错误是请求本身有问题,重试多少次结果都一样。RETRIABLE = {429, 500, 502, 503, 504}
def call_with_retry(url, body, headers, attempts=4): for attempt in range(attempts): response = requests.post(url, json=body, headers=headers, timeout=120) if response.status_code not in RETRIABLE: return response if attempt == attempts - 1: break
# 服务端说了等多久就等多久,没说才自己退避。 # 随机抖动是为了避免一批客户端在同一毫秒一起重来。 wait = response.headers.get("Retry-After") delay = float(wait) if wait else (2**attempt) + random.random() time.sleep(delay)
return response限流#
限流有 RPM、TPM、并发三个维度。RPM 与 TPM 按 API Key 与账户两级判定,并发按账户判定。
- RPM 与 TPM 超限都返回
rate_limited(429),并发占满返回concurrency_limited(429)。 - TPM 扣的是估算的输入 token——真实用量要等上游返回,那时限流已经来不及了。
- 429 一律带
Retry-After(秒,至少 1)。请按它退避,盲目重试只会把自己限得更死。 - 流式长连接会一直占着并发位直到流结束,所以并发限额通常比你以为的更早撞上。
- 限流组件自身故障时选择放行:限流不该成为可用性的单点。
怎么排查一次失败#
- 1
拿到 request_id
响应头
X-Request-Id,或错误体里与error同级的request_id,两者一致。 - 2
在调用日志里搜它
调用日志 支持按 request_id 精确查,能看到请求参数摘要、路由轨迹与计费明细。
- 3
看路由轨迹
它列出每次尝试用了哪条线路、上游状态码与耗时。
all_candidates_failed基本都能在这里看出是哪一环挂了。 - 4
还是不清楚就提工单
带上 request_id 开 工单,不用附完整请求体。
两个高频误报
把视频模型发到
/v1/chat/completions 会返回 400 并提示改用 POST /v1/videos;no_candidate 绝大多数不是「没模型」,而是 provider 的约束把所有线路都筛掉了——去 调用日志 看路由轨迹,每条线路是被哪个条件筛掉的都写着。