fix(ai): 按官方 Error codes 页重写 OpenAI 错误映射
CI / ai-gates (push) Successful in 55s

拿到 doc_temp/gpt错误码.md 后核对,**此前猜的六个键里错了五个**:
rate_limit_exceeded / moderation_blocked / content_policy_violation /
invalid_api_key / image_generation_user_error 在官方文档里都不存在。真实的是
type rate_limit_error + code slow_down、type service_unavailable_error + code
server_is_overloaded、四个 billing 码。唯一对的 insufficient_quota 也是宽 type
而非 code。这段历史留在 errors.py 顶部,作为凭记忆列错误码表代价的记录。

官方表的 13 条全是基础设施与计费,**没有任何内容审核/侵权码**——但图片接口确有
moderation 参数,说明审核存在、只是错误形态未文档化。直接后果:一次侵权或违规
拒绝很可能以普通 400 到达,只带一句人类可读消息。若一律归「服务端错误」,用户会
看到「服务出错了」并反复重试一个注定被拒的提示词。

故用 error.param 的有无做结构化区分(带 param = 参数错,不带 = 按内容被拒),
三条依据:文档的 service_tier 那条明确说参数错会带 param;内容拒绝不指向任何
具体参数;我方已在调用前校验过尺寸/画质/格式,能被 400 拒掉的参数嫌疑本就不大。
这一条是推断,走到时记 WARNING——那是日后校正它的唯一素材。client.py 相应补上
param 的解析。

两处顺带收获:
- 无 code 的 429 按限流处理,现在是**文档支持的默认**(六种 429 里只有「Rate
  limit reached for requests」没有文档化的 code),不再是猜测。
- 文档提醒视频过载已从 429 改判为 503。我们两者都归可重试,故不受影响;加了一条
  测试钉住这个「无影响」。

同一张表按 code 与 type 两个字段分别查:文档把标识列在哪个字段下,不保证响应里
它就在那个字段,查错的代价是把配额耗尽降级成限流、白烧一轮重试预算。

未接 Retry-After 并记录理由:Java 侧退避基线 30 秒、上限 10 分钟,已长于服务商
通常给的值,忽略只会偏慢、不会加剧限流;接它是一次跨语言契约变更。

测试 271 → 284。四道门禁逐条读退出码,全绿。
This commit is contained in:
2026-09-21 15:52:25 +08:00
parent 8275a04903
commit 541376e9be
6 changed files with 345 additions and 122 deletions
+5 -2
View File
@@ -138,8 +138,11 @@ app/
- **火山不支持幂等键**,「提交超时后不知道有没有扣费」只能由 `max_attempts` 兜住;
- **火山产物 URL 只有 24 小时有效期**,因此厂商 URL 不能当存储用,必须在时限内转存;
- **OpenAI 的两份文档都没有错误码表**,故 `providers/openai/errors.py` 的映射是
按 HTTP 状态码为主推断的,**未经文档证实**,拿到真实错误数据后须校正。
- **OpenAI 的官方错误码表里全是基础设施与计费错误**,**没有任何内容审核/侵权码**
——一次「这个提示词不能生成」只能以通用 400 到达。故 `providers/openai/errors.py`
用 `error.param` 的有无区分「参数错」与「内容被拒」,那一条是推断并记 WARNING;
- **`Retry-After` 目前不读**:Java 侧退避阶梯基线 30 秒、上限 10 分钟,已长于服务商
通常给的值,忽略它只会偏慢、不会加剧限流。接它是一次跨语言契约变更。
### 同步型供应商的代价