AIFreeAPI Logo

Диагностика Nano Banana API: 400, 429, 503, NO_IMAGE и тайм-аут

A
4 min readУстранение неполадок API

400 требует исправить запрос, временные HTTP-сбои — ограничить retry, NO_IMAGE — прочитать как finishReason, а тайм-аут — проверить на неизвестный результат.

Сигналы 400, 429, 503, NO_IMAGE и тайм-аут распределены по трём уровням

При сбое Nano Banana API сначала сохраните сырой ответ, а затем определите уровень сигнала. 400/429/503 — результаты HTTP-запроса, NO_IMAGE — официальная причина завершения генерации в актуальной справке Google, а тайм-аут может означать лишь то, что клиент или gateway прекратил ждать. Один цикл «Повторить» для этих веток неверен.

Наблюдаемый результатЧто сохранить первымПервое действиеПовторять без изменений?Признак восстановления
400 INVALID_ARGUMENTИтоговый URL, API version, model, обезличенный body, detailsИсправить контракт запросаНетМинимальный запрос больше не даёт 400
429 RESOURCE_EXHAUSTEDStatus/details, текущие лимиты, время и номер попыткиНайти ограничение и выровнять нагрузкуТолько ограниченноСтабильный успех при контролируемой параллельности
503 UNAVAILABLERequest ID, route, model, временной интервалПроверить состояние сервиса и отступитьТолько ограниченноМинимальный запрос на том же маршруте проходит
finishReason=NO_IMAGECandidate, finishMessage, parts, response IDПроверить модальность и минимальный запросПосле классификацииНовый ответ содержит декодируемую часть с изображением
Тайм-аут или 504Deadline каждого слоя, длительность, наличие body, request IDНайти первый закрывший слой и проверить неизвестный результатЛишь для временной веткиМаршрут завершает запрос без дубля результата

В официальном руководстве Gemini API Google рекомендует ограниченный exponential backoff для временных 408, 429 и 5xx, но не советует слепо повторять 400/403. Справочник ошибок API содержит no_image как ошибку генерации, а GenerateContent определяет NO_IMAGE как FinishReason: изображение ожидалось, но не было создано. Это официальный результат, но не HTTP-статус и не доказательство safety, перегрузки или списания средств.

Сначала соберите один диагностический пакет

Логируйте данные до того, как адаптер заменит ответ общей фразой. Промпт, входные изображения и ключи следует обезличить.

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. Gemini Developer API, Vertex AI, внешний gateway и ваш reverse proxy могут показать одинаковый текст пользователю, но иметь разные квоты и идентификаторы. Ниже используется нативная форма Gemini Developer API models/{model}:generateContent; пути Vertex с project и region сюда подставлять нельзя.

Минимальный пробник должен идти через тот же account, base URL и model, что и рабочая нагрузка. Значение GEMINI_MODEL берите из актуального официального списка моделей, а не из старого примера с preview-моделью.

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"]} }'

Если пробник проходит, а исходный запрос — нет, исследуйте входные изображения, контекст и дополнительные параметры. Если не проходит и он, переходите к account/route/capacity, не меняя всё одновременно.

Три уровня Nano Banana: HTTP-ошибка, причина завершения генерации и тайм-аут клиента
Три уровня Nano Banana: HTTP-ошибка, причина завершения генерации и тайм-аут клиента

Ошибка 400: ищите различие в запросе

400 INVALID_ARGUMENT означает, что API отклонил структуру или параметры. Повтор того же body ситуацию не изменит. Сравните итоговый URL, API version, model и responseModalities с актуальной документацией генерации изображений.

Сведите запрос к одному текстовому part и выходу IMAGE. Затем возвращайте по одному полю за тест. Для входного файла проверьте фактический MIME, способ передачи и размер байтов. Если минимальный запрос успешен, а после добавления одного параметра появляется 400, вы получили воспроизводимое доказательство вместо предположения.

Не смешивайте нативный Gemini endpoint с Vertex-путём. У Vertex есть собственные project, location и IAM-контракты; совет «сменить region» не является универсальным исправлением для Developer API.

429 и 503: общая техника повторов, разные причины

429 RESOURCE_EXHAUSTED указывает на превышение применимого ограничения. Оно может относиться к RPM, TPM, RPD, расходам или конкретной модели/ступени. Значения меняются, поэтому проверяйте их в текущем интерфейсе Google AI Studio rate limits, а не фиксируйте число из статьи.

Сначала уменьшите всплески: ограничьте worker concurrency, поставьте задания в очередь и распределите их по времени. Если все процессы повторят запрос через одну и ту же задержку, возникнет retry storm.

503 UNAVAILABLE означает временную недоступность или перегрузку. Это не доказательство неверного промпта и не обещание восстановления через определённое число минут. Проверьте официальный статус, минимальный запрос на том же model/route и сохраните request ID. После исчерпания бюджета попыток автоматический цикл должен остановиться.

Для собственного REST-клиента ограничьте попытки, timeout каждой попытки и общее время. Не добавляйте второй цикл поверх SDK, пока не проверите его встроенные повторы.

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("новая попытка не помещается в общий лимит") time.sleep(delay)

NO_IMAGE: официальный результат генерации, а не HTTP-ошибка

Не смешивайте официальный finishReason с общей надписью приложения «нет изображения». В текущем контракте Google NO_IMAGE относится к candidate; gateway может отдельно преобразовать его в no_image. Сохраните upstream body и проверьте его по порядку:

  • Нет candidates: изучите promptFeedback; проблема промпта может остановить ответ до кандидатов.
  • finishReason=NO_IMAGE: сохраните finishMessage, responseId и modelVersion, проверьте модальность и выполните минимальный запрос по тому же маршруту. Не переименовывайте результат в safety или 503 без доказательств.
  • IMAGE_SAFETY, IMAGE_PROHIBITED_CONTENT или другая policy-причина: уточните законный запрос либо примите границу; retry не должен быть обходом.
  • Есть текст, но нет image part: прочитайте ответ и проверьте модальность. Text-only — не то же доказательство, что NO_IMAGE.
  • Есть 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")] texts = [p["text"] for p in parts if p.get("text")] 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), "textCount": len(texts), "responseId": body.get("responseId"), "modelVersion": body.get("modelVersion"), }

Для отдельной работы с фильтрацией используйте руководство по отсутствию изображения и IMAGE_SAFETY. Если в инциденте есть и другие HTTP-ошибки Gemini, сверяйтесь с общим руководством по ошибкам Gemini API.

Тайм-аут: кто первым закрыл соединение

Типичная цепочка выглядит так:

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

Клиент может отменить запрос до получения любого HTTP body, а reverse proxy — создать собственный 504. Записывайте 504 провайдера только тогда, когда он пришёл в upstream-ответе. Важно и обратное: тайм-аут клиента не доказывает остановку сервера. Перед повтором операции, создающей изображение или запись, проверьте task/artifact по request или response ID, чтобы не получить дубль. Измеряйте старт, первый байт, завершение и deadline на каждом контролируемом участке.

Сравните два запроса по одному маршруту: минимальный безопасный и исходный сложный. Если первый стабилен, а второй всегда достигает client deadline, увеличивайте именно правильный timeout или уменьшайте вход. Если оба падают в одном окне с 503/504, новые тяжёлые попытки почти ничего не добавят к диагнозу.

Временная шкала ограниченного retry, остановки и передачи инцидента
Временная шкала ограниченного retry, остановки и передачи инцидента

Когда остановиться и что передать поддержке

Остановите повторы при 400 и других известных client errors; если NO_IMAGE ещё не проверен минимальным запросом и сырой структурой ответа; при safety/policy finishReason; после исчерпания попыток или общего времени; при постоянном 5xx для одного fingerprint; либо когда результат уже не нужен вызывающей стороне.

В пакет для поддержки включите UTC-интервал, provider/base URL, API version, endpoint/model, полный обезличенный error body, request и upstream request ID, задержку каждой попытки, timeout каждого слоя, минимальный промпт, MIME входа, candidate_count, promptFeedback, finishReason, finishMessage, responseId, modelVersion, список MIME частей и результат проверки артефактов после тайм-аута.

Исправление доказано только тогда, когда минимальный запрос возвращает декодируемую часть с изображением по реально используемому маршруту, а исходная нагрузка затем завершается при контролируемой параллельности. Одного HTTP 200, статуса задачи completed или исчезновения метки NO_IMAGE недостаточно.