> ## 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.

# Практики работы с длинными ответами

> Как надёжно получать результаты для задач с длинными ответами — сценариев драм, художественной литературы и статей объёмом 10 тыс. слов: используйте потоковую передачу, задайте тайм-аут чтения с учётом интервала между событиями, выделите достаточно места для max_tokens и проверяйте stop_reason. С примером для нативного API Claude.

<Info>
  **В одной строке**: когда вы просите модель сгенерировать десятки тысяч символов за один вызов (планы эпизодов, длинную художественную прозу, большие переводы, крупный код), **используйте потоковую передачу ответа, а не режим без потоковой передачи**; установите тайм-аут чтения клиента на интервал *между событиями данных* (десятки секунд — 90–120 с является безопасным значением), а не на *общее* время генерации; дайте `max_tokens` запас; и проверяйте `stop_reason` перед использованием текста. Выполните эти четыре действия, и вызовы с длинным выводом перестанут «ничего не возвращать».
</Info>

Эта страница относится ко всем большим моделям (OpenAI, Claude, Gemini, Grok и другим). В примерах кода используется нативный `/v1/messages` Claude; различия для OpenAI-совместимого формата указаны отдельно.

## Три вещи, которые нужно знать в первую очередь

1. **Для вывода на 10 тыс. слов нормальна реальная генерация в течение 10–20 минут.** Модель последовательно генерирует десятки тысяч символов token за token, а также проходит этап рассуждения/обдумывания — общая задержка действительно велика. Дело не в медленной работе шлюза; сама генерация занимает много времени.

2. **Без потоковой передачи весь результат буферизуется перед отправкой.** При отсутствии потоковой передачи (`stream` опущен или `false`) сервер должен дождаться, пока модель завершит всю генерацию, и только затем отправить весь body одним ответом. В течение этих минут тайм-аут чтения вашего клиента конкурирует с этим ожиданием, и чем дольше генерация, тем выше вероятность отключения до получения результата — при этом исключение часто оказывается пустым (`httpx.ReadError`'s `str(e)` пуст), поэтому вы не сможете увидеть причину.

3. **За разорванное соединение всё равно взимается плата, поэтому повторные попытки вслепую приводят к двойной тарификации.** После того как сервер сгенерировал вывод, вызов тарифицируется, даже если результат до вас не дошёл. Повторная попытка после того, как вы уже получили часть body, означает, что модель запустится снова и вы заплатите повторно.

## Используйте потоковую передачу, не используйте непотоковый режим

При потоковой передаче (`stream: true`) первый байт приходит в течение нескольких секунд, а затем событие данных поступает каждые несколько десятков секунд. Ваш тайм-аут чтения должен покрывать только интервал *между событиями*, а не генерацию, которая выполняется много минут — именно поэтому потоковая передача надёжно доставляет длинный вывод.

У этих двух протоколов **разные маркеры завершения** — не путайте их:

| Протокол                                    | Маркер завершения                        | Как читать текст                                                               |
| ------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------ |
| Нативный Claude `/v1/messages`              | `event: message_stop` (**без `[DONE]`**) | `delta.text` из `content_block_delta`, у которого `delta.type == "text_delta"` |
| Совместимый с OpenAI `/v1/chat/completions` | `data: [DONE]`                           | `choices[0].delta.content`                                                     |

При включённом адаптивном thinking нативный Claude **сначала** отправляет блок `type: "thinking"` (его инкременты — `thinking_delta`), затем блок `text`. При отображении направляйте `thinking_delta` и `text_delta` раздельно и не объединяйте thinking с основным текстом.

Минимальный пример потоковой передачи для нативного Claude `/v1/messages` (обычный httpx, построчный разбор SSE):

```python theme={null}
import json
import httpx

def generate_long_text(prompt, api_key, model="claude-opus-5", max_tokens=64000):
    url = "https://api.apiyi.com/v1/messages"
    headers = {
        "x-api-key": api_key,
        "anthropic-version": "2023-06-01",
        "content-type": "application/json",
        "accept": "text/event-stream",
    }
    payload = {
        "model": model,
        "max_tokens": max_tokens,
        "stream": True,                        # key: long output must stream
        "thinking": {"type": "adaptive"},      # adaptive thinking; the model decides depth
        "messages": [{"role": "user", "content": prompt}],
    }
    # Three-part timeout: read only covers the inter-event gap, not the whole generation
    timeout = httpx.Timeout(connect=30, write=120, read=120, pool=30)

    text, stop_reason = [], None
    with httpx.Client(timeout=timeout) as c:
        with c.stream("POST", url, json=payload, headers=headers) as r:
            if r.status_code != 200:
                raise RuntimeError(f"HTTP {r.status_code}: {r.read()[:400]}")
            event, data = None, []
            for line in r.iter_lines():
                if line == "":                 # events are separated by a blank line
                    if data:
                        d = json.loads("\n".join(data))
                        t = d.get("type") or event
                        if t == "content_block_delta" and d.get("delta", {}).get("type") == "text_delta":
                            text.append(d["delta"]["text"])
                        elif t == "message_delta":
                            stop_reason = d.get("delta", {}).get("stop_reason") or stop_reason
                    event, data = None, []
                    continue
                if line.startswith("event:"):
                    event = line[6:].strip()
                elif line.startswith("data:"):
                    data.append(line[5:].strip())
    # A Claude stream ends on message_stop, never [DONE]
    if stop_reason == "max_tokens":
        raise RuntimeError(f"truncated by max_tokens after {len(''.join(text))} chars; raise max_tokens and retry")
    return "".join(text).strip()
```

<Tip>
  Если вы используете официальный SDK Anthropic, направьте `base_url` на `https://api.apiyi.com` и используйте `client.messages.stream(...).get_final_message()` — SDK обработает разбор SSE, тайм-ауты и `stop_reason` за вас. Версия с httpx выше предназначена для случаев, когда вы предпочитаете не подключать SDK.
</Tip>

## Настройте тайм-аут чтения по межсобытийному интервалу, а не по общему времени

Многие задают тайм-аут чтения равным одному очень большому значению, рассчитанному на всю генерацию (например, 1800 секунд), и всё равно получают тайм-аут — потому что в режиме без потоковой передачи это значение должно покрывать всю генерацию целиком, и любая задержка приводит к сбою. Правильный подход — использовать потоковую передачу и задать тайм-аут чтения по межсобытийному интервалу.

Измеренный ориентир (`claude-opus-5` создаёт план эпизода примерно на 20 тыс. символов из входных данных примерно на 15 тыс. символов):

| Метрика                                             | Измеренное значение                              |
| --------------------------------------------------- | ------------------------------------------------ |
| Время до получения первого байта                    | 3 – 130 с                                        |
| Максимальный период без данных во время рассуждения | \~42 с (между ними отправляются keepalive-пинги) |
| Общее сквозное время                                | 9 – 12 минут                                     |

Таким образом, тайм-аут чтения **90–120 секунд** покрывает самый большой межсобытийный интервал с запасом — задавать значение в несколько минут не требуется. Трёхчастный тайм-аут разделяет этапы и позволяет настроить каждый из них отдельно:

```python theme={null}
# connect: establish; write: upload the request body; read: max gap between reads
timeout = httpx.Timeout(connect=30, write=120, read=120, pool=30)
```

## Оставьте место для max\_tokens и проверяйте stop\_reason

Длинный вывод легко достигает предела `max_tokens` и усекается. Особенно это актуально для таких моделей, как Claude, с включённым **рассуждением — само рассуждение расходует бюджет `max_tokens`**, и большой фрагмент может его исчерпать.

* **Установите `max_tokens` на 64000** (при высоком уровне усилий или глубоком рассуждении задавайте ещё больше; `claude-opus-5` поддерживает вывод размером до 128K).
* **Проверяйте `stop_reason` перед использованием ответа:**
  * `end_turn` — завершено штатно, текст полный; это единственный успешный результат.
  * `max_tokens` — усечено, текст может быть неполным или даже пустым. Это **усечение**, а не «пустой результат» — увеличьте `max_tokens` и повторите запрос.
  * `refusal` — запрос отклонён политикой безопасности; обработайте этот случай отдельно.

Определять успешность только по `str(e)` или по признаку «текст пуст» вводит в заблуждение — пустое тело обычно означает усечение `max_tokens`.

## Стратегия повторных попыток

Будьте консервативны при повторных попытках для длинного вывода, чтобы «повторная попытка при сбое» не превратилась в «двойную тарификацию плюс второй длительный запуск»:

* **Повторяйте запрос только при сбоях до получения заголовков ответа и при `5xx` / `429`** (с экспоненциальной задержкой, не более двух раз). Это проблемы с подключением или временные сбои, при которых повторная попытка имеет смысл.
* **Не повторяйте бездумно потоковую передачу, прервавшуюся после получения части тела ответа.** Сервер уже сгенерировал и тарифицировал ответ; повторная попытка запускает операцию заново и приводит к повторной оплате.
* Для сверки и устранения неполадок записывайте идентификатор запроса из заголовков ответа.

## Краткая памятка по сценариям

| Сценарий                                   | Типичный объём вывода                   | max\_tokens                       | Тайм-аут чтения |
| ------------------------------------------ | --------------------------------------- | --------------------------------- | --------------- |
| План эпизода драмы / короткой драмы        | 10k–30k символов                        | 64000                             | 90–120 с        |
| Художественная литература (романы / главы) | 10k–50k символов                        | 64000–128000                      | 90–120 с        |
| Длинный перевод                            | увеличивается вместе с исходным текстом | \~1,5× исходного количества token | 90–120 с        |
| Генерация большого объёма кода             | тысячи строк                            | 32000–64000                       | 90–120 с        |

Всегда используйте потоковую передачу. Используйте `api.apiyi.com` (рекомендуется в материковом Китае) или `vip.apiyi.com` (рекомендуется за пределами Китая) и **не `api-cf.apiyi.com`** (узел CDN возвращает `524` примерно через 100 секунд и не может обрабатывать длинные запросы).

## Связанные материалы

<CardGroup cols={2}>
  <Card title="Как избежать таймаутов API" icon="clock" href="/ru/faq/timeout-configuration">
    Значения таймаутов для разных сценариев
  </Card>

  <Card title="Потоковая передача и непотоковая передача" icon="git-compare" href="/ru/faq/streaming-vs-non-streaming">
    Компромиссы и выбор подходящего варианта
  </Card>

  <Card title="Рассуждение и усилия Claude" icon="brain" href="/ru/api-capabilities/claude-effort-thinking">
    Адаптивное рассуждение, уровни усилий, max\_tokens и усечение
  </Card>

  <Card title="Обработка ответов Claude" icon="code" href="/ru/api-capabilities/claude-response-handling">
    Нативная структура ответа, события SSE, stop\_reason
  </Card>
</CardGroup>
