AI в изолированном контуре: полностью офлайн-LLM на Ollama
Практичная архитектура для AI без исходящих соединений: Ollama как локальный LLM, LanceDB для векторов, DLP-редакция каждого промпта, всё в одном офлайн-контейнере.
Почему air-gap - это форма развёртывания, а не флаг функции
Большинство «self-hosted» AI-продуктов всё равно утекают. Здесь вызов API модели, там запрос на эмбеддинг, телеметрический пинг, проверка лицензии, звонящая домой - каждое по отдельности оправданно, а вместе они означают, что ваши внутренние документы, промпты и ответы пересекают сетевую границу, которую вы не контролируете. Для оборонных подрядчиков, больниц, банков и любой организации с требованием резидентности данных это дисквалифицирует продукт. Требование не «почти офлайн» - а AI-система, работающая с выдернутым сетевым кабелем.
Simmetric Chat построен вокруг этого требования. Работа в air-gap - это не режим, который нужно разблокировать, и не плагин, который нужно купить: конфигурация сообщества по умолчанию уже офлайн - локальный LLM, локальные эмбеддинги, локальное файловое векторное хранилище и конвейер документов, которому никогда не нужен интернет. В этой статье разбирается каждый слой, обеспечивающий работу при нулевых исходящих соединениях, и то, чем вы жертвуете - очень немногим - закрывая периметр.
Локальный стек: у каждого слоя есть офлайн-значение по умолчанию
В типичной SaaS-архитектуре у AI-чата четыре внешних зависимости: языковая модель, модель эмбеддингов, векторное хранилище и конвейер документов. Значения по умолчанию Simmetric Chat заменяют все четыре локальными компонентами:
- LLM работает в Ollama. Провайдер по умолчанию - Ollama в локальной сети, а модель по умолчанию (
gemma4:latest) скачивается на этапе установки, чтобы чат работал до любой загрузки модели во время выполнения. Ollama говорит на HTTP, совместимом с OpenAI, в вашей сети, и конфигурация провайдера указывает на него URL - а не облачным эндпоинтом. - Эмбеддинги работают локально. Провайдер эмбеддингов по умолчанию - локальная модель transformers (384 измерения, работает in-process в collector), а не размещённый API эмбеддингов. Локальные эмбеддинги air-gap-совместимы по построению, а защита от несоответствия эмбеддингов делает переключение между локальными и удалёнными провайдерами безопасным - поиск деградирует до поиска по ключевым словам, пока не завершится переиндексация, вместо того чтобы выдавать оценки из смешанных пространств.
- Векторы живут в LanceDB. Векторное хранилище по умолчанию файловое, хранится в собственном каталоге развёртывания, по одной коллекции на workspace. Без сервера векторной базы, без сетевого хопа. Qdrant, pgvector и Chroma остаются доступными для команд, чей масштаб требует сетевого индекса, но ничто их не навязывает.
- Разбор документов происходит в вашей сети. Collector - сервис, который разбирает, нарезает и эмбеддит загрузки - часть вашего развёртывания. PDF, файлы Word и Excel, презентации и транскрипты YouTube обрабатываются внутри периметра, включая отсканированные документы, направляемые через серверный OCR-конвейер на vision-модели, использующий локальную vision-модель.
Ничто в этом списке не является особым случаем или прикручено задним числом. Паттерн-стратегия на каждой границе делает локальный вариант просто провайдером по умолчанию, выбираемым конфигурацией, как и любой другой.
DLP: редакция до того, как что-то уйдёт, - или до того, как оно вообще двинется
Закрытие исходящей связи не закрывает все пути эксфильтрации, потому что контент движется и внутри вашей сети: промпты входят в платформу, ответы уходят к пользователям, и оба проходят через интерфейсы чата, откуда люди копируют. Фильтр data-loss-prevention в Simmetric Chat сканирует оба направления - входящий промпт и исходящий ответ, включая потоковые токен-за-токеном сбросы, - и редактирует совпадения до того, как контент сохранён или показан.
Сканер распознаёт классы PII, которые важны в контексте чата: адреса электронной почты, номера кредитных карт, API-ключи и приватные ключи, а также - добавленные в v0.22 - структурированные европейские идентификаторы: итальянские Partita IVA, codice fiscale и IBAN, встроенные как идемпотентные pattern'ы с привязкой к метке, чтобы обычные числа их никогда не активировали. Конфигурация pattern'ов управляется админом и хранится в базе: включайте или отключайте любой pattern, добавляйте до пятидесяти пользовательских pattern'ов с живой валидацией компиляции и тестовым превью, изучайте панель истории совпадений с фильтром по источнику.
Замысел в том, что DLP - последняя линия, а не первая. Первая линия - архитектура: с локальными моделями, локальными эмбеддингами и локальными векторами нет никакого стороннего эндпоинта, куда чувствительный контент мог бы попасть в принципе. Затем DLP покрывает оставшиеся пути - контент, покидающий платформу в сторону ваших же пользователей, или который добросовестный сотрудник вставит в промпт. Он покрывает и поверхность widget, так что обращённые к посетителям разговоры на вашем сайте редактируются той же цепочкой фильтров, а след аудита отличает чат-трафик от widget-трафика.
Развёртывание в одном контейнере: всё на одном хосте
Закрыть периметр - одно; доставить ПО в периметр - другое. Air-gap-среды обычно не могут удобно запустить compose-стек из пяти сервисов - они запускают один хост с одним образом, и чем меньше движущихся частей, тем меньше артефактов нужно проверить и перенести.
Simmetric Chat поставляет all-in-one образ в одном контейнере именно для этого случая. Один контейнер под управлением процесс-менеджера выполняет всё: PostgreSQL 16 (с расширением pgvector, скомпилированным из исходников на этапе сборки образа, то есть установленным офлайн), API-сервер, документальный collector и widget-сервис. Контейнер открывает три порта - 3000 для API, 3210 для collector, 3211 для widget - и проверяет собственное здоровье на эндпоинте /api/health, который сообщает состояние базы, collector и диска.
Доставить образ внутрь - та же дисциплина, что и с любым другим артефактом: перенесите tarball образа (или проверенное зеркало registry), загрузите и запустите. Средовые значения по умолчанию в образе - строительные заглушки: продакшен переопределяет JWT_SECRET, COLLECTOR_SECRET и ключ шифрования через конфигурацию окружения, и сервер громко падает при старте, если продакшен-секреты отсутствуют, - так что наполовину настроенный air-gap не запустится молча. После старта обновления ПО - это новые версии образа, а не установка пакетов: доступ к интернету не нужен ни в одной точке жизненного цикла.
# Перенесите внутрь периметра, затем:
docker load -i simmetric-chat-all-in-one.tar
docker run -d \
-p 3000:3000 -p 3210:3210 -p 3211:3211 \
-v simmetric-storage:/app/storage \
-e JWT_SECRET=$(openssl rand -hex 32) \
-e COLLECTOR_SECRET=$(openssl rand -hex 32) \
simmetric-chat:all-in-oneВеса моделей идут тем же путём. Модели Ollama скачиваются на подключённой машине, сохраняются как блобы и импортируются внутрь периметра - дальше платформа обращается с ними как с любой другой локальной моделью. Ничто в стеке не звонит домой, чтобы проверить модель, лицензию или себя: телеметрия отключена по умолчанию, нет внешней зависимости для отчётов об ошибках, а проверка лицензии - локальная проверка подписи RS256 встроенным публичным ключом.
Что всё ещё работает офлайн - и чего действительно нет
Air-gap-развёртывание должно быть честным насчёт своих краёв. Что работает при нулевых исходящих соединениях:
- Чат с локальными LLM через Ollama, потоковая передача токен за токеном по SSE
- Загрузка и приём документов - разбор, нарезка, локальные эмбеддинги, векторы LanceDB
- Гибридный поиск с цитатами, фильтрация по метаданным и конвейеры синтеза
- RBAC, workspaces, проекты, следы аудита и вся админ-поверхность
- Webhook'и (доставляемые вашим собственным получателям), Web Push (VAPID с вашего сервера) и API-ключи
- Встраиваемый widget, работающий против вашего же widget-сервиса
Чего действительно нет: облачных LLM-провайдеров, размещённых эмбеддингов, внешнего веб-поиска и всего остального, что требует исходящего вызова. Дизайн с graceful degradation означает, что каждая из этих интеграций - дополнение конфигурации, а не изменение кода, - но офлайн-значение по умолчанию поставляется просто без них. Платформа стартует, обслуживает запросы и обрабатывает документы одинаково - открыт периметр или закрыт.
Результат - AI-workspace, чья безопасность не зависит от доверия к обращению с данными поставщика, потому что поставщика в пути данных нет. Всё, что касается ваших документов - модель, эмбеддинги, векторный индекс, парсер - работает на оборудовании, которым вы владеете, внутри сети, которую вы контролируете, в образе контейнера, который вы можете проверить.
