
Поиск — штука настолько привычная, что её редко рассматривают как отдельную инженерную задачу. На деле это связка из четырёх частей: парсинг и нормализация исходных данных, индексация, обработка пользовательского запроса и ранжирование результатов. Каждая из них живёт по своим правилам и ломается по своим причинам.
Сложно представить более прикладную область, поэтому на хакатоне IT Academy Hack 2026 от IT Академии Samsung Innovation Campus в этом году, мы решили попросить студентов предложить варианты улучшения поиска по сообщениям в контуре корпоративного мессенджера. Кстати, VK Tech стал индустриальным партнером конкурса уже во второй раз — предоставил инфраструктуру для студентов, и стал одним из постановщиков задач.
Меня зовут Сергей Харламов, я руковожу Исследовательской лабораторией VK Tech. В этой статье расскажу об актуальных проблемах оптимизации поиска, а также о задаче и подходах, которые можно было применить для ее решения.
Немного контекста про поиск
При создании задачи, мы обратились к собственному опыту. Внутри VK WorkSpace — это наш корпоративный суперапп с почтой, мессенджером, календарём, облаком, доской и другими сервисами — поиском по сообщениям ежедневно пользуются более 20% активных пользователей. Это повседневный сценарий: люди не помнят, в каком чате обсуждали ссылку, договорённость или дату, и идут искать. Поэтому метрики качества поиска — точность выдачи и положение первого верного результата — это вполне продуктовая величина, а не академическое упражнение.
Важно, что поиск и рекомендации — разные задачи. В поиске пользователь знает, что ищет, и хочет получить конкретный результат. В рекомендациях точного запроса нет, система достраивает желание сама. Мы работаем именно с поиском.
И последнее общее место. Для классического поиска по тексту существуют две большие семьи индексов. Полнотекстовые индексы (например, Elasticsearch) хранят соответствие токен-документ и хорошо отрабатывают, когда запрос совпадает с документом по словам. Векторные индексы (например, Qdrant — это векторная СУБД, в которой объекты хранятся как векторы в пространстве смыслов) работают с эмбеддингами (векторами), то есть числовыми представлениями текста, в которых близкие по смыслу фрагменты лежат рядом. Они выручают, когда запрос и документ говорят об одном и том же разными словами. На практике обычно ставят оба индекса параллельно и склеивают результаты.
Из чего собирается поиск по сообщениям
Если разложить поиск на конвейер, получаются две независимые стадии.
Первая — индексация. Берём поток сообщений из чатов, режем его на куски (чанки), для каждого чанка получаем эмбеддинги (смысловой и полнотекстовый) и кладём всё это в индекс вместе с дополнительными полями, по которым потом будет фильтрация и ранжирование. Здесь важно не только что класть, но и как нарезать: окна разного размера и величина перекрытия (overlap) меняют, сколько контекста сохраняется внутри одного фрагмента.
Вторая — обработка запроса. На вход приходит вопрос пользователя, на выходе нужен ранжированный список ответов. Между ними — превращение исходного текста в один или несколько запросов к индексу, отбор кандидатов и реранкинг, то есть переупорядочивание результатов так, чтобы релевантные стояли ближе к началу. Запрос можно предобрабатывать: убирать мусор, доставать даты и сущности, использовать переформулировки.
Чтобы понять, работает ли всё это в итоге, нужны метрики. Классика — точность (precision), полнота (recall) и средний обратный ранг (MRR — mean reciprocal rank, обратное число к позиции первого правильного ответа). Полноту в реальной разработке получить тяжело: часто непонятно, сколько вообще в корпусе релевантных документов. А вот точность и положение первого верного ответа в выдаче считать намного проще, и они достаточно близко отражают то, что чувствует пользователь.
Задача от VK Tech
В рамках хакатона мы предложили участникам улучшить метрики поиска по сообщениям, не выходя за рамки заданного пайплайна и инфраструктуры. От участников требовалось реализовать два сервиса – сервис индексации и сервис поиска.
Index Service. На вход он получает массив сообщений (в том числе сообщения для overlap) и должен вернуть текст получившихся чанков.
Помимо этого, мы решили дать участникам больше свободы, поэтому они могли строить смысловой и полнотекстовый эмбеддинги от разных представлений текста, а также менять модель построения полнотекстового вектора.
Search Service. На вход получает обогащенный запрос пользователя: исходный запрос был заранее дополнен нами метаинформацией, подобную которой в нашем production мы получаем «на лету» от LLM: массив переформулировок вопроса пользователя, гипотетических ответов (hyde – тексты, которые похожи на релевантные сообщениях), ключевые слова, дату вопроса, упоминания дат в тексте, и блок упомянутых в вопросе сущностей — люди, email’ы, документы, имена систем и ссылки. Возвращать сервис должен ранжированный список message_ids, где сверху самые релевантные.
Вычисление семантических векторов, реранжирование и хранение чанков происходило не на стороне участников, а в нашем контуре. Студентам было необходимо лишь сдать Docker образа сервисов в тестирующую систему, и она уже вычисляла score участников.
Для каждого вопроса нашего датасета в оценивании мы учитывали только первые 50-и сообщений, полученных из сервиса поиска.
Метрики выбрали классические:
Recall@50 — доля правильных сообщений. Например, если для вопроса было 2 эталонных сообщения, а сервис поиска нашёл только 1 – Recall будет 0.5.
nDCG@50 — оценивает не только факт нахождения правильных сообщений, но и их позиции в выдаче. Отличается от MRR тем, что учитывает не только первый найденный правильный результат, а все релевантные сообщения в топ-50
Итоговый score считался по формуле score = recall_avg 0.8 + ndcg_avg 0.2.
То есть основной вес оценки заключался в полноте найденных сообщений, а ранжирование имело дополнительный вклад.
В качестве отправной точки мы выложили baseline, который укладывался в ограничения по времени и давал score равный пример 0.45.
Где можно было копать
Само построение базового решения — задача на несколько часов. Дальше начинается более содержательная часть: что именно крутить, чтобы метрики поползли вверх. В постановке мы выделили три направления, и они хорошо отражают, где у такого поиска вообще есть рычаги.
Первое — обогащение данных на этапе индексации. Baseline нарезает сообщения наивно (по символам, разрывая контекст между словами и предложениями) и игнорирует метаданные (участники переписки, даты и прочее). Можно играть с размером окна и перекрытием; можно вытаскивать из сообщений сущности и складывать их в отдельные поля; можно по-разному формировать тексты под смысловой и полнотекстовый вектора.
Второе — обогащение поискового запроса. Как мы уже говорили ранее, вместе с исходным вопросом пользователя мы предоставляли поисковому сервису обширный массив метаданных. С помощью них можно было различные фильтры для запросов в Qdrant.
Третье — стратегия поиска и реранкинг. Базовое решение делает один поход к Qdrant и склеивает результаты простым RRF. Участникам же можно было делать несколько запросов в Qdrant для каждого запроса. Здесь большое пространство для экспериментов, так как можно собирать кандидатов несколькими разными запросами, расширять выборку и аккуратнее переупорядочивать её — в том числе через предоставленный rerank-API.
Лучшие решения студентов

По итогам двухдневного хакатона абсолютным победителем в нашей задаче стала команда «азаза» (САФУ, Архангельск). Второе и третье места заняли команды «ИИКбо» (РТУ МИРЭА, Москва) и «Кофе с коньяком» (ЮУрГУ, Челябинск).
Команда-победитель грамотно воспользовалась всеми возможностями, которые мы предоставили. Они сделали качественную структурированную индексацию, активно использовали метаданные обогащающие поисковой запрос, а также сделали несколько подзапросов в Qdrant и поигрались с их слиянием и реранжированием. Что мы особенно оценили — е ребята не ограничились классическим RRF, а добавили своё доп. награждение чанков, которые содержат«следы» метаданных поискового запроса.
Ну и гиперпараметры были подобраны очень хорошо — почти также, как в нашей продакшн-среде(размер чанка, размер скользящего окна и тд). Всё это позволило команде увеличить score до 0.6, превзойдя наш baseline на 0.15, что является значительным приростом.
Команда, занявшая второе место, предложила структурированный алгоритм, подобрала необычные гиперпараметры индексации (а именно меньший размер чанков, чем предложили мы) и грамотно реализовала поиск в целом, а еще отличилась аккуратной фильтрацией по временным диапазонам — это помогает значительно улучшить качество при правильном использовании.
Команда на третьем месте применила необычные правила индексации, от посимвольного разделения сообщений перешли на чанки на параметр “количество сообщений в чанке”, написали свой алгоритм реранжирования финального набора сообщений (а не чанков!) по ключевым словам и сделали подзапрос в Qdrant с поиском чанков по фильтрам упомянутых людей и сущностей
Что в итоге
В поиске по сообщениям можно долго добиваться идеала. Можно крутить чанкинг, можно собирать запрос из десятка подготовленных полей, можно перестраивать стратегию отбора и реранкинга, и каждый из этих рычагов влияет на метрики по-своему. Хакатон в этом смысле — удобный полигон: контракты зафиксированы, инфраструктура у всех одинаковая, проверка автоматическая, и фокус смещается с перебора моделей и их гиперпараметров на гипотезы и эксперименты с имеющимися данными и их обработкой.
Если вы сами делали поиск по похожим данным — чатам, переписке, тикетам, заметкам — расскажите в комментариях, какие приёмы у вас сработали, а какие наоборот не зашли. Особенно интересно про чанкинг и про то, как вы используете дополнительные поля запроса при ранжировании. И про места, где гибридный поиск с RRF упирается в потолок и приходится менять схему.
Будем рады почитать и спасибо за прочтение статьи!
P. S. Хочу закончить благодарностями команде разработки Исследовательской лаборатории VK Tech, которая участвовала в подготовке кейса, проведении хакатона и разборе решений студентов — Заман Габдрахманов, Backend-разработчик и Даниил Хасанов, Backend-разработчик и Владимир Северов, техлид команды.





















