Перейти к содержанию

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

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

••4 мин чтения•Устранение неполадок
Сигналы 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-ошибка, причина завершения генерации и тайм-аут клиента

Ошибка 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, остановки и передачи инцидента

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

Остановите повторы при 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 недостаточно.

Нашли ошибку или столкнулись с проблемой, которой нет в статье? Отправьте адрес статьи и описание на hi@laozhang.ai. Мы проверим и обновим текст.