Перейти к основному содержанию
Коротко о главном: когда вы запрашиваете у модели десятки тысяч символов за один вызов (планы серий, объемные художественные тексты, масштабные переводы, большой объем кода), используйте потоковую передачу ответа, а не обычный режим без потоковой передачи; установите клиентский таймаут чтения на интервал между событиями данных (десятки секунд — безопасным значением является 90–120 с), а не на общее время генерации; оставьте запас для max_tokens; и проверяйте stop_reason перед использованием текста. Выполните эти четыре шага, и вызовы с длинным выводом перестанут «возвращать пустоту». Если объем превышает 20k–30k символов, добавьте еще один: разбейте задачу на несколько вызовов вместо того, чтобы удерживать одно соединение открытым 20 минут.
Эта страница применима ко всем большим моделям (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) первый байт приходит в течение нескольких секунд, а затем событие данных поступает каждые несколько десятков секунд. Ваш тайм-аут чтения должен покрывать только интервал между событиями, а не генерацию, которая выполняется много минут — именно поэтому потоковая передача надёжно доставляет длинный вывод. У этих двух протоколов разные маркеры завершения — не путайте их: При включённом адаптивном thinking нативный Claude сначала отправляет блок type: "thinking" (его инкременты — thinking_delta), затем блок text. При отображении направляйте thinking_delta и text_delta раздельно и не объединяйте thinking с основным текстом. Минимальный пример потоковой передачи для нативного Claude /v1/messages (обычный httpx, построчный разбор SSE):
Если вы используете официальный SDK Anthropic, направьте base_url на https://api.apiyi.com и используйте client.messages.stream(...).get_final_message() — SDK обработает разбор SSE, тайм-ауты и stop_reason за вас. Версия с httpx выше предназначена для случаев, когда вы предпочитаете не подключать SDK.

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

Многие устанавливают таймаут чтения на одно огромное значение, рассчитанное на всю генерацию целиком (например, 1800 секунд), и всё равно сталкиваются с таймаутом — потому что без потоковой передачи это значение вынуждено конкурировать со всей длительностью генерации, и любой сбой приводит к ошибке. Правильный подход — использовать потоковую передачу в сочетании с таймаутом чтения, настроенным на интервал между событиями. Замеренные контрольные показатели (claude-opus-5 генерирует план эпизода объемом около 20 тыс. символов из входных данных объемом около 15 тыс. символов): Таким образом, таймаут чтения длительностью 90–120 секунд с запасом покрывает наибольший интервал между событиями — устанавливать значение в десятки минут нет необходимости. Трехсоставной таймаут разделяет фазы и задает размер для каждой из них:

Настройка в различных клиентах

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

Потоки тоже могут зависать: соединение перестает принимать данные на полпути

Потоковая передача решает проблему «буферизации всего ответа перед отправкой», но существует еще один сбойный сценарий, от которого нужно защититься: зависание соединения посреди потока.
  • Симптом: сначала текст поступает нормально, затем перестает приходить вовсе — при этом соединение не возвращает ошибку и не закрывается. Клиент зависает до срабатывания собственного таймаута: если он равен одному часу, клиент прождет целый час и в итоге получит лишь небольшой начальный фрагмент.
  • Причина: на межрегиональных и трансграничных сетевых маршрутах долго удерживаемое открытым соединение может зависнуть на промежуточном сетевом оборудовании. Сервер уже давно завершил генерацию и закрыл соединение, но сигнал закрытия так и не доходит до вас. Чем дольше длится соединение, тем выше вероятность этого — соединение продолжительностью 20 минут сталкивается с этим чаще, чем 3-минутное.
  • Тарификация: сервер завершает генерацию и производит тарификацию как обычно (см. пункт 3 в «Три вещи, которые нужно знать в первую очередь»).
Что делать:
  1. Настройте таймаут чтения в соответствии с интервалом между событиями (предыдущий раздел). При таймауте чтения 120 с зависание будет стоить вам двух минут ожидания перед повторной попыткой; при таймауте в один час вы потеряете целый час. Это самый важный шаг.
  2. Сокращайте длительность каждого соединения: разбивайте очень длинные ответы на несколько вызовов — см. «Разбивайте очень длинные ответы на несколько вызовов» ниже.
  3. Возобновляйте прерванный поток с места остановки — см. «Стратегия повторных попыток» ниже.
Не устанавливайте таймаут чтения равным «общему времени генерации плюс запас» (1800 с, 3600 с и т. д.). Пока все работает штатно, разницы нет, но при зависании соединения вам придется ждать все это время только для того, чтобы узнать о сбое.

Оставьте место для 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.

Разбивайте очень длинные ответы на несколько запросов

Запросить 50k символов за один вызов, который выполняется 20 минут, — самый ненадёжный подход, даже при потоковой передаче: любой сбой сети прервёт работу, и оплата будет начислена за весь объём. Если ответ превышает 20k–30k символов, разбейте его:
  1. Сначала сгенерируйте план / список серий / список глав — это будет короткий вызов.
  2. Затем генерируйте по одной главе или серии за вызов, укладываясь в 10k–20k символов. Добавляйте план и необходимый для каждой части контекст (описания персонажей, чем закончилась предыдущая часть), чтобы сохранить согласованность.
  3. После утверждения плана части становятся независимыми, и их можно генерировать параллельно.

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

Будьте осторожны с повторными попытками при длинном выводе, чтобы «повтор при сбое» не превратился в «двойную тарификацию плюс еще один долгий запуск»:
  • Повторяйте попытки только при сбоях до получения заголовков ответа и при 5xx / 429 (с задержкой, не более двух раз). Это проблемы с подключением или кратковременные сбои, при которых повторный запрос имеет смысл.
  • Если stream обрывается после того, как вы уже получили часть тела ответа, возобновите его с места остановки вместо запуска с самого начала. Сервер уже сгенерировал эту часть и тарифицировал её; перезапуск запустит процесс заново и потребует повторной оплаты. Сохраните полученный текст, передайте его в новом вызове как уже написанное и позвольте модели продолжить — вы заплатите только за оставшуюся часть.
  • Поступайте так же и при таймауте чтения (зависшем соединении): если вы уже получили достаточное количество текста, возобновите генерацию; если почти ничего не получено, повторите весь вызов целиком.
  • Логируйте request id из заголовков ответа для сверки и устранения неполадок.
Запрос на возобновление выглядит следующим образом (работает с любой моделью):

Шпаргалка по сценариям

Для цельного материала объемом более 30k символов разделите его по главам или эпизодам (см. «Разделение очень длинного вывода на несколько вызовов»). Всегда используйте stream. Используйте api.apiyi.com (или резервный b.apiyi.com), а не api-cf.apiyi.com (узел CDN возвращает 524 примерно через 100 секунд и не может обрабатывать длительные запросы).

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

Как избежать таймаутов API

Значения таймаутов для разных сценариев

Потоковая передача и непотоковая передача

Компромиссы и выбор подходящего варианта

Рассуждение и усилия Claude

Адаптивное рассуждение, уровни усилий, max_tokens и усечение

Обработка ответов Claude

Нативная структура ответа, события SSE, stop_reason