エージェントにリファクタリングを任せて席を立ち、戻ってきたらタスクが半分で止まっていて、ログに 429 RESOURCE_EXHAUSTED と赤く出ている。Antigravity を自律的なエージェントとして使い込むほど、この場面に出くわす回数が増えていきます。私自身、4つの技術ブログのコンテンツ更新を自動パイプラインで回しているのですが、タスクの実行時刻を寄せすぎた日に限ってこの 429 が連鎖し、いくつかの処理が空振りで終わっていたことがありました。
このエラーは構文ミスやバグではなく、短時間にモデルへ送ったリクエスト(またはトークン)が割り当て上限を超えたという合図です。原因はシンプルなのに、エージェントは「待てば回復する」のか「設定を変えるべき」なのかを判断してくれないため、止まったまま放置されがちです。ここでは私が実運用で切り分けてきた手順と、同じ場所で二度止まらないための恒久対策を整理します。
「429」は3種類ある — まず見分ける
429 RESOURCE_EXHAUSTED とひとくくりに見えますが、エラーメッセージの中身で原因が3つに分かれます。ここを見分けないと対処を間違えます。
- RPM 超過(1分あたりのリクエスト数): エージェントが短時間に多数のツール呼び出しを連発したとき。数十秒待てば回復します。
- TPM 超過(1分あたりのトークン数): 大きなファイルや長いコンテキストを一度に送ったとき。コンテキスト量そのものを減らす必要があります。
- RPD / 日次クォータ超過(1日あたりの上限): 無料ティアで一日中エージェントを回したとき。これは待っても当日は回復せず、ティアの変更が必要です。
見分け方は、エラーレスポンスに含まれる quotaMetric と quotaValue を読むことです。...PerMinutePerProject なら分単位、...PerDay なら日次です。Antigravity のエージェントログでは折りたたまれていることが多いので、出力パネルを展開して原文を確認してください。
まず試す: 指数バックオフで自動リトライさせる
分単位の上限(RPM / TPM)が原因なら、待ってから再送するだけで解決します。問題は素朴なリトライだと「待ち時間が固定」で、混雑時に何度も弾かれてしまう点です。実用的なのは、待ち時間を徐々に伸ばす指数バックオフに、サーバーが返す推奨待機時間(RetryInfo)を優先して使う書き方です。
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
async function generateWithBackoff(
prompt: string,
maxRetries = 5
): Promise<string> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await ai.models.generateContent({
model: "gemini-2.5-pro",
contents: prompt,
});
return res.text ?? "";
} catch (err: any) {
const status = err?.status ?? err?.code;
if (status !== 429 || attempt === maxRetries) throw err;
// サーバーが推奨待機秒数を返していればそれを優先
const retryInfo = err?.details?.find(
(d: any) => d["@type"]?.includes("RetryInfo")
);
const serverDelay = retryInfo?.retryDelay
? parseFloat(retryInfo.retryDelay) * 1000
: 0;
// 指数バックオフ + ジッター(同時再送の衝突を避ける)
const backoff = Math.min(2 ** attempt * 1000, 32_000);
const jitter = Math.random() * 1000;
const wait = Math.max(serverDelay, backoff) + jitter;
console.warn(`429 検出。${Math.round(wait)}ms 待機して再試行します (試行 ${attempt + 1})`);
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error("最大リトライ回数に達しました");
}ポイントは2つあります。1つは RetryInfo の retryDelay を最優先すること。サーバーが「あと N 秒待て」と明示している場合、それより早く再送しても確実にまた弾かれます。もう1つはジッター(乱数の上乗せ)です。複数のエージェントやバッチが同じタイミングで復帰すると、また同時にぶつかって 429 を再生産します。少しだけ待機時間をばらつかせるだけで、この再衝突が目に見えて減ります。
TPM 超過が原因のとき: 送るコンテキストを削る
待っても直らず、エラーが ...TokensPerMinute を指している場合は、1リクエストあたりのトークン量が多すぎます。Antigravity のエージェントは関連ファイルを気前よくコンテキストに含めるため、大きなリポジトリでは無自覚にこの上限を踏みます。
有効なのは、エージェントに渡す範囲を物理的に絞ることです。.antigravityignore でビルド成果物やログ、巨大な生成物を除外し、@ 参照では「フォルダ全体」ではなく必要なファイルだけを指定します。私の場合、dist/・.next/・*.log・スナップショット用の大きな JSON を除外しただけで、長いタスク中の 429 がほぼ消えました。コンテキストは多ければ良いのではなく、エージェントが必要とする最小限に保つほうが、精度も安定性も上がります。
# .antigravityignore の例
node_modules/
dist/
.next/
*.log
coverage/
*.snapshot.json
public/content/
日次クォータ超過のとき: 待っても回復しない
エラーが ...PerDay を指している、あるいは1日中エージェントを動かした夕方に突然止まったなら、日次クォータの枯渇です。これはその日のうちは待っても回復しません。選択肢は次の3つです。
- 有料ティアへ切り替える: 無料ティアの日次上限はモデルによってはかなり低めです。継続的にエージェントを回すなら、ここが現実的な解になります。
- 使うモデルを使い分ける: 全工程を最上位モデルで回す必要はありません。コード生成だけ高性能モデル、要約や分類は軽量モデル、と振り分けるとクォータの消費が大きく変わります。
- 実行を時間的に分散する: 自動実行やバッチを使っているなら、同じ時間帯に処理を寄せないことです。私は4サイトの更新タスクをわざと別々の時刻にずらして並べることで、分単位・日次の両方の上限に同時に当たらないよう運用しています。
予防策: エージェントを「上限と仲良く」走らせる
復旧の話だけでなく、そもそも 429 を踏みにくくする設計も大事です。私が普段守っているのは次の3点です。まず、自律タスクには必ずバックオフ付きのリトライを最初から仕込んでおくこと。後付けより、止まる前提で組むほうが運用が楽になります。次に、並列度を欲張らないこと。同時に走らせるエージェントやリクエストの数を1〜2に抑えるだけで、RPM 超過はかなり防げます。最後に、長いタスクは小さく刻むこと。1回の依頼を「ファイル単位」「機能単位」に分けると、1リクエストあたりのトークンが下がり、途中で止まっても再開しやすくなります。
私は2014年から個人でアプリ開発を続けてきました。累計5,000万ダウンロードのアプリ運営でも広告配信やプッシュ通知の API でレート制限には何度も悩まされてきたので、外部 API の上限とは長い付き合いです。AI エージェントでも結局のところ本質は同じで、「上限を読み、待ち、分散する」という基本に立ち返ると、ほとんどの 429 は静かに消えていきます。
429 に出くわしたら、まずはエラー本文の quotaMetric を一度だけ確認してみてください。分単位なのか日次なのかが分かれば、待つべきか設定を変えるべきかの判断は、驚くほど簡単になります。