Перейти к содержимому
cd /blog

Мультимодальный фьюжн-поиск: выбор правильного ретривера для каждого запроса

[Поиск][Архитектура]

> Запрос вроде «где определён parseConfig» требует другого поиска, чем «как работает аутентификация». Maguyva классифицирует намерение, соответствующим образом взвешивает четыре модальности поиска и сливает результаты с помощью взвешенного Reciprocal Rank Fusion.

Поисковый запрос — это не одна вещь.

«где определён parseConfig» хочет точный символ — одно точное место, быстро. «как работает аутентификация» хочет смысл — набор связанного кода, который объясняет концепцию. «что сломается, если я изменю эту функцию» хочет граф зависимостей. «найти строку ECONNREFUSED» хочет буквальное совпадение, ничего умного.

Grep отлично справляется с буквальными совпадениями и полезен для некоторых поисков ссылок, но это не граф зависимостей, и он не понимает смысл. Эмбеддинги покрывают семантическую сторону, но это неверный инструмент для точных строк и анализа влияния. Большинство инструментов поиска по коду выбирают один движок и заставляют каждый запрос жить с этим выбором. Maguyva не выбирает. Он выясняет, какого рода вопрос вы задали, а затем смешивает четыре ретривера в той пропорции, которую заслуживает этот вопрос.

Четыре модальности

Под капотом есть четыре независимых способа найти код:

  • семантический — векторный поиск по бинарным эмбеддингам Voyage; находит код по смыслу.
  • текстовый — триграммное сопоставление; находит литералы, строки ошибок, точные идентификаторы.
  • структурный — запросы к AST; находит определения, сигнатуры и языковые конструкции.
  • графовый — граф зависимостей; находит вызывающих, вызываемых и радиус воздействия.

Каждый силён в своём классе вопросов. Хитрость в том, чтобы решить, насколько доверять каждому из них для конкретного запроса перед вами.

Классификация намерения

Прежде чем запускается любое извлечение, лёгкий классификатор сортирует запрос в одно из шести намерений, с оценкой уверенности. Он намеренно дешёвый — упорядоченные эвристики, побеждает первое совпадение — потому что он работает на горячем пути и добавляет всего миллисекунду-другую:

  • начинается с def , class , func , import … → find_definition (уверенность 0.95)
  • «кто вызывает», «использования», «ссылки на» → find_references (0.90)
  • «влияние», «радиус воздействия», «что зависит от» → impact_analysis (0.90)
  • строка в кавычках "string" или токен ошибки вроде tracebackexact_match (0.85–0.90)
  • идентификатор CamelCase или snake_casefind_definition (0.60–0.80)
  • «как», «почему», «объясни», «архитектура» → understand_code (0.75)
  • ничего не совпало → understand_code, низкая уверенность (0.40)

Каждое намерение несёт профиль весов по четырём модальностям. Вот реальные цифры:

Намерение семантический текстовый структурный (AST) графовый
find_definition 0.2 0.1 0.6 0.1
find_references 0.1 0.2 0.2 0.5
understand_code 0.5 0.2 0.2 0.1
find_similar 0.4 0.3 0.2 0.1
impact_analysis 0.1 0.1 0.1 0.7
exact_match 0.0 0.9 0.1 0.0

Так что «где определён parseConfig» сильно опирается на AST (0.6). «как работает аутентификация» опирается на семантические векторы (0.5). «что зависит от этого» — почти целиком граф (0.7). «найти ECONNREFUSED» — почти целиком триграммы (0.9), при этом модель эмбеддингов полностью отключена — потому что семантическое сходство здесь ровно неверный инструмент для точной строки.

Быстрый путь и слитый путь

Когда классификатор уверен — оценка ≥ 0.85 — и запрос обычный, Maguyva полностью пропускает слияние и направляет запрос прямо в единственную доминирующую модальность. «где определён X» не нужны четыре ретривера; ему нужен AST-индекс, прямо сейчас. Этот прямой путь возвращается как fusion_strategy: "direct".

Всё неоднозначное проходит через слияние. Четыре (или три, в дефолтном пресете) модальности запускаются параллельно, каждая возвращает свой собственный ранжированный список, и мы их объединяем.

Взвешенное Reciprocal Rank Fusion

Слияние разнородных ретриверов сложнее, чем кажется: косинусное сходство 0.82, триграммная оценка 137 и графовая центральность 0.004 находятся не в одной шкале, так что их нельзя просто сложить. Reciprocal Rank Fusion обходит проблему, отбрасывая сырые оценки и оставляя только ранг, присвоенный каждым движком. Вклад результата от одной модальности равен:

contribution = weight × 1 / (k + rank + 1)

где rank — его позиция в списке этой модальности, а k — константа сглаживания. Вклады суммируются по модальностям для любого результата, который нашёл больше чем один движок — согласие между ретриверами естественным образом всплывает наверх. Мы используем k = 40 на дефолтном пресете и 60 на thorough (quick работает только в семантическом режиме, так что слияние там никогда не срабатывает). Оригинальная работа по RRF остановилась на k = 60 для поиска общего назначения; мы по умолчанию берём чуть более резкое значение, что даёт немного больше веса топовому согласию между модальностями — и мы не рекомендуем настраивать его вручную.

Поверх этого результаты несут буст по графовой значимости. Хаб — функция, на которую опирается вся кодовая база, — должен обгонять малоизвестный лист даже при той же текстовой релевантности, поэтому мы умножаем каждый вклад на:

boost = min(1 + 0.3 × ln(1 + centrality), 1.5)

Центральность берётся из предвычисленных метрик PageRank/степени вершины конвейера, а буст ограничен сверху 1.5×, чтобы популярная функция не могла полностью похоронить более релевантную, но малоизвестную. Наконец, мы понижаем результаты из путей вендоров, сборки и архивов и убираем дубликаты, оставляя лучший фрагмент на файл.

Что всё ещё несовершенно

Классификатор намерений — это стек регулярных выражений, а не обученная модель. Он хорошо покрывает распространённые формы запроса — решение, которое его ввело, зафиксировало падение доли нулевых результатов примерно с 15% до менее чем 5% — но он эвристический, и по-настоящему неоднозначный запрос проваливается в understand_code и смесь с уклоном в сторону семантики. Это безопасное значение по умолчанию, а не хитрое. Мы не заменили его обученным классификатором, потому что дешёвая версия быстрая и достаточно хорошая, а неверный, но уверенный классификатор хуже честного отката. Сами веса — это подобранные вручную априорные значения, а не выученные по данным о кликах, которые мы не собираем.

Задавайте вопрос, а не инструмент

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

// you bring the question. it brings the tools.

Похожие материалы

Ещё из журнала разработки Maguyva

Почему мы обновили поиск по коду до voyage-4-large_

Мы перевели эмбеддинги кода на voyage-4-large — модель, которая сейчас возглавляет публичный рейтинг RTEB по поиску кода. Честная версия: какой компромисс мы принимаем, что мы на самом деле индексируем и почему платим за премиальные эмбеддинги.

[Эмбеддинги][Поиск][Архитектура]

Рекурсивное самосовершенствование языков: шлифуем интеллектуальный анализ кода на ~280 языках_

Мы поддерживаем интеллектуальный анализ кода для ~280 языков. Ни один человек не может вручную проверить это. Поэтому мы построили цикл рекурсивного самосовершенствования языков — выборочная проверка, LLM в роли судьи, исправление одной вещи, повторная валидация — и прогоняем его флотом изолированных агентов, пока извлечение не станет по-настоящему верным, а не просто «зелёным».

[Архитектура][Языки][Агенты]