> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apiyi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Запросы на изображение, которые зависают после получения данных

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

<Info>
  **Краткий ответ**: данные изображения **полностью получены** и декодируются в совершенно корректное изображение. Зависает лишь самый последний шаг HTTP-передачи — сообщить клиенту «это всё». Поэтому правильное решение — не более длинный timeout и не retry, а **самостоятельно завершить ответ, как только данные получены, и использовать уже имеющееся у вас изображение**.
</Info>

<Warning>
  ### Это **совместимость**, а не **замена**

  Приведённый ниже код добавляет защитный слой **вокруг вашей существующей логики вызова**. Это не другой способ интеграции:

  * Вам **не нужно** менять endpoint, переключать модели, заменять SDK или настраивать какой-либо параметр запроса;
  * Исправные запросы по-прежнему проходят точно по тому же пути, что и сейчас — **поведение не меняется**. Эта логика совместимости никогда не срабатывает на исправном запросе;
  * Она включается только тогда, когда данные уже полностью пришли, но соединение отказывается завершаться, и передаёт вам изображение, которое вы уже получили.

  Иными словами: **с ней сбойный случай можно восстановить; без неё сбойный случай может завершиться только ошибкой timeout.** Все остальное остаётся как есть.
</Warning>

## Симптом

При вызове нативного эндпоинта генерации изображений (`POST /v1beta/models/{model}:generateContent`) вы можете увидеть такую комбинацию:

* В журнале панели видно, что запрос **успешно выполнен** и **подлежит тарификации**;
* Клиент все равно зависает, и сбой происходит только когда срабатывает его собственный тайм-аут чтения;
* Ошибки выглядят как `Read timed out`, `ETIMEDOUT` или `UND_ERR_BODY_TIMEOUT`.

Это похоже на «панель сообщает, что задача завершилась за 30 секунд, но через 5 минут у меня по-прежнему нет изображения».

<Warning>
  **Тот же код раньше работал нормально, а теперь зависает на этом финальном шаге.** Этот сценарий появился недавно — это не давний дефект в том, как вы все интегрировали. Поэтому не нужно пересматривать ваш шаблон вызова; вам достаточно добавить слой совместимости, описанный ниже.
</Warning>

### Появляется окнами

Это важно, потому что от этого зависит, как вы воспроизводите проблему и как интерпретируете то, что видите:

* **Внутри окна**: последовательные вызовы зависают, **все без исключения**;
* **Вне окна**: десятки последовательных вызовов работают безупречно, **ни одного случая**.

То есть это не «всегда воспроизводится» и не «редкий случайный сбой». Если ваш тест случайно не попадет в окно, все выглядит на 100% нормально, и легко ошибочно заключить, что «все исправлено». Если же вы попадете внутрь окна, создается впечатление, что все сломано. **Оба впечатления верны — просто не делайте долгосрочных выводов, опираясь только на одно из них.**

<Info>
  Запросы с **потоковой передачей** (`:streamGenerateContent`) и модели только для текста обычно не затронуты. Эта страница посвящена **генерации изображений без потоковой передачи**, где тело ответа большое — тело ответа JSON для изображения 2K имеет порядок 13 MB.
</Info>

## Причина

Ответы с изображениями отправляются с `Transfer-Encoding: chunked`. Согласно спецификации HTTP/1.1, после того как сервер отправил последний data chunk, он должен отправить **завершающий chunk** (chunk нулевой длины), чтобы сообщить клиенту «это конец».

Именно на этом шаге все и ломается: **каждый data chunk приходит, но завершающий chunk так и не отправляется, а соединение не закрывается.**

У клиента остается **полный, пригодный к использованию JSON-документ** (изображение нормально декодируется из base64), и у него нет способа понять, что тело ответа завершено. Поэтому он продолжает ждать — пока не срабатывает собственный timeout на чтение.

Аналогия: **посылка уже стоит у вас на пороге, но курьер забыл нажать «доставлено».** Вы сидите и смотрите на страницу отслеживания в ожидании обновления, хотя посылка уже прямо перед вами.

<Warning>
  В определенные временные окна маршрут не выполняет этот финальный шаг в ответе. **Мы продолжаем добиваться исправления на стороне сервера**; то, что описывает эта страница, — это **клиентская защита**, которую нужно использовать пока что.

  Эта защита полезна сама по себе, и **вам не нужно откатывать ее после исправления на стороне сервера**: когда присутствует завершающий сигнал, она вообще не срабатывает, поэтому сама становится невидимой — нулевые накладные расходы, нулевая нагрузка на сопровождение.
</Warning>

Три вывода, которые напрямую определяют, как с этим работать:

<CardGroup cols={3}>
  <Card title="Данные полные" icon="circle-check">
    Это не потеря пакетов, не проблема качества сети и не обрыв передачи на середине. Те байты, которые у вас есть, корректно разбираются, и изображение полностью пригодно к использованию.
  </Card>

  <Card title="Ожидание не помогает" icon="timer-off">
    Как только все зависает, сервер не отправляет **ни одного байта больше**. Проверено ожиданием **330 секунд** без изменений. Увеличение timeout до нескольких сотен секунд только откладывает обнаружение.
  </Card>

  <Card title="Не привязано к одной машине" icon="server-off">
    Внутри одного окна несколько точек присутствия отказывают **одновременно** и восстанавливаются **одновременно**. Переключение доменов или точек входа не позволяет обойти проблему — ее нужно обрабатывать на стороне клиента.
  </Card>
</CardGroup>

## Как это определить

Если выполняются все три условия, вы почти наверняка сталкиваетесь именно с этим:

<Steps>
  <Step title="Ответ содержит Transfer-Encoding: chunked и не содержит Content-Length">
    Это означает, что длина тела заранее не была объявлена, поэтому клиент может опираться только на завершающий chunk, чтобы понять, что ответ завершен.
  </Step>

  <Step title="Полученные к этому моменту байты уже разбираются как полный JSON">
    Запустите `json.loads` над тем, что у вас есть — он завершается успешно, а `inlineData.data` внутри base64-декодируется в полное, пригодное к использованию изображение.
  </Step>

  <Step title="После того как этот разбор завершается успешно, новые байты долго не поступают">
    Нет ни завершающего chunk, ни закрытия соединения. Оно просто остается открытым.
  </Step>
</Steps>

### Чем это отличается от двух похожих случаев

Все три случая дают похожие ошибки, но причины и способы исправления полностью разные. **Не применяйте один набор критериев ко всем из них:**

| Наблюдение                    | Эта страница (зависает бесконечно)                                                         | Обрезано (`ECONNRESET`)              | Завершающий chunk запоздал, затем соединение разорвано |
| ----------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------------------------ |
| Полученные байты              | **Полное тело, JSON разбирается полностью**                                                | Обычно не хватает части полного тела | Полное тело                                            |
| Итоговое состояние соединения | **Нет терминатора, нет FIN, соединение остается открытым**                                 | Получен TCP RST                      | FIN (корректное закрытие)                              |
| Когда возникает сбой          | Никогда — пока клиент не сдастся (подтверждено: и через 330 секунд по-прежнему ничего нет) | Зависит                              | **Через +300 секунд** после последнего байта           |
| Что делать                    | **Используйте уже имеющееся изображение** (эта страница)                                   | Проверьте сетевой путь и клиент      | То же, что и на этой странице — данные тоже полные     |

Если ваша ошибка — `ECONNRESET`, это другой класс проблемы; см. [Обрывы соединения](/ru/api-capabilities/image-connection-drops).

## Слой совместимости: завершайте ответ на стороне клиента

Идея проста: **не ждите, пока соединение завершится — завершайте, как только байты, которые у вас уже есть, разбираются как полный JSON.**

### Критично: сохраняйте период ожидания

Не завершайте сразу, как только парсинг успешно завершился. В обычном случае завершающий chunk обычно находится уже в следующем TCP-сегменте, всего в нескольких миллисекундах. Если обрывать сразу после успешного парсинга, вы будете ошибочно считать, что «завершающий chunk пришел на несколько миллисекунд позже», как «сервер его вообще не отправил».

Правильный подход: как только парсинг успешен, **подождите еще немного** (3–5 секунд — хорошее значение по умолчанию). Если за это время приходят какие-либо байты, продолжайте работу как обычно. Только если ничего не приходит, объявляйте поток зависшим и завершайте ответ самостоятельно.

<Warning>
  **Это не необязательная доработка.** Пропуск периода ожидания делает обнаружение полностью бесполезным — **каждый исправный запрос ошибочно помечается как сбойный**. Наша первая реализация столкнулась ровно с этим: вся партия исправных запросов была отмечена как сломанная.
</Warning>

### Проверки, которые нужно пройти перед завершением ответа

От самых дешевых к самым дорогим. **Если хотя бы одна из них не проходит, продолжайте ожидание — не завершайте ответ:**

| # | Проверка                                                   | Почему                                                                                                                                           |
| - | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Соединение все еще получает данные и не завершилось штатно | Все, что завершилось штатно, должно идти по вашему текущему пути успешной обработки                                                              |
| 2 | HTTP-статус — 2xx                                          | Все остальное относится к вашей существующей обработке ошибок                                                                                    |
| 3 | В ответе нет `Content-Length`                              | Указанная длина означает, что это не тот сценарий; дайте вашему HTTP-клиенту завершить его                                                       |
| 4 | Полученных байтов больше минимального размера              | Отсеивает небольшие ответы с ошибками                                                                                                            |
| 5 | Количество байтов изменилось с последней попытки           | Позволяет не парсить тело размером в десяток с лишним MB снова и снова                                                                           |
| 6 | Последний непробельный символ — `}`                        | Крайне дешевый предварительный фильтр. Данные Base64 изображения не содержат `}`, поэтому неполная передача почти всегда отбрасывается уже здесь |
| 7 | Полный JSON успешно парсится                               | Окончательный вердикт                                                                                                                            |

Проверки 6 и 7 вместе сводят **частоту ложноположительных срабатываний почти к нулю**: ответ представляет собой один JSON-объект, поэтому парсинг неизбежно завершается ошибкой, пока данных еще не хватает. Иными словами, **вы можете завершить только тот ответ, который действительно пришел полностью**.

### Python

Читайте stream в фоновом потоке и реализуйте льготный период через **таймаут очереди** в главном потоке:

```python theme={null}
import json
import time
import base64
import queue
import threading
import requests

MIN_BYTES = 1024          # check 4: anything smaller cannot be an image


def _pump(raw, q):
    """Background thread: its only job is to push chunks onto the queue."""
    try:
        for chunk in raw.stream(65536, decode_content=True):
            q.put(chunk)
        q.put(None)                       # the server finished normally
    except Exception as exc:              # transport error, re-raised by main
        q.put(exc)


def _try_parse(buf):
    if len(buf) < MIN_BYTES:                    # check 4
        return None
    if not buf.rstrip().endswith(b"}"):         # check 6: cheap pre-filter
        return None
    try:
        return json.loads(buf.decode("utf-8"))  # check 7: the final verdict
    except ValueError:
        return None


def generate_image(url, headers, payload,
                   ttfb_timeout=180, term_grace=5, body_timeout=60):
    """Non-streaming image generation with client-side completion.

    ttfb_timeout: waiting for the first byte. The upstream is generating during
                  this phase — slow is not the same as broken, so leave room.
    term_grace:   how long to wait for the terminating signal once the body is
                  complete. If it never comes, finish the response ourselves.
    body_timeout: how long the whole body may take after the first byte.
    """
    resp = requests.post(url, headers=headers, json=payload,
                         stream=True, timeout=(10, ttfb_timeout))
    resp.raise_for_status()                     # check 2

    if resp.headers.get("Content-Length"):      # check 3
        return resp.json()                      # length declared: let the library finish

    q = queue.Queue()
    threading.Thread(target=_pump, args=(resp.raw, q), daemon=True).start()

    buf = bytearray()
    deadline = time.monotonic() + body_timeout

    while True:
        try:
            item = q.get(timeout=term_grace)
        except queue.Empty:                     # nothing new within the grace period
            obj = _try_parse(buf)
            if obj is not None:
                resp.close()                    # body is complete: finish it ourselves
                return obj
            if time.monotonic() > deadline:
                raise TimeoutError("incomplete body and no new data for a long time")
            continue                            # still incomplete, keep waiting

        if item is None:                        # healthy path: the server finished it
            break
        if isinstance(item, Exception):
            raise item
        buf += item

    obj = _try_parse(buf)
    if obj is None:
        raise ValueError("Incomplete response")
    return obj


def extract_image(obj):
    for cand in obj.get("candidates", []):
        for part in cand.get("content", {}).get("parts", []):
            if "inlineData" in part:
                return base64.b64decode(part["inlineData"]["data"])
    return None
```

<Warning>
  **Зачем нужен дополнительный поток?** Потому что `requests` имеет **один таймаут чтения**, который одновременно управляет и «ожиданием первого байта», и «ожиданием между чанками». Когда ответ зависает, цикл чтения блокируется на следующем чтении, и льготный период так и не получает шанса сработать — простой вариант с таймером на основе `for chunk in ...` **не срабатывает** именно в том случае, который он должен ловить.

  Чтение в фоновом потоке и вызов `q.get(timeout=term_grace)` в главном потоке — вот что на самом деле разделяет два таймаута. Мы сами столкнулись с этим: один таймаут, покрывающий две разные вещи, объединяет «медленную генерацию» и «не завершено» в одну неразличимую ошибку.
</Warning>

<Note>
  Этот вариант выполняет парсинг **один раз, когда истекает льготный период**, а не после каждого чанка — благодаря чему проверка 5 выше выполняется бесплатно. Тело размером более десятка МБ никогда не парсится повторно.
</Note>

### Node.js

Node.js не нужен дополнительный поток — `reader.read()` уже является promise, поэтому `Promise.race` может ограничить время ожидания следующего фрагмента:

```javascript theme={null}
const TERM_GRACE_MS = 5000;       // how long to wait for the terminating signal
const TOTAL_TIMEOUT_MS = 180000;  // total timeout: must cover upstream generation
const MIN_BYTES = 1024;

async function generateImage(url, headers, payload) {
  const resp = await fetch(url, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(TOTAL_TIMEOUT_MS),
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);        // check 2
  if (resp.headers.get("content-length")) return resp.json();  // check 3

  const reader = resp.body.getReader();
  const chunks = [];
  let total = 0;

  while (true) {
    // Promise.race caps how long we wait for the next chunk
    const next = reader.read();
    const timer = new Promise((r) => setTimeout(() => r("GRACE"), TERM_GRACE_MS));
    const winner = await Promise.race([next, timer]);

    if (winner === "GRACE") {
      const parsed = tryParse(chunks, total);
      if (parsed) {                 // body is complete: server never finished
        reader.cancel().catch(() => {});
        return parsed;
      }
      continue;                     // body still incomplete, keep waiting
    }

    const { done, value } = winner;
    if (done) break;                // healthy path: the server finished it
    chunks.push(value);
    total += value.length;
  }

  const parsed = tryParse(chunks, total);
  if (!parsed) throw new Error("Incomplete response");
  return parsed;
}

function tryParse(chunks, total) {
  if (total < MIN_BYTES) return null;                 // check 4
  const buf = new Uint8Array(total);
  let off = 0;
  for (const c of chunks) { buf.set(c, off); off += c.length; }
  const text = new TextDecoder().decode(buf).trimEnd();
  if (!text.endsWith("}")) return null;               // check 6
  try { return JSON.parse(text); } catch { return null; }   // check 7
}
```

<Tip>
  Обратите внимание на комментарий **здоровый путь** в обоих фрагментах: когда сервер корректно завершает ответ, цикл естественным образом выходит через `done` или по завершении итерации, и ветка льготного периода так и не выполняется. Именно это на практике означает «совместимость, а не замена» — ваш существующий успешный сценарий остается неизменным вплоть до байта.
</Tip>

## Выбор таймаутов

Самая простая ошибка здесь — **использовать одно значение таймаута для двух разных вещей**: «ожидание, пока upstream сгенерирует изображение» и «пауза между двумя чанками после первого байта». Их обычная длительность отличается на порядок. Объедините их в одно значение, и вы либо преждевременно оборвете медленную генерацию, приняв ее за сбой, либо оставите действительно зависшие запросы ждать минутами.

| Фаза                                             | Нормальная длительность         | Рекомендуемое               | Почему                                                                                 |
| ------------------------------------------------ | ------------------------------- | --------------------------- | -------------------------------------------------------------------------------------- |
| **Ожидание первого байта** (upstream генерирует) | \~20–30 с при 2K, дольше при 4K | **120–180 с**               | Медленно на этом этапе — еще не сбой; если оборвать раньше, вы убьете здоровые запросы |
| **Пауза между чанками** после первого байта      | Миллисекунды                    | **3–5 с** (льготный период) | Когда на таком масштабе ничего не движется, пора принять решение и завершить           |

<CardGroup cols={2}>
  <Card title="✅ Рекомендуется" icon="check">
    Настройте эти два параметра отдельно: оставьте запас на генерацию до первого байта,
    затем удерживайте паузу между чанками на уровне секунд и пусть указанное выше
    завершение на стороне клиента добирает остальное. Сбои проявляются за секунды, и ни
    один здоровый запрос не пострадает.
  </Card>

  <Card title="❌ Избегайте" icon="ban">
    Один таймаут на 300 секунд для всего «на всякий случай».
    Когда ответ зависает, сервер больше ничего не отправляет, так что более долгое ожидание
    ничего не меняет и лишь откладывает обнаружение.
  </Card>
</CardGroup>

<Note>
  Если ваш продукт включает более медленные уровни, например 4K: **общий таймаут может быть длиннее** (модели действительно нужно это время), но **порог паузы между чанками не должен расти вместе с ним** — это две разные вещи, поэтому не масштабируйте их вместе.
</Note>

<Note>
  Пользователям Node.js: undici (движок, лежащий в основе встроенного `fetch` в Node 18+) имеет **три независимых таймаута**, а опция `timeout` в SDK не затрагивает ни один из них. См. раздел «Node.js: три независимых таймаута» в [Обрывы соединения](/ru/api-capabilities/image-connection-drops) для правильной настройки.
</Note>

## Повторные попытки и тарификация

Если вы определили, что сервер так и не завершил ответ, действуйте в таком порядке:

<Steps>
  <Step title="Используйте уже полученное изображение — в почти всех случаях на этом все заканчивается">
    Данные полные, и изображение полностью пригодно к использованию, поэтому **повторная попытка не нужна**. Это и самый дешевый вариант, и тот, который позволяет избежать двойной тарификации.
  </Step>

  <Step title="Повторяйте только если разбор действительно не удался">
    Если полученные байты действительно невозможно разобрать в полный JSON, тогда повторите попытку. Используйте новое соединение и выдерживайте 2–3 секунды между попытками.
  </Step>

  <Step title="Снижайте частоту после повторных сбоев вместо плотных повторных попыток">
    Поскольку проблема проявляется окнами, немедленная повторная попытка, скорее всего, попадет в то же окно. Если три попытки подряд зависают, сделайте паузу на 30 секунд, прежде чем пробовать снова.
  </Step>
</Steps>

<Warning>
  **Тарификация**: для этих запросов вышестоящая система уже сгенерировала изображение и начала отправлять его обратно, поэтому доставка считается завершенной, и запрос **тарифицируется в обычном порядке**. Фраза «Мой клиент превысил время ожидания» не означает «с меня не списали плату» — именно поэтому первый шаг важнее всего: вы уже заплатили за это изображение, так что выбросить его — и есть настоящая трата.

  Полную разбивку по тому, какие обрывы соединения тарифицируются, а какие нет, см. в разделе «Влияние на тарификацию» в [Обрывы соединения](/ru/api-capabilities/image-connection-drops).
</Warning>

## Заметки по развёртыванию

Это рекомендации, не зависящие от языка и фреймворка, основанные на нашем собственном развёртывании:

* **Разместите это в одной сетевой точке входа, а не разбросанно по местам вызова.** Сделайте это частью самой операции «отправить запрос». Так вы охватите сразу все пути работы с изображениями, не затронете бизнес-код и оставите только одно место для изменений, когда серверная часть будет исправлена.

* **На самом деле вам нужен инкрементальный доступ на чтение.** Необходимое условие — возможность видеть, что уже пришло, до завершения ответа. Почти каждый HTTP-клиент это поддерживает (потоковое чтение, callbacks по чанкам, события прогресса), но **обычно это не режим по умолчанию** — режим «просто отдайте мне всё тело» как раз и приводит к зависанию. **Именно здесь заключается большая часть работы.**

* **Отсчитывайте время от последнего полученного чанка, а не от начала запроса.** Сбрасывайте таймер льготного периода при каждом чанке. Так вы не будете наказывать медленные сети и не пропустите состояние «вообще ничего не движется».

* **Спрячьте это за переключателем.** Держите поведение за флагом, который можно в любой момент отключить. Если сразу после релиза появится что-то неожиданное, выключите его, чтобы вернуть старое поведение — без экстренного деплоя.

* **Добавьте телеметрию.** Логируйте каждый раз, когда срабатывает путь завершения (временная метка, количество байт, длительность ожидания). Это служит трём целям: показывает, как часто это действительно происходит, подтверждает, что слой выполняет свою работу, и подтверждает, что счётчик падает до нуля после серверного исправления — а это единственная объективная основа для решения, что этот слой можно вывести из эксплуатации.

* **Дополнительное улучшение, пока вы уже этим занимаетесь.** Как только у вас появится инкрементальное чтение, вы сможете показывать пользователям реальный прогресс («получаем данные, X.X MB»). Для них эта длительная загрузка раньше была полной чёрной коробкой.

## Как мы развернули это у себя

Мы уже завершили и проверили эту работу в нашей собственной студии генерации изображений с ИИ (`imagen.apiyi.com`). Используя имитирующий сервис, который воспроизводит сбой (отправляет все тело, затем не подает сигнал завершения и не закрывает соединение), мы провели такое сравнение:

| Сценарий                                          | Результат                                                                                           |
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Исправный ответ (есть завершающий сигнал)         | Возвращается по существующему пути, **completion** никогда не срабатывает — ложных срабатываний нет |
| Тело полностью получено, завершающего сигнала нет | Завершается после периода ожидания, данные полные и пригодны к использованию                        |
| То же, при выключенном переключателе              | Старое поведение сохраняется (продолжает ждать) — переключатель работает, доступен откат            |
| Зависает, когда получена только половина тела     | **Не** завершается ошибочно; продолжает ждать                                                       |
| Полное тело, но присутствует `Content-Length`     | Блокируется защитным ограничением, не завершается                                                   |

Вывод: **нулевое влияние на исправные запросы, тогда как для неудачных запросов путь меняется с «ждать истечения тайм-аута, а затем завершиться с ошибкой» на «получить изображение за несколько секунд».**

## Частые вопросы

<AccordionGroup>
  <Accordion title="Может ли это обрезать запрос, который на самом деле был исправен?">
    Нет. Завершение требует, чтобы полученные байты распарсились как **полный JSON-документ** — пока данных не хватает, парсинг неизбежно завершается с ошибкой. Добавьте сверху 3–5-секундный льготный период, и исправные запросы не будут помечаться ошибочно. Первые две строки сравнительной таблицы выше как раз показывают эти два случая рядом.
  </Accordion>

  <Accordion title="Могу ли я в итоге получить половину изображения?">
    Нет. Проверяется целостность **всего тела ответа**, а не самого изображения. Если JSON парсится, данные изображения полные — половина изображения соответствует ошибке парсинга, а она никогда не запускает завершение.
  </Accordion>

  <Accordion title="Разве это не просто маскирует проблему на стороне сервера?">
    Нет. Это не заменяет исправление на стороне сервера. Это передает пользователю результат, который уже был получен и уже был тарифицирован, при этом избегая двойной тарификации, к которой приводят слепые повторные попытки. Создаваемая этим телеметрия также помогает определить, когда возникает сбой.
  </Accordion>

  <Accordion title="Нужно ли убрать это, когда проблема на стороне сервера будет исправлена?">
    Спешить не нужно. Когда присутствует завершающий сигнал, эта логика никогда не срабатывает, так что она ничего не стоит. Подождите, пока телеметрия будет на нуле в течение устойчивого периода, а затем решите, стоит ли это убирать.
  </Accordion>
</AccordionGroup>

## Когда обращаться в поддержку

Если после добавления указанного выше слоя совместимости по-прежнему выполняется хотя бы одно из следующих условий, соберите материалы и обратитесь в поддержку:

* Полученные байты **никогда не разбираются в полный JSON** (это не тот сценарий, который описан на этой странице — передача действительно была прервана раньше времени);
* Даже при завершении на стороне клиента вы **очень долго не получаете вообще никаких заголовков ответа** (это означает, что upstream еще не начал отправку — медленная генерация или сбой upstream, а не проблема завершения);
* Частота зависаний остается **стабильно высокой** и не группируется в окнах времени, воспроизводясь устойчиво на протяжении длительного периода.

При создании тикета укажите: `x-request-id`, время вызова (**с указанием часового пояса**, например `2026-08-03 13:15 (UTC+8)`), название модели и ключевые параметры, такие как `imageSize`, исходное исключение на стороне клиента и сколько байт было получено к моменту зависания.

## Связанная документация

<CardGroup cols={3}>
  <Card title="Обрывы соединения" icon="unplug" href="/ru/api-capabilities/image-connection-drops">
    `ECONNRESET`, SSL EOF, трёх таймаутов undici и диагностическая матрица локального proxy
  </Card>

  <Card title="Обязательно к прочтению и лучшие практики" icon="book-check" href="/ru/api-capabilities/image-api-best-practices">
    Синхронные вызовы, уровни таймаута, обработка base64 и тарификация при обрыве соединения
  </Card>

  <Card title="Создайте собственную асинхронную очередь" icon="list-checks" href="/ru/api-capabilities/image-async-queue">
    Оборачивайте синхронные вызовы в task queue, сглаживая редкие сбои с помощью повторных попыток и сохранения состояния
  </Card>
</CardGroup>
