错误语义
网关的错误分两类,形状不同。客户端要同时处理两种。
一、两种错误体
网关自己产生的错误
json
{"error": "unauthorized"}error 是一个字符串。这类错误来自网关自身的判断,请求没有到达上游:
| 场景 | 状态码 | error 文本 |
|---|---|---|
| 令牌缺失 / 无效 / scope 不足 | 401 | unauthorized |
| 令牌配额耗尽 | 402 | token quota exceeded |
| 令牌分组配额耗尽 | 402 | group quota exceeded |
| 团队账户额度耗尽 | 402 | account quota exceeded |
| 分组速率超限 | 429 | group rate limit exceeded |
| 模型速率超限 | 429 | model rate limit exceeded |
| 请求转换失败(适配器) | 501 / 400 | 见下 |
上游透传的错误
上游返回什么就转发什么,包括错误体。所以一个 OpenAI 形态的上游会给出:
json
{"error": {"message": "model not found", "type": "invalid_request_error", "code": "model_not_found"}}这是刻意的:网关不伪造响应。客户端看到的错误体与直连上游时一致。
IMPORTANT
不要把两种形状混起来解析。 网关自身错误的 error 是字符串,上游错误的 error 通常是对象。先判类型再取字段。
适配器转换失败的状态码
请求在本地转换失败时(TransformRequest 报错),状态码按错误种类给出:
| 错误 | 状态码 | 含义 |
|---|---|---|
| 路径不被该适配器支持 | 501 | 例如往 Gemini 渠道发 /v1/embeddings |
| 特性不被该适配器支持 | 501 | 例如往 Gemini 渠道发 tools |
| 内容被上游拦截 | 400 | 上游的内容策略拒绝了这个 prompt |
| 其他转换失败 | 400 | 兜底 |
这四类都是本地失败,代理直接返回、不会换渠道重试——换个渠道也一样错。
二、分类与错误类
每个错误都带一个原始分类(snake_case 标识符),控制台把它归并成一小撮面向操作员的错误类:
- 分类决定重试与冷却行为;
- 错误类决定界面上显示什么标题与修复建议。
完整对照表由代码生成,见参考 / 错误码与分类。
日志行里携带的是原始分类,所以拿日志排查时对照那张表即可。
三、可重试性与冷却范围
这是最容易误解的一处:「会重试」与「会冷却」不是一回事。
| 情况 | 换渠道重试 | 成员冷却 | 计入渠道连续失败 |
|---|---|---|---|
传输失败、408 / 429 / 5xx | ✅ | ✅ | ✅ |
上游 4xx(其他) | ✅ | ✅ | ❌ |
| 本地适配器 / 配置错误 | ❌ | ❌ | ❌ |
图像 / 音频 / 视频 / 音乐生成、responses | ❌ | 视情况 | — |
| 客户端已取消 | ❌ | ❌ | ❌ |
两个关键判断:
- 上游 4xx 会换渠道,但不会把渠道记成不健康。 渠道能力是异构的(一个上游拒绝的
reasoning_effort取值另一个可能接受),但「请求本身有问题」不该污染渠道健康度。 - 本地错误立即返回。 故障转移帮不上忙,重试只是把同一个错误重复几遍。
响应已经提交给客户端之后不可能重试。
四、请求侧的可诊断头
| 头 | 作用 |
|---|---|
X-Meta-Session-Id | 会话键,用于会话粘性 |
X-Meta-Client | 客户端自报家族(用于日志统计与 payload 规则的 header 条件);不在已知集合内的值会被忽略 |
X-Meta-Upstream-Path | 本次请求的端点覆盖(payload 规则路径) |
五、响应侧的诊断头
| 头 | 作用 |
|---|---|
X-Meta-Upstream-URL | 本次尝试实际打到的上游地址(scheme + host + path) |
X-Meta-Hook-Decision | 插件钩子对本次请求的决策 |
X-Meta-Image-Shim | 图像编辑 shim 实际使用的路径 |
X-Meta-Upstream-URL剥离了 query、fragment 与 userinfo——它们经常携带凭据(?api_key=…),不能回传给客户端、不能进日志、不能进审计。
排查「为什么打到那个地址」时,这个头与日志页的 upstream_url 是同一个值。
六、常见上游错误与含义
| 上游返回 | 通常意味着 |
|---|---|
401 / 403 | 凭据无效、过期,或该 Key 没有这个模型的权限 |
400 且提到未知字段 | 该上游对自己的请求模型校验很严 —— 用请求体字段映射的白名单写法(keep)把信封削到它认识的键 |
404 且提到 model | 上游不认这个模型名 —— 检查路由的映射,或该渠道是否真的提供它 |
429 | 上游限流 —— 会进入冷却并换渠道;持续出现应降低频率或加渠道 |
400 且是内容策略 | 上游内容过滤拦截了 prompt |
501(网关自己给出) | 该渠道类型不支持这个端点或这个特性 |
5xx | 上游故障 —— 会换渠道;连续出现会触发渠道保护 |
排查「上游报错」类问题时,先看日志里的
upstream_url与上游返回的 body。本项目是透明网关,多数「网关的 bug」其实是上游协议不匹配——例如某上游的/v1/images/edits只收 JSON,multipart 直接415。
七、排查顺序
- 日志页按
status=failed过滤,展开出错的日志行。 - 看
upstream_url:是不是你配的地址?不是 → 端点映射或基础 URL 的问题。 - 看
upstream_model:与客户端请求的模型名是否一致?不一致是正常的(映射的结果),但要确认映射是否符合预期。 - 看
channel_id与key_fingerprint:是不是你以为的那个渠道和那把密钥。 - 看上游返回的 body:它通常直接说出了原因。
- 对照参考 / 错误码与分类确认这个分类的语义与建议。
更多按症状的排查见故障排查。