AIFreeAPI Logo

Nano Banana API 장애 분류: 400·429·503·NO_IMAGE·시간 초과

A
4 min readAPI 문제 해결

400은 요청을 수정하고, 일시 HTTP 오류는 제한 retry하며, NO_IMAGE는 finishReason으로 읽고, timeout은 upstream 결과부터 확인합니다.

400, 429, 503, NO_IMAGE, 시간 초과를 세 증거 계층으로 분류한 그림

이 문서는 Gemini 앱 일반 팁이 아니라 Nano Banana 이미지 API·SDK·gateway 장애 대응용입니다. 재시도 전에 원본 증거를 보관하세요. 400/429/503은 요청 단계의 HTTP 결과이고, NO_IMAGE는 현재 Google API 문서의 공식 생성 종료 사유이며, timeout은 client나 gateway가 먼저 기다리기를 멈춘 것일 수 있습니다. 같은 retry 분기에 넣으면 안 됩니다.

보이는 증상먼저 보관할 증거첫 조치그대로 재시도?복구 증거
400 INVALID_ARGUMENT최종 URL, API version, model, 마스킹한 body, details요청 계약 수정아니요동일 최소 요청에서 400이 사라짐
429 RESOURCE_EXHAUSTEDstatus/details, 현재 한도, 시간, attempt제한 축 확인 후 트래픽 평탄화제한적으로만제어한 동시성에서 성공이 안정됨
503 UNAVAILABLErequest ID, route, model, 발생 구간상태 확인 후 backoff제한적으로만같은 route의 최소 probe 성공
finishReason=NO_IMAGEcandidate, finishMessage, parts, response ID출력 모달리티와 최소 probe 확인분류 후 결정새 응답에 image part 존재
timeout / 504계층별 deadline, 경과 시간, body 수신 여부, request ID최초 종료 계층과 미확정 결과 확인일시 오류만중복 없이 같은 경로에서 완료

Google의 Gemini API 문제 해결 문서는 408, 429, 5xx 같은 일시 오류에 제한된 exponential backoff를 권장하고 400/403의 맹목적 retry를 피하라고 설명합니다. API errorsno_image를 generation error로, GenerateContentNO_IMAGE를 “이미지를 기대했지만 생성되지 않음”이라는 FinishReason으로 정의합니다. 공식 결과이지만 HTTP 상태가 아니며 safety, 과부하, 과금의 단독 증거도 아닙니다.

원본 응답을 한 번에 보관하세요

adapter가 모든 오류를 “생성 실패”로 바꾸기 전에 다음 필드를 기록합니다. 프롬프트, 입력 이미지, 인증 정보는 마스킹합니다.

text
timestamp_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 경로와 섞지 마세요.

bash
API_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만 성공하면 원래 입력, 대화 문맥, 선택 필드로 범위를 좁힐 수 있습니다. 둘 다 실패하면 route, 계정, 용량 증거를 봅니다.

Nano Banana API의 HTTP 오류, 생성 종료 사유, 호출자 시간 초과 3계층
Nano Banana API의 HTTP 오류, 생성 종료 사유, 호출자 시간 초과 3계층

400은 재시도가 아니라 요청 차이로 해결합니다

400 INVALID_ARGUMENT는 API가 형식 또는 파라미터를 거절했다는 뜻입니다. 같은 body를 다시 보내도 회복되지 않습니다. 최종 URL, API version, model, responseModalities, 입력 이미지 MIME을 현재 이미지 생성 문서와 비교합니다.

선택 필드를 제거하고 text part 하나와 IMAGE 출력만 남기세요. 성공하면 필드를 하나씩 다시 넣습니다. 큰 payload가 “맞아 보인다”는 판단보다 특정 필드를 넣는 순간 400이 재현되는 차이가 훨씬 유용합니다. Developer API 문제에 Vertex region/IAM 조언을 그대로 적용하지도 마세요.

429와 503은 원인이 다르고 retry 방식만 겹칩니다

429 RESOURCE_EXHAUSTED는 적용 중인 사용 제한 중 하나를 넘었다는 뜻입니다. RPM, TPM, RPD, 지출 또는 model/tier별 한도가 될 수 있고 값은 변합니다. 현재 값은 Google AI Studio rate limits에서 확인합니다.

먼저 burst를 줄이세요. worker concurrency를 제한하고 queue로 요청을 분산합니다. 모든 worker가 같은 시간에 깨어나면 retry storm이 생기므로 jitter가 필요합니다.

503 UNAVAILABLE는 일시적 과부하 또는 중단입니다. 잘못된 프롬프트의 증거도 아니고 일정 시간 안에 복구된다는 보장도 아닙니다. 동일 account/model/route의 최소 probe와 공식 상태를 비교하고 request ID를 남깁니다. attempt 수 또는 총 경과 시간 상한에 도달하면 자동 retry를 멈춥니다.

python
import 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"재시도 전에 요청 수정: {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를 한다면 외부에 같은 루프를 겹치지 마세요. 두 계층의 시도 횟수가 곱해질 수 있습니다.

NO_IMAGE는 공식 생성 결과이지 HTTP 오류가 아닙니다

공식 finishReason과 앱의 일반 “이미지 없음” 메시지를 섞지 마세요. 현재 Google contract에서 NO_IMAGE는 candidate에 속하고, gateway가 별도로 no_image로 변환할 수 있습니다. upstream body를 보관하고 다음 순서로 봅니다.

  1. candidates 없음: promptFeedback를 확인합니다.
  2. finishReason=NO_IMAGE: finishMessage, responseId, modelVersion을 저장하고 출력 모달리티와 같은 route의 최소 probe를 확인합니다. 근거 없이 safety나 503으로 바꾸지 않습니다.
  3. IMAGE_SAFETY, IMAGE_PROHIBITED_CONTENT 등: 정당한 입력을 검토하거나 정책 경계를 수용하며 retry를 우회 수단으로 쓰지 않습니다.
  4. text part만 있음: 반환 텍스트와 모달리티를 확인합니다. text-only는 공식 NO_IMAGE와 다른 증거입니다.
  5. inlineData가 있는데도 앱은 이미지 없음: parser, MIME, base64, 저장소 쓰기를 점검합니다.
python
def 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 오류 종합 가이드를 함께 확인하세요.

시간 초과는 먼저 종료한 계층을 찾습니다

text
client/SDK → application → reverse proxy/API gateway → provider → model

client가 먼저 취소하면 Google HTTP body가 없을 수 있고 reverse proxy가 자체 504를 만들 수도 있습니다. provider 504는 upstream 응답에서 확인된 경우에만 기록합니다. 반대로 client timeout은 server 중단을 증명하지 않습니다. 이미지나 downstream write를 만드는 작업은 retry 전에 request/response ID로 task 또는 artifact를 조회해 중복 생성을 막으세요. 제어하는 각 구간에서 시작, first byte, 종료, deadline, 종료 주체를 측정합니다.

같은 account/route/model에서 최소 probe와 원래 복잡한 입력을 비교합니다. 최소 요청만 성공하고 복잡한 요청이 client deadline에서 끝나면 올바른 계층의 timeout을 조정하거나 입력을 줄입니다. 둘 다 같은 503/504 구간에서 실패하면 큰 요청을 반복해도 진단 정보가 거의 늘지 않습니다.

Nano Banana API의 제한된 retry, 중단, escalation 타임라인
Nano Banana API의 제한된 retry, 중단, escalation 타임라인

중단 조건과 지원팀 전달 항목

400 등 non-retryable client error, 최소 probe와 raw response로 아직 분류하지 않은 NO_IMAGE, safety/policy finishReason, attempt/총시간 상한, 동일 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 표시가 사라진 것만으로는 충분하지 않습니다.