AI-шлюз
pacod-gw
Один шлюз перед всеми моделями — с ключами, ценами, квотами и учётом использования, которые вашим приложениям не нужно держать у себя.
pacod-gw стоит между вашими приложениями и LLM-провайдерами. Ядро на Rust проксирует или транслирует каждый вызов, не допускает ключи провайдеров в ваши приложения, следит за квотами, рассчитывает стоимость каждого запроса и передаёт данные об использовании в аналитику на ClickHouse — и всё это управляется из консоли на SvelteKit.
- Apache-2.0
- Версия 0.1.0
- Предварительная
Возможности
-
Proxy или managed
Проксируйте API провайдера байт в байт или доверьте шлюзу разрешение шаблонов, слияние параметров и трансляцию между диалектами.
-
Много диалектов, один контракт
OpenAI, Anthropic, Gemini и собственный диалект; проверено вживую на DeepSeek и GLM, а Kimi и Ollama эмулируются mock-провайдером.
-
Ключи остаются в шлюзе
Хранимые учётные данные провайдеров и ключи шлюза для каждого рабочего пространства. Приложения никогда не видят ключ провайдера.
-
Квоты и лимиты
Лимиты на токены и запросы для организации, рабочего пространства и ключа применяются до того, как вызов покинет шлюз.
-
Стоимость каждого вызова
Журнал использования со стоимостью прямо в ответе, так что команды сразу видят, во что обошёлся каждый запрос.
-
Аналитика на ClickHouse
События поступают в отдельный сервис аналитики с отчётами и задачами по расписанию; для эксплуатации — Prometheus, Grafana и OpenTelemetry.
-
Маршрутизация и намерения
Схемы и правила маршрутизации, а также плагин определения намерений с движками LLM, Jev и cascade, чтобы выбрать подходящую модель для каждого запроса.
-
Режим Lite
Та же плоскость данных в одном контейнере — без базы данных и консоли — с настройкой через один файл .env и журналированием каждого вызова.
Место в экосистеме
Зачем нужен шлюз
Каждый продукт, работающий с языковой моделью, в итоге заново реализует одно и то же: хранение ключей провайдеров, трансляцию между API, подсчёт токенов, соблюдение лимитов и объяснение счёта. pacod-gw забирает всё это из приложения. Приложение обращается к одной конечной точке с ключом шлюза; шлюз решает, какая модель обслужит вызов, сколько он может стоить и кто за него платит.
Первая цель конкретна: заменить LiteLLM-прокси перед приложением, изменив только его базовый URL и ключ.
Два режима, одно ядро
Ядро — это один бинарный файл на Rust с двумя слушателями: плоскостью данных для приложений и API управления для консоли и скриптов. Режим выбирается префиксом URL и разрешается для каждого ключа.
- Режим proxy (
/proxy/{provider}/…) передаёт собственный API провайдера байт в байт и добавляет аутентификацию, лимиты и учёт. Шаблон при этом всё равно может задать модель и её параметры. - Режим managed обслуживает
/v1/chat/completionsот OpenAI,/v1/messagesот Anthropic,generateContentот Gemini и собственный/gw/v1/chat, а также эмбеддинги и синтез речи.
В режиме managed маршрут определяют диалект вызывающей стороны и провайдер модели:
| Вызывающая сторона говорит | Модель находится у | Что происходит |
|---|---|---|
| Диалект OpenAI | OpenAI-совместимого провайдера (OpenAI, DeepSeek, GLM, Kimi, Ollama, …) | Байты вызывающей стороны правятся точечно; стоимость добавляется в usage. |
| Диалект Anthropic | Anthropic | Диалог доходит до Anthropic в том виде, в каком был отправлен; стоимость возвращается в заголовке. |
| Любая другая пара и собственный интерфейс | любого провайдера | Трансляция через нейтральную модель и обратно, поэтому вызов может переключиться на резервную модель и через границу диалектов. |
Один вызов, шаг за шагом
- Аутентификация. Ключ ищется по HMAC в снимке конфигурации в памяти.
- Чтение. Сканер находит модель и флаги потоковой передачи, не разбирая всё тело; плагины рабочего пространства видят диалог.
- Разрешение. Шаблон (
@nameилиx-gw-template) или идентификатор разрешённой модели определяет модель и её параметры. - Допуск. Ограничение одновременных вызовов ключа и каждый применимый лимит должны быть соблюдены.
- Отправка. Сохранённые учётные данные провайдера заменяют ключ шлюза. В режиме managed ответ 429, 5xx или потерянное соединение повторяются или передаются следующей модели, прежде чем вызывающая сторона получит хоть один байт.
- Передача и расчёт. Ответ возвращается потоком, а отвод читает использование из копии; затем обновляются стоимость и счётчики, а событие использования уходит в спул на диске.
Ответ содержит x-gw-request-id, x-gw-delay-ms (время, добавленное самим шлюзом),
x-gw-model-served и, для вызовов managed без потоковой передачи, x-gw-cost.
Конфигурация хранится в PostgreSQL, но путь запроса её не ждёт: изменение подменяет снимок примерно за две секунды, а если какое-то хранилище недоступно, трафик продолжает идти на последнем снимке.
Организации, рабочие пространства и ключи
Организация — это клиент или крупное подразделение. Рабочее пространство — одно приложение, проект, среда или команда; ему принадлежат его ключи, шаблоны, лимиты, настройки и использование. Два правила покрывают любое ограничение: списки разрешений пересекаются, а числовые пределы берут минимум.
Ключи провайдеров принадлежат инсталляции, организации или рабочему пространству, и вызов использует ближайший: собственный ключ рабочего пространства, затем ключ его организации, затем общий ключ инсталляции. Ключ, который шлюз уже хранит, можно передать вниз организации или рабочему пространству, не вводя его заново; секрет никогда не покидает шлюз.
Роли описаны в одном файле, authz/gw.yaml, из которого генерируются проверки и ядра, и консоли:
| Роль | Что охватывает |
|---|---|
| Администратор платформы | Провайдеры, учётные данные, модели, цены, организации, люди |
| Администратор организации | Её настройки, рабочие пространства, участники и выданные модели |
| Администратор рабочего пространства | Шаблоны, ключи, лимиты, настройки и режим аудита одного рабочего пространства |
| Аудитор | Читает конфигурацию, использование и записи режима аудита |
| Наблюдатель | Читает конфигурацию и использование; без секретов и записей режима аудита |
Вызовы несут атрибуцию — end_user, session_id, trace_id, объявленные теги, agent_id,
task, complexity, — которую используют отчёты, лимиты и маршрутизация. Пользователи входят в
консоль с локальными учётными записями или, при PGW_AUTH=oidc, через pacod-id.
Лимиты и стоимость
Лимит применяется к организации, рабочему пространству, ключу или конечному пользователю. Он
считает токены, запросы или расходы за минуту, час, день, неделю, месяц или за всё время и либо
отклоняет вызовы (429, 402), либо только считает и сообщает. Приложения могут узнать состояние
квоты пользователя через GET /gw/v1/quota. Счётчики объединяются между инстансами через
PostgreSQL примерно раз в секунду, поэтому лимит может быть превышен примерно на секунду трафика.
Каждый вызов оценивается при завершении по версионированной цене, которая учитывает чтение и запись кеша, рассуждения, пакетные варианты, ступени по размеру входа и внепиковые окна. Цены по умолчанию нет: модель можно активировать только с ценой или с явной пометкой «бесплатно». Обнаружение моделей по умолчанию раз в день запрашивает у каждого провайдера список его моделей и предлагает те, которых в шлюзе нет; модель, добавленная из предложения, остаётся черновиком, пока у неё нет цены. Трекер цен по расписанию читает прайс-лист LiteLLM или страницу цен провайдера и применяет изменения в пределах заданной границы либо предлагает их на утверждение. Пока у него нет источников, консоль предлагает список LiteLLM для OpenAI, Anthropic, Gemini, DeepSeek и z.ai.
Маршрутизация и намерения
Шаблон может содержать пул моделей с уровнями, весами и приоритетами. Без дополнительной настройки
обслуживает первая модель, а следующая подхватывает при 429, 5xx или потере соединения. Схема
маршрутизации добавляет правила по тому, что сообщает вызывающая сторона (complexity, task,
теги, размер входа или выражение CEL), и алгоритмы: по приоритету, взвешенный, round robin,
наименьшая задержка и самый дешёвый, с необязательной привязкой к сессии, чтобы кеши промптов
оставались «тёплыми». Предохранитель (circuit breaker) отодвигает сбоящую модель, а каждый ответ
называет в x-gw-route правило, по которому он был направлен.
Плагин намерений отвечает на вопрос «для чего этот вызов?» по каталогу, который пишет организация или рабочее пространство, с помощью LLM, движка Jev или каскада из обоих. Его вердикт может заблокировать вызов, направить его, учесть в лимитах по намерению или пометить для проверки; сначала он может работать в теневом режиме и никогда не меняет диалог.
Новый каталог начинается со встроенного шаблона starter с намерениями универсального ассистента
и правилами, которые помечают и блокируют. Новая привязка начинается с шаблона, который шлюз
заполняет для своей области: теневой режим, этот каталог и самая дешёвая выданная модель, которая
отвечает по JSON-схеме, или Jev, если такой нет. Панели пробы и оценки в консоли запускают движки
на примерах текстов, сначала на демонстрационных данных, подготовленных для каталога, чтобы
сравнить движки до того, как привязка начнёт применять решения. См.
руководство по маршрутизации и
концепцию плагинов.
Наблюдаемость и аудит
События использования приходят в сервис аналитики пакетами из спула каждого инстанса; доставка выполняется как минимум один раз, а ClickHouse удаляет дубликаты. Сервис строит отчёты по использованию, ошибкам, задержке шлюза, рейтингам и конечным пользователям, с экспортом в CSV, и выполняет задачи по расписанию. Консоль обращается к нему только через ядро, которое проверяет роль вызывающего и принудительно задаёт область видимости.
Prometheus, дашборды Grafana, правила Alertmanager и трейсы OpenTelemetry покрывают эксплуатацию; консоль показывает текущие показатели, состояние провайдеров и открытые оповещения.
Режим аудита сохраняет полные вызовы для отладки: запрос, каждую попытку к провайдеру и ответы. По умолчанию он выключен, организация может его потребовать или запретить, учётные данные удаляются до сохранения, а записи по умолчанию истекают через 14 дней.
Модель безопасности
- Ключи провайдеров запечатываются по отдельности с AES-256-GCM под ключом шифрования ключей, который берётся из окружения или файла, но никогда из базы данных. API показывает только отпечаток.
- Ключи шлюза (
pgw_…) содержат 32 случайных байта, хранятся как HMAC-SHA256 с pepper и показываются один раз. - Разделение арендаторов. Рабочее пространство определяется ключом, а не запросом, и ядро ограничивает отчёты тем, что разрешают роли вызывающего.
- Исходящие соединения к провайдерам и источникам цен проверяются после разрешения имён: частные и link-local адреса отклоняются, если оператор явно их не открыл.
- Промпты не попадают в логи, метрики и трейсы, которые содержат только метаданные; тела сохраняются только в режиме аудита.
Модель угроз перечисляет и то, что остаётся открытым: например, порт метрик не требует аутентификации и несёт метки арендаторов, а в общей сети любой контейнер pacod может обратиться к API управления, которое тогда защищено только собственной аутентификацией.
Развёртывание и эксплуатация
Ядро работает в роли all или разделяется на инстансы gateway и один инстанс admin. Инстансы
делят один PostgreSQL и больше ничего.
| Схема | Файлы |
|---|---|
| Одно ядро | deploy/docker-compose.yml с docker-compose.standalone.yml |
| Несколько шлюзов на одном хосте | плюс docker-compose.scale.yml |
| Шлюзы на других машинах | docker-compose.hub.yml централизованно, deploy/node/ на каждой машине |
| Kubernetes | deploy/helm/pacod-gw |
| Минимальный шлюз | deploy/lite/docker-compose.yml |
Как и все проекты pacod, шлюз подключается к общей Docker-сети pacod-net, где у каждого сервиса
своё имя: ядро называется pacod-gw, остальные сервисы — gw-*. docker-compose.yml содержит
собственные сервисы шлюза; docker-compose.standalone.yml добавляет его копии общих компонентов:
PostgreSQL, pacod-id, Caddy и мониторинг. pacod-demo
запускает шлюз вместе с остальными проектами pacod на demo.pacod.io.
pacod-demo загружает только первый файл и запускает каждый общий компонент один раз; ClickHouse остаётся
у шлюза.
Caddy завершает TLS и направляет каждый вызов на готовый шлюз с наименьшим числом вызовов в работе. Ранбук описывает секреты, резервное копирование и восстановление, ротацию ключей, обновления и устранение неполадок; встроенный тестер запускает тест-кейсы и нагрузочные тесты из консоли.
pacod-gw-lite сохраняет трансляцию, потоковую передачу, повторы и переключение на резервные
модели, с лимитами на ключ в памяти и одной строкой лога на каждый вызов с его стоимостью. Его
клиенты делят ключи провайдеров и не отделены друг от друга на стороне провайдера. В общей сети
минимальный шлюз тоже называется pacod-gw, поэтому на одном хосте работает либо он, либо полный
шлюз (подробнее).
Что проверено
- OpenAI, Anthropic, Gemini, DeepSeek и GLM проверены живыми вызовами; Kimi, Ollama, xAI и Vertex AI — только по их документации и на имитирующем провайдере.
- Проектная цель — 10 000 одновременных потоков в режиме proxy и 5 000 в режиме managed на инстанс (2 CPU, 1 GB) — пока не подтверждена нагрузочным тестом.
- Пока не опробованы: сертификаты Let's Encrypt, хост на Linux, обновление между релизами и Helm-чарт в реальном кластере.
- Не входят в первый релиз: управление MCP и инструментами, контентные ограничители и кеширование ответов.
Страница об архитектуре показывает, как устроен код и где он отличается от концепции дизайна.
Технологии
- Языки
- Rust
- TypeScript
- Python
- Технологии
- Rust
- PostgreSQL
- ClickHouse
- SvelteKit
- Caddy
- Docker
- Helm
- Prometheus
- OpenTelemetry
Быстрый старт
make test-servicesmake verifymake dev-upmake smoke-mockopen http://localhost:13000make lite-dev-upmake lite-smokeРаботает с
-
pacod-id
С PGW_AUTH=oidc пользователи входят в консоль через pacod-id; роли и рабочие пространства берутся из его ленты авторизации.
-
pacod-ap
Каждый вызов LLM от агента проходит через шлюз, который управляет моделями, ценами, ключами и учётом использования.
-
pacod-cms
AI-перевод в студии обращается к шлюзу, поэтому CMS никогда не хранит ключ провайдера.
-
pacod-kb
Необязательный проход очистки через LLM в базе знаний маршрутизируется, тарифицируется и атрибутируется шлюзом.
Лицензия
Apache-2.0