Полтора года назад мы запустили AI-ассистента внутри «Первой Формы», чтобы он помогал сотрудникам компании выполнять рабочие процессы. Сегодня он отвечает на вопросы по регламентам, ищет документы, подсказывает по задачам, даже запускает проверку контрагентов и формирует КП. Всё это работает через большую языковую модель, а точнее — через набор моделей, потому что разные задачи требуют разного подхода: для быстрой классификации входящего запроса нужна одна, для развёрнутой генерации ответа — другая, для работы с длинным контекстом — третья.
Модели живут в облаке, и это даёт свободу выбора. Инструменты, которыми агент пользуется, тоже ходят в интернет: поиск по документации обращается к облачным моделям векторизации текста, проверка контрагентов — к внешним сервисам вроде Контур.Фокуса и так далее. Агент обновляется из GitLab, CI/CD развозит изменения по стендам автоматически, мониторинг стекается в один дашборд. Нас это устраивало.
Недавно заказчик из промышленного сектора обратился к нам с задачей: «У нас закрытый контур, интернета нет и доступа к облачным API — тоже. Единственное, что у нас есть — это сервер с локальной моделью и наша внутренняя инфраструктура. Хотим такого же ассистента, как у вас». В статье рассказываем, как мы с этим справились. Спойлер: не без приключений.
Как агент устроен в «открытом мире»
Чтобы понять, что именно ломается при отключении интернета, стоит сначала разобраться, на чём держится работа агента в нормальных условиях.
Сам агент — это набор из примерно семидесяти независимых модулей, каждый из которых отвечает за свой инструмент или этап обработки. Один модуль принимает запрос пользователя и решает, к какой категории он относится. Второй отвечает за поиск по документации. Третий отправляет запрос к большой языковой модели и получает ответ. Четвёртый может проверить контрагента по ИНН. Пятый — найти задачу в системе по фильтру. И так далее. Всего таких инструментов — больше тридцати семи, и каждый из них в какой-то момент обращается либо к внешнему API, либо к облачной модели, либо к интернет-ресурсу.
Поиск по документации построен на гибридном подходе: сначала BM25 (классический текстовый поиск по ключевым словам), затем dense vector поиск (где запрос и документы переводятся в векторные представления и сравниваются семантически), а затем cross-encoder rerank — повторное ранжирование результатов более тяжёлой моделью, которая даёт финальную оценку релевантности.
Все эти стадии, кроме BM25, требуют обращения к облачным моделям векторизации. Индекс хранится на отдельном сервисе, а для его обновления используется GitLab webhook — как только в документацию вносятся изменения, файл автоматически обновляется в индексе.
Деплой кода агента выглядит так: разработчик вносит изменения в один из семидесяти C# скриптов, отправляет изменения в GitLab, оттуда через API скрипт попадает сначала на dev-стенд, затем на preprod, затем на production. Между стендами синхронизация тоже идёт через Git. Мониторинг — HTTP-логи, smoke-тесты, алерты — тоже завязан на внешние системы.
Что ломается при работе без интернета
Когда мы сели разбирать, что именно требуется изменить для работы в закрытом контуре, список получился длиннее, чем мы ожидали. И модель в этом списке занимала далеко не первое место.
Языковая модель
Самое очевидное: вместо набора облачных моделей у нас остаётся одна локальная. Заказчик предоставил сервер с NVIDIA H200, на котором крутится Qwen 3.5 с примерно 122 миллиардами параметров. По качеству она нас устраивала, проблема была в задержке ответа.
Облачная модель отвечает за одну-три секунды, локальная — заметно дольше. Агент встроен в интерфейс системы: пользователь задаёт вопрос и ждёт ответа прямо в рабочем окне. Если ответ идёт слишком долго, это ломает пользовательский опыт. Нам пришлось пересматривать всю стратегию промптов.
Если в облаке мы могли позволить себе роутинг — быструю модель для простых задач вроде классификации и мощную для сложных (генерация, анализ), то в закрытом контуре модель одна, и она должна справляться со всем спектром задач. Это значит, что промпты нужно адаптировать под её сильные и слабые стороны, а процесс генерации разбивать на чанки с использованием стриминга, чтобы пользователь видел, что ответ пишется, а не висит «в молчании».
Инструменты, которые ходят в интернет
Часть инструментов агента просто перестала существовать. Например:
-
web search и web fetch — поиск в интернете и чтение веб-страниц;
-
kontur focus check — проверка контрагентов по ИНН;
-
browser agent run — автоматизация браузера для получения данных из внешних источников.
Здесь возникает проблема Агент должен не падать с ошибкой, а понимать, что инструмент недоступен, и честно сообщать об этом пользователю: «Проверить контрагента сейчас не могу — проверка обращается к внешним сервисам, которые недоступны в вашем контуре». Иначе он начинает придумывать данные, а это может привести к репутационным и финансовым рискам.
Поиск по документации
Здесь мы столкнулись с одной из самых трудоёмких проблем. Гибридный поиск, который в открытом контуре работает через облачные модели векторизации, в закрытом контуре рассыпается на части. BM25 работает без интернета, это чистая математика над индексом, а вот dense vector поиск и rerank требуют вызова облачной модели — и в закрытом контуре они просто не работают.
Подробнее о том, как мы решили проблемы деплоя и поиска
Мы пошли по пути manifest-driven деплоя через скрипт deploy-cutover.py. Скрипт держит манифест — список из скриптов, которые должны быть на стенде. При запуске он сравнивает размер каждого скрипта на сервере с размером в локальном манифесте. Если размеры совпадают, скрипт считается in sync. Если нет — его нужно переписать.
Почему размер, а не хеш? Потому что C# скрипты хранятся как plain text, и совпадение длины в сочетании с визуальной сверкой diff даёт достаточную уверенность в идентичности. Хеш потребовал бы хранения эталонов на каждом стенде и дополнительных вычислений — мы посчитали, что это избыточно для этой задачи.
Скрипт работает в два этапа: сначала dry-run — показывает план, какие скрипты будут перезаписаны и какой реальный drift между манифестом и сервером, а затем, после подтверждения, — выполнение. При этом у каждого стенда могут быть исключённые из деплоя скрипты и свои параметры. С каждым обновлением мы автоматически накатываем их поверх стандартной конфигурации, чтобы они не затирались.
Чтобы реализовать поиск, мы подняли контейнер с собственным сервисом, который назвали zvec-search. Это FastAPI-приложение, которое включает в себя модуль векторизации, модуль ранжирования и управление индексом. Выбор стека зависит от того, какое железо есть у клиента:
-
Если есть GPU — можно использовать vLLM с моделью BGE-m3 для векторизации текста.
-
Если GPU нет — llama.cpp GGUF на CPU, медленнее, но работает.
Мы пошли по первому пути, потому что у заказчика были GPU-сервера под модель, и часть ресурсов мы смогли выделить под векторизацию.
Первичная индексация пяти тысяч с лишним файлов документации заняла примерно столько же, сколько в облаке. А вот инкрементальное обновление без webhook’а пришлось делать через отдельный скрипт — он забирает обновления документации через прокси-сервер и запускает частичную переиндексацию.
А что по мониторингу
Smoke-тесты после деплоя остались. Алерты пришлось убрать, потому что мессенджеры недоступны. HTTP-логи пишутся, но не уходят во внешний агрегатор. Отладка ведётся через прямой доступ к стенду. Это, пожалуй, единственное, что мы не смогли полностью автоматизировать — мониторинг в закрытом контуре остаётся зоной ручного контроля.
Что получилось в результате
Агент в закрытом контуре отвечает на вопросы в задачах, ищет по документации, помогает с бизнес-процессами. Деплой скриптов на пять стендов проходит за один прогон, а задержка ответа укладывается в ожидание пользователя за счёт стриминга и чанковой генерации.
Главный вывод, который мы для себя сделали: при переходе AI-агента в закрытый контур модель — наименьшая из проблем. LLM сегодня достаточно хороши, чтобы работать в enterprise-сценариях, и локальные сервера для инференса (H200, A100) дают приемлемую производительность.
А вот всё, что было «дано по умолчанию» в открытом контуре, превращается в отдельный инженерный проект. Примерно восемьдесят процентов работы пришлось на инфраструктуру, и только двадцать — на адаптацию модели.
Мы нашли рабочий пайплайн и готовы повторять его для других клиентов — главное, заранее понимать, что инфраструктура в закрытом контуре требует такого же внимания, как и сам AI.
Автор: 1forma


