先分类,再决定怎么处理
「所有错误都重试三次」是最常见的错误做法。不同错误码的语义完全不同,处理策略也必须不同。下面的表是我们网关实际采用的策略,也可以作为你自己实现的参考。
- 连接失败 / 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。完整清单见模型目录。
| 模型名称与 ID | 上下文窗口 | 输入价格 / 1M | 输出价格 / 1M | 缓存命中 / 1M |
|---|---|---|---|---|
Claude Opusanthropic/claude-opus | 200K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
Claude Sonnetanthropic/claude-sonnet | 200K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
DeepSeek Chatdeepseek/deepseek-chat | 64K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
DeepSeek Reasonerdeepseek/deepseek-reasoner | 64K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
Gemini 1.5 Flashgoogle/gemini-1.5-flash | 1M | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
Gemini 1.5 Progoogle/gemini-1.5-pro | 1M | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
MiniMax ababminimax/abab | 25K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
MiniMax Textminimax/minimax-text | 25K | $1.00 / 1M | $3.00 / 1M | $0.1 / 1M |
以上为标准公有通道单价,按 Token 实际用量结算。 查看完整模型目录