Gemini API 错误码三大族怎么分、怎么对号入座
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
Gemini API 的错误码不该当成一张平铺的对照表来背。按官方错误参考页的组织方式,它实际上分成三族:一族是标准请求级错误码,官方给每个都标了对应的 HTTP 状态码,问题落在请求这一层;一族是生成被阻止码,请求是合法的,但生成因政策、安全或内容限制被拦下;还有一族是生成结构错误码,模型确实生成了,可是输出本身的结构不对,官方在这一组里排在最前面的就是函数调用解析不了。三族的入口都是响应里那个 error 对象的 code 字段,但处置逻辑完全不同——第一族里有的能退避重试有的重试一万次也没用,第二族官方只给了「改输入再试」一条路,第三族的问题落在模型输出的结构上,官方把它单列成了一组。还要注意,官方明确写了流式请求的错误通过 SSE 事件传递而不是 HTTP 状态码,只判状态码的客户端会漏掉流式过程中的错误。
需要先说明一个限定条件:下面这套 snake_case 的 code 枚举,出处是官方的 Interactions API 错误参考页。不同端点的错误表述形式未必完全一致,对号入座之前先确认你调的是哪个接口。
先看清 error 对象长什么样
官方给的错误响应统一是一个 error 对象,里面两个字段:
code:机器可读,snake_case 形式,就是下面三族里的那些值message:人类可读的描述
这个结构决定了排查的正确姿势是读 code 而不是正则去抠 message。message 是给人看的,措辞会随文档和服务端版本变化;code 才是你应该写进 if 分支里的东西。
还有一条容易忽略的兜底规则:官方明确说明,未列在参考表里的错误码,API 会以标准 HTTP 状态文本的 snake_case 形式返回。也就是说你的错误分发逻辑不能写成「命中已知枚举才处理,否则当成未知崩掉」,因为那些没被列出来的码依然是有规律、可解析的。留一个默认分支按 HTTP 状态大类兜底,比硬编码一张白名单稳。
第一族:标准请求级错误码
这一族的特征是每个 code 都绑定一个 HTTP 状态码,问题出在请求本身。官方参考页列出的有这些:
400 段。 invalid_request 是请求格式有误或参数无效,官方推荐的处置是对照 API 参考检查输入;parameter_unknown 是请求里含未知参数,处置是移除无法识别的参数后重试。这两个分开列是有用意的:前者说明你传的东西格式或取值不对,后者说明你传了服务端根本不认识的字段。跨厂商迁移时要留意后者——把别家 SDK 的参数原样搬过来,字段名服务端根本不认识,就会归到这一格,而官方给的处置也很直接:把无法识别的参数移除掉再重试。
401 与 403。 authentication 对应 401,含义是 API 密钥缺失或无效,处置是验证密钥;permission_denied 对应 403,含义是密钥没有该资源的权限,处置是检查密钥权限与项目访问权限。这两个的区别是「你是谁没认出来」和「认出来了但你不能碰这个」。403 尤其要往项目层面查,因为 Gemini 的密钥没有独立的结算设置,它继承所属项目的层级与结算状态。
404 段。 not_found 是资源不存在,处置是验证资源路径与参数;model_not_found 是指定的模型不存在,官方给的处置除了核对模型名,还包括回退到其他模型。后半句是个实用提示:模型代际更新很快,把模型名硬编码在代码里、又没有回退路径的服务,迟早会在某次型号变更后整条链路挂掉。
429 段——这一族里最需要分清的两个码。 rate_limit_exceeded 是超出每分钟或每秒的请求或 token 限制,官方处置是等待加指数退避重试;quota_exceeded 是超出每日配额,官方处置是等待配额重置或申请增加配额。名字都是 429,语义和救法完全不是一回事:前者官方给的处置就是等待加指数退避重试,后者退避多少次都没用,因为额度是按天算的,官方给的路只有等配额重置或者申请提额两条。这一格值得单独展开,可以看 Gemini 的两种 429 到底差在哪;跨厂商通用的 429 处理骨架则在 API 429 错误的通用处理方式。
顺带一提,官方在限流文档里还写到另一层「基于支出的速率限制」,触发时返回的是 429 RESOURCE_EXHAUSTED。它和错误参考页里的 snake_case 命名不是同一种写法,两套命名不一致这件事本身要记住,别把两套名字混进一个 switch 里;至于为什么不一致,官方文档里没有找到相关说明。限流本身按 RPM / TPM / RPD 几个维度算,超出任何一个维度即触发,不是综合评估,这部分可以参考 RPM 和 TPM 到底限的是什么。
499。 cancelled,含义是客户端在请求完成前取消了请求,官方明说无需处置,通常就是客户端断连。这个码在监控面板上很容易被误当成服务端故障计入错误率,但按官方的说法它代表的是客户端在请求完成前主动取消——客户端超时设置、页面跳转、上游任务取消都会走到这条路径上。把它单独摘出来统计,比混在 5xx 里更能反映真实情况。
500 与 503。 api_error 是服务端意外错误,处置是重试,持续出现则联系支持;service_unavailable 是服务暂时过载或关闭,处置是等待加指数退避重试。两者都属于「重试有意义」的一类。官方在限流文档里还有一句免责原话值得记住:指定的速率限制无法保证,实际容量可能有所变化。既然容量本身不保证,客户端就必须自带退避与重试,而不是假设配额内的请求一定成功。
第二族:生成被阻止码
这一族的性质和上面完全不同。请求是合法的,鉴权也没问题,模型也接收到了,但生成因为政策、安全或内容限制被拦了下来。官方列出的码包括:safety(有害内容)、recitation(版权或引述限制)、language(不支持的语言)、prohibited_content、spii(敏感个人身份信息)、blocklist(封锁名单禁用词)、content_blocked(不明政策原因),以及一组图像相关的:image_safety、image_prohibited_content、image_recitation、image_other。
官方对整族给出的统一处置只有一句:修改输入后重试。这句话看着敷衍,实际上信息量不小——它意味着这一族没有任何「等一等」「换个密钥」「加大重试次数」的解法,不改输入就永远是同一个结果。把它和第一族混在同一个重试队列里,只会白白烧掉配额。
其中两个码在中文材料里几乎没人讲清楚,官方给出的释义也很短:recitation 是版权或引述限制,spii 是敏感个人身份信息。至于什么样的输入会具体触发它们,官方错误参考页里没有给出判定条件,所以别去猜阈值,能做的只有按官方那句统一处置办——改输入。做批量数据处理、文档改写、简历解析这类任务时,值得把这两个码单独挂一个计数器,它们和 safety、prohibited_content 的成因不是一回事,混在一起统计就看不出到底该改哪一类输入。
这一族里的码名本身也提示了排查的方向:language 是不支持的语言,blocklist 是命中封锁名单里的禁用词,content_blocked 官方标注的是不明政策原因。最后这一个尤其要留意——官方给的释义就是「不明」,说明并不是每次拦截都会告诉你具体是哪条政策,遇到它没有比改输入更细的排查路径。剩下四个是图像相关的:image_safety、image_prohibited_content、image_recitation、image_other,它们与前面几个文本侧的码是并列列在同一族里的。
工程上的处理方式是把整族当成「需要人工介入的输入问题」,落一条带原始输入摘要的日志,而不是简单丢进死信队列了事。这里还有一个组织上的分工问题:第一族的处置基本落在写代码的人身上,第二族的处置落在写提示词、准备数据的人身上,如果错误告警只发给后端值班,这一族的信息就传不到该改东西的人手里。
第三族:生成结构错误码
第三族是模型确实生成了,但输出本身的结构有问题。官方列出的有:malformed_function_call(函数调用无法解析)、malformed_tool_call、unexpected_tool_call(调用了请求中未声明的工具)、no_image、too_many_tool_calls(工具调用数超上限)、missing_thought_signature(响应缺少必需的 thought 签名)。
从码名与官方释义能看出,这一族集中在工具调用与函数调用这条链路上。逐个对一下官方给的释义:malformed_function_call 是函数调用无法解析,malformed_tool_call 官方只列了码名没有额外释义;unexpected_tool_call 是调用了请求中未声明的工具,也就是声明与实际调用对不上;no_image 同样只有码名;too_many_tool_calls 是工具调用数超上限;missing_thought_signature 是响应缺少必需的 thought 签名。官方在 OpenAI 兼容层的说明里提到 Gemini 3 支持在 chat completions API 中使用 OpenAI 兼容的思考签名,这个码显然与该机制相关,但具体在什么调用序列下会缺失签名,官方文档里没有找到相关说明。
注意官方给出的都是「现象」而不是「修改点」:malformed_function_call 说的是这次调用解析不了,并没有断定问题一定出在你的 schema 描述上;too_many_tool_calls 说的是超了上限,上限本身按什么口径统计、是单轮还是整个会话,参考页里也没有写。所以这一族的正确用法是拿它定位链路,而不是拿它当修复指令——先把出问题那一轮的完整请求体和响应体留下来,再决定改声明还是改编排。
与工具声明有关的还有一份枚举:官方列出的受支持工具类型,来自一条错误 message 里的取值列表,包含 function、code_execution、mcp_server、filesystem、google_maps、google_search、bash、computer_use、file_search、url_context。写工具声明时类型名对照这份清单核一遍,能省掉不少无谓的排查。这份枚举以官方参考文档的当前版本为准,实际取值可能随版本调整。
三族之间怎么对号入座
先说清一个容易被想当然的地方:官方只对第一族逐行标注了 HTTP 状态码映射,第二族和第三族在错误参考页里并没有绑定状态码。所以判定流程不能建立在「二三族一定是 200」这种假设上,唯一在三族之间都可靠的判据是 error 对象里的 code 字段本身。按这个顺序走:
- 先看有没有 HTTP 状态码可用。 如果拿到的是非 2xx,第一族的映射表就能直接用:4xx 归到请求侧(鉴权、参数、路径、配额),5xx 归到服务端侧,后者官方给的处置里明确写了重试或退避重试。
- 不论状态码如何,都读一遍
code。 第二族和第三族的区分完全靠 code 值:命中被阻止那一组,问题在输入内容,处置是改输入;命中结构那一组,问题在工具与函数调用这条链路上,改重试次数没有用。把三族的 code 值维护成三个集合去匹配,比写状态码判断可靠。 - 流式请求要单独一条路径。 官方明确写了标准请求与流式请求的错误传递方式不同:非流式是设置 HTTP 状态码,同时在 JSON body 里返回
error对象;流式(SSE)则是通过 SSE 流发送event_type: "error"事件,error字段的结构相同。直接后果就是只判 HTTP 状态码的客户端会漏掉流式过程中的错误——连接建立时状态码是正常的,错误是在流中途才发出来的。这一条展开在 Gemini 流式错误为什么不走状态码。
重试策略按族来定,别按码来定
把三族的重试语义列一遍就很清楚了:
- 重试有意义:
rate_limit_exceeded、api_error、service_unavailable——官方给的处置里明确带「等待 + 指数退避重试」或「重试」。 - 重试没意义、要等或要提额:
quota_exceeded——每日配额耗尽,退避解决不了。顺带说一句,官方文档里写明 RPD 配额是在太平洋时间午夜重置的,既不是本地时间也不是 UTC,估算「什么时候能恢复」时别按本地零点算。 - 重试没意义、要改代码或配置:
invalid_request、parameter_unknown、authentication、permission_denied、not_found、model_not_found。这一组进重试队列纯属浪费。 - 重试没意义、要改输入:整个第二族。
- 重试没意义、要查工具调用链:整个第三族。官方给的是现象描述而非处置建议,得靠请求体和响应体自己定位。
- 不算错误:
cancelled,官方说无需处置。
一个实践上的建议:错误处理代码按族分文件、按族写策略,而不是写成一个几十分支的 switch。三族的处置维度根本不同,硬塞进一个分发函数,迟早会出现「被阻止码进了退避重试队列」这种事故。
最容易栽的坑
第一个是把 code 当成 message 的同义词。日志里只记 message 不记 code,事后复盘时你会发现自己什么都定位不了。
第二个是只判 HTTP 状态码。官方明确写了流式请求的错误是通过 SSE 事件发出来的,只看状态码就会漏掉这一路;再加上第二族和第三族在参考页里本来就没有绑定状态码,把状态码当成唯一入口,等于放弃了大半个错误体系。
第三个是把两个 429 当成一个。分钟级限流和每日配额耗尽的救法方向完全相反,混在一起处理的结果通常是配额早就见底了,客户端还在傻等着退避重试。
最后提醒一句,本文引用的错误码枚举与处置建议均来自官方错误参考页,具体取值和推荐做法以官方文档的当前版本为准。做完错误分类之后,下一步值得看的是限流本身怎么算,以及项目层面的配额是怎么归属的——那才是决定你会不会频繁撞上 429 的根本因素。