LiteLLM: единый прокси для моделей, лимитов и логов

LiteLLM: единый прокси для моделей, лимитов и логов

Единый прокси решает одну проблему и тут же создаёт другую. Он убирает из приложений десятки ключей и base_url, но забирает себе все решения о маршрутизации, которые раньше были размазаны по коду. Пока прокси работает, это выглядит как чистая экономия сложности. В момент отказа выясняется, что вся маршрутизация ушла в один компонент, а у этого компонента может не оказаться журнала, объясняющего, что произошло.

Дальше следует build diary минимального прокси на LiteLLM: не платформа на сто моделей, а конфигурация из двух маршрутов, одного лимита, одного журнала и одного правила отказа. Прогон должен ответить на узкий вопрос: какие политики реально сохраняются, когда модель меняется через прокси, и какие только выглядят сохранёнными в конфиге.

Сразу разделю факт и метод. Всё, что ниже сказано о полях конфигурации, стратегиях роутинга и жизненном цикле запроса, взято из документации LiteLLM (docs.litellm.ai), проверенной 18 июля 2026 года. Сам прогон двух маршрутов, срабатывание лимита и подтверждение по логу пока не выполнены: это предложенный метод, а не готовый результат. Разделяю их не из педантизма, а потому что именно на смешении задокументированного механизма и непроверенного прогона обычно и рождается ложное чувство контроля.

Что именно централизует litellm proxy?

Начнём с того, что физически описывает маршруты. В конфигурации config.yaml за это отвечает секция model_list: каждая запись связывает пользовательский псевдоним model_name с блоком litellm_params, где лежит настоящая провайдерская строка model, а также api_base и api_key. Из-за этого два разных маршрута, например Azure OpenAI и AWS Bedrock, оказываются под одним набором алиасов, и вызывающее приложение больше не знает, куда именно уходит запрос (S1: docs.litellm.ai/docs/proxy/configs).

Здесь стоит разобрать распространённое заблуждение честно: считается, что единый endpoint сам по себе сохраняет контроль над моделями. Это не так. Серверная аутентификация задаётся один раз через general_settings.master_key, но учётные данные каждого маршрута по-прежнему лежат внутри его собственного litellm_params. Централизация вызова не отменяет управление ключами и лимитами каждого провайдера, она их только перемещает в одно место (S1). Ключи исчезли из приложений, но не из системы.

Тот же принцип объясняет, зачем в model_list вообще можно добавить внешний совместимый маршрут. Клиент, поддерживающий OpenAI-совместимый протокол, подключается заменой base_url и API-ключа в пределах моделей, доступных в текущем каталоге, — это ровно то же движение, которое LiteLLM уже требует от каждой записи model_list. Добавить provod.ai как ещё одну запись model_list можно тем же способом: своя строка api_base и свой api_key, без изменения остальной конфигурации прокси.

Сам пакет ставится обычной командой pip install litellm. Дальше вся работа сводится к дисциплине заполнения config.yaml, а не к магии единого адреса.

LiteLLM: единый прокси для моделей, лимитов и логов

Маршрут и лимит связаны напрямую

Выбор маршрута и лимит не независимы друг от друга. Router поддерживает несколько значений routing_strategy: simple-shuffle по умолчанию, least-busy, usage-based-routing, latency-based-routing. Когда для развёртывания заданы tpm и rpm, стратегия simple-shuffle делает взвешенный выбор именно на основе этих лимитов (S2: docs.litellm.ai/docs/routing). Лимит здесь не только предохранитель, но и вход в решение о маршрутизации.

Второй уровень лимитов работает на виртуальном ключе. Его выдаёт эндпоинт /key/generate или конфигурация: max_budget в долларах вместе с budget_duration (например, "30s" или "30d") ограничивает трату, а tpm_limit/rpm_limit (глобально) или model_tpm_limit/model_rpm_limit (по моделям) ограничивают пропускную способность. По умолчанию max_budget равен null и не действует, пока его не задать явно (S3: docs.litellm.ai/docs/proxy/users). Для проверяемости это ключевой момент: незаданный бюджет означает не мягкий лимит, а полное его отсутствие.

Вот минимальная конфигурация двух маршрутов для прогона. Версию LiteLLM стоит зафиксировать в requirements.txt, а дату проверки документации записать отдельно: имена полей исторически менялись между релизами.

# config.yaml - минимальный litellm proxy, дата проверки docs: 2026-07-18 model_list: - model_name: chat-primary litellm_params: model: azure/gpt-4o api_base: os.environ/AZURE_API_BASE api_key: os.environ/AZURE_API_KEY tpm: 20000 rpm: 100 - model_name: chat-backup litellm_params: model: bedrock/anthropic.claude-3-sonnet aws_region_name: os.environ/AWS_REGION router_settings: routing_strategy: simple-shuffle fallbacks: [{"chat-primary": ["chat-backup"]}] general_settings: master_key: os.environ/LITELLM_MASTER_KEY

Ключ с реальным лимитом выдаётся отдельно. max_budget и budget_duration ниже — документированные примеры значений, а не цена какого-либо вендора:

curl -X POST "$PROXY/key/generate" \ -H "Authorization: Bearer $LITELLM_MASTER_KEY" \ -d '{ "models": ["chat-primary", "chat-backup"], "max_budget": 5, "budget_duration": "30d", "tpm_limit": 5000, "rpm_limit": 60 }'

Когда бюджет превышен, поднимается ошибка типа ExceededTokenBudget, а стоимость по ключу записывается в таблицу LiteLLM_VerificationToken (S3). Это не деталь схемы, а место, куда можно заглянуть после прогона и убедиться, что ограничение действительно сработало, а не просто было объявлено в конфиге.

LiteLLM: единый прокси для моделей, лимитов и логов

Что происходит при отказе маршрута?

Правило отказа устроено иначе. Fallback настраивается в router_settings.fallbacks как список словарей, где имя исходной модели отображается на упорядоченный массив резервных, например fallbacks: [{"chat-primary": ["chat-backup"]}]. Есть специализированные варианты: context_window_fallbacks, content_policy_fallbacks, default_fallbacks, и каждый срабатывает от своего типа ошибки (S4: docs.litellm.ai/docs/proxy/reliability). Отказ распадается на несколько разных типов, и каждый нужно закрывать отдельным правилом.

Важен и порядок обработки внутри Router. Провалившийся запрос сначала проходит через function_with_fallbacks (переключает группу моделей), затем через function_with_retries (повторы внутри той же группы), и только потом попадает в litellm.completion() (S5: docs.litellm.ai/docs/proxy/architecture). Fallback пробуется после того, как исчерпаны ретраи на исходном развёртывании, а не на первой же ошибке. Кто этого не знает, неверно прочитает собственные логи: увидит задержку и решит, что fallback «завис», хотя на самом деле отрабатывали ретраи.

Таблица ниже сводит типы отказа с политиками, которые их закрывают. Она нужна не для украшения, а как чек-лист перед вводом прокси: если хотя бы одна строка пустая, правило отказа считается непроверенным.

  • Тип отказа: Обычная ошибка провайдера • Что настраивать: fallbacks • Триггер: ошибка вызова после ретраев • Что проверить в прогоне: резерв реально принял запрос
  • Тип отказа: Превышение контекста • Что настраивать: context_window_fallbacks • Триггер: ошибка длины контекста • Что проверить в прогоне: маршрут ушёл на модель с большим окном
  • Тип отказа: Блок по контент-политике • Что настраивать: content_policy_fallbacks • Триггер: ошибка контент-фильтра • Что проверить в прогоне: fallback не зациклился на том же фильтре
  • Тип отказа: Модель не указана явно • Что настраивать: default_fallbacks • Триггер: отсутствие спец-правила • Что проверить в прогоне: сработал общий резерв, а не отказ
  • Тип отказа: Лимит трат • Что настраивать: ExceededTokenBudget • Триггер: max_budget превышен • Что проверить в прогоне: запись появилась в LiteLLM_VerificationToken

Компромисс тут стоит назвать прямо, а не прятать в скобках. Централизация снижает дублирование настроек в приложениях, но одновременно повышает критичность одного компонента: до прокси отказ одного ключа гасил один сервис, после прокси неверное правило отказа гасит все маршруты сразу. Выгода реальна, и цена реальна, обе стоит принимать осознанно, а не постфактум.

LiteLLM: единый прокси для моделей, лимитов и логов

Лог не всегда подтверждает лимит

Здесь неудобный момент для тезиса «логи подтверждают маршрут». По документированному жизненному циклу запроса проверки аутентификации и бюджета, а также лимитер параллельных запросов (на уровне сервера, виртуального ключа, пользователя и команды) выполняются синхронно, до обращения к провайдеру. А логирование трат, учёт лимитов и колбэки логирования выполняются как асинхронные фоновые задачи уже после ответа (S5). Практический вывод жёсткий: запись в логе не гарантирует, что лимит был применён именно в момент запроса, это два разных участка кода.

Дальше видимость маршрута зависит от того, какой колбэк вообще включён. LiteLLM proxy поддерживает подключаемые колбэки наблюдаемости: Langfuse, OpenTelemetry, GCS/S3/Azure Blob, DataDog, Lunary, MLflow и собственные колбэки, через success_callback и failure_callback, а заголовок запроса x-litellm-disable-callbacks может отключить логирование для конкретного вызова (S6: docs.litellm.ai/docs/proxy/logging; S7: docs.litellm.ai/docs/proxy/dynamic_logging). Видимость существует ровно настолько, насколько её включили для конкретного ключа или команды.

Финальный подвох касается «версионированной конфигурации». Поведение колбэков и логирования можно менять динамически по ключу или команде, без передеплоя базового config.yaml, через отдельные управляющие эндпоинты (S7). Отсюда следствие: файл в git не гарантирует, что логирование в конкретном прогоне совпадает с закоммиченным, если динамические переопределения не зафиксированы тоже. Версионируешь ты файл, а действует комбинация файла и переопределений.

Отсюда моя нормативная позиция, и она не риторическая: прокси стоит вводить только вместе с наблюдаемостью и заданным правилом отказа. Без журнала маршрутизации централизация мгновенно превращается в слепую зону, ради устранения которой она вообще затевалась.

Как выглядит минимальный прогон и чего он ещё не доказал

План проверки короткий, и его границы стоит держать честно. Поднять прокси с двумя маршрутами из конфига выше. Выдать ключ с заданным max_budget и tpm_limit. Прогнать запросы на chat-primary, специально упереться в лимит и проверить два исхода: поднялась ли ошибка ExceededTokenBudget вместе с тем, какой маршрут показал включённый колбэк, и переключился ли вызов на chat-backup по правилу fallback. Гипотеза простая: смена модели через прокси сохранит лимит и трассировку. Проверяемой она станет только после прогона.

Пока прогон не выполнен, называть эти политики подтверждёнными нельзя. Документация подтверждает механизм, конфигурация подтверждает намерение, но «сработало» относится только к конкретно протестированным маршрутам и сценарию. Минимальная конфигурация не доказывает ни производительность под нагрузкой, ни безопасность полной платформы, ни поведение при всех видах отказа: это ограничение build diary, а не недосмотр.

Отдельный практический вопрос: где взять маршрут для модели, недоступной по прямому провайдерскому ключу. Технически это ещё одна запись в том же model_list, добавленная тем же способом, что описан выше. Вопрос уже не технический, а процедурный: как её оплачивать. Для российской команды здесь имеет значение оплата рублёвым балансом без зарубежной карты и VPN: картой, через СБП или по счёту. Это закрывает вопрос оплаты, а не заменяет лимит, журнал и правило отказа — их по-прежнему описываешь на своём прокси.

# ещё один маршрут в том же model_list - внешний совместимый endpoint - model_name: chat-external litellm_params: model: openai/claude-sonnet-5 api_base: https://api.provod.ai/v1 api_key: os.environ/PROVOD_API_KEY tpm: 10000 rpm: 50

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

LiteLLM: единый прокси для моделей, лимитов и логов

Чего это не решает

Минимальный прокси не отвечает на вопрос о нагрузке. Два маршрута и один ключ ничего не говорят о поведении под реальным трафиком и обо всех типах отказа, это прямо вынесено в список неизвестного. Пройденный happy path не гарантия устойчивости.

Он не заменяет собственную политику внешним API. Стабильная маршрутизация на стороне внешнего провайдера может помогать при временном сбое одного канала, но не подменяет твои лимиты и журнал. Если внешний маршрут выдают за замену этих контролей, слепая зона просто переезжает наружу вместе с ним.

Он не отменяет работу с секретами. Централизация убрала ключи из приложений, но api_key каждого маршрута всё ещё живёт в litellm_params, и ротацию, и доступ к нему по-прежнему настраиваешь сам.

И он не подменяет специализированные продукты. Внешний совместимый маршрут не заменяет платформы автоматизации, работу по внедрению интеграции, приватную или on-prem инфраструктуру, функции, доступные только внутри подписки конкретного вендора, а GigaChat остаётся отдельным продуктом со своим контуром доступа. Прокси остаётся точкой политики, а не всей платформой.

Отдельно про типовой запрос новичков в эту тему. Когда platform-инженер ищет референсную сборку, он часто вбивает в поиск «мультиагентная система ollama litellm», имея в виду локальные модели Ollama за общим прокси и несколько агентов поверх него. Сценарий законный: Ollama добавляется в model_list как ещё один маршрут. Но даже такая локальная сборка наследует ту же обязанность: лимит, журнал и правило отказа на маршруте, иначе агенты дружно замолчат при первом же сбое канала.

FAQ

Как найти готовый пример многоагентной сборки, чтобы не начинать с нуля? По запросу «мультиагентная система ollama litellm github» находятся публичные репозитории с образцами config.yaml и оркестрацией агентов. Их удобно брать как каркас, но не как политику: лимиты, включённые колбэки и правила fallback под свою инсталляцию всё равно описываешь сам и проверяешь прогоном.

Достаточно ли задать master_key, чтобы считать доступ защищённым? Нет. master_key отвечает за серверную аутентификацию, а бюджеты и лимиты выдаются на виртуальных ключах, причём max_budget по умолчанию null и не действует (S3). Защита складывается из связки ключа, лимита и включённого журнала, а не из одного параметра.

Почему в логе есть запись, а лимит будто не сработал? Это тот же разрыв синхронного и асинхронного путей, что разобран выше (S5): запись подтверждает вызов, но не заменяет проверку enforcement в таблице LiteLLM_VerificationToken.

Гарантирует ли закоммиченный config.yaml, что логи в проде такие же? Нет. Поведение колбэков меняется динамически по ключу и команде без передеплоя (S7), поэтому фиксируй в версии и динамические переопределения тоже, иначе прогон и git разойдутся.

Можно ли отключить логирование для отдельного вызова? Да, заголовком x-litellm-disable-callbacks (S6). Это удобно и рискованно одновременно: для таких вызовов ты сознательно создаёшь слепую зону.

LiteLLM: единый прокси для моделей, лимитов и логов

Если после прогона тебе нужен внешний маршрут для моделей, которых нет по прямому провайдерскому ключу, его надёжность стоит проверять тем же способом, что и собственный fallback: мультиканальная маршрутизация provod.ai может удерживать запросы в работе, когда один вышестоящий канал временно недоступен, но она остаётся частью чужой инфраструктуры, а не твоим правилом отказа. Последнее по-прежнему настраиваешь в router_settings.fallbacks на своём прокси.

Источники

  • LiteLLM, конфигурация прокси (config.yaml, model_list, master_key): docs.litellm.ai/docs/proxy/configs (проверено 2026-07-18).
  • LiteLLM, стратегии роутинга и связь tpm/rpm с выбором маршрута: docs.litellm.ai/docs/routing (проверено 2026-07-18).
  • LiteLLM, бюджеты и лимиты виртуальных ключей, ExceededTokenBudget, LiteLLM_VerificationToken: docs.litellm.ai/docs/proxy/users (проверено 2026-07-18).
  • LiteLLM, конфигурация fallback и специализированные варианты: docs.litellm.ai/docs/proxy/reliability (проверено 2026-07-18).
  • LiteLLM, жизненный цикл запроса, ретраи и fallback, sync/async логирование: docs.litellm.ai/docs/proxy/architecture (проверено 2026-07-18).
  • LiteLLM, колбэки логирования и x-litellm-disable-callbacks: docs.litellm.ai/docs/proxy/logging (проверено 2026-07-18).
  • LiteLLM, динамическое логирование и переопределения по ключу/команде: docs.litellm.ai/docs/proxy/dynamic_logging (проверено 2026-07-18).
  • LiteLLM, полный справочник полей config.yaml: docs.litellm.ai/docs/proxy/config_settings (проверено 2026-07-18).

provod.ai — понятный расчётный контур для юридических лиц

Переведите AI из личных оплат в нормальную закупку: компания получает рублёвые расчёты, договор, счёт и закрывающие документы, а техническая команда — единый API.

В одном каталоге — актуальные модели для текста и медиа: GPT от OpenAI, Claude от Anthropic, Gemini от Google, Grok от xAI, DeepSeek, Qwen, GLM, Kimi и MiniMax; для изображений — Nano Banana 2 Pro и GPT Image; для видео — последние версии Seedance, Kling, Veo и Google Omni. Также доступны модели для reasoning, поиска, документов, эмбеддингов, музыки и аудио.

Документальный контур не маскирует дополнительную маржу: модели оплачиваются по официальным ценам 1:1, без наценки provod.ai.