# Gemini API 429、503 怎么解决：查配额、读错误与正确重试

> 遇到 Gemini API 429 或 503，先暂停批量发送，再检查错误正文和项目额度。本文给出从 QuotaFailure、RetryInfo 到有限重试的完整例子，说明何时降速、何时等额度恢复，以及怎样避免重试放大故障。

- URL: https://blog.laozhang.ai/zh/posts/google-server-capacity-limit
- Published: 2026-02-22
- Updated: 2026-09-08
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: 故障排查
- Tags: Gemini API, 429, 503, 速率限制, 指数退避, 断路器

---
**Gemini API 提示“服务器容量已达上限”时，先暂停批量请求，保留一条请求的 HTTP 状态码与完整错误正文。** 如果确认每日额度已经耗尽，就停止秒级重试；如果是短时限流，降低发送速率并退避；如果返回 `503`，先做有限重试，再结合服务状态决定是否切换已经验证过的备用方案。

最快的排查顺序是：**看错误详情，查对应项目的有效额度，单并发验证，再逐步恢复流量。** 本文针对 Google AI Studio 项目使用的 Gemini Developer API，官方规则核验于 2026 年 9 月 8 日。Vertex AI、Gemini 网页版以及网盘存储容量应查看各自的设置。

## 先完成这四步，避免越重试越严重

1. **暂停队列自动重发。** 保留一个人工控制的测试请求，暂时把并发降到 1。先弄清楚失败原因，再恢复积压任务。
2. **记录实际调用条件。** 包括时间与时区、项目、模型 ID、接口地址、HTTP 状态、错误正文，以及响应提供的请求标识和等待提示。
3. **打开 AI Studio 的有效速率限制。** 选择真正处理请求的项目和型号，对照错误里的额度名称。入口可从[官方速率限制页](https://ai.google.dev/gemini-api/docs/rate-limits?hl=zh-cn)进入，避免拿旧文章中的固定 RPM 表代替自己的当前额度。
4. **根据下表处理。** 每次只改变一个因素，记录是否恢复。不要同时换网络、模型和密钥，导致结果无法解释。

| 你看到的证据 | 现在采取的动作 | 恢复条件 |
| --- | --- | --- |
| 429，明确为每日请求或每日 token 额度 | 停止短周期重试，核对额度重置或调整安排 | 相应额度恢复后再发 |
| 429，明确为每分钟请求或输入 token 限制 | 降低队列发送速率，必要时缩短输入，有限退避 | 降载后最小请求能完成 |
| 429，命中滚动支出窗口 | 减少短时间内的高成本请求，等待窗口腾出空间 | 当前用量回到有效限制内 |
| 429，原因不明确 | 保留日志，对照项目额度和服务情况 | 原因查明后选择恢复方法 |
| 503，服务暂时不可用 | 有限退避，查看官方状态，保留备用方案 | 探测完成后逐步放量 |
| 400、401、403、404 | 检查参数、密钥、权限或模型名称 | 修正具体配置后再试 |

[Google 排障指南](https://ai.google.dev/gemini-api/docs/troubleshooting?hl=zh-cn)建议对暂时性错误使用指数退避、随机抖动和次数上限。一条 503 响应说明这次请求无法完成，不能单独证明整个模型对所有用户都不可用。

## 从错误正文中找到额度和等待时间

直接使用 `generateContent` 时，应保留 `error.code`、`error.status` 和 `error.details`。下面是**合成教学示例**，展示字段位置；额度名称、数量和等待时间都要以你的实际响应为准。

```json
{
  "error": {
    "code": 429,
    "status": "RESOURCE_EXHAUSTED",
    "message": "Quota exceeded",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.QuotaFailure",
        "violations": [
          {
            "quotaId": "ExampleRequestsPerMinute",
            "quotaValue": "20"
          }
        ]
      },
      {
        "@type": "type.googleapis.com/google.rpc.RetryInfo",
        "retryDelay": "12s"
      }
    ]
  }
}
```

这里的 `quotaId` 帮你定位触发的限制，`quotaValue` 是响应列出的额度，`retryDelay` 表示再次尝试前至少等待多久。字段定义可见 Google 的 [QuotaFailure 与 RetryInfo](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)。某次响应可能没有这些详情，不能因此自行补出一个“每日额度已满”的结论。

如果详情同时指向每日限制和短时间等待提示，先处理每日限制。等待十几秒不会增加已经耗尽的每日额度。也不要仅凭 `message` 里出现单词 `quota` 就把它归类为每日额度。

**Interactions 使用另一套错误对象。** 它的 `error.code` 是 `rate_limit_exceeded`、`quota_exceeded` 等字符串；[官方错误页](https://ai.google.dev/gemini-api/docs/api-errors?hl=zh-cn)分别将它们解释为短时限流和每日额度超限。不要把该字符串字段直接套到上面的 `generateContent` JSON。

![从 Gemini API 错误详情识别额度与重试等待提示](https://blog.laozhang.ai/posts/zh/google-server-capacity-limit/img/quota-error-details.webp)

## 请求很少，为什么仍会遇到 429？

Gemini API 会分别检查多个限制，最先触发的那一项就能拦住请求。最常见的误判是只数调用次数，忽略每次输入大小。

假设某项目的有效限制是 **60 RPM、120,000 输入 TPM**，每次请求平均输入 **8,000 token**。仅按 token 维度计算，理论上每分钟最多容纳 `120,000 ÷ 8,000 = 15` 次请求，已经低于 60 RPM。按 60 次发送仍会超限。这里是便于理解的假设额度，不能复制成某个模型的官方配额。

实际队列还要给输入波动和其他程序留余量。固定间隔发送、限制积压任务释放速度，比只设置“最多 10 个并发”更直接地控制每分钟请求数。并发限制控制同时在途的数量，无法独自约束一批很快完成的小请求。

另外还要检查：

- **同项目的其他应用。** 官方限制按项目应用，不按 API 密钥应用。另建同项目密钥，不能增加总额度。
- **长对话与附件。** 后续请求携带的历史、文件和工具结果都会影响输入用量，最后一句话很短也可能是一个大请求。
- **滚动支出限制。** 当前部分账号存在按 10 分钟窗口计算的支出限制，是否适用取决于结算记录和账号状态。
- **每日请求数。** RPD 在太平洋时间午夜重置，不是从报错时刻开始再等 24 小时，也不固定在北京时间零点。

以上规则来自[官方速率限制文档](https://ai.google.dev/gemini-api/docs/rate-limits?hl=zh-cn)。模型、层级和账号状态会改变有效额度；详细的层级与额度检查可继续看[Gemini API 速率限制指南](https://blog.laozhang.ai/zh/posts/gemini-api-rate-limits-guide)。

## 一个能直接连接请求的 Python 有限重试示例

下面使用 Python 3 标准库，直接调用非流式 `generateContent`，最多实际发送 **4 次**，累计主动等待不超过 **30 秒**。它读取 `RetryInfo` 和 `Retry-After`，对已识别的每日限制立即停止，未知 429 留给人工核对。没有再叠加 SDK 的自动重试。

把以下代码保存为 `gemini_retry.py`，在自己的环境配置 `GEMINI_API_KEY` 后运行。每次实际生成请求都可能产生用量。额度名称匹配只覆盖含 `PerDay`、`PerMinute` 的明确标识；其他命名会停在待查分支，需要按实际额度补充，而不是猜测。

```python
import json
import math
import os
import random
import re
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime


def error_policy(status, body):
    error = body.get("error", {}) if isinstance(body, dict) else {}
    details = error.get("details", []) if isinstance(error, dict) else []
    quota_ids, delays = [], []
    details = details if isinstance(details, list) else []
    for item in details:
        if not isinstance(item, dict):
            continue
        if item.get("@type") == "type.googleapis.com/google.rpc.QuotaFailure":
            violations = item.get("violations", [])
            if not isinstance(violations, list):
                violations = []
            for violation in violations:
                quota_ids.append(str(violation.get("quotaId", ""))
                                 if isinstance(violation, dict) else "")
        if item.get("@type") == "type.googleapis.com/google.rpc.RetryInfo":
            value = str(item.get("retryDelay", ""))
            if re.fullmatch(r"\d+(?:\.\d{1,9})?s", value):
                delays.append(float(value[:-1]))
    if status == 429:
        if any("perday" in q.lower() for q in quota_ids):
            return "quota_reset", 0
        if not quota_ids or not all("perminute" in q.lower() for q in quota_ids):
            return "inspect_quota", 0
    elif status not in (500, 502, 503, 504):
        return "fix_request", 0
    return "retry", max(delays, default=0)


def retry_after(value):
    if value is None:
        return 0
    try:
        seconds = float(value)
    except ValueError:
        try:
            seconds = (parsedate_to_datetime(value) - datetime.now(timezone.utc)).total_seconds()
        except (TypeError, ValueError, OverflowError):
            return 0
    return max(0, seconds) if math.isfinite(seconds) else 0


def generate(payload, model="gemini-3.1-pro-preview"):
    waited = 0.0
    for attempt in range(4):
        request = urllib.request.Request(
            f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent",
            data=json.dumps(payload).encode(),
            headers={"Content-Type": "application/json",
                     "x-goog-api-key": os.environ["GEMINI_API_KEY"]},
            method="POST"
        )
        try:
            with urllib.request.urlopen(request, timeout=20) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            raw = error.read().decode("utf-8", errors="replace")
            try:
                body = json.loads(raw)
            except json.JSONDecodeError:
                body = {}
            action, hint = error_policy(error.code, body)
            print(f"第{attempt + 1}次，HTTP {error.code}，处理：{action}")
            if action != "retry" or attempt == 3:
                raise RuntimeError(raw) from error
            delay = max(hint, retry_after(error.headers.get("Retry-After")),
                        random.uniform(0, min(8, 2 ** attempt)))
            if waited + delay > 30:
                raise RuntimeError("建议等待时间超出本轮预算，停止发送") from error
        time.sleep(delay)
        waited += delay
    raise RuntimeError("尝试次数已耗尽")


if __name__ == "__main__":
    result = generate({
        "contents": [{"parts": [{"text": "请只回复：连接检查完成"}]}],
        "generationConfig": {"thinkingConfig": {"thinkingLevel": "LOW"}}
    })
    candidates = result.get("candidates", [])
    if not candidates or candidates[0].get("finishReason") != "STOP":
        raise RuntimeError("生成未正常结束，请检查返回正文")
    parts = candidates[0].get("content", {}).get("parts", [])
    text = "".join(p.get("text", "") for p in parts if not p.get("thought"))
    if not text.strip():
        raise RuntimeError("没有最终文本，请检查返回正文")
    print(text)
    print("实际用量：", result.get("usageMetadata", {}))
```

30 秒限制的是代码主动退避的累计等待，单次连接另设 20 秒超时，二者不能相加当成严格的整轮截止时间。若业务要求“用户等待最多 30 秒”，应在调用层增加可取消的总超时，并停止后续尝试。

这份例子不会自动重发网络断连或已返回 200 但内容无法解析的请求，因为服务端可能已经完成生成。应先记录这种不确定结果，再决定如何处理。重试循环也不应包住订单写入、发邮件等业务操作；这些动作需要独立去重。

如果使用官方 SDK，先检查版本及重试配置。[当前排障文档](https://ai.google.dev/gemini-api/docs/troubleshooting?hl=zh-cn)说明 SDK 自带部分暂时性错误重试。外层 3 轮、内层每轮 5 次实际发送，就可能变成 15 次请求；保留一层主要重试，才能看清真实尝试次数。

## 503 持续出现时，怎样恢复业务？

先检查[官方服务状态](https://aistudio.google.com/status)，并用[模型停用表](https://ai.google.dev/gemini-api/docs/deprecations)确认模型仍受支持。然后保留同一项目、模型和接口，去掉复杂附件或可选工具，用一条简短输入观察。一次成功只说明小请求可完成，恢复正常负载后还要继续观察。

可以把恢复过程分成三个动作：暂停普通流量，冷却后只放行一个探测，成功后逐档增加发送速率。多线程程序需要共享探测许可，不能每个请求各建一个断路器；多进程或多台服务器则要通过共享存储或任务队列协调。本地线程锁不能限制整个集群。

若备用模型能够满足相同任务，在正常时期就验证它的输入、工具、输出结构和费用。切换后也要检查实际结果，不能只看状态码成功。图像任务可参考[Gemini 图像生成过载排查](https://blog.laozhang.ai/zh/posts/fix-gemini-3-pro-image-503-overloaded)；文本任务无需套用图片专属配置。

更换应用服务器的地理位置，不等于为 Gemini Developer API 选择了一份独立区域容量。第三方入口也可能使用同一上游。备用方案是否有效，要靠该方案当时实际完成请求来判断。

![暂停批量请求、单个探测与逐步恢复 API 流量的顺序](https://blog.laozhang.ai/posts/zh/google-server-capacity-limit/img/gradual-traffic-recovery.webp)

## 怎样确认这次修复真的有效？

本文的重试例使用合成响应离线检查：每日额度立即停止，未知 429 不盲重试，分钟限额和暂时性 5xx 尊重等待提示，超出等待预算或发送次数后结束。这些检查验证的是示例控制逻辑，没有进行付费故障实测。

在自己的服务上，保存恢复前后相同条件的记录：实际发送次数、触发额度、等待时间、最终结束状态、结果是否符合任务要求，以及恢复正常流量后是否再次失败。流式接口还应检查最终事件，建立 HTTP 连接不等于生成完成。

如果低负载下仍持续失败，向支持提供发生时间、项目与模型、错误详情、请求标识和最小复现步骤。移除密钥及私人输入后再共享日志。这样支持方能核对具体请求，你也能区分额度问题、配置问题与服务端故障。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-09-08。

- [官方速率限制页](https://ai.google.dev/gemini-api/docs/rate-limits?hl=zh-cn) (ai.google.dev)
- [Google 排障指南](https://ai.google.dev/gemini-api/docs/troubleshooting?hl=zh-cn) (ai.google.dev)
- [QuotaFailure 与 RetryInfo](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto) (github.com)
- [官方错误页](https://ai.google.dev/gemini-api/docs/api-errors?hl=zh-cn) (ai.google.dev)
- [官方服务状态](https://aistudio.google.com/status) (aistudio.google.com)
- [模型停用表](https://ai.google.dev/gemini-api/docs/deprecations) (ai.google.dev)
