Всем привет! На связи Валерий Калинин и Олег Кудинов, мы представляем команду сопровождения тестовых стендов приемочного тестирования Московской Биржи. В этой статье расскажем, как сделали поиск по внутренней документации в таск-трекере и корпоративной wiki на порядок быстрее, не написав ни единой строчки кода бэкенда.
Проблематика
Наше корпоративное пространство в wiki разрослось до десятков тысяч страниц, а поиск нужной информации стал иногда представлять из себя квест. Проблема заключается в том, что в wiki есть встроенный поиск, но он не понимает суть поискового запроса. Напишешь «как настроить интеграцию с Kafka» — получишь страницы, где слова «интеграция» и «Kafka» встречаются, но не обязательно вместе и не обязательно в нужном контексте.
Вроде бытовые мелочи, но есть большие интеграционные процессы, которые проходят через множество ИТ-систем. Для того, чтобы узнать подробности, как они работают целиком, нужно внимательно изучить документацию. К тому же команды меняются: разработчик приходит на готовый процесс «на поддержку», не успев поучаствовать в его создании, и пока еще не глубоко погружен. Экспертиза фиксируется в документации, но порой документация растёт быстрее, чем люди успевают в ней ориентироваться. А это очень важно, например, при разборе инцидентов на проде, когда счёт идёт на минуты.
Или вот еще один кейс от команды E2E-тестирования. У коллег есть ежедневные дежурства по анализу отчётов регресс-тестов. По итогам анализа заводятся тикеты и перед этим нужно проверить, нет ли дублей. Обычный поиск таск-трекера ищет по жёсткому совпадению, а у них тикеты часто содержат почти одинаковые стектрейсы, которые отличаются только переменными данными — UID, датами, трейд-кодами. Сложности начались, когда за 4 года работы команды количество решённых задач превысило 9000. В итоге ребята ежедневно тратили время на ручной поиск дублей, а это по 10 мин на каждый поиск. Ниже расскажем, как мы сэкономили ребятам несколько десятков часов монотонной работы.
В итоге мы решили сделать поиск быстрым, удобным и не по конкретным ключевым словам, а по смыслу и на естественном человеческом языке.

Архитектурный стек
Перед тем как описывать решение, обозначим, какие мы использовали инструменты.
Движок автоматизации— open-source low-code платформа для создания workflow-автоматизаций. Уже из коробки имеет сотни готовых интеграций и простой интерфейс для взаимодействия с LLM и поддержку мульти-агентных систем.
Ключевое для нас: open-source low-code платформа разворачивается полностью self-hosted. Именно так она и используется в Московской Бирже — никаких внешних API-запросов с нашими данными.
Low-code платформа в нашей системе:
— оркестрирует весь пайплайн (загрузка → чанкинг → эмбеддинг → поиск → ответ);
— взаимодействует с API wiki-платформ, LLM и базами данных;
— позволяет визуально строить и отлаживать сложные цепочки без написания кода.
Второй ключевой инструмент — векторная СУБД. Хранит эмбеддинги документов и выполняет семантический поиск по близости векторов. В отличие от полнотекстового поиска, она:
— находит смысловые совпадения, а не только точные вхождения слов;
— поддерживает фильтрацию по метаданным (автор, дата, пространство в wiki);
— возвращает результаты с оценкой релевантности;
— горизонтально масштабируется.
Именно векторная СУБД позволяет задать вопрос «где описан порядок эскалации инцидентов?» и получить нужную страницу, даже если в ней нет слова «эскалация».
LLM используется для понимания запросов и генерации ответов.
Языковая модель решает две задачи:
1. Понять запрос пользователя и преобразовать его в форму, удобную для векторного поиска.
2. Сформулировать ответ на основе найденных чанков документации, добавив ссылки на источники.
Модель работает внутри периметра Московской Биржи — данные не уходят во внешние сервисы.
PostgreSQL — выступает реляционным хранилищем для промежуточных данных перед загрузкой в векторную базу. Там же хранятся метаданные страниц, статусы индексации и история обновлений.
Решение
Для наглядности на изображении проиллюстрированы действия в первом воркфлоу.
Мы использовали 2 промежуточные таблицы postgres для того, чтобы в первую собирать id нужных страниц, по которым будет работать поиск. Во вторую таблицу уже записывали контекст страницы и использовали её для проверок (пройденные LLM-кой страницы, загруженные в векторную БД и т.д.). Этого можно не делать, но на пространстве в 10000+ страниц при работе воркфлоу возможны разного рода коллизии и проверки id по таблицам помогают продолжать работу с того места, где случилась ошибка.
0 этап
Собираем список необходимых страниц пространства, из которых будем с помощью LLMполучать контекст. Мы делали это скриптом на python, который обходит все подстраницы пространства и записывает id страницы в БД postgres.
1 этап (синяя подложка на воркфлоу)
С помощью стандартной ноды берём id страниц из первой БД и проверяем, обрабатывалась она или нет. Если нет, то группой нод получаем её содержимое http-запросом в API wiki, подчищаем от лишнего форматирования и превращаем в markdown. Самые длинные страницы мы обрезали, чтобы они влезали в контекстное окно LLM. Далее простым промптом просим LLM выделить из страницы контекст по стандартной форме и сформировать несколько тегов ключевых слов, описывающих эту статью. После чего грузим всё во вторую БД.
2 этап. Жёлтая подложка
Получившуюся таблицу мы просматривали вручную и выборочно проверяли некоторые строки. Убедившись, что всё ок, проставили скриптами галки true в колонку validate и запустили с помощью ручного триггера и стандартной ноды для работы postgres обход всех строк таблицы с проставленной галкой validate=true и loaded=false. Дальше с помощью стандартного для такой задачи набора нод (embeding, splitter) загружаем необходимые данные в векторную БД, формируя базу для контекстного поиска.
3 этап. Зелёная подложка
Тут реализован модуль общения с ботом. Всё стандартно, триггер чата обращается в агент с LLM-моделью, у которого в качестве инструмента есть получившееся шагом ранее хранилище. Количество выборки и формат ответа регулируется промптом.
Общение в чате реализовано через простой веб-интерфейс. На той же машине, что и low-code платформа, развернут локальный веб-сервер, отдающий идущую из коробки страницу чата low-code платформы.
Как мы реализовали аналогичное решение для таск-трекера
И, как и обещали, рассказываем, как нам удалось тиражировать данное решение на работу c таск-трекером для команды E2E-тестирования.
Штатный поиск по таск-трекеру работал так же, как и по wiki. Нужно помнить точные keyword. Немного ошибся, выбрал синоним или сокращение — и нужную задачу найти уже не получится.
Наше решение работало по той же схеме:
1. python скрипт разбирает все задачи в пространстве и складывает содержимое в postgres БД;
2. embedding перегружает в векторную базу и оттуда уже выдается чат-боту;
3. чат-бот помогает тестировщикам искать по сути ФЗ, не утруждая себя вспоминать дословные формулировки.
Подводные камни
· Несколько раз падало из-за невозможности обработать LLM-кой запросы, поэтому и пришли к версии с 2-мя таблицами и retry на всех важных нодах.
· Так же следует заранее подумать о данных, которые вам необходимо индексировать. Если там есть обязательные поля (например, названия проектов на странице автора) и вы хотите гарантированно получать их в ответе на свой поисковый запрос — стоит их заранее разметить как метаданные для векторной БД и записывать как metadata.key для точного поиска.
· Обновление индексов в векторной базе – из коробки через low-code платформу специальной нодой обновить метаданные конкретной записи нельзя. Если вам требуется периодически изменять страницы в wiki и изменять соответствующие векторы, потребуется хранить id страницы как метаданные metadata.key и уже по ним искать в векторной базе id записи и уже по нему – обращением в api векторной базы обновлять или удалять запись.
Что мы в итоге получили
· Поиск по смыслу, а не по словам.
Запросы в стиле «как откатить деплой на стенде» находят нужную документацию, какими бы словами она ни была написана. Сотрудники тратят меньше времени на поиск информации.
· Система не требует точной формулировки.
Опечатка, синоним, смешение латиницы и кириллицы — раньше это затрудняло поиск. Снижается риск потери знаний в командах, а также снижается порог входа для новых сотрудников.
· Для того конкретного кейса для команды E2E
Поиск дубля тикета стал в 10 раз быстрее. До внедрения дежурный тратил на поиск дубля среди 9000+ тикетов около 10 минут — и делал это 1-2 раза за рабочий день. После внедрения тот же поиск занимает минуту. Это дало команде 50+ часов сэкономленного времени в год. И таких кейсов можно найти несколько у каждой команды.
· Все данные остаются внутри периметра компании.
Low-code платформа, LLM и базы данных развёрнуты self-hosted, ни один запрос и ни один фрагмент документации не уходит во внешние сервисы. Отсутствуют интеграционные риски с внешними API.
· Переиспользование коллекций.
Коллекции векторной БД можно выгружать и передавать смежным командам — не нужно переиндексировать одно и то же дважды.
Мы только в начале пути. В ближайшее время планируем:
· расширить покрытие на другие источники внутренней документации;
· добавить возможность задавать вопросы с уточнениями (диалоговый режим);
· улучшить качество чанкинга с учётом структуры страниц wiki (заголовки, таблицы, блоки кода);
· настроить аналитику запросов, чтобы понимать, что сотрудники ищут чаще всего.
Если у вас растёт корпоративная база знаний и стандартный поиск перестаёт справляться — контекстный поиск на базе LLM и векторных баз данных решает задачу.
При этом не обязательно писать бэкенд с нуля: low-code платформа позволяет собрать весь пайплайн, а данные можно оставить внутри периметра.
Стек low-code платформа + векторная СУБД + PostgreSQL + LLM оказался практичным выбором: каждый инструмент делает своё, они хорошо интегрируются друг с другом, и вся система прозрачна для отладки.
Если есть вопросы по архитектуре или хотите поделиться своим опытом — пишите в комментариях.
ссылка на оригинал статьи https://habr.com/ru/articles/1061242/