mamaev.coach
EN
← Блог

Одна дверь в машинный зал: как я перестал раздавать AI-ключи каждому приложению

18 сентября 2026 · Александр Мамаев · ~2 мин

Раньше я выдавал ключи всем приложениям подряд, не думая о последствиях. После обновления модели в приложении 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.

Зачем нужна диспетчерская

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

Это показывает, где узкое место и куда направить исправление.

Три наблюдателя:

  1. Проверка живости входа с авторизацией.
  2. Контрактные пробы по полкам.
  3. Дрейф — исчезла ли модель, схлопнулся ли пул, растёт ли количество падений.

Есть третье состояние — «не проверено». Если журнал недоступен, это не зелёный свет, а выключенный прибор.

Как я теперь подключаю новое приложение

Я перестал выдавать ключи моделей и теперь каждому приложению даю один адрес шлюза, один внутренний ключ и имя полки.

При старте приложение проверяет доступ к шлюзу и падает с явной ошибкой, если дверь закрыта; каждый его запрос несёт своё имя, чтобы диспетчерская не фиксировала 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.

Открыть handoff: как поднять суверенный gateway в лабе

Прочитать с ИИ

Один клик — и агент разбирает статью, вытаскивает принципы и помогает применить их к твоей задаче.

ChatGPT →Claude →
По теме
← Блог

Новые тексты — письмом. Редко и по делу.