Асинхронные запросы OpenAI в Python: тысячи запросов с повторами и учётом
Рабочий скрипт для партии из тысяч запросов: ограничение параллельности, повторы только там, где они помогают, подсчёт токенов и денег.
6 мин · · разработка · автоматизация
Асинхронные запросы OpenAI в Python нужны, когда надо прогнать через модель тысячи текстов: описания товаров, обращения клиентов, строки из базы. Последовательный цикл здесь слишком медленный, а «запустить всё разом» упирается в ошибки и неучтённые расходы. Ниже — готовый скрипт на asyncio и AsyncOpenAI с ограничением параллельности, повторами при 429 и 503, учётом токенов и стоимости, а также расчёт цены партии.
Асинхронные запросы OpenAI в Python: из чего состоит скрипт
Четыре части:
AsyncOpenAI— асинхронный клиент из официального пакетаopenai(pip install openai). Подключается к Zukko так же, как обычный:base_url="https://api.zukko.pro/v1"и ключfetch_....asyncio.Semaphore— ограничивает, сколько запросов выполняется одновременно. Без него 10 000 задач стартуют разом.- Повторы с экспоненциальной задержкой — только для временных ошибок. Ошибки ключа, баланса и формата запроса повторять бессмысленно.
- Учёт
usage— считаем входные, выходные и кэшированные токены и сверяем стоимость.
Модель в примере — deepseek-v4.1-flash, одна из самых дешёвых в каталоге ($0.013 за 1M входа, $0.038 за 1M выхода). Любую другую модель можно подставить в MODEL. Актуальный список и цены — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.
Какие ошибки повторять
Ошибки Zukko и что с ними делать в пакетной обработке:
| Код | Что значит | Что делать |
|---|---|---|
| 401 | неверный ключ | остановить партию, проверить ключ |
| 402 | не хватает баланса | остановить, пополнить баланс |
403 model_not_allowed_for_key | модели нет в списке разрешённых для ключа | остановить, поправить ключ или модель |
| 413 | слишком большой запрос | не повторять, уменьшить вход |
429 key_daily_limit_reached / key_monthly_limit_reached | исчерпан лимит ключа | остановить: повтор не поможет до сброса лимита |
| 429 с другим кодом | ограничение на стороне провайдера модели (общего rate limit у Zukko нет) | повторить с паузой |
| 503 | провайдер недоступен | повторить с паузой |
503 model_disabled | модель выключена | остановить и взять другую модель из GET /v1/models |
Если сервер прислал заголовок Retry-After, ждём столько секунд, сколько в нём указано. Иначе пауза растёт экспоненциально: 1, 2, 4, 8… секунд плюс случайная добавка, чтобы повторы разных задач не совпадали по времени.
Подробнее про 429 — в статье об ошибке 429, а как переключаться на запасную модель при 503 — в статье об обработке ошибок и fallback.
Полный скрипт
import asyncio
import random
import openai
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.zukko.pro/v1",
api_key="fetch_...",
max_retries=0, # повторы делаем сами, чтобы видеть коды ошибок
timeout=120,
)
MODEL = "deepseek-v4.1-flash"Разбор ключевых мест
Почему max_retries=0
OpenAI SDK по умолчанию сам повторяет часть ошибок (два раза). Это удобно для единичных вызовов, но в партии мешает: повторы SDK не видят кодов Zukko и не отличают исчерпанный лимит ключа от временного 429. Поэтому встроенные повторы выключены, и вся логика — в функции call.
Семафор
asyncio.Semaphore(CONCURRENCY) пропускает к API не больше 16 запросов одновременно, остальные задачи ждут. Значение 16 — стартовое, а не рекомендация провайдера. Если 429 почти нет, можно поднимать; если их много, снижайте. Пауза перед повтором выполняется внутри семафора: пока задача ждёт, она держит слот, и при волне ошибок общий темп сам замедляется.
Повторы и Idempotency-Key
Каждому элементу партии назначен свой заголовок Idempotency-Key. Если запрос уже выполнился и списал деньги, а ответ до вас не дошёл, повтор с тем же ключом вернёт сохранённый ответ и не спишет второй раз. Ключ живёт 24 часа и работает для успешных ответов. Повтор нужно отправлять после того, как первый запрос завершился или упал, — в скрипте так и происходит. Исключение — таймаут на стороне клиента: если сервер в это время ещё обрабатывает первый запрос, повтор может уйти к модели повторно. Поэтому таймаут стоит ставить с запасом. Подробности — в документации о лимитах и идемпотентности.
Если партию перезапускают, используйте другой префикс в ключе (например, номер прогона), иначе повтор с тем же ключом, но другим текстом запроса вернёт 422.
Учёт usage и стоимости
Скрипт считает стоимость двумя способами:
- по прайсу: свежий вход × цена входа + кэш × цена чтения кэша + выход × цена выхода. Свежий вход — это
prompt_tokensминусprompt_tokens_details.cached_tokens; - по заголовку
X-Zukko-Cost-Micros: обычный (не потоковый) ответ Zukko несёт стоимость запроса в микродолларах (1 000 000 = $1). Чтобы прочитать заголовки, вызов идёт черезwith_raw_response, а объект ответа получаем черезraw.parse().
Второе число точнее: оно учитывает всё, что реально списано. У потоковых ответов заголовка нет — там стоимость смотрите в истории списаний.
Сколько стоит партия
Формула для партии без кэша:
стоимость = N × (вход на запрос × цена входа + выход на запрос × цена выхода) / 1 000 000
Пример: 10 000 запросов, в каждом 600 токенов на входе и 200 на выходе. Всего 6 млн входа и 2 млн выхода.
deepseek-v4.1-flash: 6 × $0.013 + 2 × $0.038 = $0.078 + $0.076 = $0.154gpt-6.1-sol: 6 × $0.27 + 2 × $1.32 = $1.62 + $2.64 = $4.26
С кэшем. У GPT-моделей кэшированная часть входа видна в cached_tokens. Допустим, 400 из 600 токенов — общая инструкция, и со второго запроса она читается из кэша. Запись в кэш у GPT отдельно не списывается: первый запрос идёт целиком по цене входа. Тогда для gpt-6.1-sol (чтение кэша $0.013 за 1M):
- первый запрос: 600 × $0.27 / 1 000 000 = $0.000162;
- свежий вход остальных: 9 999 × 200 = 1 999 800 × $0.27 / 1 000 000 = $0.539946;
- чтение кэша: 9 999 × 400 = 3 999 600 × $0.013 / 1 000 000 = $0.0519948;
- выход: 2 млн × $1.32 = $2.64;
- итого: $3.2321028, около $3.232.
Это допущение: сколько входа попадёт в кэш, решает провайдер, и при паузах кэш может истечь — тогда запрос снова идёт целиком по цене входа. Точную сумму дают X-Zukko-Cost-Micros и история списаний.
Перед большим прогоном запустите 50–100 запросов из реальных данных и умножьте их фактическую стоимость на размер партии. Это надёжнее, чем угадывать средний размер ответа.
Если запросов сотни тысяч
- Сохраняйте результаты по ходу. Пишите ответ в файл или базу сразу после получения, а при перезапуске пропускайте уже обработанные
item_id. - Делите на порции. Обрабатывайте по 5–10 тысяч задач за вызов
main, чтобы не держать в памяти сотни тысяч объектов. - Отдельный ключ с лимитом. Создайте в кабинете ключ для пайплайна с дневным лимитом в долларах и списком разрешённых моделей. Ошибка в цикле тогда не съест весь баланс: партия остановится на 429
key_daily_limit_reached. - Смотрите расход. Итог прогона — в кабинете, раздел «Использование», и через API истории списаний.
Базовые примеры клиента — в документации по Python.
Частые вопросы
Сколько запросов отправлять параллельно?
Начните с 10–20 и смотрите на долю 429. Универсального числа нет: оно зависит от модели и провайдера.
Нужно ли повторять 429?
Только если это не лимит ключа. При кодах key_daily_limit_reached и key_monthly_limit_reached повтор не поможет до сброса лимита (дневной сбрасывается в 00:00 UTC).
Почему не использовать встроенные повторы SDK?
Они не различают коды Zukko: лимит ключа или выключенную модель SDK будет повторять так же, как временный сбой. Своя логика позволяет остановить партию сразу.
Как узнать точную стоимость партии?
Сложите X-Zukko-Cost-Micros по всем ответам, как в скрипте, или посмотрите историю списаний в кабинете.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.