Wielomodalne wyszukiwanie z fuzją: dobór właściwego retrievera do każdego zapytania
> Zapytanie w stylu „gdzie zdefiniowano parseConfig” potrzebuje innego wyszukiwania niż „jak działa autoryzacja”. Maguyva klasyfikuje intencję, odpowiednio waży cztery tryby wyszukiwania i łączy wyniki za pomocą ważonej Reciprocal Rank Fusion.
Zapytanie wyszukiwania to nie jedna rzecz.
„gdzie zdefiniowano parseConfig” chce dokładnego symbolu — jednej precyzyjnej lokalizacji, szybko. „jak działa uwierzytelnianie” chce znaczenia — rozproszenia powiązanego kodu, które wyjaśnia koncept. „co się zepsuje, jeśli zmienię tę funkcję” chce grafu zależności. „znajdź ciąg ECONNREFUSED” chce dosłownego dopasowania, niczego sprytnego.
Grep jest świetny do dosłownych dopasowań i przydatny do części poszukiwań referencji, ale to nie graf zależności i nie rozumie znaczenia. Embeddingi pokrywają stronę semantyczną, ale są niewłaściwym narzędziem do dokładnych ciągów i analizy wpływu. Większość narzędzi do wyszukiwania kodu wybiera jeden silnik i zmusza każde zapytanie, by żyło z tym wyborem. Maguyva nie wybiera. Ustala, jakiego rodzaju pytanie zadałeś, a następnie miesza cztery retrievery w proporcji, na jaką to pytanie zasługuje.
Cztery tryby
Pod maską istnieją cztery niezależne sposoby na znalezienie kodu:
- semantyczny — wyszukiwanie wektorowe na binarnych embeddingach Voyage; znajduje kod według znaczenia.
- tekstowy — dopasowywanie trigramowe; znajduje literały, ciągi błędów, dokładne identyfikatory.
- strukturalny — zapytania AST; znajduje definicje, sygnatury i konstrukty językowe.
- grafowy — graf zależności; znajduje wywołujących, wywoływanych i promień rażenia.
Każdy jest silny w innej klasie pytań. Sztuka polega na tym, by zdecydować, jak bardzo ufać każdemu z nich dla zapytania, które masz przed sobą.
Klasyfikacja intencji
Zanim uruchomi się jakiekolwiek wyszukiwanie, lekki klasyfikator sortuje zapytanie do jednej z sześciu intencji, z wynikiem pewności. Jest celowo tani — uporządkowane heurystyki, pierwsze dopasowanie wygrywa — ponieważ działa na ścieżce krytycznej i dodaje tylko milisekundę lub dwie:
- zaczyna się od
def,class,func,import… → find_definition (pewność 0,95) - „kto wywołuje”, „użycia”, „referencje do” → find_references (0,90)
- „wpływ”, „promień rażenia”, „co zależy od” → impact_analysis (0,90)
- cytowany
"string"lub token błędu jaktraceback→ exact_match (0,85–0,90) - identyfikator
CamelCaselubsnake_case→ find_definition (0,60–0,80) - „jak”, „dlaczego”, „wyjaśnij”, „architektura” → understand_code (0,75)
- nic nie pasuje → understand_code, niska pewność (0,40)
Każda intencja niesie profil wag rozłożony na cztery tryby. Oto prawdziwe liczby:
| Intencja | semantyczny | tekstowy | strukturalny (AST) | grafowy |
|---|---|---|---|---|
| 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 |
Więc „gdzie zdefiniowano parseConfig” mocno opiera się na AST (0,6). „jak działa uwierzytelnianie” opiera się na wektorach semantycznych (0,5). „co od tego zależy” to niemal wyłącznie graf (0,7). „znajdź ECONNREFUSED” to niemal wyłącznie trigram (0,9), z modelem embeddingów całkowicie wyłączonym — ponieważ podobieństwo semantyczne jest dokładnie niewłaściwym narzędziem dla dokładnego ciągu.
Szybka ścieżka i ścieżka fuzji
Gdy klasyfikator jest pewny — wynik ≥ 0,85 — a zapytanie jest zwyczajne, Maguyva całkowicie pomija fuzję i kieruje bezpośrednio do jednego dominującego trybu. „gdzie zdefiniowano X” nie potrzebuje czterech retrieverów; potrzebuje indeksu AST, natychmiast. Ta bezpośrednia ścieżka jest raportowana z powrotem jako fusion_strategy: "direct".
Wszystko niejednoznaczne przechodzi przez fuzję. Cztery (lub trzy, w domyślnym presecie) tryby uruchamiają się równolegle, każdy zwracając własną uszeregowaną listę, a my je łączymy.
Ważona Reciprocal Rank Fusion
Fuzja niejednorodnych retrieverów jest trudniejsza, niż się wydaje: podobieństwo kosinusowe 0,82, wynik trigramowy 137 i centralność grafu 0,004 nie są na tej samej skali, więc nie można ich po prostu dodać. Reciprocal Rank Fusion omija ten problem, odrzucając surowe wyniki i zachowując tylko rangę, jaką przypisał każdy silnik. Wkład wyniku z jednego trybu to:
contribution = weight × 1 / (k + rank + 1)
gdzie rank to jego pozycja na liście danego trybu, a k to stała wygładzająca. Wkłady są sumowane po trybach dla każdego wyniku, który znalazł więcej niż jeden silnik — zgodność między retrieverami naturalnie unosi się na szczyt. Używamy k = 40 w domyślnym presecie i 60 w thorough (quick działa wyłącznie semantycznie, więc fuzja nigdy się tam nie włącza). Oryginalna praca o RRF wylądowała na k = 60 dla wyszukiwania ogólnego przeznaczenia; my domyślnie ustawiamy nieco ostrzej, co daje zgodności rankingowej między trybami trochę więcej wagi — i nie polecamy ręcznego dostrajania tego.
Ponad to wyniki niosą boost związany z ważnością w grafie. Hub — funkcja, na której opiera się cała baza kodu — powinien wyprzedzać niejasny liść nawet przy tej samej trafności tekstowej, więc mnożymy każdy wkład przez:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
Centralność pochodzi z wstępnie obliczonych metryk PageRank/stopnia w pipeline, a boost jest ograniczony do 1,5×, żeby popularna funkcja nie mogła całkowicie zagłuszyć bardziej trafnej, niejasnej funkcji. Na koniec degradujemy wyniki ze ścieżek vendor, build i archiwalnych oraz deduplikujemy do najlepszego fragmentu na plik.
Co wciąż jest niedoskonałe
Klasyfikator intencji to stos wyrażeń regularnych, nie wyuczony model. Dobrze pokrywa popularne kształty zapytania — decyzja, która go wprowadziła, odnotowała spadek wskaźnika zerowych wyników z około 15% do poniżej 5% — ale jest heurystyczny, a naprawdę niejednoznaczne zapytanie przechodzi do understand_code i mieszanki nachylonej ku semantyce. To bezpieczna wartość domyślna, nie sprytna. Nie zastąpiliśmy go wytrenowanym klasyfikatorem, ponieważ tania wersja jest szybka i wystarczająco dobra, a niewłaściwy, ale pewny siebie klasyfikator jest gorszy niż uczciwy fallback. Same wagi to ręcznie dobrane priory, nie wyuczone z danych o kliknięciach, których nie zbieramy.
Zadaj pytanie, nie narzędzie
Agent nie powinien musieć wiedzieć, czy sięgnąć po grep, embeddingi, czy graf wywołań — powinien zadać swoje pytanie prostymi słowami i uzyskać właściwą odpowiedź. Fuzja wielomodalna jest tym, co pozwala, by find_symbol, wyszukiwanie semantyczne i analiza zależności siedziały za jedną powierzchnią zapytania: system odczytuje kształt pytania i po cichu składa dla niego właściwy retriever. Model, który ocenia embeddingi, ma znaczenie, ale tak samo ważne jest, by wiedzieć, kiedy nie ich używać. Dobór właściwego narzędzia do każdego zapytania to swój własny rodzaj jakości i wolelibyśmy być za niego odpowiedzialni, niż przerzucać go na wywołującego.
// you bring the question. it brings the tools.
Powiązane treści
Więcej z dziennika budowy Maguyva
Dlaczego zaktualizowaliśmy wyszukiwanie kodu do voyage-4-large_
Przenieśliśmy nasze embeddingi kodu na voyage-4-large — obecnie na szczycie publicznego rankingu RTEB dla wyszukiwania kodu. Wersja uczciwa: kompromis, na jaki idziemy, co faktycznie indeksujemy i dlaczego płacimy za embeddingi premium.
Rekurencyjne samodoskonalenie językowe: szlifowanie inteligencji kodu w ~280 językach_
Obsługujemy inteligencję kodu dla ~280 języków. Żaden człowiek nie jest w stanie tego ręcznie zweryfikować. Zbudowaliśmy więc pętlę rekurencyjnego samodoskonalenia językowego — wyrywkowa kontrola, LLM jako sędzia, naprawa jednej rzeczy, ponowna walidacja — i uruchamiamy ją z flotą izolowanych agentów, dopóki ekstrakcja nie będzie naprawdę poprawna, a nie tylko zielona.
Obserwowalność agentów: hooki, Alloy i Grafana_
Podłączyliśmy Claude Code i Codex do jednego stosu Grafana za pomocą OpenTelemetry i Alloy, a następnie użyliśmy śladów (traces) i logów, by znajdować i naprawiać problemy z zachowaniem agentów u źródła.