Для AI-агентов: markdown этой страницы — /docs-content/ai/chat/rate-limits.md индекс документации — /llms.txt

Лимиты запросов и повторы

Обращения к моделям ограничены двухуровневой корзиной, а при пиковой нагрузке платформа может ненадолго отказывать в обслуживании. Оба сигнала — 429 и 503 — означают «повтори позже», а не «запрос неверен». Ни один из них не списывает квоту.

Два уровня корзины

  • На ключ — настройка по умолчанию равна 600 запросам в минуту до деления между репликами бэкенда. На каждой реплике корзина привязана к ключу, а не к IP-адресу: клиент за NAT не делит её с другими ключами, а один и тот же ключ с разных адресов попадает в одну корзину этой реплики.
  • На пользователя — настройка по умолчанию равна 1500 запросам в минуту суммарно по всем ключам одного пользователя до деления между репликами. Это защищает платформу, когда один аккаунт раздаёт десяток ключей агентам и каждый выбирает свой лимит.

Запрос проверяется сначала по ключу, затем по пользователю. Первый исчерпанный счётчик выдаёт 429.

Для отдельных ключей — партнёрских интеграций, пакетных обработчиков — администратор платформы может поднять лимит на ключ. Лимит на пользователя при этом продолжает действовать.

Ответ `429`

Код rate_limit_exceeded приходит двумя путями: когда лимит наложила сама платформа и когда запрос ограничил сам кластер моделей. Ниже — ответ платформенного лимита, второй путь описан в конце раздела.

В примере лимит ключа настроен на 600, работают три реплики бэкенда, индивидуальная настройка ключа отсутствует. Каждая реплика применяет ceil(600 / 3) = 200 запросов в минуту.

JSON
{
  "error": {
    "type": "rate_limit_exceeded",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded (per-key). Limit: 200 per minute. Retry after 12 seconds.",
    "scope": "per-key",
    "limit": 200,
    "retryAfter": 12
  }
}

Поля error.limit и error.scope приходят только на пути платформенного лимита. Поле error.limit содержит лимит корзины на реплике, которая обработала запрос. При другом числе реплик или индивидуальной настройке ключа значение будет другим.

Поле scope на этом пути принимает одно из двух значений:

  • per-key — исчерпан лимит конкретного ключа. Снизьте число одновременных запросов этого ключа.
  • per-user — исчерпан общий лимит аккаунта. Снизьте суммарную нагрузку по всем ключам или попросите администратора платформы поднять лимит.

Вместе с 429 платформенного лимита приходят заголовки:

  • Retry-After: <секунды> — сколько ждать перед следующей попыткой.
  • X-RateLimit-Scope: per-key | per-user — уровень, в который упёрлись. Полезно для логов и метрик клиента. На пути троттлинга кластера этого заголовка нет.

Лимит проверяется после проверки ключа, поэтому запросы с некорректным ключом (401) квоту не расходуют.

Второй путь — запрос ограничил сам кластер моделей. Код тот же, rate_limit_exceeded, но лимит наложила не платформа: в теле есть поле providerStatusCode, полей scope и limit нет, заголовка X-RateLimit-Scope тоже нет, а пауза берётся из Retry-After (в потоковом режиме — из поля retryAfter кадра ошибки). Ветвитесь по коду и по паузе, а поле scope и заголовок X-RateLimit-Scope проверяйте на наличие, не считая их гарантированными для этого кода.

Транзитные `503`: `pool_exhausted` и `db_transient`

Помимо 429, который означает превышение клиентской квоты, платформа может отдать 503 с одним из двух кодов. pool_exhausted — внутренний пул соединений с базой временно исчерпан, например при одновременном пике у нескольких клиентов. db_transient — транзакция в базе закрылась или истекла до того, как операция завершилась, и платформа откатила её целиком. Причины разные, реакция одна: это сигнал «попробуй ещё раз через несколько секунд», а не «инфраструктура сломана». Ответ всегда содержит заголовок Retry-After — целое число секунд от 3 до 7, со случайным разбросом на стороне сервера, чтобы массовое восстановление не било повторно в пул.

В обычном (не потоковом) режиме обрабатывайте оба кода одинаково — по статусу 503 вместе с Retry-After, а не списком известных вам строк: так клиент переживёт появление третьего транзитного кода.

JSON
{
  "error": {
    "code": "pool_exhausted",
    "type": "server_error",
    "retryAfter": 5
  }
}

В потоковом режиме (stream: true) статус 200 уже отправлен до того, как платформа узнаёт о перегрузке, поэтому сигнал приходит последним событием потока: data: { "error": { "code": "pool_exhausted", "retryAfter": <число> } }, после чего идёт обычный data: [DONE]. Распознайте code === "pool_exhausted" и повторите запрос через retryAfter секунд.

Есть и третий сигнал перегрузки — 429 ai_congested. Он приходит, когда перегружен пул AI-кластера. Запрос при этом не выполнялся и списания нет. Отличить его от превышения квоты можно по заголовку X-AI-Admission: shed, которого нет у ответа 429 rate_limit_exceeded.

Ответ `429 ai_pacing_limited`

Четвёртый ответ со статусом 429 не связан с нагрузкой на платформу. Он приходит, когда администратор портала включил равномерное расходование AI-квоты и вызов пробил суточное или недельное окно. Тело такого ответа приходит в конверте { "success": false, "error": { ... } }, а не в сыром формате остальных ошибок этой страницы, и несёт поля reason, overageDenied и resetAt. Разбор полей и режимов — на странице AI-квота компании.

Ответ `429 ai_provider_cooldown`

Пятый ответ со статусом 429 приходит, когда кластер моделей отвечает ошибками подряд и платформа перестаёт обращаться к нему, чтобы повторы не мешали ему восстановиться. Пауза снимается сама. Заголовок Retry-After несёт её остаток в секундах. Запрос не выполнялся, списания нет.

Длительность паузы не фиксирована: первая — около минуты, но если по её истечении кластер всё ещё отвечает ошибками, следующая пауза становится длиннее (до четырёх минут). Поэтому берите время ожидания из заголовка Retry-After, а не из фиксированной константы в своём коде.

Отличить его от остальных 429 можно по коду и по отсутствию заголовков: у него нет ни X-RateLimit-Scope (он есть у rate_limit_exceeded, когда лимит наложила сама платформа), ни X-AI-Admission (он есть у ai_congested). В потоковом режиме статус 200 уже отправлен, поэтому сигнал приходит последним событием потока — тем же способом, что и pool_exhausted, с полем retryAfter внутри кадра ошибки.

Ответ `429 ai_deadline_exceeded`

Шестой ответ со статусом 429 приходит, когда запрос не уложился в срок обслуживания — предельное время, которое платформа готова держать вызов, прежде чем честно отказать. Раньше такой вызов просто обрывался по таймауту без тела. Теперь у него есть объявленная граница и понятный отказ.

Тело несёт код ai_deadline_exceeded, тип rate_limit_exceeded и поле retryAfter. Вместе с ним приходят два заголовка: Retry-After — пауза перед повтором в секундах, и X-AI-Deadline-Ms — бюджет, который реально был у этого запроса, в миллисекундах.

Свой, более короткий бюджет можно назвать заголовком запроса X-AI-Deadline-Ms (миллисекунды). Он только сокращает срок: значение больше платформенного не выдаётся, а там, где срок отключён администратором, заголовок его не включает. Нечисловое или неположительное значение считается неуказанным и вызов не отклоняет.

Срок отсчитывается от прихода запроса и покрывает всю его обработку целиком, включая автоматический повтор на резервной модели. В потоковом режиме статус 200 уже отправлен, поэтому отказ приходит последним событием потока — как у pool_exhausted и stream_idle_timeout, с полем retryAfter внутри кадра ошибки.

Отличить его от stream_idle_timeout просто: тот означает «кластер замолчал» — посреди ответа, а то и в самом начале, так и не отдав заголовки, — а этот «времени на весь запрос больше нет», и он срабатывает даже когда фрагменты продолжают идти.

Ошибки

HTTP Код Описание
429 rate_limit_exceeded Превышен лимит запросов. Уровень — в поле scope и заголовке X-RateLimit-Scope. Тот же код приходит, когда ограничил сам кластер моделей: тогда в теле есть поле providerStatusCode, а поля scope и заголовка X-RateLimit-Scope нет, пауза берётся из Retry-After
429 ai_congested Пул AI-кластера перегружен. Запрос не выполнялся, списания нет. Ответ несёт заголовок X-AI-Admission: shed
429 ai_pacing_limited Пробито суточное или недельное окно равномерного расходования квоты. Повторите запрос по заголовку Retry-After
429 ai_provider_cooldown Кластер моделей временно недоступен, платформа держит короткую паузу. Запрос не выполнялся, списания нет. Повторите его через число секунд из Retry-After
429 ai_deadline_exceeded Запрос не уложился в срок обслуживания. Повторите его через число секунд из Retry-After. Фактический бюджет — в заголовке X-AI-Deadline-Ms
503 pool_exhausted Платформа временно перегружена. Повторите запрос через число секунд из Retry-After
503 db_transient Транзакция в базе закрылась до завершения операции, изменение откатилось целиком. Повторите запрос через число секунд из Retry-After

Полный список общих ошибок API — Ошибки.

Готовый рецепт повторов

javascript
async function callWithBackoff(makeRequest, { maxAttempts = 5 } = {}) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await makeRequest()
    if (res.status === 429 || res.status === 503) {
      const retryAfterSec = Number(res.headers.get('retry-after')) || 0
      // Соблюдаем Retry-After и добавляем небольшой случайный разброс,
      // чтобы параллельные обработчики не возвращались синхронно.
      const baseMs = retryAfterSec * 1000 || Math.min(1000 * 2 ** attempt, 30000)
      const jitterMs = Math.floor(Math.random() * 1000)
      await new Promise(r => setTimeout(r, baseMs + jitterMs))
      continue
    }
    return res
  }
  throw new Error('Повторы исчерпаны')
}

Что важно:

  • Всегда соблюдайте Retry-After. Он соответствует ожидаемому времени восстановления, меньший интервал ничего не ускоряет, а только нагружает платформу.
  • Добавляйте собственный случайный разброс поверх серверного — задержка в 100-1000 мс разрывает синхронные циклы обработчиков.
  • Ограничивайте количество попыток. Пяти достаточно для большинства сценариев, дальше возвращайте ошибку наружу.
  • При 5xx без Retry-After, например при сбое на стороне провайдера, увеличивайте паузу по экспоненте: min(2^attempt × 1s, 30s) со случайным разбросом.
  • Не запускайте Promise.all поверх тысяч записей. Без пауз пакетная обработка упрётся в один из уровней корзины.

Смотрите также