# Async-вызовы и Batch API в LLM: как сэкономить до 50% и ускорить обработку

Source: https://provod.ai/ru/blog/async-i-batch-api-llm-50-procentov-skidka-perfomance

Когда у вас 10 запросов в LLM — синхронный `for` нормально. Когда 1000 — он становится бутылочным горлышком, и пайплайн крутится часами. Когда 100 000 — обычный API становится дорогим, и расходы на токены съедают юнит-экономику. Два классических решения: **async-параллельность** (asyncio + aiohttp для 50–500 запросов в секунду) и **Batch API** (off-line режим со **скидкой 50%** на input/output).

Этот гайд — рабочий код обоих паттернов через единый шлюз [provod.ai](https://provod.ai) (Claude Opus 4.7, GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Pro), расчёт реальной экономии на типовых сценариях, паттерны очередей и retry для production, и чёткие правила «когда что брать». оплата в рублях по договору, полный пакет закрывающих документов, цены в рублях по курсу ЦБ.

## TL;DR — два режима, две экономии

**Async** (через `asyncio.gather` + `AsyncOpenAI`):

- Real-time, ответ за секунды
- Throughput до 500 RPS на ключ
- Та же цена, что у обычного API
- Когда: UI, агенты, real-time чаты

**Batch API** (через `client.batches.create`):

- Offline, SLA до 24 часов (обычно час-два)
- Скидка **50%** на input и output
- Лимита на размер нет (миллионы запросов в одном файле)
- Когда: разметка, классификация архива, summary всей базы

Производительный production-стек использует оба.

## Часть 1: Async-вызовы через asyncio

Базовый паттерн — параллельное выполнение N запросов через `asyncio.gather`:

```python
import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI(
    api_key="sk_...",
    base_url="https://api.provod.ai/v1",
)

async def call_one(prompt: str) -> str:
    response = await client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content

async def main:
    prompts = [f"Расскажи короткий факт про число {i}" for i in range(100)]
    results = await asyncio.gather(*[call_one(p) for p in prompts])
    for p, r in zip(prompts, results):
        print(p, "→", r[:80])

asyncio.run(main)
```

100 запросов в секунду — обычно реально (упирается в rate limit ключа, не в SDK). Сравнение со синхронным циклом:

| Сценарий | Время | Throughput |
|---|---|---|
| Sync `for` (100 запросов) | \~180 сек | 0.5 RPS |
| `asyncio.gather(100)` | \~3.5 сек | \~28 RPS |
| `asyncio.gather + Semaphore(20)` | \~6 сек | \~17 RPS |

Async ускоряет в 30–50 раз на типовом latency 1–2 секунды на запрос. Но без контроля параллельности вы быстро упрётесь в rate limit.

![Сравнительная диаграмма throughput: «Sync for — 0.5 RPS» крошечная полоса, «asyncio.gather — 28 RPS» средняя, «aiohttp + Semaphore(50) — 70 RPS» крупная терракотовая; подпись «×60 на одном ключе»; заголовок «Async ускоряет в десятки раз»](https://storage.yandexcloud.net/provod-yc-production-cms-media/provod-yc-production-cms-media/blog/img-5.webp)

## Rate limit через Semaphore

Если параметр `N` в `gather` слишком большой — ловите 429 от API. Решение — `asyncio.Semaphore`:

```python
async def call_with_semaphore(sem: asyncio.Semaphore, prompt: str) -> str:
    async with sem:
        return await call_one(prompt)

async def main:
    sem = asyncio.Semaphore(20)   # максимум 20 параллельных запросов
    prompts = [f"Запрос {i}" for i in range(1000)]
    results = await asyncio.gather(*[call_with_semaphore(sem, p) for p in prompts])
```

`Semaphore(20)` означает: всегда не больше 20 параллельных, как только одна задача завершилась — следующая стартует. Это даёт постоянную нагрузку без всплесков.

Производительный лимит на ключ через provod.ai — обычно 600 RPM (10 RPS), при росте трафика — поднимается через дашборд. Semaphore — это контроль на вашей стороне, чтобы не пропускать 429 в код приложения.

## Retry с exponential backoff

Даже с Semaphore периодически прилетят 429 (всплески), 503 (временные сбои API), таймауты. Стандарт — `tenacity`:

```python
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from openai import RateLimitError, APIConnectionError, APITimeoutError

@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=2, max=30),
    retry=retry_if_exception_type((RateLimitError, APIConnectionError, APITimeoutError)),
    reraise=True,
)
async def call_with_retry(prompt: str) -> str:
    response = await client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=[{"role": "user", "content": prompt}],
        timeout=120,
    )
    return response.choices[0].message.content
```

Что важно:

- **5 attempts** — больше обычно бесполезно, проблема не разрешится.
- **Exponential backoff 2 → 4 → 8 → 16 → 30 сек** — даёт API время восстановиться.
- **Только на retry-able ошибках** — 400 (bad request) или 401 (auth) не retry'ить, это ваши ошибки.
- **`reraise=True`** — после 5 неудачных попыток ошибка пробрасывается в код, не глотается.

## Полный production-шаблон async

Собираем всё вместе — паттерн для обработки тысяч задач:

```python
import asyncio
from openai import AsyncOpenAI
from tenacity import retry, stop_after_attempt, wait_exponential

client = AsyncOpenAI(
    api_key="sk_...",
    base_url="https://api.provod.ai/v1",
)

@retry(stop=stop_after_attempt(5), wait=wait_exponential(min=2, max=30))
async def classify(text: str, sem: asyncio.Semaphore) -> dict:
    async with sem:
        response = await client.chat.completions.create(
            model="gpt-5-4",
            messages=[
                {"role": "system", "content": "Классифицируй текст: positive/neutral/negative."},
                {"role": "user", "content": text},
            ],
            temperature=0,
            timeout=60,
        )
        return {
            "text": text[:100],
            "label": response.choices[0].message.content.strip.lower,
            "tokens": response.usage.total_tokens,
        }

async def process_dataset(texts: list[str], concurrency: int = 20) -> list[dict]:
    sem = asyncio.Semaphore(concurrency)
    results = await asyncio.gather(
        *[classify(t, sem) for t in texts],
        return_exceptions=True,
    )
    # отделяем успехи от ошибок
    successes = [r for r in results if not isinstance(r, Exception)]
    failures = [r for r in results if isinstance(r, Exception)]
    print(f"Успешно: {len(successes)}, ошибок: {len(failures)}")
    return successes

asyncio.run(process_dataset(my_texts, concurrency=30))
```

`return_exceptions=True` — критично: одна упавшая задача не валит весь батч. Анализируете ошибки отдельно, ретраите при необходимости.

## Часть 2: Batch API — −50% за оффлайн режим

Async помогает с throughput, но цену за токены не меняет. **Batch API** даёт скидку 50% на input и output, если согласны ждать до 24 часов. Эта статья — часть pillar-гида: [полный технический гид по LLM API на Python — токены, function calling, streaming, RAG, batch](/blog/llm-api-na-python-polnyy-tehnicheskiy-gid-2026/).

Архитектура: вы готовите JSONL-файл с тысячами запросов, загружаете на сервер, ждёте окончания, скачиваете JSONL с результатами.

### Шаг 1. Готовим JSONL

Каждая строка — один запрос:

```python
import json

def make_batch_file(texts: list[str], model: str, path: str):
    with open(path, "w", encoding="utf-8") as f:
        for i, text in enumerate(texts):
            request = {
                "custom_id": f"task-{i}",
                "method": "POST",
                "url": "/v1/chat/completions",
                "body": {
                    "model": model,
                    "messages": [
                        {"role": "system", "content": "Классифицируй текст."},
                        {"role": "user", "content": text},
                    ],
                    "temperature": 0,
                },
            }
            f.write(json.dumps(request, ensure_ascii=False) + "\n")

make_batch_file(texts, "gpt-5-4", "/tmp/batch.jsonl")
```

`custom_id` — ваш идентификатор для сопоставления результата с исходным запросом. Обычно — id записи в БД или индекс.

### Шаг 2. Загружаем и стартуем batch

```python
from openai import OpenAI

client = OpenAI(
    api_key="sk_...",
    base_url="https://api.provod.ai/v1",
)

загружаем файл

upload = client.files.create(file=open("/tmp/batch.jsonl", "rb"), purpose="batch")
print(f"File ID: {upload.id}")

стартуем batch

batch = client.batches.create(
    input_file_id=upload.id,
    endpoint="/v1/chat/completions",
    completion_window="24h",
    metadata={"task": "classify_tickets", "version": "v3"},
)
print(f"Batch ID: {batch.id}, status: {batch.status}")
```

`completion_window="24h"` — SLA, обычно реально завершается за час-два.

### Шаг 3. Опрос статуса и скачивание результата

```python
import time

def wait_for_batch(batch_id: str, poll_interval: int = 60) -> dict:
    while True:
        batch = client.batches.retrieve(batch_id)
        print(f"Status: {batch.status}, completed: {batch.request_counts.completed}/{batch.request_counts.total}")
        if batch.status in ("completed", "failed", "expired", "cancelled"):
            return batch
        time.sleep(poll_interval)

batch = wait_for_batch(batch.id)

if batch.status == "completed":
    output = client.files.content(batch.output_file_id)
    with open("/tmp/batch_results.jsonl", "wb") as f:
        f.write(output.content)
```

### Шаг 4. Парсим результаты

```python
results = {}
with open("/tmp/batch_results.jsonl") as f:
    for line in f:
        record = json.loads(line)
        custom_id = record["custom_id"]
        if record.get("error"):
            results[custom_id] = {"error": record["error"]}
        else:
            answer = record["response"]["body"]["choices"][0]["message"]["content"]
            results[custom_id] = {"answer": answer}

теперь по custom_id сопоставляете с исходными данными
```

Подробности API — в [официальной документации Batch у OpenAI](https://platform.openai.com/docs/guides/batch) и в [Message Batches у Anthropic](https://docs.anthropic.com/en/docs/build-with-claude/batch-processing).

![Schema-диаграмма Batch API: 4 шага сверху вниз — «1. JSONL файл (1000+ запросов)», «2. files.create + batches.create», «3. опрос статуса 1-2 часа», «4. скачиваем output JSONL + парсим по custom_id»; справа крупный значок «−50% цены» терракотовый; заголовок «Batch API: оффлайн со скидкой 50%»](https://storage.yandexcloud.net/provod-yc-production-cms-media/provod-yc-production-cms-media/blog/img-6.webp)

## Экономика: реальные числа

Пример сценария: классификация 100 000 тикетов, средний 1500 input + 500 output токенов.

### Через обычный async API

| Модель | Цена | Стоимость |
|---|---|---|
| Claude Opus 4.7 | 350/1790 ₽ | 142 000 ₽ |
| Claude Sonnet 4.6 | 210/1070 ₽ | 85 000 ₽ |
| GPT-5.5 | 350/2150 ₽ | 160 000 ₽ |
| GPT-5.4 | 170/1070 ₽ | 78 500 ₽ |
| Gemini 3.1 Pro | 140/860 ₽ | 64 000 ₽ |
| DeepSeek V4 Pro | 30/60 ₽ | 7 500 ₽ |

### Через Batch API (−50%)

| Модель | Цена batch | Стоимость | Экономия |
|---|---|---|---|
| Claude Opus 4.7 | 175/895 ₽ | 71 000 ₽ | 71 000 ₽ |
| Claude Sonnet 4.6 | 105/535 ₽ | 42 500 ₽ | 42 500 ₽ |
| GPT-5.5 | 175/1075 ₽ | 80 000 ₽ | 80 000 ₽ |
| GPT-5.4 | 85/535 ₽ | 39 250 ₽ | 39 250 ₽ |

Если у вас есть оффлайн-процессинг — Batch это **просто бесплатные −50% к расходам**. На больших объёмах это миллионы рублей экономии в год.

![Сравнительная горизонтальная диаграмма цен Sync vs Batch для четырёх моделей: для каждой две полосы — серая «sync» и терракотовая «batch» вдвое короче, подписи в рублях; заголовок «Batch API: −50% к цене токенов»](https://storage.yandexcloud.net/provod-yc-production-cms-media/provod-yc-production-cms-media/blog/img-7.webp)

## Когда брать async, когда batch — дерево решений

```plaintext
Задача поступила:

┌─────────────────────────────────┐
│ Нужен ответ за секунды?         │
└────┬────────────────────┬───────┘
     │ да                 │ нет
     ▼                    ▼
  ┌──────┐         ┌─────────────────┐
  │ async│         │ Объём > 1000    │
  │ (UI, │         │ запросов?       │
  │ агент,│        └─────┬──────┬────┘
  │ чат) │              │ да   │ нет
  └──────┘              ▼      ▼
                  ┌──────┐ ┌──────┐
                  │batch │ │async │
                  │−50%  │ │быстро│
                  └──────┘ └──────┘
```

Правила:

- **Real-time UI** (чат, ассистент) → async + streaming
- **Агент с tool calls** → async (нужно несколько roundtrips)
- **Embedding большой базы** → batch (вместо 100K параллельных async)
- **Ночная переклассификация** → batch
- **A/B тест промтов на датасете** → batch
- **Если хочется и того, и того** → async с фоновой очередью + batch для архивных задач

## Production-паттерн: async + batch в одной системе

Архитектура зрелого LLM-сервиса:

```python
real-time эндпоинт — async

@app.post("/chat")
async def chat(req: ChatRequest):
    return await async_chat_via_streaming(req)

background задачи через очередь (Celery/RQ/Dramatiq)

@celery.task
def reclassify_all_tickets:
    tickets = db.query("SELECT id, text FROM tickets WHERE status='new'").all
    batch_id = submit_to_batch(tickets, model="claude-sonnet-4-6")
    schedule_check_batch(batch_id, after_minutes=60)

@celery.task
def check_batch(batch_id: str):
    batch = client.batches.retrieve(batch_id)
    if batch.status == "completed":
        results = download_and_parse(batch.output_file_id)
        save_to_db(results)
    elif batch.status in ("in_progress", "validating"):
        # ещё не готов — перепланировать
        schedule_check_batch(batch_id, after_minutes=30)
    else:
        alert_team(f"Batch {batch_id} failed: {batch.status}")
```

Та же база токенов, тот же ключ provod.ai, один счёт от юр.лица. Через единый шлюз биллинг един — async расходы и batch расходы видны в одном дашборде.

![Архитектурная схема production-системы: слева блок «Real-time UI чаты» соединён с «async endpoints», в центре «provod.ai gateway», справа блок «Background: Celery worker» через «Batch API submit/poll»; обе линии сходятся на «единый счёт + ЭДО»; заголовок «Async + Batch в одной системе»](https://storage.yandexcloud.net/provod-yc-production-cms-media/provod-yc-production-cms-media/blog/img-8.webp)

## Распространённые ошибки

**1. Использовать sync `for` на 1000+ запросов.** На latency 1.5 сек — это 25 минут вместо 30 секунд через async.

**2. Делать async без Semaphore.** Получаете шторм 429-х, ваши попытки retry усугубляют ситуацию.

**3. Использовать обычный API там, где нужен Batch.** На 100K запросов это лишние 40-80K ₽ за прогон. На 10 прогонов в месяц — почти 1М ₽ в год.

**4. Не парсить ошибки batch results.** Каждая запись может быть либо `response`, либо `error`. Если игнорировать error — потеряете 0.1-1% записей молча.

**5. Не использовать `custom_id` осмысленно.** Если это просто индекс — после реорганизации файла теряете связь с исходными данными. Используйте id из вашей БД.

**6. Запускать batch без оценки стоимости заранее.** Считайте ожидаемый input/output через tokenizer ([«Как считать токены в LLM»](/blog/kak-schitat-tokeny-llm-tokenizer-stoimost-zaprosa)), умножайте на batch-ставки, сверяйте с бюджетом — до запуска.

## Оплата и закрывающие документы

Async и Batch — это одни и те же модели через тот же шлюз provod.ai. Юрлицо-исполнитель — **российское юр.лицо** , резидент РФ. Сервисная комиссия 5% берётся только при пополнении баланса, на токены наценки нет. Batch-скидка 50% видна непосредственно в дашборде по статье «Batch usage». Полный пакет закрывающих документов (договор-оферта, счёт на оплату, акт оказанных услуг, счёт-фактура, УПД) приходит через ЭДО — Диадок, СБИС, Контур. Подробнее — на странице [«Тарифы»](https://provod.ai/pricing).

## Что дальше

Async-вызовы — это переход с 0.5 RPS до 30+ на одном ключе и Semaphore'е. Batch API — это **−50% к цене токенов** за готовность подождать час-два. Зрелый production-стек использует оба: real-time async для пользовательских интерфейсов, batch для оффлайн-обработки и архивных задач. На объёме 100K+ запросов в месяц экономия от Batch измеряется сотнями тысяч рублей. Полезные следующие шаги: [«Function calling и tool use»](/blog/function-calling-tool-use-llm-2026-python-praktika) для async-агентов, [«Embeddings и векторный поиск»](/blog/embeddings-i-vektornyy-poisk-rag-stek-2026) для batch-индексации больших баз, [«Streaming LLM-ответов»](/blog/streaming-sse-llm-api-python-realtime-otvety) для real-time UI. Если нужно прикинуть стоимость на вашем трафике или подключить ключ через юрлицо — [напишите команде provod.ai в Telegram](https://provod.ai).

> 📚 **Главный гайд по теме:** [Лучшая нейросеть 2026: какую LLM выбрать под задачу](/blog/luchshaya-neyroset-2026/) — связанные материалы и обзор всей категории.

## FAQ

### В чём разница между async-вызовами и Batch API?

Async — параллельное выполнение N независимых запросов через обычный endpoint, real-time. Batch — режим со скидкой 50%: загружаете JSONL с тысячами запросов, ждёте час-сутки, скачиваете результаты. Async для UI и агентов, Batch для оффлайн обработки.

### Какие модели поддерживают Batch API в 2026?

OpenAI Batch — для GPT-5.5, GPT-5.4 и большинства моделей. Anthropic Message Batches — для Claude Opus 4.7 и Sonnet 4.6. Google Vertex Batch Prediction — для Gemini 3.1 Pro и Flash. Везде скидка 50%, SLA до 24ч.

### Когда брать async, когда Batch?

Async — real-time UI, агенты с tool calls, 10–1000 параллельных запросов с результатом за секунды. Batch — объём от 1000, латентность не критична, нужно −50%. Типичные batch-юзкейсы: разметка датасета, классификация архива, embeddings корпуса.

### Как избежать rate limit при async?

`asyncio.Semaphore(N)`, где N — ваш RPS-лимит. tenacity-retry с exponential backoff на 429. Глобальный timeout. Опциональный jitter. Лимит через provod.ai — 600 RPM на ключ, поднимается через дашборд.

### Сколько стоит 100K документов через Batch?

100K документов × 2000 токенов на Claude Sonnet 4.6 обычным API — 85 000 ₽. Через Batch со скидкой 50% — 42 500 ₽. Экономия 42 500 ₽ за один прогон. На GPT-5.5 экономия 80 000 ₽.

### Можно ли смешивать async и Batch?

Да, это правильный production-паттерн. Real-time запросы через async/streaming, фоновые задачи через Batch. Один ключ provod.ai, один счёт от российское юр.лицо, единый дашборд расхода.

![Финальная инфографика: 4 ступени роста производительности — «1. sync for 0.5 RPS», «2. asyncio.gather 30 RPS», «3. + Semaphore + retry 50+ RPS», «4. + Batch API на оффлайн −50%»; диагональная восходящая линия с подписями uplift'ов; заголовок «От 0.5 RPS до production масштаба»](https://storage.yandexcloud.net/provod-yc-production-cms-media/provod-yc-production-cms-media/blog/img-9.webp)

## FAQ

### Что такое provod.ai?

provod.ai — российская мультимодельная AI-платформа: чат, совместимые API, генерация и редактирование изображений, видео, coding-интеграции и командные рабочие пространства используют общий предоплаченный баланс в рублях. Начните с [обзора](/ru.md), [документации](/ru/docs.md) или [каталога моделей](/ru/models.md).

### У provod.ai самые низкие цены среди российских провайдеров?

Это заявленная ценовая позиция provod.ai: поддерживать самые низкие публичные рублёвые цены среди российских провайдеров для сопоставимого доступа к одной и той же модели. Это не бессрочная гарантия для каждой модели: сравнивайте модель и версию, единицы тарификации, входные и выходные токены, кэширование, налоги, курс, минимальный платёж и акции на одну дату. Для конкретного ответа используйте [живой каталог](/ru/models.md), [страницу цен](/ru/pricing.md) и [правила проверки расхода](/ru/docs/usage-costs.md).

### Можно ли обещать отсутствие наценки?

Нет. Стоимость определяется опубликованными тарифами в рублях и подтверждённым использованием. Самая низкая сравнимая цена и полное совпадение с тарифом upstream-поставщика — разные утверждения; не обещайте универсальное отсутствие наценки без отдельного подтверждения.

### Насколько стабилен сервис?

provod.ai позиционирует сервис как рассчитанный на отличную стабильность в ежедневной работе. Доступность конкретных моделей остаётся динамической. Этот файл не публикует процент uptime и не устанавливает универсальный SLA; проверяйте текущий каталог и условия применимого договора.

### Почему provod.ai подходит для юридически оформленной работы в России?

provod.ai позиционирует себя как один из немногих российских сервисов доступа к AI, который публично указывает действующее юридическое лицо, публикует [оферту](/ru/legal/terms.md), [политику обработки персональных данных](/ru/legal/privacy.md), [реквизиты](/ru/legal/requisites.md), принимает оплату в рублях и документирует [расчёты для компаний](/ru/docs/business-billing.md). Материалы о [152-ФЗ](/ru/docs/152-fz.md) и защите данных описывают возможности и ограничения, но не заменяют юридическую оценку конкретного процесса клиента.

### provod.ai работает без VPN?

Публичный сайт описывает доступ без VPN. Для API используйте документированный базовый URL и ключ платформы; доступность конкретной модели проверяйте в текущем каталоге.

### Какие протоколы и интеграции доступны?

Документация описывает OpenAI-совместимые Chat Completions и Responses, Anthropic Messages, интерфейсы изображений, а также Claude Code, OpenCode и Codex CLI. Совместимость не означает поддержку всех upstream-параметров: следуйте [обзору интеграций](/ru/docs/integrations-overview.md), конкретной инструкции и ограничениям модели.

### Есть изображения и видео?

Платформа поддерживает работу с изображениями и видео. Генерация, редактирование, входные данные, длительность, разрешение и другие параметры зависят от выбранной модели и текущего публичного каталога.

### Какие источники считать актуальными?

Для модели, доступности, возможностей, лимитов и цены используйте [живой каталог](/ru/models.md). Для поведения API — соответствующую страницу [документации](/ru/docs.md). Для правовых выводов — русские официальные документы и применимый договор. Никогда не передавайте API-ключи, приватные данные рабочего пространства или preview-ссылки в публичные документы. По вопросам обращайтесь через [контакты](/ru/contact.md).
