Назад в блог

API и SDKИнструкция

OpenAI Python SDK: timeout и max_retries для CheapTokens

Как задать timeout, max_retries и max_tokens в OpenAI Python SDK, какие ошибки ловить и как не ждать зависший запрос 10 минут.

Автор: CheapTokens TeamОпубликовано 4 мин чтения

Код Python с timeout=30.0 и max_retries=2 на тёмном фоне
Содержание
  1. Что делает SDK, если ничего не задавать
  2. Задайте timeout и max_retries на клиенте
  3. Переопределите значения на один запрос
  4. Ловите ошибки, которые важны
  5. Ограничьте max_tokens, чтобы ответ не разрастался
  6. Во что повторы обходятся
  7. Перед выкаткой

Скрипт вызвал модель, ответа нет, а через десять минут он всё ещё ждёт. Так работает OpenAI Python SDK по умолчанию: таймаут 10 минут и 2 повтора. Чтобы это изменить, передайте timeout=30.0 и max_retries=2 в OpenAI(...), переопределяйте их на один вызов через client.with_options(...) и ограничивайте ответ параметром max_tokens.

Всё это описано в README SDK (проверено 27 октября 2026). Ниже оно применено к адресу CheapTokens, https://api.cheaptokens.dev/v1, который понимает формат OpenAI Chat Completions. Про сам запуск — в статье Первый запрос к CheapTokens и в документации.

Что делает SDK, если ничего не задавать#

Важны два умолчания. Таймаут — 10 минут. Повторов — 2, с короткой экспоненциальной паузой, для ошибок соединения и ответов 408, 409, 429 и 5xx, и только если тело запроса можно безопасно отправить ещё раз. Запрос, упавший по таймауту, тоже повторяется.

У каждой попытки свой таймаут, поэтому худший случай — примерно таймаут, умноженный на число попыток, плюс паузы между ними:

  • по умолчанию: 600 × 3 = 1800 секунд, полчаса
  • timeout=30.0, max_retries=2: 30 × 3 = 90 секунд

Допустим, веб-эндпоинт обязан ответить за 30 секунд. С настройками по умолчанию он может висеть полчаса, прежде чем ваш код увидит ошибку. Попробуйте на другой настройке, прежде чем читать дальше.

Угадайте число

У клиента timeout=45.0 и max_retries=3. Сколько секунд максимум пройдёт до исключения, не считая пауз между попытками?

Показать ответ

Ответ: 180

45 × (3 повтора + 1 первая попытка) = 180 секунд; паузы между попытками добавят немного сверху.

Задайте timeout и max_retries на клиенте#

Проще всего задать оба значения один раз, при создании клиента:

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    timeout=30.0,      # секунд на одну попытку
    max_retries=2,     # 0 выключает повторы
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Summarize this ticket in two sentences: ..."}],
    max_tokens=800,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

max_retries — неотрицательное целое. timeout принимает число с плавающей точкой или объект Timeout из httpx, если нужны отдельные лимиты на соединение и чтение. В README текущей ветки main для этого импортируется httpx2, в старых версиях был httpx, так что выполните pip show openai и посмотрите, что стоит у вас. Обычное число импорта не требует и работает в обоих случаях.

Путь запроса при timeout=30 и двух повторах
Один запрос: попытка, таймаут, до двух повторов, затем исключение

Используйте полный id модели из документации CheapTokens, здесь это deepseek-ai/DeepSeek-V4-Flash-0731: у разных провайдеров id отличаются. Модель выдаёт до 32 768 токенов в одном ответе, контекст у неё 380K, поэтому строка с max_tokens в примере стоит не случайно. Подробнее об этом ниже, а о самой модели — на странице cheap DeepSeek.

Переопределите значения на один запрос#

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

python
quick = client.with_options(timeout=5.0, max_retries=0)
resp = quick.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Reply with one word: ok"}],
    max_tokens=5,
)

with_options возвращает копию с новыми значениями, а client не меняется. max_retries=0 уместен, когда повторы уже делает ваш код, например очередь задач возвращает задание в работу. Иначе два уровня перемножаются: 3 попытки SDK внутри 3 попыток очереди — это 9 запросов на одну задачу.

Ловите ошибки, которые важны#

Когда повторы закончились, SDK выбрасывает исключение. Все классы ошибок наследуются от openai.APIError, а у неудачных запросов есть request_id — его и пишите в лог:

python
import openai

def ask(prompt: str) -> str | None:
    try:
        r = client.chat.completions.create(
            model="deepseek-ai/DeepSeek-V4-Flash-0731",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=800,
        )
        return r.choices[0].message.content
    except openai.APITimeoutError:
        print("timed out after retries")
    except openai.RateLimitError as e:
        print("429, slow down:", e.request_id)
    except openai.APIConnectionError:
        print("cannot reach the API")
    except openai.APIStatusError as e:
        print("HTTP", e.status_code, e.request_id)
    return None

Порядок — от узкого к широкому. APIStatusError покрывает любой ответ 4xx и 5xx, и, окажись он первым, перехватил бы RateLimitError. С таймаутом так же: он стоит выше общей ошибки соединения. В пакетном скрипте return None и есть смысл: записали промпт в лог, пошли дальше, а упавшие прогнали заново в конце.

Когда таймаут сработал и повторы кончились, исключение выглядит так:

Сообщение APITimeoutError после исчерпанных повторов
Как пойманный таймаут выглядит в логе

Про случай 429, включая паузы между попытками, — в статье 429 Too Many Requests от AI API.

Ограничьте max_tokens, чтобы ответ не разрастался#

Таймаут перестаёт ждать, но не заставляет модель писать меньше. Без max_tokens модель с лимитом ответа 32 768 токенов вправе написать очень длинный текст, а длинный текст — это долго. Ставьте лимит под задачу: 800 для сводки обращения, 5 для ответа «да/нет».

Цена такого лимита — ответ может обрезаться. Смотрите resp.choices[0].finish_reason: length значит, что лимит кончился раньше, чем модель закончила. Подробно — в статье Почему ответ ИИ обрывается на полуслове. Если выводить ответ потоком, первые слова приходят сразу и ждать кажется короче, см. стриминг в Python.

Во что повторы обходятся#

Повтор — это новый запрос. Если попытка была обработана, это отдельный запрос со своими токенами. Допустим, сводка обращения — 1 500 токенов на входе и 400 на выходе: один запрос стоит $0.000063 по сегодняшним ценам, а остальные модели есть в таблице цен. Цена модели фиксирована, пиковых часов нет, поэтому повтор в три часа ночи стоит столько же, сколько в полдень.

Подвиньте ползунки под свой пакет:

Калькулятор стоимости

Пакет из 1 000 сводок обращений

Входные токены на запрос: 1 500 · Выходные токены на запрос: 400 · Запросов в день: 1 000

МодельВ деньВ месяц
GLM-5.3-Flash$0.046$1.38
DeepSeek-V4-Flash$0.0625$1.88
MiniMax-M2.7$0.135$4.05

Таблица показывает один проход. Если десятая часть пакета требует второй попытки, добавьте десятую часть. Повторы — дешёвая страховка, пока сбои редки, и дорогая привычка, если поставить max_retries=10 на эндпоинт, который постоянно падает.

Перед выкаткой#

Чек-лист

Таймауты и повторы, готово к продакшену