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

Содержание
Скрипт вызвал модель, ответа нет, а через десять минут он всё ещё ждёт. Так работает OpenAI Python SDK по умолчанию: таймаут 10 минут и 2 повтора. Чтобы это изменить, передайте timeout=30.0 и max_retries=2 в OpenAI(...), переопределяйте их на один вызов через client. и ограничивайте ответ параметром max_tokens.
Всё это описано в README SDK (проверено 27 октября 2026). Ниже оно применено к адресу CheapTokens, https:/, который понимает формат 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. Сколько секунд максимум пройдёт до исключения, не считая пауз между попытками?
Ваш вариант
0400
Показать ответ
Ответ: 180
45 × (3 повтора + 1 первая попытка) = 180 секунд; паузы между попытками добавят немного сверху.
Задайте timeout и max_retries на клиенте#
Проще всего задать оба значения один раз, при создании клиента:
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 и посмотрите, что стоит у вас. Обычное число импорта не требует и работает в обоих случаях.

Используйте полный id модели из документации CheapTokens, здесь это deepseek-: у разных провайдеров id отличаются. Модель выдаёт до 32 768 токенов в одном ответе, контекст у неё 380K, поэтому строка с max_tokens в примере стоит не случайно. Подробнее об этом ниже, а о самой модели — на странице cheap DeepSeek.
Переопределите значения на один запрос#
Один клиент, разные задачи. Ответ в чате должен сдаваться быстро, ночная сводка может подождать. Вместо второго клиента сделайте копию на один вызов:
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 — его и пишите в лог:
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 и есть смысл: записали промпт в лог, пошли дальше, а упавшие прогнали заново в конце.
Когда таймаут сработал и повторы кончились, исключение выглядит так:

Про случай 429, включая паузы между попытками, — в статье 429 Too Many Requests от AI API.
Ограничьте max_tokens, чтобы ответ не разрастался#
Таймаут перестаёт ждать, но не заставляет модель писать меньше. Без max_tokens модель с лимитом ответа 32 768 токенов вправе написать очень длинный текст, а длинный текст — это долго. Ставьте лимит под задачу: 800 для сводки обращения, 5 для ответа «да/нет».
Цена такого лимита — ответ может обрезаться. Смотрите resp.: length значит, что лимит кончился раньше, чем модель закончила. Подробно — в статье Почему ответ ИИ обрывается на полуслове. Если выводить ответ потоком, первые слова приходят сразу и ждать кажется короче, см. стриминг в Python.
Во что повторы обходятся#
Повтор — это новый запрос. Если попытка была обработана, это отдельный запрос со своими токенами. Допустим, сводка обращения — 1 500 токенов на входе и 400 на выходе: один запрос стоит $0.000063 по сегодняшним ценам, а остальные модели есть в таблице цен. Цена модели фиксирована, пиковых часов нет, поэтому повтор в три часа ночи стоит столько же, сколько в полдень.
Подвиньте ползунки под свой пакет:
Калькулятор стоимости
Пакет из 1 000 сводок обращений
Входные токены на запрос: 1 500 · Выходные токены на запрос: 400 · Запросов в день: 1 000
| Модель | В день | В месяц |
|---|---|---|
| GLM-5.3-Flash$0.02$0.04 | $0.046 | $1.38 |
| DeepSeek-V4-Flash$0.015$0.10 | $0.0625 | $1.88 |
| MiniMax-M2.7$0.05$0.15 | $0.135 | $4.05 |
Таблица показывает один проход. Если десятая часть пакета требует второй попытки, добавьте десятую часть. Повторы — дешёвая страховка, пока сбои редки, и дорогая привычка, если поставить max_retries=10 на эндпоинт, который постоянно падает.
Перед выкаткой#
Чек-лист
Таймауты и повторы, готово к продакшену



