
Меня зовут Максим Шакуров, я ML-инженер в VK.
Сегодня индустрия активно внедряет LLM для оптимизации рабочих процессов. Наша команда решила идти не от самой технологии, а от реальных потребностей. Чтобы найти процессы с наибольшим потенциалом для автоматизации, мы начали с аудита текущей рутины: проанализировали, с какими запросами аналитики и менеджеры приходят в чаты поддержки к инженерам Data Office (специалистам, отвечающим за сбор, хранение и миграцию корпоративных данных) и к разработчикам нашей платформы данных (команде, которая поддерживает и дорабатывает DWH). Оказалось, что львиная доля типовых запросов сводится к трём категориям:

Из этих категорий логично выросли три ключевые функции нашего будущего бота:
-
Поиск нужных данных среди тысяч витрин.
-
Генерация валидного SQL и ClickHouse-кода под задачу пользователя.
-
Ответы по внутренней документации.
Отсюда сложился образ нашей будущей системы: она помогает ориентироваться в каталоге витрин, может рассказать, что и где хранится, помогает заполнить заявку на доступы, отвечает на вопросы по специфической документации и пишет скрипты, которые люди могут сразу забрать к себе в ETL-процессы.
Чтобы обозначить ценность получившегося решения, приведу числа за февраль 2026 года: нашей новой системой воспользовался 731 сотрудник, при этом Retention составил 68% (499 человек вернулись). По нашим оценкам, это ежедневно экономит десятки часов чистого времени специалистов. Статистика показывает, что инструмент помог снизить нагрузку по типовым запросам сотрудников компании.
В этой статье я расскажу, как мы реализовали такого помощника, перейдя от классического RAG к роевой архитектуре ИИ-агентов. Этот материал будет полезен разработчикам и менеджерам, которым нужно запустить масштабируемую ИИ-систему в закрытом контуре компании для работы с проприетарными данными. Я покажу, какой путь мы прошли, с какими архитектурными вызовами столкнулись и как вы можете адаптировать этот опыт для своего проекта.
Архитектура: почему рой, а не обычный RAG?

С проблематикой и верхнеуровневым представлением мы определились, далее перед нами встал вопрос реализации. Первая мысль: сделать очередного RAG-бота и в его базу загрузить всю доступную информацию. В качестве эксперимента мы действительно попробовали реализовать классический подход, но на практике столкнулись с рядом существенных ограничений.
Для начала краткая сводка, как устроен RAG.
Мы взяли:
-
Redis в качестве векторного хранилища;
-
BERT — в качестве эмбеддера (превращение текста в вектора);
-
bge-m3 — в качестве реранкера (сортирует топ N ответов из БД по релевантности);
-
LLM с поддержкой tool calling — в качестве генератора итоговых ответов и вызова внешних инструментов.
Парсим нашу базу знаний и загружаем её в Redis, предварительно превратив каждый чанк в эмбеддинг. Когда пользователь задаёт вопрос, идём в Redis по расстоянию l2 и находим в базе знаний 50 актуальных кусочков. При помощи реранкера сортируем эти 50 кусочков по релевантности (так как расстояние l2 даёт только приблизительное, неточное понимание релевантности данных и запроса). Выбираем 15 актуальных кусочков и собираем промт для модели: ЗНАНИЯ + ЗАПРОС ПОЛЬЗОВАТЕЛЯ.

Сложность масштабируемости. Из единого решения трудно вычленить отдельные логические цепочки, чтобы доработать их изолированно. Внося изменения в один блок, мы не можем гарантировать, что это не вызовет регрессию в остальных частях системы.
Снижение точности маршрутизации и генерации. Если один ИИ-агент получает комплексный запрос (например: «Найди мне таблицу X, напиши скрипт для получения колонок по условию Y и расскажи про синтаксис использованных функций»), то возникает перегрузка контекстом. Пытаясь одновременно искать данные, генерировать код и обращаться к документации, модель начинает смешивать эти предметные области. В результате страдает предсказуемость: повышается вероятность генерации синтаксически неверных функций или выбора нерелевантных таблиц
Избыточность контекста. При генерации скрипта агенту совершенно не обязательно знать логику форматирования итогового ответа. Аналогично, для поиска нужной таблицы в DWH не требуется понимание синтаксиса SQL-запросов. Объединяя все инструкции в единый системный промпт, мы неизбежно перегружаем контекстное окно модели лишним шумом, что напрямую снижает качество конечной генерации.
Стало понятно: нужно разнести функциональность и промпты по разным сущностям. Заставить их взаимодействовать друг с другом, декомпозируя и делегируя выполнение частей комплексной задачи.
Здесь на помощь приходит LangGraph, который имеет несколько заранее реализованных архитектур для создания мультиагентных систем. Из всего многообразия подходов мы остановились на паттерне Swarm (Рой).
В отличие от жёстких иерархических структур (Supervisor-паттерн), Swarm представляет собой сеть узлов, где каждый агент — это независимый эксперт в своей узкой предметной области. У него есть собственный набор системных промптов агентов и набор инструментов. Агенты общаются между собой внутри системы, передавая контекст задачи и результаты выполнения, декомпозируя сложный запрос на атомарные шаги
Знакомство с командой: четыре агента-эксперта

-
Support (Оркестратор). Его главная цель — определить намерения пользователя, правильно скоординировать и декомпозировать запрос. Оркестратор классифицирует запрос пользователя и определяет сценарий. Содержит в себе большой промпт, описывающий общее поведение системы и пути взаимодействия всех агентов
-
Data Search (Сыщик). Главная цель агента — найти путь к нужной витрине через RAG по корпоративному каталогу данных, и объяснить пользователю, как правильно с ней работать. Агент раскрывает бизнес-смысл найденных сущностей и подсказывает, как именно эти данные применимы к задаче. При этом важно отметить: он оперирует исключительно метаданными (схемами и описаниями полей), не имея доступа к самому содержимому таблиц.
-
Фича с доступами. Система строго соблюдает внутренние политики безопасности. Если найденная таблица находится в директории, на доступ к которой у сотрудника нет прав, то агент не раскрывает конфиденциальную информацию. Вместо этого он выдаёт точный путь к ресурсу и генерирует готовую ссылку с инструкцией для легитимного запроса доступов через внутреннюю систему управления правами (IDM).
-
-
Doc Assistant (Библиотекарь). Его главная цель — поиск ответов в массиве технической документации. Агент реализует RAG-подход по базе знаний, которая объединяет инструкции по SQL-диалектам платформы, BI-сервисам, системе оркестрации задач и другим инфраструктурным инструментам. Помогает разобраться в нюансах работы движков, подсказывает корректный синтаксис функций и предоставляет прямые ссылки на соответствующие разделы документации.
-
QL generator (Кодер). Его главная цель — написать корректный SQL-запрос. Агент знает проприетарный синтаксис наших БД, умеет получать схему нужной таблицы, ориентироваться во вложенности данных и определять, как именно надо обращаться к хранилищу (к конкретной таблице или диапазону партиций). Также он умеет программно валидировать свой код и исправлять ошибки по traceback’у.
Важный нюанс безопасности: подключение агента к хранилищу для разведки схем и валидации кода осуществляется строго через ограниченную сервисную роль (service account). Агент работает только с метаданными, а все его действия и сгенерированные скрипты сохраняются для прозрачного аудита.
В нашей реализации агенты физически разделены: каждый работает в отдельном сервисе, а Swarm обращается к ним по HTTP. Это дало нам дополнительную устойчивость, более удобное развёртывание и возможность обновлять отдельные части системы без остановки всей мультиагентной цепочки. При этом такой уровень разделения не обязателен: в более компактной реализации оркестратор и агенты вполне могут жить в одном сервисе.
Теперь самое интересное: рассмотрим, как агенты связаны и какие инструменты им доступны.

Расскажу про несколько архитектурных решений, которые мы используем для повышения качества генерирования кода:
-
Анализ метаинформации таблиц. Если ваша платформа позволяет получать метаданные SQL- или NoSQL-таблиц, то стоит реализовать отдельный инструмент для агента. В момент генерирования запроса агент обращается к пути расположения данных, проверяет физическое существование таблиц и считывает их схему. Это даёт модели точный контекст для написания корректного скрипта с правильными именами колонок и типами данных.
-
Предварительная валидация скриптов. Перед тем как отдать готовый код пользователю, мы программно запускаем его в базе в режиме
validate(аналог Dry Run). Если возникает синтаксическая или логическая ошибка, то traceback вместе с исходным кодом автоматически возвращается агенту-генератору для самокоррекции. -
Делегирование при неизвестном синтаксисе. Если агент-кодер сталкивается со специфической функцией, которая не поддерживается нашей внутренней платформой, то он передаёт управление агенту документации. Вызов сопровождается чётким системным указанием: найти справку по конкретной функции и вернуть контекст обратно агенту написания скриптов для завершения задачи.
Внедрение этих подходов позволило повысить долю успешно сгенерированных запросов к БД более чем в два раза. Если вы используете стандартный и широко известный SQL-диалект без специфических внутренних модификаций, то часть этих проверок может оказаться избыточной.
Observability: как не сойти с ума при дебаге (Langfuse)

Неизбежно наступает момент, когда нужно анализировать работу системы в проде. Искать причины галлюцинаций в сухих текстовых логах бэкенда — занятие мучительное. Логи разных вызовов смешиваются, а понять контекст общения агентов между собой практически невозможно.
Существует много готовых решений для трассировки LLM-приложений (LangSmith, Langfuse и др.). Мы остановились на Langfuse. Его главное преимущество для нас — возможность развернуть self-hosted версию на серверах компании. Мы не делимся конфиденциальными трейсами со сторонними облаками и при этом из коробки получаем понятный интерфейс для аналитики.
У нас логируется каждый шаг: для любого запроса пользователя создается отдельный трейс, где видно всю историю сообщений, тайминги, отработавшие инструменты и, главное, логику передачи управления между агентами.
Как это выглядит: у нас есть вызовы на каждый запрос пользователя, каждый трейс можно открыть и посмотреть на историю сообщений пользователя, то как отработал каждый из доступных инструментов и как агенты общались друг с другом.
Такая прозрачность позволяет нам быстро находить системные ошибки. Приведу пару примеров из практики.
Адаптация логики работы с партиционированными данными. В процессе эксплуатации системы мы выявили специфические сценарии, требующие более тонкой настройки агента SQL-генератора. В нашем хранилище логи записываются по путям с датами (например, ../1d/07-01-2026, ../1d/08-01-2026 и т. д.). Чтобы модель лучше ориентировалась во временных интервалах, мы доработали инструмент сканирования путей. Теперь если переданный путь похож на партицию, то агент автоматически анализирует соседние таблицы. Это даёт ему полный контекст для того, чтобы считывать нужный срез через конструкцию RANGE(). Такое расширение алгоритма кардинально улучшило качество генерирования скриптов для работы с историческими данными.
Нарушение маршрутизации (перехват управления). Пользователи жаловались, что иногда вместо готового скрипта система выдавала просто выдержку из документации с абстрактными примерами функций. Анализ трейсов показал причину: когда агент-кодер запрашивал у агента документации справку по неизвестной функции, то последний перехватывал контекст и сам формировал финальный ответ пользователю. Мы скорректировали системные промпты, добавив строгое правило: агент документации должен возвращать найденную информацию и обязательно передавать управление обратно агенту-кодеру. Эта небольшая правка свела к нулю случаи некорректной маршрутизации.
Боевой пример (сквозной сценарий)
Чтобы закрепить понимание архитектуры, давайте на примере тестовой среды посмотрим, как наша система шаг за шагом обрабатывает комплексный запрос пользователя.
Запрос пользователя: «Где лежат логи нашего тестового стенда (sandbox) и напиши скрипт для подсчёта количества ошибок 404 за январь»
-
Оркестрация (Support Agent). Анализирует интент и понимает, что пользователю нужны как метаданные, так и готовый код. Передаёт базовый контекст агенту DataSearch.
-
Поиск таблиц (Data Search Agent). Вызывает инструмент
find_tables. Получает путь к директории в песочнице (например,//sandbox/test_env/logs/1d) и актуальную схему колонок. Формирует подробное ТЗ и передаёт управление агенту-кодеру (SQL generator Agent). -
Разведка (SQL generator Agent. Получает задачу и вызывает инструмент
explore_path_structure, чтобы изучить физическое расположение тестовых данных перед написанием запроса. Инструмент возвращает контекст:{"type": "directory", "found_partitions": ["2026-01-01", ... "2026-01-31"], "suggestion": "USE_PARTITION_READ_FUNCTION"}. -
Генерация и ошибка валидации. Агент пишет первую версию SQL-кода и прогоняет его через инструмент
validate_query. Движок базы данных возвращает синтаксическую ошибку:"validation_error": "Unknown builtin: RANGE. Consider using FROM RANGE(_) function(s) instead." -
Самокоррекция. Агент-кодер анализирует полученный traceback, исправляет синтаксис вызова функции для чтения партиций и повторно вызывает
validate_query. На этот раз ответ базы успешен:{"status": "success", "message": "Query validation passed", "job_url": "https://dwh.internal/test_queries/d561..."} -
Финальный ответ. SWARM возвращает пользователю готовый, проверенный скрипт и ссылку на успешную валидацию на выделенном тестовом кластере.
Вот так этот конвейер можно представить в виде диаграммы:

Итоги и планы на будущее
В результате мы получили масштабируемую систему, в которой каждый агент — это независимый модуль. Мы можем дебажить, усложнять промпты и добавлять новые инструменты отдельным специалистам нашей архитектуры, не ломая общую логику маршрутизации.
Мы находимся в активной фазе доработки. В ближайших планах — научить агентов выполнять тяжёлые скрипты в фоне и отдавать пользователю не просто код, а готовые дашборды и графики по результатам выполнения.
Я не призываю вас слепо копировать текущую реализацию, но надеюсь, что наш опыт с LangGraph и подход к валидации кода будет полезен при проектировании ваших умных систем. Призываю в комментариях делиться вашим опытом и вопросами — возможно, именно ваши идеи помогут нам внедрить новые крутые подходы!
Автор: Maxim_Shakurov


