LiteLLM: ошибка 429 rate limit — причины и решение
Шлюз работал неделю, а потом клиенты начали ловить 429 Too Many Requests. Код понятный, но чей это лимит: OpenAI, вашего виртуального ключа или роутера LiteLLM, который увёл единственный деплоймент в кулдаун? Под одним HTTP-кодом прячутся четыре разные причины, и лечатся они противоположными способами.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Четыре разных 429 под одним кодом
LiteLLM — прокси: он транслирует ошибки апстрима и добавляет свои. Поэтому 429 рождается в четырёх местах, а по коду они неотличимы — различать надо по телу ответа.
Первое: лимит провайдера. LiteLLM обернул чужой 429 в litellm.RateLimitError, текст сохранился почти дословно:
litellm.RateLimitError: RateLimitError: OpenAIException - Rate limit reached for gpt-4o
in organization org-8fJq2 on tokens per min (TPM): Limit 30000, Used 29812, Requested 1024.
Please try again in 1.672s.
У Anthropic лимиты раздельные на вход и выход, и текст соответствующий: AnthropicException - {"type":"rate_limit_error","message":"This request would exceed your organization's rate limit of 40000 input tokens per minute."}.
Второе: внутренний лимитер LiteLLM. Сработал rpm_limit/tpm_limit, повешенный вами на ключ, команду или пользователя. Признак — хэш ключа прямо в тексте: {"message":"Max parallel request limit reached. Hit limit for api_key: 6f2ea1c9b4... tpm_limit: 100000, current tpm: 100742","code":"429"}.
Третье: роутер вывел все деплойменты в кулдаун. Квота есть, но пробовать LiteLLM отказывается:
litellm.RateLimitError: No deployments available for selected model, Try again in 30 seconds.
Passed model=gpt-4o. pre-call-checks=True, cooldown_list=[('a3f1d0e2', {'status_code': '429'})]
Четвёртое, самое коварное: это вообще не rate limit. OpenAI отдаёт 429 и при нуле на счёте: {"message":"You exceeded your current quota, please check your plan and billing details.","code":"insufficient_quota"}. Ретраи тут бесполезны — сколько ни повторяй, баланс не пополнится. Смотрите на поле code: rate_limit_exceeded — ждём, insufficient_quota — идём в биллинг.
| Что в теле ответа | Источник | Что делать |
|---|---|---|
Limit 30000, Used ..., try again in Ns | квота провайдера | ретраи, фолбэк, второй ключ |
Hit limit for api_key: ... | ваш лимитер | поднять лимит ключа |
No deployments available ... cooldown_list | роутер | правка cooldown_time |
insufficient_quota | пустой баланс | пополнить счёт |
Диагностика за пять минут
Не гадайте — снимите заголовки: LiteLLM отдаёт свои x-litellm-* и пробрасывает x-ratelimit-* от апстрима.
curl -i -s -X POST http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-ваш-ключ" -H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"ping"}],"max_tokens":8}' | head -40
Что смотреть:
x-litellm-key-remaining-requestsи-tokens— остаток по вашему ключу. Ноль значит, что до провайдера запрос не дошёл.x-ratelimit-remaining-tokens— остаток по квоте провайдера.x-litellm-attempted-retries— сколько попыток сделал роутер. Там 3, а ошибка вернулась — ретраи не спасают.retry-after— сколько просит подождать апстрим. Заголовка нет вовсе — скорее всего отбил не сам API.
Дальше логи. Подробный вывод даёт флаг --detailed_debug, в Docker — переменная LITELLM_LOG=DEBUG; старый litellm.set_verbose помечен deprecated и только шумит предупреждением. Грепать удобнее так: docker compose logs -f litellm | grep -iE "rate.?limit|429|cooldown|No deployments".
И лимиты конкретного ключа:
curl -s -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
"http://127.0.0.1:4000/key/info?key=sk-abc123" | jq '.info | {tpm_limit, rpm_limit, spend}'
Если tpm_limit показывает null, а 429 идёт — лимит точно не ваш, ищите выше по цепочке.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LiteLLMКогда лимит ваш собственный
Внутренних уровней три: деплоймент в model_list, виртуальный ключ (команда, пользователь) и глобальный на весь процесс. Срабатывает самый жёсткий. Лимит деплоймента — подсказка роутеру, сколько можно лить в конкретный апстрим:
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
rpm: 450
tpm: 265000
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o-eu
api_base: os.environ/AZURE_API_BASE
rpm: 300
Ставьте цифры на 10–15% ниже реальной квоты: напишете ровно 30000 при квоте 30000 — роутер пропустит запрос до границы, а апстрим отобьёт, окно там считается чуть иначе.
Лимиты ключа задаются при генерации и меняются через /key/update:
curl -s -X POST http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-d '{"models":["gpt-4o","claude-sonnet-4-5"],"rpm_limit":60,"tpm_limit":120000,
"max_parallel_requests":8,"duration":"30d"}'
Глобальный предохранитель на весь процесс — general_settings: global_max_parallel_requests: 200.
Честный минус механизма: внутренний лимитер не создаёт квоту, а делит вашу же. Если все ключи суммарно хотят 400k TPM при 300k у аккаунта, кто-то получит 429 в любом случае — вопрос лишь в том, будет это предсказуемый отказ шлюза или обрыв посреди цепочки агента.
Redis: почему без него счётчики врут
Самая частая скрытая причина «настроил лимиты, а они не работают» — многопроцессный запуск без общего хранилища: по умолчанию счётчики TPM/RPM живут в памяти процесса. Запускаете с --num_workers 4 — получаете четыре независимых счётчика, каждый считает свою четверть трафика и думает, что до лимита далеко.
Замер на стенде, ключ с rpm_limit: 60 и четыре воркера:
hey -n 300 -c 20 -m POST -H "Authorization: Bearer sk-test" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":5}' \
http://127.0.0.1:4000/v1/chat/completions
Ожидалось 60 успешных ответов. Фактически прошло 238, а 429 прилетели уже не от LiteLLM, а от OpenAI — лимит превысили почти вчетверо, ровно по числу воркеров. Лечится подключением Redis:
router_settings:
redis_host: os.environ/REDIS_HOST
redis_port: os.environ/REDIS_PORT
redis_password: os.environ/REDIS_PASSWORD
routing_strategy: usage-based-routing-v2
Стратегия usage-based-routing-v2 без Redis не работает в принципе: она выбирает деплоймент по фактически израсходованным TPM, а эти числа должны быть общими для всех воркеров. Проверка, что счётчики поехали наружу — redis-cli -a "$REDIS_PASSWORD" --scan --pattern '*tpm*' | head. Пусто после нагрузки — переменные не подхватились, смотрите docker compose exec litellm env | grep REDIS.
Два нюанса. Redis держите на том же сервере, с bind 127.0.0.1 и requirepass в /etc/redis/redis.conf: обращение к счётчику идёт синхронно перед вызовом модели, и Redis в чужом дата-центре добавит 20–40 мс к каждому запросу. И не смешивайте счётчики с кэшем ответов: при maxmemory-policy allkeys-lru вытеснение выкинет счётчик лимита. Кэшу — отдельный db.
Ретраи, кулдауны и фолбэки
Блок настроек, который убирает большую часть 429 из ответов клиентам:
router_settings:
num_retries: 3
allowed_fails: 3
cooldown_time: 5
enable_pre_call_checks: true
retry_policy:
RateLimitErrorRetries: 3
AuthenticationErrorRetries: 0
allowed_fails_policy:
RateLimitErrorAllowedFails: 20
fallbacks: [{"gpt-4o": ["gpt-4o-azure", "claude-sonnet-4-5"]}]
RateLimitErrorRetries: 3 отделяет повторы при 429 от прочих ошибок: на AuthenticationErrorRetries намеренно ноль — повторять запрос с битым ключом бессмысленно. Если апстрим прислал retry-after, LiteLLM ждёт указанное время, иначе уходит в бэкофф.
Главная ловушка — cooldown_time в связке с allowed_fails. При дефолтных 30 секундах и единственном апстриме три подряд пойманных 429 выбивают ваш единственный деплоймент из ротации на полминуты, и всё это время клиенты получают No deployments available: короткий всплеск превращается в гарантированный отказ. Поэтому выше стоит cooldown_time: 5, а RateLimitErrorAllowedFails поднят до 20 — 429 это рабочее состояние загруженного API, а не поломка. Кулдаун в исходном виде осмыслен, когда у одного model_name два-три реальных апстрима. Фолбэки, кстати, требуют, чтобы запасные имена были в model_list отдельными записями.
Честно про пределы подхода: ретраи не создают квоту. При стабильных 40% отказов три повтора дают примерно двукратный рост p95-латентности и деньги за частично сгенерированные ответы, которые вы выбросите. Если 429 идёт постоянно, а не всплесками, лечение другое — второй аккаунт провайдера, дешёвая модель на части трафика или кэш ответов (litellm_settings: cache: true, cache_params: {type: redis, ttl: 3600}). На повторяющихся промптах кэш снимает заметную долю запросов и честнее маскировки повторами.
Когда 429 приходит не от лимита, а от инфраструктуры
Часть случаев в config.yaml не чинится вообще.
Низкий tier аккаунта. У OpenAI лимиты привязаны к уровню: свежий платный аккаунт на Tier 1 получает порядка 500 RPM и 30 000 TPM на gpt-4o. Тридцать тысяч токенов в минуту — это четыре-пять одновременных запросов с промптом на 6k токенов. Роутером это не обходится, уровень растёт с тратами и стажем. У Gemini похожая история: строка generate_content_free_tier_requests в поле quota_metric означает, что биллинг к проекту не привязан.
Фронт провайдера, а не API. 429 пришёл без единого заголовка x-ratelimit-*, а тело — HTML вместо JSON? Проверьте код Cloudflare:
curl -s -o /tmp/r.html https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"
grep -o 'error code: [0-9]*' /tmp/r.html
error code: 1015 значит, что вас режут по IP-адресу, а не по квоте аккаунта.
Общий или грязный IP. На дешёвых VPS с NAT-пулом десятки клиентов ходят в один API с одного адреса, а провайдер применяет ограничения и по IP. Итог — 429 при свободной собственной квоте. Проверьте адрес выхода: curl -s https://api.ipify.org. Не совпал с адресом сервера — вы за общим NAT, и чужой трафик влияет на ваши лимиты. Лечится выделенным белым IP.
Гео-ограничения. Из России напрямую OpenAI и Anthropic отдают не 429, а 403 с unsupported_country. Увидели 403 — дело в стране, а не в частоте, роутером это не лечится.
Какой сервер нужен под LiteLLM в MAATRIX
Сам прокси лёгкий: он не считает нейросети, а перекладывает JSON и ждёт апстрим. Требования задают соседи по машине — Redis со счётчиками лимитов и PostgreSQL с ключами и логами трат.
Минимум — 2 vCPU / 4 GB RAM / 40 GB NVMe. Один воркер, Redis и Postgres, десятки запросов в секунду на коротких ответах. Честный минус: при --num_workers 4 на четырёх гигабайтах Redis и Postgres конкурируют за память, а запись spend-логов на каждый запрос в пиках добавляет задержку. Для личного шлюза нормально, для команды тесно.
Комфортный вариант — 4 vCPU / 8 GB RAM / 80 GB NVMe. Четыре воркера, Redis с maxmemory 512mb, Postgres с shared_buffers = 1GB, запас под логи. Именно здесь лимитер предсказуем: счётчики общие, кулдауны не наслаиваются, всплески не выбивают базу.
Шлюз на несколько команд с веб-интерфейсом — 8 vCPU / 16 GB, диск от 160 GB. Таблица LiteLLM_SpendLogs растёт быстро: 1–2 КБ на запрос, миллион запросов — порядка 1,5 ГБ плюс индексы. Ретеншен заводите сразу.
Локация под эту задачу — US, Нью-Йорк, и это прямо связано с темой статьи: короткий маршрут до api.openai.com и api.anthropic.com, нет гео-отказов, а главное — выделенный белый IP, который не делят сотни соседей. Именно чужой трафик с общего адреса даёт «необъяснимые» 429 при пустой квоте. Задержка из Москвы — 110–130 мс, на фоне секунд генерации незаметно.
В первый час после установки:
ufw allow 22/tcp
ufw allow 443/tcp
ufw deny 4000/tcp
ufw enable
Порт 4000 наружу не выставляйте — только через Nginx или Caddy с TLS, иначе мастер-ключ уедет открытым текстом. Redis и Postgres держите на 127.0.0.1.
Оплата в MAATRIX — картами российских банков, по СБП, криптой или токеном MAAT; иностранная карта для американской площадки не нужна. Не уверены в конфигурации — напишите профиль нагрузки: запросов в минуту, средняя длина промпта, число команд.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LiteLLMОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Как понять, чей это 429 — мой или провайдера?
Отправьте запрос через curl -i и посмотрите заголовки: x-litellm-key-remaining-requests в нуле означает, что сработал ваш лимитер. Если он в норме, а x-ratelimit-remaining-tokens близок к нулю — упёрлись в квоту апстрима.
Почему лимиты на ключе не соблюдаются?
Почти всегда из-за запуска с --num_workers больше единицы без Redis: каждый воркер считает отдельно, и фактический лимит умножается на их число. Пропишите redis_host/redis_port/redis_password в router_settings.
Что делать с ошибкой No deployments available for selected model?
Это не квота, а кулдаун роутера: деплоймент выбит из ротации после allowed_fails отказов. При единственном апстриме уменьшите cooldown_time до 5 секунд и поднимите RateLimitErrorAllowedFails до 20–50.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.