この記事はGeminiアプリ一般ではなく、画像API・SDK・gatewayの障害切り分けを扱います。最初にraw evidenceを保存してください。400/429/503はrequest-levelのHTTP結果、NO_IMAGEは現行Google API referenceにある公式の生成終了理由、timeoutはclientやgatewayが先に待機をやめた可能性があります。同じretryに流してはいけません。
| 現象 | 最初に保存する証拠 | 最初の対応 | 同じ内容を再試行? | 復旧の確認 |
|---|---|---|---|---|
400 INVALID_ARGUMENT | 最終URL、API version、model、伏せ字済みbody、details | リクエスト契約を修正 | しない | 最小リクエストで400が消える |
429 RESOURCE_EXHAUSTED | status/details、現在の制限、時刻、attempt | 制限軸を確認して流量を平準化 | 上限付きのみ | 制御した並列数で成功が安定 |
503 UNAVAILABLE | request ID、route、model、発生時間帯 | 稼働状況を確認してbackoff | 上限付きのみ | 同じrouteの最小probeが成功 |
finishReason=NO_IMAGE | candidate、finishMessage、parts、response ID | output modalityと最小probeを確認 | 分類後に決める | 新しい応答にimage partがある |
timeout / 504 | 各層のdeadline、経過時間、body有無、request ID | 最初の終了層と未知の上流結果を確認 | 一時障害だけ | 重複なく同じ経路で完了 |
GoogleのGemini APIトラブルシューティングは408、429、5xxなどの一時障害に上限付き指数backoffを勧め、400/403の盲目的retryを避けています。API errorsはno_imageを生成エラーとして掲載し、GenerateContentはNO_IMAGEを「画像が期待されたが生成されなかった」FinishReasonとして定義しています。公式結果ですがHTTP statusではなく、safety、過負荷、課金の証拠でもありません。
raw responseを失わない準備
ユーザー向けメッセージに変換する前に、次の項目を記録します。プロンプト、入力画像、認証情報は伏せ字にしてください。
texttimestamp_utc, provider, base_url, api_version, endpoint, model http_status, canonical_status, request_id, upstream_request_id attempt, started_at, latency_ms, client_timeout_ms candidate_count, prompt_block_reason, finish_reason part_mime_types, has_text_part, has_image_part, error_body_summary
Nano Bananaは通称なので、実際のrouteも残します。Gemini Developer API、Vertex AI、外部gateway、自社reverse proxyでは、同じ画面表示でも責任レイヤーが異なります。以下のprobeはGemini Developer APIのnativeなmodels/{model}:generateContent形式です。Vertexのproject/locationパスと混ぜないでください。
bashAPI_VERSION="${GEMINI_API_VERSION:-v1beta}" MODEL="${GEMINI_MODEL:?set GEMINI_MODEL}" curl --fail-with-body --silent --show-error \ -X POST \ "https://generativelanguage.googleapis.com/${API_VERSION}/models/${MODEL}:generateContent" \ -H "x-goog-api-key: ${GEMINI_API_KEY:?set GEMINI_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "contents": [{"parts": [{"text": "白い背景に青い陶器のマグカップを置いた商品写真を生成してください。"}]}], "generationConfig": {"responseModalities": ["IMAGE"]} }'
GEMINI_MODELには、現在の公式モデル一覧でアカウントから利用できる画像モデルを設定します。この最小probeだけ成功するなら、元の入力、会話コンテキスト、追加パラメータへ調査範囲を絞れます。

400はリクエスト差分で直す
400 INVALID_ARGUMENTに同じbodyを送り直しても回復しません。最終URL、API version、model、responseModalities、画像入力のMIMEを、現在の画像生成ドキュメントと比較します。
オプションを外し、テキストpart 1個とIMAGE出力だけにします。成功したら1項目ずつ戻してください。「全部正しそう」より、1項目を追加した瞬間に400が再現する差分の方が強い証拠です。Developer APIのnative endpointにVertexのregionやIAM前提を持ち込まないことも重要です。
429と503は原因を分け、retryだけ共有する
429 RESOURCE_EXHAUSTEDは、適用される利用制限のどれかを超えた状態です。RPM、TPM、RPD、支出、model/tier固有の制限などがあり、数値は変わります。現在値はGoogle AI Studioのrate limitsで確認します。
quota申請より先にburstを確認してください。worker concurrencyを制限し、queueで平準化します。全workerが同じ秒数で再開するとretry stormになります。
503 UNAVAILABLEは一時的な過負荷または中断です。prompt不正の証拠でも、一定時間で必ず戻るという保証でもありません。同じaccount/model/routeの最小probe、公式の稼働状況、request IDを揃え、試行回数または総時間の上限に達したら自動retryを止めます。
pythonimport random import time import requests TRANSIENT = {408, 429, 500, 502, 503, 504} def post_with_retry( url, headers, payload, *, max_attempts=4, total_budget_s=120 ): deadline = time.monotonic() + total_budget_s for attempt in range(1, max_attempts + 1): remaining = deadline - time.monotonic() if remaining <= 0: raise TimeoutError("retryの総時間上限に到達") try: response = requests.post( url, headers=headers, json=payload, timeout=(10, min(60, remaining)) ) except (requests.Timeout, requests.ConnectionError): if attempt == max_attempts: raise else: if response.status_code == 400: raise ValueError(f"retryせずリクエストを修正: {response.text[:500]}") if response.status_code not in TRANSIENT: response.raise_for_status() return response.json() if attempt == max_attempts: response.raise_for_status() base = min(2 ** (attempt - 1), 30) delay = base + random.uniform(0, base * 0.5) if delay >= deadline - time.monotonic(): raise TimeoutError("次のretryが総時間上限に収まらない") time.sleep(delay)
この値は調整例です。SDKに自動retryがある場合、外側に同じループを重ねると呼び出し回数が乗算されるため、retryレイヤーは一つにします。
NO_IMAGEは公式の生成結果でありHTTP errorではない
公式finishReasonと、アプリが独自に出す「画像なし」を混同しないでください。現行Google contractではNO_IMAGEはcandidateに属し、gatewayが別途no_imageへ変換する場合があります。upstream bodyを保存して順に確認します。
candidatesがない:promptFeedbackを確認します。finishReason=NO_IMAGE:finishMessage、responseId、modelVersionを保存し、output modalityと同一routeの最小probeを確認します。根拠なくsafetyや503へ置き換えません。IMAGE_SAFETY、IMAGE_PROHIBITED_CONTENTなど:正当な入力を見直すか境界を受け入れ、retryを回避手段にしません。- text partだけ:返却テキストとmodalityを確認します。text-onlyと公式
NO_IMAGEは別の証拠です。 inlineDataがあるのにアプリが画像なし:parser、MIME、base64、storage書き込みを確認します。
pythondef inspect_result(body): candidates = body.get("candidates") or [] if not candidates: return {"kind": "no_candidates", "feedback": body.get("promptFeedback")} candidate = candidates[0] finish_reason = candidate.get("finishReason") parts = ((candidate.get("content") or {}).get("parts") or []) images = [p["inlineData"] for p in parts if p.get("inlineData")] if images: kind = "image" elif finish_reason == "NO_IMAGE": kind = "no_image" elif finish_reason in { "SAFETY", "IMAGE_SAFETY", "PROHIBITED_CONTENT", "IMAGE_PROHIBITED_CONTENT", "IMAGE_RECITATION", }: kind = "blocked_or_policy" else: kind = "candidate_without_image" return { "kind": kind, "finishReason": finish_reason, "finishMessage": candidate.get("finishMessage"), "imageCount": len(images), "responseId": body.get("responseId"), "modelVersion": body.get("modelVersion"), }
安全停止やテキストのみの応答を詳しく調べる場合は、Gemini画像なし・IMAGE_SAFETYガイドを参照してください。認証など別のHTTPエラーも混在する場合はGemini APIエラー総合ガイドが適しています。
タイムアウトは最初に閉じた層を探す
textclient/SDK → application → reverse proxy/API gateway → provider → model
clientが先にキャンセルすればGoogleのHTTP bodyはありません。reverse proxyが独自に504を作る場合もあるため、providerの504はupstream応答で確認できた場合だけ記録します。逆にclient timeoutはserver停止の証拠でもありません。画像や下流書き込みを作る処理は、retry前にrequest/response IDでtaskやartifactを検索し、重複生成を防ぎます。開始、first byte、終了、設定deadline、接続を閉じた層を測定してください。
同じaccount/route/modelで、最小probeと元の複雑な入力を比較します。最小だけ成功し、複雑な入力がclient deadlineで終わるなら、正しい層のtimeoutを見直すかリクエストを小さくします。両方が同じ503/504時間帯で失敗するなら、大きなリクエストを繰り返しても診断情報は増えません。

復旧判定とサポートへの引き継ぎ
400などの非retryable error、最小probeとraw responseで未分類のNO_IMAGE、安全/policy finishReason、試行回数/総時間の上限、同一fingerprintの継続5xx、または呼び出し側が結果を利用できない時点で自動retryを止めます。
引き継ぎには、UTC時間帯、provider/base URL、API version、endpoint/model、伏せ字済みerror body、request/upstream request ID、各attemptのlatency、全層のtimeout、最小prompt、入力MIME、candidate数、promptFeedback、finishReason、finishMessage、responseId、modelVersion、part MIME、timeout後のartifact確認結果を含めます。
復旧の証拠は、実運用と同じrouteで最小リクエストがデコード可能なimage partを返し、その後に元の負荷が制御した並列数で完了することです。HTTP 200、taskのcompleted、またはadapterのNO_IMAGE表示が消えただけでは十分ではありません。
