错误码与重试语义

不要一律重试。分类错了,故障转移反而让体验更差 —— 你等 15 秒才拿到一个本该 1 秒返回的 400。

我们内部怎么分类

类型判据我们的动作
连接前失败DNS / TCP / TLS / 连接超时换渠道,你无感
首字节前失败5xx、上游错误 JSON、200 但空流后断开换渠道,你无感
限流429先在渠道内换账号,并收窄该账号的自适应并发窗口
配额耗尽429 + 配额语义该账号冷却到具体时间点(小时级),不是固定 30 秒
凭据失效401 / 403(上游侧)禁用该账号并告警,不再重试该账号
客户端错误400 / 413 / 422 / 上下文超长绝不重试,直接返回给你
流中断已输出内容后上游断开仅在纯文本且已输出很少时尝试续写,否则结束
假成功200 但内容异常(如 finish_reason 缺失)计入该渠道失败率,影响后续排序
400 绝不跨渠道重试
400 类错误意味着请求本身有问题:换一家商户结果一样,只是让你多等几秒。 我们会立刻把上游的错误原文按你的协议形状返回,你据此改请求即可。

容灾的边界:首字节

在第一个内容片段真正发给你之前,我们会先握住上游的响应。 这让「首 token 超时 / 空回复 / 静默拒绝」从不可恢复变成可以换渠道重试。

反过来的规则同样是硬的:已经向你写出语义字节之后,绝不再 failover。拼接两个上游的流会腐化对话内容,而你察觉不到 —— 那比直接报错严重得多。

  • 首字节前失败 → 自动换渠道,你只感觉到「响应慢了几秒」
  • 首字节后失败 → 流中断,我们不会用另一家的输出接上去
  • 只写出 SSE 注释 / 心跳的情况不算「已输出」,仍可换渠道

你会看到的状态码

状态码含义建议动作
400请求体不合法 / 上下文超长修请求。重试无意义
401akrouter API Key 无效或已吊销检查 key
402余额不足充值。余额是硬门槛,不赊账
403该 key 不允许调用这个模型检查 key 的模型白名单
429你触发了 key 的速率限制按 Retry-After 退避。上游侧的 429 不会透给你,我们内部换账号处理
排队超时所有候选渠道的并发槽位都满且排队超时重试通常有效;持续出现说明该模型供给紧张
无可用渠道成本护栏过滤后候选为空,或硬约束(requireNativeProtocol 等)无法满足放宽上限或关闭对应硬约束 —— 我们不会替你突破它
5xx本站自身异常重试;持续出现请看状态页
「无可用渠道」是设计的一部分
成本上限过滤后为空时,我们返回明确错误,不会静默突破你设定的上限。 你设的上限导致失败是可接受的;悄悄花掉超预算的钱不可接受。

错误响应形状

按你的客户端协议返回,不做统一封装。

json
// anthropic
{ "type": "error", "error": { "type": "invalid_request_error", "message": "..." } }

// openai_chat / openai_responses
{ "error": { "message": "...", "type": "invalid_request_error", "code": "..." } }

排障

  • 使用日志 的请求详情里有完整的 attempts 时间线:尝试了哪些渠道、各自的状态码与错误分类
  • 状态页 是全局视角:各模型近 24h 的 uptime 与最近 50 分钟的成功率
  • 模型市场 能看到具体是哪家商户在抖