
做过多模型接入的朋友应该都有体会401 和 429 这两个状态码几乎是所有 LLM API 集成的“必修课”。我在后端团队里接过多个接入 OpenAI、Claude、Gemini、通义、文心等大模型的项目无论用官方 SDK 还是自研网关401 Unauthorized 和 429 Too Many Requests 都是出现频率最高的两类异常而且往往不是业务参数写错而是认证、限流、配置这三类问题纠缠在一起。这篇文章打算把这两类报错的排查路径完整捋一遍从现象、原因、操作步骤到可以直接抄的重试代码做成一份实战向的清单适合正在做多模型聚合、统一 API 网关或者模型切换功能的后端同学参考。1. 多模型接入的报错全景1.1 多模型接入到底在接什么现在很多项目已经不是单打独斗只接一家大模型了。有的产品需要根据场景选模型比如长文本用 A 家、低成本对话用 B 家有的业务要做容灾主模型挂了自动切备用还有的是公司内部统一做了一层“模型网关”对外只暴露一个标准接口对内管理多家厂商的 key 和配额。这种接入模式下报错会有两个特点一是错误可能被网关包装一次原始状态码可能保留也可能被翻译成统一格式二是不同模型的鉴权方式、限流维度、返回结构完全不一样导致同一个错误码背后的排查路径差异很大。我们在排错时首先要明确自己在整个调用链路的哪一层是 SDK 直连、经过自研网关还是接了一个第三方聚合平台。因为在不同层级看到同样的401问题可能完全不同。直连时 401 基本就是 key、地址、认证头的问题过网关时还得额外检查网关是否改写或丢失了认证头。1.2 为什么报错集中在 401 和 429简单说401 是“身份没验过”429 是“请求太猛被限流”。这两个状态码在大多数模型 API 里都是高频出现的。401 磨人是因为鉴权方式不统一。有的用Authorization: Bearer key有的用x-api-key有的还要额外传版本头比如 Anthropic 必须带anthropic-version。再加上多模型接入后 key 变多环境变量一覆盖很容易拿着 A 家的 key 去请求 B 家的接口返回 401 一点也不奇怪。429 磨人是因为限流维度不统一。有的按每分钟请求数限有的按每分钟 token 数限有的按并发数限还有的按账户或项目的配额限。同一个 key 在测试环境没事到了生产环境一压测就频繁 429这是正常现象因为不同环境分配的配额不一样。所以我把 401 和 429 放在同一张排查清单里不是说它们没有关联而是在多模型接入场景下它们都是“配置与容量”问题的典型信号不能只盯着代码看。1.3 排错的核心原则不管遇到哪个状态码我建议先做到三件事先复现、再动配置、最后改代码。第一用 curl 走一遍最原始的 HTTP 请求跳过所有业务封装。这一步能区分是服务端的问题还是我们自己代码的问题。第二保存完整的响应头和响应体特别是Retry-After、x-ratelimit-*这类限流头很多 429 的根因都藏在里面。第三不要凭感觉改配置要确认当前进程到底加载了哪个 key、哪条 base_url大概率是配置覆盖而不是代码逻辑出错。2. 401 的排查轨迹从 invalid_api_key 到签名失效2.1 401 的典型返回形态不同平台的 401 返回体差异很大但基本都会告诉你认证失败。常见的几种格式{error: {message: Incorrect API key provided: sk-***, type: invalid_request_error, code: invalid_api_key}} {error: {code: unauthorized, message: Authentication Fails}} {message: Unauthorized}有的平台会把 key 的一部分回显在错误信息里这其实是帮你定位的看回显的 key 前缀和你本地配置的 key 是否一致。如果平台明确说invalid_api_key那就先别查代码先确认 key 本身有没有问题。但也有平台只给一个干巴巴的Unauthorized没有任何细节。这时候就只能靠排查路径一步步缩小范围了。2.2 401 的常见原因我在实际项目里遇到的 401原因基本逃不出下面这几类。第一key 本身不对。包括复制少了一位、多了一个换行符、环境变量引用错误、key 被误截断。这类问题最不起眼但发生率最高。第二key 和 base_url 不匹配。比如你用的是 OpenAI 的 key但base_url被配成了某个网关地址或者你在 Azure OpenAI 部署模型时endpoint 的 resource name 和 key 不是一个资源组。这类问题在多模型场景尤其常见配置文件一堆很容易串。第三认证头格式不对。有的平台要求Authorization: Bearer key有的要求直接Authorization: key还有的用x-api-key。用官方 SDK 时通常不用关心但只要你自研封装或过网关header 传错是大概率事件。第四请求被网关或转发层改写。网关做了二次认证把上游 key 覆盖掉或者把原始 header 丢掉了上游就返回 401。这个最隐蔽因为代码里明明写了 key但实际到服务端时 header 已经变了。第五签名鉴权类。部分云厂商不是简单 key而是用 AccessKey 加时间戳做签名这时候客户端和服务端时间偏差太大会直接鉴权失败。排查时记得看一眼服务器时间我之前就遇到过本机时间慢了两分钟结果签名始终不过。第六key 过期、被禁用或账号欠费。这种情况不是立即发生的可能白天还好好的晚上突然开始 401。如果你没改过代码先查一下 key 的生效状态。2.3 一步步定位 401我个人的排查顺序从外到内第一步确认 key 信息。运行echo $API_KEY看有没有多余空格或换行。如果你是写在配置文件里检查 YAML 或 env 文件有没有被 IDE 自动转义。第二步用 curl 直连。跳过所有代码和 SDK直接请求目标接口这是最干净的一次验证。curl -i https://api.example.com/v1/chat/completions \ -H Authorization: Bearer $MODEL_API_KEY \ -H Content-Type: application/json \ -d { model: demo-model, messages: [{role: user, content: ping}] }加-i是为了同时看响应头和响应体。如果 curl 都能返回 200说明 key 和 base_url 没问题问题在代码或网关。如果 curl 仍然 401说明问题在更前面的环节。第三步检查 base_url。看有没有末尾斜杠、有没有拼错路径、是不是https。多模型接入时我很喜欢把 base_url 单独抽到配置中心而不是硬编码在代码里这样排查会方便很多。第四步检查网关转发规则。如果你的请求经过内部网关要看网关路由到哪个上游是否在转发时添加、覆盖或删除了认证 header。有时候网关为了做内部鉴权会把上游的 key 替换成网关自己的 key结果上游不认就 401 了。第五步如果还是查不到开 SDK 的 debug 模式打印实际发出的 headers。注意把 key 脱敏只打印前几位和后几位不要完整打印否则日志一泄露就麻烦了。2.4 多模型场景下的 401 避坑经验在多模型接入项目里我踩过最深的坑是“key 串环境”。测试环境的 key 被同步到生产环境或者生产配置文件的 key 被同事本地调试时覆盖了。后来我定了一个规矩每个环境必须用独立的企业级密钥管理代码里禁止出现明文 key环境变量命名也要把 provider 和环境带进去比如PROVIDER_A_PROD_API_KEY和PROVIDER_A_STAGING_API_KEY避免混淆。另外如果你用的是某个 SDK要注意它可能默认带了一些 header比如Authorization可能被 SDK 的拦截器覆盖。这种情况 curl 正常、代码 401绝大多数是因为代码里设置了自定义 header但 SDK 在底层又用默认认证头覆盖了一遍。解决方式是优先设置 SDK 的 api_key 字段而不是手动加 header。3. 429 的排查轨迹限流、配额与重试3.1 429 的几种真实形态429 的含义很简单请求过多服务端拒绝处理。但“过多”的定义很多常见维度包括每分钟请求数也就是 RPMRequests Per Minute每分钟 token 数也就是 TPMTokens Per Minute对话场景更常见并发请求数同时发起的请求数量超过上限账户总配额或项目配额按月或按天限制免费额度或体验额度耗尽部分平台会返回 429我遇到过一种很隐蔽的 429请求本身量不大但每个请求携带的上下文都很长导致 TPM 先被耗尽返回的却是标准的429 rate_limit_exceeded。这种情况只看请求数看不出来必须统计所有请求的 token 消耗量。3.2 读响应头判断限流维度排查 429 时响应头比响应体更重要。不同平台会在响应头里放一些限流信息常见的有这些响应头含义示例值Retry-After多少秒后才能继续请求15x-ratelimit-limit-requests时间窗口内允许的总请求数200x-ratelimit-remaining-requests还剩多少请求额度0x-ratelimit-reset-requests配额重置的倒计时15sx-ratelimit-limit-tokens时间窗口内允许的总 token 数100000实际排错时我会先把这些头全部记到日志里。看到remaining-requests: 0说明配额确实用完了看到Retry-After: 15说明 15 秒后可以再试。但不同厂商的响应头命名不一样有的平台用自定义头比如x-ratelimit-remaining或ratelimittime所以不能只看一个名字要通用解析。还有个容易忽略的点429 不一定每次都携带Retry-After。如果第一次遇到 429 时响应头里没有这个字段通常说明限流是瞬时策略比如并发过高过一小段随机时间再试即可。3.3 多模型接入时 429 为什么更容易出现单模型接入时限流范围比较清晰就一个 key 的配额。多模型接入后情况变复杂了。第一多个业务共用一个 provider 的 key比如三个服务都在调用同一个模型接口某一瞬间并发叠加就触发了限流。第二模型切换时可能出现“交叉流量”比如 A 模型 429 了你把流量切到 B 模型但 B 模型本身也承担着其他业务结果 B 也迅速被打到 429。第三自研网关没有做全局限流每个节点都往上游发请求整体量看起来不大但集中在同一秒就爆了。所以多模型接入项目做限流治理不能只依赖厂商的配额。我通常在网关层做三层保护本地令牌桶控制总体并发每个 provider 分别配置独立的 max_qps超出部分直接排队或拒绝而不是一股脑发到上游。3.4 重试策略的代码级实现遇到 429 时很多人的第一反应是“等一下再重试”但等多久是个讲究。如果服务端给了Retry-After就按它的值来如果没给就用指数退避加随机抖动。指数退避的逻辑是第一次失败等 1 秒第二次等 2 秒第三次等 4 秒依此类推同时加一个随机值避免多个请求同时重试造成“惊群”。我常用的一个基础版本import random import time import requests def post_with_retry(url, headers, json_body, max_retries5): last_resp None for attempt in range(max_retries): last_resp requests.post(url, headersheaders, jsonjson_body, timeout30) if last_resp.status_code 429: delay min(2 ** attempt random.uniform(0, 0.5), 20) retry_after last_resp.headers.get(Retry-After) if retry_after and retry_after.isdigit(): delay max(delay, int(retry_after)) print(f收到 429等待 {delay:.2f}s 后重试当前第 {attempt 1} 次) time.sleep(delay) continue return last_resp return last_resp注意几个细节。第一max_retries必须设上限否则上游一直限流你的请求会一直堆积。我一般设 4 到 5 次超过就直接失败进入降级流程。第二可重试的错误要区分。429 可重试5xx 可选重试但 401 千万不能自动重试因为 key 错了重试一万次也是 401反而浪费时间和配额。400 这类参数错误也不要重试那是代码问题。第三如果并发场景很高重试里要加“全局限流信号量”确保同一时刻打出去的请求数可控而不是所有线程都在退避之后同时重试。3.5 利用多模型做容灾时的限流保护多模型接入最大的优势是容灾比如 A 模型持续 429可以切到 B 模型。但这里头有个容易踩的坑无脑切换会把 B 模型也打挂。我实践下来的策略是每个 provider 维护一个健康状态。连续触发 3 次 429 或 5 次 5xx就把该 provider 标记为“熔断”暂时不分配新流量但每隔 30 秒放一个探测请求试试是否恢复。恢复成功后再慢慢把流量切回来而不是一次性恢复所有流量。另一个方式是渐进切换。假设 A 模型开始 429先把 10% 的流量切到备用模型 B观察 B 的响应延迟和错误率如果稳定再逐步提高到 30%、50%。很多线上事故都是瞬间切流量导致的所以我宁可让一部分请求排队等一会儿也不愿意把两个厂商都打挂。4. 从 401 到 429 的排查工具与日志规范4.1 结构化日志是排错的第一生产力很多报错难排查不是真的找不到原因而是日志里没有足够信息。我接多模型项目时会强制要求每个模型 API 调用都记录一个结构化日志至少包含这些字段时间戳和请求 IDprovider 和 model 标识请求的 endpoint 和最终实际调用的 base_url状态码、延迟、错误类型是否可重试一个典型的日志字段设计{ ts: 2026-05-01T10:00:00Z, request_id: req_1234, provider: provider_a, model: demo-model, endpoint: /v1/chat/completions, status_code: 429, latency_ms: 120, error_code: rate_limit_exceeded, retryable: true }注意一点绝对不要把完整的 API Key 或完整请求体打进去。真要打最多显示 key 的前四位和后四位或者做哈希脱敏。我有一次看到团队同学把整个Authorization头打进日志当时就头大了这种日志一旦被拉取等于把生产 key 直接送人。4.2 常见的排查工具组合curl 是最快的验证工具因为你能看到最原始的响应头。加-v参数还能看到 TLS 握手和实际发送的 header对于判断 base_url 是否被 DNS 解析到别的地址很有帮助。curl -v -i https://api.example.com/v1/chat/completions \ -H Authorization: Bearer $MODEL_API_KEY \ -d {model: demo-model, messages: [{role: user, content: hi}]}除了 curl我还会用本地调试工具把请求前后的 header 对比一遍尤其是在排查网关改写的场景时。把入口请求和出口请求各抓一份看Authorization有没有变化。这一步往往能快速区分是上游问题还是中间链路问题。4.3 状态码速查表做一个通用速查表方便给团队同学培训状态码常见原因排查方向是否能重试401key 无效、过期、鉴权头被改写验证 key、base_url、header、网关规则否403权限不足、IP 白名单、账号欠费检查账户权限、网络白名单、余额否404模型名不存在、endpoint 路径错误查模型名是否在当前 provider 可用否429限流、配额耗尽、并发过高看响应头、控制流量、退避重试是500服务端内部错误关注平台状态配合重试可选503服务不可用、正在重启看平台公告配合重试是这些状态码在多模型接入时都会遇到但 401 和 429 是配置类问题最集中的区域所以排错优先级最高。4.4 监控和告警怎么设置多模型接入的监控我建议别只盯着整体成功率要把 401 和 429 单独拆出来看。401 如果突然增多很可能是某个 key 过期或被盗属于安全事件告警级别要高。429 如果增多不一定是坏事也可能是业务流量涨了但你需要知道是哪个 provider、哪个 key 被限流了。我习惯按 provider 维度统计各状态的请求占比这样一眼就能看出是不是某个上游开始不稳定。延迟也要盯。429 会严重影响 P99因为重试会拉长整体耗时过了很久才失败。所以监控数据里我会把“重试次数”和“因限流放弃的次数”也加进去避免被表面延迟数字骗了。5. 实战踩坑后沉淀的几条硬经验5.1 接入阶段就做最小连通性测试多模型接入一开始我建议写一个“最小 API 连通性测试”脚本对每个 provider 跑一次真实请求。脚本不用复杂就用 curl 或几十行 Python验证 key、endpoint、model 三个要素全配套。这个步骤能提前消灭大量 401 和 404。有次我们接了三个新模型配置中心里写了四个环境变量结果两个环境变量名写反了都是靠这个脚本最先发现的。不要等业务方上线来报错主动把问题挡在前面。5.2 统一错误码结构别让上层猜不同 provider 返回的错误结构不一样如果你把这些原始错误直接透传给前端前端就得写一堆分支判断。我建议在网关层做错误归一化统一转成一种结构{ provider: provider_a, http_status: 401, error_type: invalid_api_key, message: Incorrect API key provided, retryable: false }编排层只需要看http_status和retryable两个字段就能决定是否重试、是否切换模型、是否直接抛给上层。这看起来是额外开发量但在排错时收益非常大每次报错都会少很多沟通成本。5.3 配置中心比环境变量更可靠环境变量在单机部署时够用但多模型、多环境、多服务的场景下特别容易出错。我建议把 key、base_url、model 映射、限流阈值都放到配置中心按服务和应用维度隔离。改配置不重启出问题回滚也快。关键的一点是配置变更要有审计记录。某天 401 突然变多先看配置是不是被改了。很多时候不是你代码有问题而是配置被某个人动了一下只是没人在群里说而已。5.4 最后分享一个我的排查习惯看到 401我第一反应永远是去敲 curl而不是查代码。看到 429我第一反应永远是去翻响应头而不是调重试参数。很多时候你会在 GitHub issue 里看到有人说“报 401是不是 SDK 的 bug”但最后基本都被证明是 key 的换行符问题或环境变量覆盖问题。我这几年最大的体会是多模型接入的报错大多数不是模型厂商的锅而是我们自己的配置管理和流量治理没做到位。把基础认证、限流、重试、日志这四件事做扎实从 401 到 429 的排错效率会明显提升。这次整理的清单基本上覆盖了我日常排查的大多数场景希望对你也有用。