API中转站错误码统一封装:跨厂商异常收敛、可重试标记与计费对齐的落地方法
多模型接入之后,各家上游的状态码、业务码与错误文案各不相同,调用方被迫写下一堆判断分支。本文从错误结构设计、分层归类、映射表维护、可重试判断、流式场景与计费对齐几个角度,讲清API中转站错误码统一封装的落地方法,并给出可观测性与客户端侧配合的实操建议。
做过大模型接入的团队大多踩过同一类坑:接第一家模型时,错误处理随手写几个 if 就够用;等到通过 API 中转站接入五六家模型,错误码就变成一团乱麻。同一个「额度不足」,有的上游返回 429,有的返回 400 加一段中文提示,还有的 HTTP 200 却在响应体里塞了业务码。客户端的重试、提示、降级逻辑,全被这些差异拖住。
更麻烦的是这类差异会层层放大。中转站把上游原始响应直接透传,调用方就得为每个模型写一套判断分支;一旦上游调整文案或新增错误码,线上就开始出现「未知错误,请稍后重试」这种既不能重试、也无法定位的兜底提示。错误码统一封装要解决的,就是把多源异常收敛成一套稳定、可判断、可追溯的内部错误结构。
第一步是定结构。比较通行的做法是沿用 OpenAI 兼容的错误对象,在 error 里保留 code、type、message,再扩展三个内部字段:upstream_code 记录上游原始码,request_id 贯穿整条链路,retryable 与 retry_after 直接告诉调用方「能不能重试、隔多久重试」。结构定下来之后,前端和 SDK 只需要认这一套,不用再关心背后换的是哪家模型。
第二步是分层归类。实践中按五层划分比较清晰:传输与网络层,包括连接超时、DNS 解析失败、TLS 握手异常;鉴权层,包括 Key 无效、权限不足、Key 被禁用;配额与计费层,包括余额不足、单 Key 限额、并发超限;上游模型层,包括模型不存在、上下文超长、上游过载;内容与安全层,包括审核拦截、参数不合法。层与层之间职责不同,对应的重试策略也完全不同。
第三步是维护映射表。把每一家上游的 HTTP 状态码、业务码、错误文案关键词整理成表,映射到内部枚举,同时把原始值原样存进 upstream_code。映射表要当成配置而不是硬编码,上游改版时改配置即可。关键字匹配只能作为兜底,优先走状态码与结构化字段,否则一句「请求过于频繁」被误判成限流的概率相当高,反而把可重试的错误挡在门外。
可重试判断是最容易出错的一环。429 通常意味着稍后重试有救,但如果 retry_after 缺失,指数退避加随机抖动比固定间隔更稳;500、502、503、504 属于上游侧问题,可重试但要设次数上限;401、403、404、422 基本重试无意义,重试只会浪费时间与额度。连接重置与读取超时要单独处理,因为请求可能已经到达上游并产生了消耗。
流式响应会让错误封装更棘手。SSE 场景下 HTTP 200 往往已经发出,上游在生成中途报错,此时无法再用状态码表达,只能在事件流里补一个 error 事件,把统一结构塞进去,再正常结束连接。调用方要约定:收到 error 事件即视为本次生成失败,已产出的内容按「不完整」处理,避免半截回答被当成完整结果入库。
计费对齐同样绕不开。失败请求要不要计费,取决于上游是否已经产生用量。比较稳妥的做法是把 usage 与错误码一起写进用量日志:上游返回了 token 用量就照实记录,未返回则标记为不计费;中转层自身的路由失败、超时未发出请求,不应计入调用方消耗。调用方对账时按 request_id 拉取记录,比只看账户总量更容易定位异常订单。
多模型路由会让错误码的价值进一步放大。同一套接口背后挂着不同厂商的模型,如果错误结构不统一,调用方根本没法做「主模型失败自动切备用模型」这类降级。快米兔在这一点上的处理比较贴近工程习惯:接口保持 OpenAI 兼容,错误结构统一收敛,调用方按 retryable 字段决定重试还是切换模型,鉴权与计费相关的错误则单独归类,避免把「余额不足」误当成「上游抖动」反复重试。按量计费、注册赠 5 元测试金,方便在正式接入前把各类错误分支先跑一遍,具体规则以官方说明为准。
光有结构还不够,可观测性要跟上。request_id 从入口生成,透传到上游请求头,写进访问日志、错误日志与用量记录;再按错误码、模型、Key 三个维度做分布看板。很多问题会在这里现形:某个 Key 的 401 突然上涨,多半是密钥轮换没同步;某个模型的 429 集中出现,往往是并发配置没跟上;404 抬头则可能是模型名称在上游下线后没有及时更新路由表。
客户端侧也要配合。建议统一封装一层调用器:先做参数校验,再发请求;捕获网络异常与统一错误结构;对 retryable 为真的错误执行带抖动的退避重试;重试仍失败则走降级路由,或直接抛出可读提示。把「未知错误」这类兜底文案压到最低,出问题时工单里能直接带上 request_id 与 upstream_code,排查成本会下降一个量级。
落地时可以先做三件事:定义统一错误结构与枚举;整理上游映射表并把 upstream_code 保留下来;把 retryable、retry_after 与计费口径写进接口文档。错误码封装看起来是脏活,却决定了接入方能不能在一个下午把多家模型接完。对需要同时挂多个模型、又不想为每家写一套异常分支的团队来说,选择错误结构统一、日志可追溯的中转服务通常更省事,后续做稳定性优化也更有抓手。
