429、5xx 与超时:LLM 网关容灾的工程实践

上游限流和故障是 LLM 应用的常态而不是异常。本文梳理 429/5xx/timeout/401 各自的正确处理方式,什么情况下可以自动切换通道、什么情况下绝不能重试,以及如何避免重复计费。

先分类,再决定怎么处理

「所有错误都重试三次」是最常见的错误做法。不同错误码的语义完全不同,处理策略也必须不同。下面的表是我们网关实际采用的策略,也可以作为你自己实现的参考。

  • 连接失败 / DNS / TLS:立即切备用通道。
  • Timeout(尚未开始输出):切换备用通道,最多 1~2 次。
  • HTTP 429:短期冷却后切换通道,同时下调该通道的权重。
  • HTTP 5xx:计入失败窗口,连续失败触发熔断。
  • 401 / 403:凭证或权限问题,禁用该凭证并告警,不要重试。
  • 余额不足:暂停该通道并告警。
  • 400 参数错误:归一化后直接返回给客户,不重试、不计费。
  • SSE 已输出部分内容后中断:返回 upstream_stream_error,默认不重试。

为什么 SSE 中断不能简单重试

流式响应一旦开始向客户端输出 Token,就说明上游已经产生了(并且会被计费的)输出。此时如果网关悄悄换一个通道重来一遍,用户会看到内容重复,而你为两段输出付了钱。

正确做法是把这半截流当作一次部分成功:返回错误码告诉客户端「上游中断」,由业务侧决定是展示已有内容 + 重试提示,还是整体重来。

熔断器:比重试更重要的那一层

重试是对单个请求的处理,熔断是对一段时间内整体状态的判断。当某个通道连续失败达到阈值时,直接把它标为「打开」,在一段时间内不再向它发流量,然后放少量探测流量试探(半开),探测成功若干次后再完全恢复(闭合)。

没有熔断,重试会把一个已经挂掉的通道压得更死,也会让客户的平均延迟急剧上升。

重试的幂等与计费

  • 非流式请求:重试后只按最终成功的那一次的 usage 计费。
  • 流式请求:已经输出 Token 的那一次会真实产生上游成本,必须记录,不能因为重试就把账抹平。
  • 为每个用户请求分配 request_id,所有重试共享它,事后可以完整回放「为什么走了这个通道」。
  • 退款/调整必须走账本流水,不能直接改余额。

Admesh 已经做了什么

上面这套策略在 Admesh 网关里是默认行为:同一 public_model 的多个上游通道之间做自动切换,每次路由决策都会落库,客户可以在用量记录里看到 request_id、实际使用的模型与通道健康状态;错误码按统一格式返回(error.type / error.code / request_id)。

值得注意的是:路由只在相同 public_model 的通道池内发生,不会因为你请求 anthropic/xxx 就把你切到别家的模型——跨模型替换会直接改变输出质量,这是我们不能替你做的决定。

Admesh 实时费率(节选)

以下价格来自 Admesh 后台实时配置,单位为美元 / 每百万 Token。完整清单见模型目录。

Admesh.ai 模型价目表:8 个已启用模型的价格与上下文窗口
模型名称与 ID上下文窗口输入价格 / 1M输出价格 / 1M缓存命中 / 1M
Claude Opusanthropic/claude-opus200K$1.00 / 1M$3.00 / 1M$0.1 / 1M
Claude Sonnetanthropic/claude-sonnet200K$1.00 / 1M$3.00 / 1M$0.1 / 1M
Claude Sonnet 5.5anthropic/claude-sonnet-5-51M$0.6 / 1M$3.00 / 1M$0.03 / 1M
DeepSeek Chatdeepseek/deepseek-chat64K$1.00 / 1M$3.00 / 1M$0.1 / 1M
DeepSeek Reasonerdeepseek/deepseek-reasoner64K$1.00 / 1M$3.00 / 1M$0.1 / 1M
Gemini 1.5 Flashgoogle/gemini-1.5-flash1M$1.00 / 1M$3.00 / 1M$0.1 / 1M
Gemini 1.5 Progoogle/gemini-1.5-pro1M$1.00 / 1M$3.00 / 1M$0.1 / 1M
MiniMax ababminimax/abab25K$1.00 / 1M$3.00 / 1M$0.1 / 1M

以上为标准公有通道单价,按 Token 实际用量结算。 查看完整模型目录

常见问题

遇到 429 应该 sleep 还是换模型?
都不理想。正确做法是换成同一个模型的另一条通道,或者按响应头里的重试时间排队等待。直接换模型会改变输出质量;无脑 sleep 会拖慢整体吞吐。
超时时间设多长合适?
分两段看:连接超时应该很短(几秒),因为连接阶段失败可以立即切换;总超时取决于任务,长文本生成可能需要几十秒。不要用一个值同时管这两件事。
重试会不会导致重复扣费?
在 Admesh 上不会:非流式请求按最终成功的那次 usage 结算;流式请求中已产生输出的部分会如实计费,不会因为切换通道而重复收费。所有结算都基于账本流水,可以在用量页逐笔核对。

参考来源