Назад в блог

Решение проблемИнструкция

429 Too Many Requests в API нейросети: повторы с backoff

Что значит 429 в API нейросети и как её обработать: экспоненциальная задержка с джиттером, Retry-After и параллелизм агента, код на Python и JavaScript.

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

Схема цикла запросов: ответ 429, шаг ожидания с растущей паузой и повтор
Содержание
  1. Что значит 429
  2. Сначала заголовки, потом догадки о паузе
  3. Python: backoff с джиттером
  4. JavaScript: тот же цикл
  5. Как агенту обрабатывать 429
  6. Циклы агентов на CheapTokens
  7. Если всё равно не работает

Ответ 429 Too Many Requests от API нейросети значит, что запрос отклонён: вы отправили слишком много и слишком быстро, либо упёрлись в лимит аккаунта. Решение — подождать и повторить с экспоненциальной задержкой и джиттером, взять паузу из заголовка Retry-After, если он есть, и ограничить число параллельных запросов агента. Не повторяйте 429, который на самом деле лимит расходов или баланса: ожидание его не исправит.

Руководство основано на официальных материалах о лимитах OpenAI и Anthropic, проверенных 22 октября 2026 года. На обеих страницах нет даты обновления.

Что значит 429#

OpenAI описывает 429 как запрос, отклонённый из-за превышения лимита (rate_limit_error, с кодом slow_down, когда трафик растёт слишком быстро), либо из-за достигнутого жёсткого лимита расходов. У Anthropic 429 тоже приходит как rate_limit_error. 429 из-за лимита расходов приходит без заголовка retry-after и продолжает повторяться, пока доступ не вернётся.

С ним легко перепутать два соседних ответа. 503 server_is_overloaded у OpenAI и 529 overloaded_error у Anthropic — это перегрузка, а не ваш лимит. Обычно их обрабатывают тем же циклом повторов, но причина у них другая.

Итого три случая, и реакция на каждый своя:

Ответ Причина Что делать
429, лимит запросов Слишком много запросов или токенов в минуту Подождать, повторить с backoff
429, лимит расходов или баланса Достигнут лимит аккаунта Не повторять; поднять лимит или пополнить баланс
503 / 529, перегрузка Занята сторона провайдера Повторять с backoff и ограничением

Если закончился баланс, читайте ошибку «not enough tokens». Про сетевые сбои вроде таймаутов и отказов в соединении — частые ошибки подключения.

Сначала заголовки, потом догадки о паузе#

OpenAI документирует такие заголовки в ответах с ограничением: Retry-After, а также x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests и варианты -tokens. Другие API могут присылать часть из них или ни одного, поэтому пишите клиент так: если Retry-After есть — используйте его, если нет — свой график.

Совет OpenAI по повторам, коротко:

  • Retry-After — это минимальное ожидание; добавьте сверху небольшую случайную паузу.
  • Без него — экспоненциальный backoff с джиттером.
  • Ограничьте и число попыток, и общее время повторов.
  • Не накладывайте свои повторы на повторы SDK.
  • Не повторяйте ошибки квоты и биллинга.
  • Неудачные запросы тоже считаются в минутный лимит, поэтому агрессивный цикл делает хуже.
  • При стриминге не повторяйте запрос, если вы уже получили часть вывода.

Python: backoff с джиттером#

Официальный пакет openai работает с любым OpenAI-совместимым адресом. Отключите собственные повторы SDK (max_retries=0), чтобы они не умножались на ваши. Id модели берите из документации CheapTokens: у разных провайдеров они отличаются.

python
import os
import random
import time

from openai import OpenAI, APIStatusError

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    max_retries=0,  # we retry ourselves
)

RETRYABLE = {429, 500, 502, 503, 504}


def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
    started = time.monotonic()
    for attempt in range(max_attempts):
        try:
            return client.chat.completions.create(model=model, messages=messages)
        except APIStatusError as err:
            last = attempt == max_attempts - 1
            if err.status_code not in RETRYABLE or last:
                raise
            header = err.response.headers.get("retry-after")
            if header and header.replace(".", "", 1).isdigit():
                delay = float(header) + random.uniform(0, 1)  # minimum wait + jitter
            else:
                delay = random.uniform(0, min(30.0, 2.0 ** attempt))  # full jitter
            if time.monotonic() - started + delay > max_total:
                raise
            time.sleep(delay)

Верхняя граница паузы без заголовка растёт 1, 2, 4, 8 секунд (максимум 30), а random.uniform выбирает случайное значение в её пределах, чтобы много воркеров не просыпались одновременно. Retry-After может быть и HTTP-датой; код выше обрабатывает числовую форму, иначе переходит на backoff.

JavaScript: тот же цикл#

javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.cheaptokens.dev/v1",
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  maxRetries: 0, // we retry ourselves
});

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
  const started = Date.now();
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await client.chat.completions.create({ model, messages });
    } catch (err) {
      const last = attempt === maxAttempts - 1;
      if (!RETRYABLE.has(err.status) || last) throw err;
      const header = Number(err.headers?.get?.("retry-after"));
      const delayMs = Number.isFinite(header) && header > 0
        ? header * 1000 + Math.random() * 1000 // minimum wait + jitter
        : Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
      if (Date.now() - started + delayMs > maxTotalMs) throw err;
      await sleep(delayMs);
    }
  }
}

Когда всё работает, запрос с ограничением просто занимает больше времени и возвращает обычный ответ. Если после последней попытки ошибка осталась, поднимается исходное исключение — залогируйте его или отложите задачу.

Как агенту обрабатывать 429#

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

  1. Ограничьте параллелизм. Семафор на 3–5 запросов — разумное начало; повышайте, пока ошибки редки.
  2. Один ограничитель на всех. Если несколько воркеров используют один ключ, лимиты у них общие, и цикла повторов на каждого мало: нужна одна очередь или один семафор на процесс.
  3. Пауза общая. Когда один вызов получил 429 с паузой, остановите весь пул на это время, а не только упавший вызов.
  4. Делайте запросы меньше. Меньше токенов в запросе — меньше давление на лимиты токенов в минуту: обрезайте историю и вывод инструментов. Куда уходят токены — в статье как оценить стоимость ИИ-агента.
  5. Останавливайтесь на ошибках без повтора. 401, 400 или ошибка баланса сами не пройдут: завершайте сразу и сообщайте.
python
import asyncio

limit = asyncio.Semaphore(4)  # at most 4 requests in flight


async def run(job):
    async with limit:
        return await call_model(job)  # your async call with the retry loop above

Циклы агентов на CheapTokens#

Для циклов агентов с множеством коротких вызовов в CheapTokens — MiniMax-M2.7: одна цена в любое время суток, контекст 180K, до 8 192 токенов в ответе и один баланс на все модели. Выход стоит $0.15 за 1M токенов, а в сумме, с учётом входа и выхода, дешевле OpenRouter. Работает через OpenAI-совместимый адрес https://api.cheaptokens.dev/v1. Код выше уже указывает на этот адрес; подставьте id модели из документации CheapTokens и запускайте.

Живые цены для нагрузки 500 000 токенов на входе и 100 000 на выходе:

Модель Стоимость OpenRouter
GLM-5.3-Flash $0.014 $0.0282
DeepSeek-V4-Flash $0.0175 $0.0394
MiniMax-M2.7 $0.04 $0.27

500 000 входных и 100 000 выходных токенов всего. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.

Смотрите все цены и документацию по настройке, создайте ключ и отправьте первый запрос.

Если всё равно не работает#

  • 429 на самом первом запросе. Прочитайте тип и текст ошибки в теле ответа: 429 из-за лимита расходов или аккаунта продолжает падать при повторах, пока лимит не поднят или доступ не вернулся, а 429 из-за лимита скорости проходит после паузы.
  • 429 приходит в длинном цикле. Возможно, повторы накладываются: повторы SDK умножаются на ваши. Выставьте для SDK ноль, как в примерах.
  • Ошибки пачками в начале пакета. Наращивайте нагрузку постепенно, а не запускайте всех воркеров разом; OpenAI сообщает slow_down, когда трафик растёт слишком быстро.
  • Стриминг оборвался посередине. Не повторяйте его вслепую после того, как получили часть вывода: решите, начинать ответ заново или продолжать с того, что есть.
  • В ответе нет Retry-After. Код переходит на backoff с джиттером, менять ничего не нужно.