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

> Отделите HTTP-ошибку запроса, причину завершения генерации и тайм-аут вызывающей стороны, прежде чем исправлять запрос или запускать ограниченный retry.

- Source: https://www.aifreeapi.com/ru/posts/nano-banana2-api-error-codes
- Language: ru
- Published: 2026-08-05
- Updated: 2026-08-05
- Publisher: AI Free API (https://www.aifreeapi.com)

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

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

В [официальном руководстве Gemini API](https://ai.google.dev/gemini-api/docs/troubleshooting) Google рекомендует ограниченный exponential backoff для временных 408, 429 и 5xx, но не советует слепо повторять 400/403. [Справочник ошибок API](https://ai.google.dev/gemini-api/docs/api-errors) содержит `no_image` как ошибку генерации, а [GenerateContent](https://ai.google.dev/api/generate-content) определяет `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-ошибка, причина завершения генерации и тайм-аут клиента](https://www.aifreeapi.com/posts/ru/nano-banana2-api-error-codes/img/response-layers.webp)

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

`400 INVALID_ARGUMENT` означает, что API отклонил структуру или параметры. Повтор того же body ситуацию не изменит. Сравните итоговый URL, API version, model и `responseModalities` с актуальной [документацией генерации изображений](https://ai.google.dev/gemini-api/docs/generate-content/image-generation).

Сведите запрос к одному текстовому 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](https://ai.google.dev/gemini-api/docs/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](/ru/posts/gemini-image-silent-failure-image-safety-fix). Если в инциденте есть и другие HTTP-ошибки Gemini, сверяйтесь с [общим руководством по ошибкам Gemini API](/ru/posts/gemini-api-error-troubleshooting-guide).

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

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

```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, остановки и передачи инцидента](https://www.aifreeapi.com/posts/ru/nano-banana2-api-error-codes/img/retry-timeline.webp)

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

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