Одна дверь в машинный зал: как я перестал раздавать AI-ключи каждому приложению
Раньше я выдавал ключи всем приложениям подряд, не думая о последствиях. После обновления модели в приложении X код пришлось править вручную. Теперь одна дверь в машинный зал, и ключи выдаю только по запросу.
Что болело
Раньше я отдавал каждому приложению прямой доступ к провайдерам AI, как в коммунальной квартире с кучей ключей, где никто не знал, где главный замок. Клиенту приходилось запоминать имя модели, адрес, API‑ключ, таймаут и нюансы ответа. Когда я сменил модель, пришлось переписать правила для всех. Провайдер упал, и я стал разбирать каждый сервис вручную. Добавив локальную модель, я понял, что надо контролировать, кто может к ней зайти, иначе запросы уйдут в облако при первом 429. Болело, что каждый сервис требовал свои настройки и ломался при смене модели. Это уже не интеграция, а постоянный контроль, кто где сломался.
Ментальная модель: дверь, полки, аварийный выход и диспетчерская
Дверь: единый вход (gateway), через который приходят все запросы. Приложения видят один адрес и один ключ. Честно: сначала я сам думал, что это магический портал.
Полки: виды работы: быстрый ответ, размышление, вызов инструмента, приватная обработка. Приложение просит не конкретную модель, а нужную квалификацию.
Аварийный выход: маршрут отказа. Если один участник полки занят или исчерпал квоту, дверь перебрасывает запрос к следующему свободному экземпляру; когда все экземпляры исчерпаны, возвращается контролируемый fallback‑ответ и запрос логируется для последующего анализа.
Диспетчерская: observability. Сохраняет метрики: кто пришёл, какую полку запросил, время ожидания, путь маршрута и итоговый статус; на основе этих данных формируются алерты и отчёты, позволяющие быстро находить узкие места.
одна дверь → полка по способности → явный маршрут отказа → диспетчерская видит каждый переход.
Ключевой вывод: по моему опыту, без такой схемы gateway становится чёрным ящиком. Это мой опыт.
Что я собрал
LiteLLM я использую как шлюз, который принимает запросы в формате OpenAI‑совместимого API (по моему тесту). Поэтому потребителям не нужны отдельные SDK.
На полке fast может находиться несколько моделей; приложение просто пишет POST /v1/chat/completions с заголовком X-Polka: fast. Если текущая модель исчерпала лимит, шлюз автоматически переключает запрос на соседнюю модель из той же полки. Код приложения не меняется и даже не знает, какая модель ответила.
Как через дверь ходят агенты и делегаты
У нас gateway обслуживает не только приложения. Оркестратор выдаёт делегату задачу и границы работы, а сам делегат идёт за inference через ту же дверь — с профилем, именем операции и своим request ID. Gateway не решает, кому поручить задачу: это делает orchestration layer. Gateway решает, как безопасно и наблюдаемо довезти запрос до нужной полки.
Поэтому в диспетчерской можно разделить два вопроса: кто поручил работу и какой backend фактически ответил. Для делегаций важны не только latency и HTTP-код, но и исход: done, missing_evidence, run_failed, needs_user_input или not_verified. Зелёный ответ модели ещё не доказывает, что делегат изменил правильные файлы и прошёл собственную проверку.
В этой архитектуре один OpenAI-совместимый gateway на control plane становится общей дверью для агентов, hooks, CLI и скриптов. Профили делегатов выбирают pool alias, а не провайдерскую модель: например, обычный Codex идёт через codex-pool, суверенный вариант — через codex-floor. Для Responses API у Codex есть совместимый мост к chat-бэкендам; он вычищает неподдерживаемые поля и формы инструментов до того, как запрос попадёт в пул.
Полки обещают поведение, а не бренд
Самая полезная перемена произошла в голове. Я перестал спрашивать: «Какую модель подключить?», — вместо этого спрашиваю: «Что эта полка обещает потребителю?». Например, в схеме полки указываем: быстрая отдаёт непустой текст, инструментальная выдаёт вызов функции, думающая предусматривает цепочку рассуждений, приватная исключает утечку.
Выбирая полку, я задаю ожидаемое поведение: быстрая даёт короткий непустой текст, инструментальная выдаёт реальный tool call, думающая предоставляет время на рассуждение (цепочку мыслей), приватная гарантирует, что результат остаётся внутри системы.
Проверка идёт не по вывеске, а по работе: HTTP 200 — ещё не успех: нужен правильный контент, соответствующий обещанию полки. Для быстрой проверяю, что ответ не пустой, для инструментальной ищу поле tool_calls, для думающей смотрю наличие блоков thought или рассудительных токенов, для приватной проверяю отсутствие исходящего трафика.
У полок разные контракты. fast-pool — рабочая лошадка для короткого текста, но не полка инструментов; tools-pool принимает только проверенных function-calling участников. Для embeddings, STT и vision существуют отдельные маршруты — не надо отправлять фотографию или аудио в обычный chat-пул «на авось».
В telemetry я отдельно сохраняю finish_reason. Если reasoning-модель потратила весь лимит на размышление и вернула finish_reason=length с пустым content, это не успешный ответ и не повод молча ретраить бесконечно — это контрактный сбой, который должен быть виден в диспетчерской.
Где всё это однажды соврало
Зелёный health‑check спрашивал у двери только «ты существуешь?», но не пробовал открыть её рабочим ключом, поэтому система считала дверь исправной, хотя на самом деле ключ не проверялся.
Пул выглядел здоровым: два участника из двух, но оба делили одну квоту у одного провайдера. На схеме было резервирование, в реальности — одна корзина.
Fallback по умолчанию спасал запрос, но иногда отправлял приватный текст в облако. Теперь конец маршрута явно записан: дальше нельзя. Лучше видимый отказ, чем невидимая подмена.
Где находится суверенность
Gateway живёт на постоянно включённом control-plane узле и доступен по tailnet. Провайдерские ключи остаются на узле gateway; потребители видят только внутренний токен и capability alias. Бесплатные облака можно дренировать первыми ради скорости и квот, но цепочка должна заканчиваться на локальном sovereign inference floor. Рабочая станция — клиент и место разработки, не суверенный сервер.
Для private-полки fallback пустой намеренно: если локальный участник занят или выключен, запрос должен упасть. Для public-пула, наоборот, допустим отдельный маршрут с другой политикой данных. Нельзя смешивать эти два режима одним «универсальным» fallback.
Зачем нужна диспетчерская
Каждый вызов оставляет след: потребитель, полка, реальный участник, задержка, расход, результат, класс ошибки.
Это показывает, где узкое место и куда направить исправление.
Три наблюдателя:
- Проверка живости входа с авторизацией.
- Контрактные пробы по полкам.
- Дрейф — исчезла ли модель, схлопнулся ли пул, растёт ли количество падений.
Есть третье состояние — «не проверено». Если журнал недоступен, это не зелёный свет, а выключенный прибор.
Как я теперь подключаю новое приложение
Я перестал выдавать ключи моделей и теперь каждому приложению даю один адрес шлюза, один внутренний ключ и имя полки.
При старте приложение проверяет доступ к шлюзу и падает с явной ошибкой, если дверь закрыта; каждый его запрос несёт своё имя, чтобы диспетчерская не фиксировала unattributed.
Все сетевые шаги имеют таймаут, а платная модель попадает в маршрут только при явном выборе.
Конфигурация пулов, policy и probes живёт в Git и имеет ревизию. Версию LiteLLM и мосты совместимости нужно pin-ить: обновление gateway — это изменение протокола, а не безобидный pip install latest.
Что остаётся твоим
Gateway берёт: ключи провайдеров, выбор участника, лимиты, fallback и журнал фактического маршрута.
Остаётся твоим: решить, какие виды работы существуют, где проходит граница приватности и какой отказ честнее тихой подмены.
Дверь делает правила видимыми; видимые можно проверять, менять и держать одинаковыми для всех — пока не пришёл стажёр. Сделай шлюз своим.
Если ты поднимаешь gateway сам
Пост даёт ментальную модель. Для перехода к собственной лаборатории я собрал отдельный handoff для агента-оператора: аудит узлов и trust boundaries, capability-пулы, privacy-aware fallback, observability от ingress до backend, contract probes, rollout и rollback.
Один клик — и агент разбирает статью, вытаскивает принципы и помогает применить их к твоей задаче.
Новые тексты — письмом. Редко и по делу.