Gemini API 频繁 429:免费额度变化与限流排查方案
调用 gemini-2.5-flash 时,跑几次就返回 429 RESOURCE_EXHAUSTED,很多人怀疑免费额度被砍。本文讲清 429 错误体怎么读、单位时间限流与每日配额的区别、退避重试和降低调用量的具体做法,以及付费额度、多项目拆分、第三方中转各自的取舍与风险。
问题现象:从“够用”到“调几次就报错”
用 gemini-2.5-flash 写个小工具或做一次批量处理,之前每天跑几百次也没事,最近连着调十几次就开始返回 429 RESOURCE_EXHAUSTED,等一会儿再用又恢复。这不是代码逻辑坏了,而是两层原因叠加:一是免费层(Free Tier)的配额规则确实在收紧,二是很多人把“每日总量用尽”和“单位时间限流”当成同一回事,于是重试反而把额度烧得更快。
下面按“先把错误读懂 → 判断属于哪种限额 → 再决定怎么省、怎么绕、怎么花钱”的顺序说。
先读懂 429 的响应体
429 不是一句“超限”就完了,响应 JSON 的 error.details 里通常带两类信息:
QuotaFailure:里面每个violations都有quotaId,直接写明是哪种额度触顶,例如带PerMinute字样的多半是 RPM/TPM 这类速率限制,带PerDay的是当日总量。RetryInfo:里面的retryDelay是服务端建议的等待时间。
先用一次最小的 curl 把完整错误打印出来,比在业务代码里猜快得多:
curl -sS -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contents":[{"parts":[{"text":"hi"}]}]}' | jq '.error'
看清楚被限制的是哪一种,后面的处理方式完全不同:速率限制靠“降并发 + 等”;每日配额用尽只能“等重置、降低调用量或升级付费”。
免费额度是不是真的缩水了
社区里流传的说法是:Flash 一档的每日免费请求数被大幅下调,Pro 及更高档基本不再向免费层开放,Lite 档也向同一水平看齐。这类数字调整很频繁,不同模型、不同时间点、不同账号所在地区都可能不一致,二手截图和几天前的教程都不能当作容量规划依据。
需要以官方 Rate limits / 定价页面和你自己控制台里实际显示的配额为准,并且把结论定成一句话:免费层只适合调试和轻量试用,不适合当生产依赖。
常见触发原因清单
- 循环里没有节流,短时间几十个请求并发打出去,撞上每分钟请求数上限。
- 遇到 429 立刻重试,甚至多层重试叠加,形成重试风暴。
- 以为“多建几个 API Key 就能扩容”,但配额是挂在项目(Project)上的,同一项目下新建 Key 共享同一份额度。
- 单次请求上下文很长,token 数很高,先撞上每分钟 token 限制。
- 定时任务、后台脚本、重复运行的 notebook 单元格在悄悄重复发请求。
- 请求超时后自动重试:网络抖动导致的失败同样会消耗配额,本地机器休眠、断网、代理不稳都会放大这个问题。
解决方案
1. 指数退避,尊重 retryDelay
不要写固定 sleep,也不要无上限重试。用带抖动的指数退避,并把服务端给的等待时间作为下界:
import time, random
from google import genai
from google.genai import errors
client = genai.Client()
def generate(prompt, model="gemini-2.5-flash", max_retries=5):
for attempt in range(max_retries):
try:
return client.models.generate_content(model=model, contents=prompt)
except errors.APIError as e:
if e.code != 429:
raise
delay = min(2 ** attempt, 60) + random.uniform(0, 1)
time.sleep(delay)
raise RuntimeError("quota exhausted, abort instead of retrying forever")
超过重试次数就明确失败,让上游知道“这批任务今天做不完”,比无限重试温和得多。
2. 把请求量压下来
- 对完全相同的问题做结果缓存,尤其是调试时反复跑同一条 prompt。
- 用批处理接口代替逐个调用,能显著减少请求次数。
- 不重要的环节换成更轻的模型,只在关键步骤用大模型。
- 限制
max_output_tokens,避免长输出把 token 配额吃光。 - 合并任务:一次请求里处理多条待判定文本,而不是一条一个请求。
3. 拆分项目,而不是拆分 Key
如果确实需要多份独立配额(例如一个项目跑线上、一个项目做实验),正确做法是建立相互独立的项目并分别启用 API,而不是在同一项目里堆 Key。注意:这是为了隔离用途,不是为了绕过配额规则批量薅免费额度,后者违反服务条款,账号和额度都有被回收的风险。
4. 付费额度与替代方案
要稳定跑量,最终还是要开结算账号。绑定支付方式受发卡行和支付渠道策略影响,同一种卡在不同人手里成功率可能不一样,只能自己实测,具体支持的卡种和地区以官方结算文档为准。不建议使用来源不明的代充或共享账号,账期、退款和账号安全都没人兜底。
5. 稳定运行环境
有一部分 429 其实是“超时后自动重试”贡献的:本地机器合盖休眠、网络切换、代理抖动,都会让同一批请求白发一遍。如果跑的是长时间在线的批处理或 Agent 循环,可以考虑把脚本放到网络更稳定的远程环境里长期挂着,例如整台 Mac mini 放在机房、按月远程使用的独享 Mac,避免本地掉线导致重复请求。这只是环境层面的可选优化,配额本身仍然要在代码里控。
两个看起来省事的坑
- 批量注册新账号领试用额度:违反服务条款,触发风控会连带封号,而且试用额度通常需要绑定结算方式,并不是“零成本”。不建议。
- 第三方聚合中转站:把多个模型包一层统一接口,短期省事,但 API Key 和数据都会过第三方的手,稳定性与合规性无法保证。真的要用,至少不要传敏感数据,也不要把它当唯一通道。
排查检查清单
| 检查项 | 目的 |
|---|---|
| 打印完整 429 错误体,看 quotaId | 区分速率限制还是每日配额 |
| 统计近 24 小时实际请求数 | 判断是否只是自己跑超了 |
| 确认所有 Key 是否同属一个项目 | 排除“多 Key 无用”的误会 |
| 检查代码里有无嵌套重试 | 消除重试风暴 |
| 检查是否有重复运行的定时任务 | 找出隐形流量 |
| 记录失败请求是否由超时引起 | 判断要不要换运行环境 |
小结
免费额度收紧是趋势,但“调几次就 429”往往不只是额度变小,而是速率限制、重试策略和项目结构一起出了问题。先把错误体读明白,再用退避重试和请求合并把消耗压到最低,最后根据业务重要性决定要不要付费。配额数字属于时效性较强的信息,请以官方最新说明和你在控制台看到的实际值为准。