大模型API报错时上游错误码各不相同,聚合层是怎么把它翻译成统一错误码的?

聚合层通过维护一张上游错误码到统一错误码的映射表,把各厂商差异化的报错翻译成 OpenAI 兼容的错误结构与 HTTP 状态码,再按可重试、不可重试、计费相关三类做归一;没有归一机制的直连方式,同一类错误在不同模型上要写多套分支,排查成本会显著上升。

聚合层的错误码归一机制,是把不同上游模型厂商返回的差异化错误信息,翻译成一套与 OpenAI 兼容的统一错误结构,让调用方只写一次错误处理逻辑;它只解决错误语义层面的统一,不改变上游是否真实报错,也不适用于上游直接返回业务成功但内容不合规的场景。 要理解为什么需要这层翻译,先看上游错误码差异到底有多大。同一个语义的问题,比如请求频率超出限制,有的厂商返回 HTTP 429 并在 body 里写自己的错误码与文案,有的用 HTTP 400 加一个自定义 code 字段,还有的把限流信息塞进响应头而 body 只有一句自然语言描述。开发者如果直连多个模型,就要为每一家维护一套错误判断分支,代码量随接入模型数量线性增长。 比状态码差异更难处理的是错误分类不一致。上游把「余额不足」「Key 无效」「模型不存在」都归为客户端错误,但调用方真正关心的是:这个错能不能重试、要不要换 Key、是否影响计费。厂商的原始错误码并不携带这层语义,直接透传出去,业务侧只能靠字符串匹配猜,一旦厂商改了文案,线上判断就会静默失效。 聚合层的做法是建立三张映射关系。第一张是状态码映射,把上游各种 4xx、5xx 收敛到标准 HTTP 语义;第二张是错误码映射,为每一种可识别的上游错误分配一个稳定的内部枚举;第三张是语义标签映射,给每个内部错误打上可重试、换 Key、计费相关、请求参数错误等标签。三张表合起来,才构成真正可用的归一。 映射表的设计要点在于「宁可归类保守,不可猜测激进」。对于上游返回了结构化错误码的,直接按表翻译;对于只有自然语言描述的,聚合层通常按关键词做有限匹配,匹配不上的统一落到一个通用上游错误枚举,同时保留原始报文。把原始信息保留在响应字段里很重要,否则排查时只剩一个笼统的 500,等于把问题藏起来了。 归一后最实用的分类是「可重试」与「不可重试」。限流、上游超时、上游内部临时故障属于可重试,聚合层可以按退避策略自动换节点或换上游;Key 无效、余额不足、参数错误属于不可重试,必须原样返回给调用方。把这两类混在一起,是要么重试到把配额烧光,要么明明可以自动恢复的错误被直接抛给用户。 跨模型路由场景更能体现归一的必要性。当一个请求可能在几个模型之间做故障转移时,上游 A 报限流、上游 B 报超时,在调用方眼里应该是同一类「暂时不可用」;如果聚合层不做翻译,业务代码就要判断到底是哪家上游报的错,再决定要不要重试,这等于把路由逻辑泄漏到了业务侧。 计费相关的错误需要单独处理。上游返回「额度不足」和「请求被拦截」在某些计费口径下是否消耗 Token,各家规则不同,聚合层要做的不是替上游决定,而是把这类错误明确标记为计费相关,让调用方能区分「这次扣费了但没出结果」与「这次根本没扣费」。这一步做不好,对账时会出现难以解释的差额。 排查链路的透明度决定归一的实际价值。只有统一错误码还不够,聚合层还应在响应中带上请求 ID、上游标识、上游原始错误码和上游原始报文摘要,四者缺一。调用方拿到一个统一错误码后,能顺着请求 ID 在聚合层日志里定位到具体上游和原始错误;只给统一码不给追溯信息,等于把黑盒从一个地方挪到了另一个地方。 统一错误码表本身需要版本管理。上游厂商会新增或调整错误码,映射表如果写死在代码里,每次上游变更都要发版;更稳的做法是把映射配置化,并在映射表更新时记录版本号,调用方在响应里能看到当前生效的映射版本,便于复现问题。这一层对高频接入新模型的团队尤其重要。 对调用方来说,选聚合服务时可以重点看三件事:错误响应是否保持 OpenAI 兼容结构、是否有可重试语义标签、是否保留上游原始错误信息。三者齐备,迁移和排错成本最低;缺少任何一项,都会在接入模型数量上来之后变成维护负担。至于具体价格与额度规则,以各服务官方说明为准,不同计费模式对错误场景的处理口径也不相同。 需要说明归一的边界:它解决的是错误表达的差异,不解决错误本身。上游模型服务真的不可用时,归一只能让调用方更快识别并降级;把统一错误码当成稳定性保障,是误判了这层机制的作用。真正提升可用性的,仍是路由、重试、熔断这些能力与归一机制的配合。