429 Too Many Requests — самый полезный ответ, который отправляет ваш LLM-провайдер. Полезнее тела JSON. Действеннее кода ошибки. Потому что в его заголовках спрятан измеритель ёмкости в реальном времени — x-ratelimit-remaining, retry-after — который большинство продакшн-кода игнорирует. Инженеры относятся к лимитам запросов как к стене, о которую разбиваются. Это измеритель, который нужно читать.
Вот как выглядит столкновение со стеной. Ваше приложение отлично работает в 14:00. Трафик растёт к 15:00. К 15:15 каждый запрос возвращает 429. Ваша логика повторных попыток срабатывает — фиксированные интервалы в одну секунду — и создаёт табун (thundering herd). Каждый повтор попадает в то же окно лимита. Десять минут каскадных сбоев. Пользователи видят ошибки. Дежурного поднимают по пейджеру. Исправлением было не «повторять усерднее». Это никогда не было «повторять усерднее».
Разрыв между этими двумя исходами — разбиться о стену против чтения измерителя — это три слоя кода. Эта статья охватывает все три. Реактивный слой: экспоненциальный бэкофф с джиттером. Проактивный слой: троттлинг с учётом заголовков, который читает оставшийся бюджет и замедляется до столкновения со стеной. Предиктивный слой: приостановка до вызова, которая делает контрольную точку состояния агента до того, как следующий вызов исчерпает квоту. Рабочий код Python для каждого слоя. Проверенные в продакшне паттерны.
Фундаментальные концепции аутентификации API и управления ключами, лежащие в основе обработки лимитов, смотрите в нашем руководстве по безопасности API-ключей.
Почему существуют лимиты запросов — и как они на самом деле работают
Лимиты запросов — не наказание. Это защита инфраструктуры. Каждый запрос к API потребляет память GPU и вычисления. Неограниченный клиент может насытить инференс-кластер провайдера за секунды. Лимиты обеспечивают справедливое распределение между всеми пользователями.
Три лимита, о которых нужно заботиться:
- RPM (Requests Per Minute): сколько вызовов API вы можете сделать. Тарифы с оплатой по мере использования: обычно 500–3,000 RPM. Бесплатные: 10–50 RPM. Корпоративные: индивидуально.
- TPM (Tokens Per Minute): суммарные токены всех запросов — вход + выход. Один промпт на 100K токенов потребляет столько же квоты, сколько 500 обычных запросов. Ограничение TPM защищает от этого.
- Параллельные запросы: сколько запросов может быть в полёте одновременно. Превысите — новые запросы встают в очередь или отклоняются. Этот лимит часто не задокументирован и познаётся через болезненный опыт.
Тарифы конкретных провайдеров дают этим лимитам реальные числа. Tier 5 OpenAI (высший уровень с оплатой по мере использования) даёт 10,000 RPM и 30,000,000 TPM для моделей GPT-4.x — но Tier 1 начинается всего с 500 RPM и 200,000 TPM. Claude API от Anthropic предлагает 1,000 RPM на стандартном тарифе с 80,000 TPM для Claude Opus и 400,000 TPM для Claude Sonnet, отражая разную стоимость инференса на модель.
Gemini API от Google даёт 1,500 RPM с оплатой по мере использования при потолке 2,000,000 TPM. Каждый провайдер также применяет переопределения на модель — лимит TPM Claude Opus 4 жёстче, чем Claude Sonnet 4, потому что более крупные модели потребляют пропорционально больше вычислений. Переход с Tier 1 на Tier 5 в OpenAI требует и повышенной истории расходов (от $250 в месяц), и подтверждённого опыта незлоупотребительного использования за 30+ дней.
Высокие лимиты не получают вежливой просьбой — их зарабатывают устойчивыми паттернами продакшн-трафика, доказывающими, что вы не зальёте кластер. Знать свой точный тариф и его лимиты — первый шаг к построению любого из трёх последующих слоёв.
Как читать свои текущие лимиты. Каждый ответ API содержит заголовки лимитов — и почти никто их не читает. Документация OpenAI по лимитам объясняет формат заголовков и структуру тарифов:
x-ratelimit-remaining-requests: 487
x-ratelimit-remaining-tokens: 823000
x-ratelimit-reset-requests: 12s
Они приходят на 200-ответах, а не только на 429. Они точно говорят, сколько бюджета осталось до троттлинга. Выведите их как измеритель на дашборде мониторинга. Срабатывайте при падении остатка ниже 20%. Разница между «мы упёрлись в лимит» и «мы видели его приближение и обошли» — это чтение этих заголовков.
Истории ужасов с лимитами: два инцидента, которые не хотите повторять
Европейская e-commerce платформа запустила AI-ассистента для шопинга на чёрную пятницу на базе GPT-4.5. QA тестировал при 50 параллельных пользователях — продакшн за первый час достиг 2,300. Повторные попытки с фиксированным интервалом превратили 429 в 47-минутный сбой.
Потеря выручки: $180,000 в отслеженных брошенных корзинах за это окно. Корневая причина — не объём трафика, а логика повторов, усилившая всплеск вместо его поглощения.
SaaS-аналитическая компания мигрировала между версиями API OpenAI, не читая журнал изменений лимитов. Новая версия уполовинила их RPM с 3,000 до 1,500 на их тарифе. Существующий код троттлинга предполагал старый лимит.
Продакшен работал шесть дней — пока месячный цикл отчётности не утроил объём запросов. Каждое задание отчёта одновременно получило 429. Обнаружение заняло 22 минуты, потому что их мониторинг отслеживал только ошибки 5xx, а не 429.
Исправление заняло три строки: обновить константу RPM. Урок был постоянным: каждая миграция версии API — это миграция лимитов.
Слой 1: клиентское троттлингование
Самый простой слой. Ведро токенов или семафор, не позволяющие вашему приложению превысить заявленные провайдером лимиты.
import asyncio
import time
class RateLimiter:
"""Token bucket rate limiter for LLM API calls."""
def __init__(self, max_rpm: int):
self.max_rpm = max_rpm
self.tokens = max_rpm
self.last_refill = time.monotonic()
self.semaphore = asyncio.Semaphore(max_rpm // 6) # Concurrency cap
async def acquire(self):
"""Wait until a request can be sent without exceeding RPM."""
# Refill tokens based on elapsed time
now = time.monotonic()
elapsed = now - self.last_refill
refill = elapsed * (self.max_rpm / 60)
self.tokens = min(self.max_rpm, self.tokens + refill)
self.last_refill = now
if self.tokens < 1:
wait_time = (1 - self.tokens) / (self.max_rpm / 60)
await asyncio.sleep(wait_time)
self.tokens = 1
self.tokens -= 1
limiter = RateLimiter(max_rpm=500)
async def rate_limited_api_call(model: str, messages: list):
await limiter.acquire()
# Make the API call
Важный порядок: всегда получайте токен RPM до семафора параллельности. Обратный порядок вызывает блокировку головы очереди — слоты параллельности заполняются запросами, которые не могут отправиться, оставляя запросы, которые могут, без обслуживания.
Этот слой предотвращает самую распространённую самонанесённую рану лимитов: превышение заявленного лимита вашего тарифа, потому что вы не отслеживали скорость вызовов. Для большинства приложений с малым и средним трафиком этого достаточно.
Слой 2: бэкофф с учётом заголовков
Слой 1 предотвращает превышение известных лимитов. Слой 2 разбирается с тем, что происходит, когда фактическая ёмкость провайдера колеблется — а она колеблется постоянно, в зависимости от общей нагрузки кластера.
import random
from openai import OpenAI
client = OpenAI(
base_url="https://api.tokspan.com/v1",
api_key="ts-your-key-here"
)
def chat_with_backoff(messages, model="claude-opus-4-8", max_retries=4):
"""Exponential backoff with jitter + header awareness."""
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages,
timeout=30
)
# Read remaining budget from headers —even on success
remaining = response.headers.get("x-ratelimit-remaining-requests")
if remaining and int(remaining) < 50:
print(f"Rate limit low: {remaining} requests remaining. Slow down.")
return response
except Exception as e:
if "429" in str(e) or "rate_limit" in str(e).lower():
if attempt == max_retries - 1:
raise # Out of retries
# Exponential backoff: 1s —2s —4s —8s
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
import time
time.sleep(wait)
else:
raise # Not a rate limit error —don't retry
Три правила бэкоффа:
- Никогда не повторяйте с фиксированным интервалом. Это создаёт табун. Каждый клиент, повторяющий через t+1с, попадает в то же окно лимита.
- Всегда добавляйте джиттер.
+ random.uniform(0, 1)распределяет повторы по окну. Только это предотвращает большинство каскадных сбоев. - Никогда не повторяйте 401, 403 или 400. Повтор неверного ключа или некорректного запроса не исправит их. Повторяйте только 429 и 5xx.
Чего НЕ делать. Этот паттерн — на удивление часто встречающийся в продакшн-коде — эквивалент крика на человека, который не говорит на вашем языке:
# DO NOT DO THIS
while True:
try:
response = client.chat.completions.create(...)
break
except:
time.sleep(1) # Fixed interval, no jitter, infinite retry
Это создаёт табун и гарантирует, что вы останетесь под лимитом. Каждый повтор приходит в точно ту же точку окна лимита. Инфраструктура провайдера видит всплеск идентичных запросов, троттлит их все, и ваше приложение входит в спираль смерти.
Слой 3: предиктивная приостановка
Слои 1 и 2 реактивны — они отвечают после попадания (или приближения) к лимиту. Слой 3 предиктивен — он читает бюджет до вызова и решает: продолжить, подождать или сделать контрольную точку и приостановиться.
def predict_rate_limit(response_headers: dict) -> str:
"""Three-valued decision based on remaining budget."""
remaining_req = int(response_headers.get("x-ratelimit-remaining-requests", 1000))
remaining_tok = int(response_headers.get("x-ratelimit-remaining-tokens", 1000000))
if remaining_req > 100 and remaining_tok > 200000:
return "continue" # Plenty of budget
elif remaining_req > 20:
return "wait" # Budget running low —short pause
else:
return "checkpoint" # Budget nearly exhausted —suspend
# Usage in an agent loop:
for step in agent_steps:
response = call_llm(current_state)
decision = predict_rate_limit(response.headers)
if decision == "continue":
process(response)
elif decision == "wait":
time.sleep(5) # Short pause, let budget recover
process(response)
else: # checkpoint
save_agent_state(current_state) # Save progress
time.sleep(60) # Wait for rate-limit window reset
resume_agent_from_checkpoint() # Resume without losing work
Состояние искусства 2026 года: agentpause. Библиотека Python, которая читает заголовки лимитов на каждом ответе, предсказывает исчерпание до следующего вызова и делает контрольную точку состояния агента перед приостановкой. Измеренные результаты: 0% крашей против 100% реактивного базового уровня. Ноль ошибок 429. На 80% меньше отходов токенов от неудачных повторов. Если вы запускаете высоконагруженных продакшн-агентов, agentpause или эквивалентная предиктивная логика больше не опциональны — это разница между «наши агенты надёжны» и «наши агенты случайно падают при скачке трафика».
Маршрутизация по нескольким провайдерам: лучшее решение для лимитов
Каждая стратегия выше предполагает общение с одним провайдером. Самая эффективная стратегия лимитов — общение с несколькими.
Ключевое понимание: вместо ожидания сброса лимита одного провайдера отправьте запрос другому. Ваш эффективный лимит становится суммой всех провайдеров, к которым вы можете маршрутизировать.
Раунд-робин маршрутизатор с ведрами токенов на провайдера и автоматическим кулдауном:
PROVIDERS = {
"openai": {"rpm": 2000, "cooldown_until": 0},
"anthropic": {"rpm": 1500, "cooldown_until": 0},
"google": {"rpm": 1000, "cooldown_until": 0},
}
def route_request(messages):
now = time.time()
available = [
p for p, cfg in PROVIDERS.items()
if now > cfg["cooldown_until"]
]
if not available:
raise RuntimeError("All providers in cooldown")
# Round-robin among available providers
provider = available[hash(str(messages)) % len(available)]
try:
return call_provider(provider, messages)
except RateLimitError:
PROVIDERS[provider]["cooldown_until"] = now + 30 # Cooldown for 30s
return route_request(messages) # Retry with a different provider
Агрегационные платформы обрабатывают это на уровне инфраструктуры — ваш единственный endpoint маршрутизирует ко всем провайдерам, с автоматическим кулдауном, отказоустойчивостью и мониторингом лимитов. Вы задаёте желаемую пропускную способность. Платформа управляет лимитами на провайдера. Полную реализацию маршрутизации и отказоустойчивость с учётом задержки смотрите в нашем руководстве по продакшн-мультимодельной настройке и документации по кастомной маршрутизации.
FAQ
Какая самая распространённая ошибка с лимитами?
Повтор с фиксированным интервалом. Одну секунду на всех клиентах создаёт табун, гарантирующий больше 429. Всегда используйте экспоненциальный бэкофф со случайным джиттером. Один джиттер — добавление random.uniform(0, 1) к времени ожидания — предотвращает большинство каскадных сбоев.
Как узнать свои лимиты?
Проверьте дашборд провайдера на заявленные лимиты вашего тарифа. Затем читайте заголовки x-ratelimit-remaining-* на каждом ответе API — они точно показывают оставшийся бюджет в реальном времени. Мониторьте их. Вы не узнаете свои лимиты, пока не упрётесь в них — так работают большинство команд.
Помогает ли использование нескольких провайдеров решить проблему лимитов?
Да — эффективно. Лимит 500 RPM на провайдера становится 2,000 RPM на четырёх провайдерах с раунд-робин маршрутизатором. Агрегационные платформы с маршрутизацией по нескольким провайдерам делают это прозрачным: один endpoint, автоматическое управление лимитами на уровне провайдера. Сочетайте многопровайдерную маршрутизацию со стратегиями оптимизации затрат, чтобы повысить пропускную способность без удвоения счёта — маршрутизируйте более дешёвые модели для некритичных запросов и резервируйте дорогие для задач, которым они нужны.
Какое самое простое исправление можно внедрить сегодня?
Замените повтор с фиксированным интервалом на экспоненциальный бэкофф + джиттер. Пять строк кода. Предотвращает табун, превращающий один 429 в каскадный сбой. Код слоя 2 в этой статье готов к копированию.
Могут ли агрегационные платформы обрабатывать лимиты за меня?
Да. Многопровайдерная маршрутизация, автоматический кулдаун для троттленных провайдеров и унифицированные дашборды лимитов — стандартные функции. Вы настраиваете желаемую пропускную способность. Платформа обрабатывает управление квотами на провайдера, мониторинг заголовков и автоматическую отказоустойчивость. Один endpoint. Никаких 429.
Три слоя здесь — троттлинг, бэкофф, предикция — превращают лимиты из угрозы надёжности в решённую инженерную проблему. Но по мере того как многопровайдерная маршрутизация становится стандартом, а провайдеры конкурируют гарантиями пропускной способности, вопрос меняется: присоединится ли «лимит превышен» к «диск полон» и «память исчерпана» как к ошибкам, которые современная инфраструктура просто делает устаревшими? Пока что код в этой статье держит вас в работе. Длинная дуга указывает на нечто более интересное.
Слой 1 и слой 2 вы можете реализовать за полдня с кодом выше. Слой 3 — предиктивная приостановка — требует больших инвестиций. Агрегационные платформы встраивают все три в путь запроса: многопровайдерная маршрутизация поглощает троттлинг на уровне провайдера, мониторинг заголовков питает общий дашборд лимитов, а автоматический кулдаун держит здоровых провайдеров в ротации, когда один деградирует. Никакая архитектура не устраняет 429 полностью, но распределение трафика по провайдерам и чтение заголовков до столкновения со стеной превращают их из еженедельного инцидента в редкий крайний случай.