← 返回资讯

Groq API 错误码逐条排查:从 401 到 499 怎么定位怎么修

2026-08-07

同样是「调不通」,401 和 429 的处理方向几乎相反。401 说明配置错了,重试一万次也不会突然变对,得回去改代码或改环境变量;429 说明配置全对,只是打得太快,这时唯一该做的是等一会儿再来,改代码反而没用。我见过太多人把这两类混在一个 except Exception: retry() 里,结果 key 拼错了还在那儿指数退避,眼睁睁看着脚本空转十分钟才报错退出。

所以排查 Groq 的报错,第一步不是查那条 message 什么意思,而是先按状态码把问题分到「改代码」还是「等一等」两个桶里。这篇按 Groq 官方错误码清单逐条过一遍,每条说清三件事:什么原因、怎么定位、怎么修。

先看清错误响应长什么样

Groq 的错误响应是 JSON,里面有一个 error 对象,包含两个关键字段:

  • message:人读的描述文本
  • type:分类,例如 invalid_request_error

写代码时请记住一条纪律:判断逻辑只依赖 HTTP 状态码和 type,不要去正则匹配 message 里的文本。message 是给人看的,措辞随时可能被平台改掉,今天匹配得上明天就匹配不上,而且这种失效是静默的——你的重试分支会悄悄走到 else 里去,线上跑一周才发现某类错误从来没被正确处理过。状态码和 type 是契约的一部分,稳定得多。

message 当然要读,但读它的场景是「人在排查」,不是「代码在判断」。日志里把整段 error 对象原样打出来就行。

逐条过错误码

400 Bad Request:语法非法

请求在语法层面就过不去。最常见的是 JSON 拼错了——少个逗号、多个尾随逗号、body 编码不对,或者根本没设 Content-Type。

定位办法很土但有效:把你实际发出去的 body 打印出来(不是你以为发出去的那个),扔进任意一个 JSON 校验器过一遍。用 SDK 时这类错误少,用 requests 手拼 body 时特别多。修法就是把请求体拼对,重试没有任何意义。

401 Unauthorized:缺失或无效凭据

字面意思是凭据没给或者给错了。但实际排查时它其实是两个完全不同的问题,混在同一个码里:

情况一:key 压根没读到。 Groq 的 key 走环境变量 GROQ_API_KEY 配置。你在终端里 export 了,但 IDE 里跑的进程没继承到;或者 .env 文件放错了目录、忘了调加载函数;或者容器起来的时候环境变量没注入。这种情况下客户端拿到的是空字符串或者 None,发出去的自然是无效凭据。

情况二:key 本身失效了。 被轮换、被删除、或者复制的时候少了尾字符。

分辨这两者只要一行代码——在发请求之前,先把 key 的长度和前后几位打印出来(绝不要打印完整 key):

import os
k = os.environ.get("GROQ_API_KEY")
print("key loaded:", bool(k), "len:", len(k) if k else 0, "tail:", k[-4:] if k else "")

长度是 0 或者变量根本不存在,那是环境变量问题,去查加载链路;长度正常、尾部也对得上控制台里那条 key,那就是 key 本身的问题,去控制台重新签发一把。这一步能省掉至少一半的瞎猜时间。401 的通用处理思路可以再看下 401 鉴权失败排查。

403 Forbidden:权限不足

凭据是真的、也认出来了,但这把 key 或者这个组织没有权限做你要做的事。和 401 的区别在于:401 是「你是谁我不知道」,403 是「我知道你是谁,但你不能干这个」。

定位方向是去控制台确认这把 key 所属组织的权限范围,以及你调的这个能力是不是需要额外开通。改代码里的重试次数对 403 一点用没有。

404 Not Found:资源不存在

这条在 Groq 上有一个高频陷阱:base_url 写错,表现出来非常像 404。

Groq 的 OpenAI 兼容端点是 https://api.groq.com/openai/v1。少写了后面的 /openai/v1,或者只写到 https://api.groq.com,请求打到的路径就不是 chat completions 那个位置,返回的东西自然对不上。

所以看到 404,先别急着怀疑模型 ID 打错了,按这个顺序查:

  1. base_url 是不是精确等于 https://api.groq.com/openai/v1(注意别多一个尾斜杠、别少 /v1)
  2. SDK 是不是会在 base_url 后面自动再拼一次 /v1,导致最终 URL 变成两个 v1
  3. 最后才去查模型 ID 拼写

第 2 点特别阴——某些 OpenAI 兼容客户端默认会追加版本段,你配了完整路径反而错了。判断方法是打开客户端的调试日志,看真实请求 URL。base_url 这块的通用坑可以参考 base_url 配置与常见错法。

413 Request Entity Too Large:请求体过大

你塞进去的东西超过了服务端能接收的请求体上限。做长文档摘要、把整个 RAG 检索结果一股脑拼进 messages、或者传大音频文件时容易撞上。

定位方法:在发送前打印一下序列化后 body 的字节数。修法只有一个方向——把请求变小:切分文档分批处理、裁剪检索结果条数、压缩或分段处理音频。

注意 413 是请求体大小的问题,跟上下文窗口是两回事,虽然它们经常同时被触发。

422 Unprocessable Entity:格式合法但语义错误

这条是最容易和 400 混的,讲清楚它们的区别其实很简单:

  • 400 是「我看不懂你在说什么」——JSON 都解析不出来,语法层面就废了。
  • 422 是「我看懂了你在说什么,但你说的这事不成立」——JSON 完全合法,字段名也对,但值不合理。

举几个落到 422 这一侧的典型:某个参数超出了允许区间、两个参数互相冲突、结构上要求成对出现的字段只给了一半、枚举值给了个不存在的选项。请求在语法上无可挑剔,只是语义上说不通。

定位就靠读 error.message——422 的 message 通常会点名是哪个字段有问题,这是人工排查时 message 最有价值的场景。修法是改参数,同样不该重试。

顺便提醒一句:Groq 的 OpenAI 兼容层有一份明确的不支持参数清单,包括 logprobs、logit_bias、top_logprobs、messages[].name,N 若传必须等于 1,音频格式不支持 vtt 和 srt。另外 temperature 传 0 会被转换成 1e-8。从 OpenAI 那边直接搬代码过来的时候,如果原代码里带了这些参数,是排查参数类报错时第一个该看的地方。

424 Failed Dependency:Remote MCP 鉴权问题

这个码别家平台基本见不到,值得单独说。Groq 文档里明确它出现在 Remote MCP 鉴权出问题的场景。

它的语义是「你的请求没毛病,但我依赖的那个外部东西出问题了」。所以看到 424,排查方向不在你这边的请求体,而在你配置的那个远端 MCP:它的凭据是不是过期了、是不是配错了、是不是本身就拒绝了这次连接。

实践里 424 最大的价值是它帮你省时间——它明确告诉你「别在自己的 payload 上浪费精力了」。如果你的应用没接 Remote MCP 却收到 424,那说明配置里挂了一个你不知道的依赖,去翻配置。

429 Too Many Requests:不是你算错了额度

429 表示单位时间内打得太多了。但 Groq 这里有两个官方说明,直接解释了「为什么我明明算过额度还是撞了」:

第一,速率限制适用于组织级别,不适用于个别用户。 你自己那个脚本算得再准也没用——同一个组织下的其他人、其他服务、CI 里那个定时任务,全都在啃同一份配额。本地测得好好的,一上生产就 429,十有八九是这个原因。

第二,限速是多维的,先撞到哪个阈值哪个就生效。 Groq 免费层的限速表是逐模型列出 RPM、RPD、TPM、TPD 四个维度的。你可能请求数离 RPM 上限还很远,但因为每次请求都塞了很长的上下文,TPM 先满了;也可能 RPM、TPM 都宽裕,但跑了一整天,RPD 那个日上限先到顶——RPD 撞顶最难受,退避多久都没用,得等日窗口滚过去。

所以撞到 429,先确认是哪个维度爆的,再决定动作:RPM 爆了就退避加限速;TPM 爆了就缩短 prompt,光退避没用,因为每次重试的体积一点没变;RPD 爆了就别硬刚了,排到下一个窗口或者换配额。退避与限速的具体写法见 429 限流与指数退避。

498 与 499:两个 Groq 自定义码

这两个是 Groq 自己定义的,标准 HTTP 状态码里没有:

  • 498:Flex Tier 容量超限
  • 499:调用方取消请求

它们的麻烦之处不在于难懂,而在于通用 HTTP 库和 SDK 往往不认识它们。很多客户端的错误映射表是按标准状态码写的,碰到 498/499 会落进「未知错误」的兜底分支,或者干脆抛一个语焉不详的异常。你的重试逻辑里如果只列了 429 和 5xx,这两个码就会走到「不重试、直接失败」那一支——或者更糟,被兜底逻辑当成可重试的一直重试。

所以要在自己的错误处理里显式列出 498 和 499。

498 意味着 Flex Tier 那侧的容量满了,跟你请求写得对不对无关,退避后重试是合理动作。499 是调用方取消请求,先去查你自己这边:客户端超时设太短了?上游把连接掐了?用户点了取消?如果是超时设置太紧导致的自我取消,重试只是在重复制造同一个 499,该调的是超时值——这块可以看 超时设置。

5xx:500 / 502 / 503,而且不计费

  • 500 Internal Server Error:服务端内部错误
  • 502 Bad Gateway:网关错误
  • 503 Service Unavailable:维护或过载

这三个都是服务端侧的问题,你的请求本身通常没毛病。有一条官方事实直接影响策略:Groq 的 5xx 响应不计费。

这意味着 5xx 的重试可以更积极——失败的那次不产生费用,重试成本只有时间。当然「更积极」不等于无脑打,退避该有还得有,不然你只是在给一个已经过载的服务继续加压。

206 Partial Content:不是错误

清单里还有一个 206,属于成功类,表示带 range 头时的分段返回。写错误处理时别把它归到失败里去——有些手写状态码判断的代码会写成 if status != 200: raise,那就会把 206 误伤。

一段可以直接抄的错误分流

把上面这些整理成代码,核心就是一张表:哪些码重试有用,哪些码重试纯属浪费。

import random
import time

# 重试有意义:服务端问题、容量问题、限速
RETRIABLE = {429, 498, 500, 502, 503}
# 重试是浪费:不改代码/配置,重试一万次也一样
FATAL = {400, 401, 403, 404, 413, 422, 424}


def call_with_retry(do_request, max_attempts=5, base=1.0, cap=30.0):
    for attempt in range(max_attempts):
        resp = do_request()

        if resp.status_code < 300:          # 200、206 都在这里放行
            return resp

        code = resp.status_code
        err = (resp.json() or {}).get("error", {})
        # 只记录 message,不拿它做判断
        print(f"[groq] status={code} type={err.get('type')} message={err.get('message')}")

        if code in FATAL:
            raise RuntimeError(f"不可重试,请修配置或请求体: {code}")

        if code == 499:
            # 调用方取消:先确认是不是自己的超时太紧,
            # 确认非本地原因后再纳入重试,否则只是重复制造 499
            raise RuntimeError("请求被调用方取消,检查客户端超时与连接")

        if code not in RETRIABLE:
            raise RuntimeError(f"未预期的状态码: {code}")

        sleep = min(cap, base * (2 ** attempt))
        sleep = sleep * (0.5 + random.random() / 2)   # 抖动,避免同时重试
        time.sleep(sleep)

    raise RuntimeError("重试次数耗尽")

几点说明。第一,FATAL 那一组遇到就立刻抛,不要客气——早失败早发现,比在日志里静默退避十分钟强。第二,抖动别省,多个 worker 一起撞 429 的时候,没有抖动它们会同步退避、同步重来,然后一起再撞一次。第三,429 撞的如果是日维度,指数退避那点秒数根本救不了,成熟一点的做法是给日配额单独记一个本地计数,撞顶后直接停批而不是继续重试。

排查顺序清单

按这个顺序走,从最省事的开始,大部分问题在前三步就能收掉:

  1. 先看状态码,分桶。 落在 400/401/403/404/413/422/424 里的,直接判定「要改东西」,别浪费时间在重试上;落在 429/498/5xx 里的,判定「等一等」。
  2. 把整段 error 对象打出来看。 type 告诉你分类,message 告诉你细节,尤其 422 的 message 通常直接点名出问题的字段。
  3. 验 key 加载。 打印 key 的长度和末四位,区分「环境变量没读到」和「key 本身失效」,一行代码的事。
  4. 核对 base_url。 确认精确等于 https://api.groq.com/openai/v1,并打开客户端调试日志看真实请求 URL,防止 SDK 自动追加版本段。
  5. 对照不支持参数清单。 从 OpenAI 迁过来的代码,重点看有没有带 logprobs、logit_bias、top_logprobs、messages[].name,以及 N 是否等于 1。
  6. 429 定位到具体维度。 分清是 RPM、TPM 还是 RPD 撞顶,并记住限速是组织级的——别只盯着自己那个脚本算账。
  7. 最后才怀疑平台。 确认是 5xx 且请求本身没问题,那就交给重试逻辑,反正 5xx 不计费。

真正难查的报错其实很少,大部分时间浪费在「用错了方向」——对着一个 401 反复调退避参数,或者对着一个 TPM 超限反复缩短重试间隔。先分对桶,剩下的都是体力活。

相关阅读

加微信,把这一页发到你手机上

扫码加站长企业微信,会自动把这一页的链接发给你。模型接入、token 计费上有问题,也可以直接问。

去留微信

要在多家推理平台之间切换?

力达云是国内可直连的 OpenAI 兼容端点,一期提供 DeepSeek,注册送 ¥5 额度。

去试用

这个页面有问题?

提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。