Multimodal fusionssökning: att välja rätt hämtare för varje sökfråga
> En sökfråga som 'var är parseConfig definierad' vill ha en annan typ av sökning än 'hur fungerar auth'. Maguyva klassificerar avsikten, viktar fyra hämtningslägen därefter, och slår samman resultaten med viktad Reciprocal Rank Fusion.
En sökfråga är inte en enda sak.
“var är parseConfig definierad” vill ha en exakt symbol — en precis plats, snabbt. “hur fungerar autentisering” vill ha mening — spridningen av relaterad kod som förklarar ett koncept. “vad går sönder om jag ändrar den här funktionen” vill ha beroendegrafen. “hitta strängen ECONNREFUSED” vill ha en bokstavlig träff, ingenting smart.
Grep är utmärkt för bokstavliga träffar och användbart för viss referensjakt, men det är ingen beroendegraf och det förstår inte mening. Inbäddningar täcker den semantiska sidan, men de är fel verktyg för exakta strängar och konsekvensanalys. De flesta kodsökverktyg väljer en motor och tvingar varje sökfråga att leva med det valet. Maguyva väljer inte. Det räknar ut vilken typ av fråga du ställde, och blandar sedan fyra hämtare i den proportion frågan förtjänar.
Fyra hämtningslägen
Under huven finns fyra oberoende sätt att hitta kod:
- semantisk — vektorsökning över Voyage-binärinbäddningar; hittar kod efter mening.
- text — trigrammatchning; hittar bokstavliga strängar, felmeddelanden, exakta identifierare.
- strukturell — AST-queries; hittar definitioner, signaturer och språkkonstruktioner.
- graf — beroendegrafen; hittar anropare, anropade och påverkansradie.
Var och en är stark på en egen klass av frågor. Konsten ligger i att avgöra hur mycket man ska lita på var och en för sökfrågan framför sig.
Avsiktsklassificering
Innan någon hämtning körs sorterar en lättviktig klassificerare sökfrågan i en av sex avsikter, med en konfidenspoäng. Den är medvetet billig — ordnad heuristik, första träff vinner — eftersom den körs i den heta vägen och bara lägger till en millisekund eller två:
- börjar med
def,class,func,import… → find_definition (konfidens 0,95) - “vem anropar”, “användningar av”, “referenser till” → find_references (0,90)
- “påverkan”, “påverkansradie”, “vad beror på” → impact_analysis (0,90)
- en citerad
"string"eller ett felmeddelande-token somtraceback→ exact_match (0,85–0,90) - en identifierare av typen
CamelCaseellersnake_case→ find_definition (0,60–0,80) - “hur”, “varför”, “förklara”, “arkitektur” → understand_code (0,75)
- inget matchar → understand_code, låg konfidens (0,40)
Varje avsikt bär en viktprofil över de fyra hämtningslägena. Det här är de verkliga talen:
| Avsikt | semantisk | text | strukturell (AST) | graf |
|---|---|---|---|---|
| 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 |
Så “var är parseConfig definierad” lutar tungt mot AST (0,6). “hur fungerar auth” lutar mot semantiska vektorer (0,5). “vad beror på det här” är nästan helt graf (0,7). “hitta ECONNREFUSED” är nästan helt trigram (0,9), med inbäddningsmodellen helt avstängd — eftersom semantisk likhet är exakt fel verktyg för en exakt sträng.
Den snabba vägen och den sammanslagna vägen
När klassificeraren är säker — poäng ≥ 0,85 — och sökfrågan är en normal sådan, hoppar Maguyva över fusion helt och dirigerar direkt till det enda dominerande hämtningsläget. “var är X definierad” behöver inte fyra hämtare; den behöver AST-indexet, direkt. Den direkta vägen rapporteras tillbaka som fusion_strategy: "direct".
Allt tvetydigt går genom fusion. De fyra (eller tre, i standardinställningen) hämtningslägena körs parallellt, var och en returnerar sin egen rankade lista, och vi kombinerar dem.
Viktad Reciprocal Rank Fusion
Att slå samman heterogena hämtare är svårare än det låter: en cosinuslikhet på 0,82 och en trigrampoäng på 137 och en grafcentralitet på 0,004 ligger inte på samma skala, så man kan inte bara addera dem. Reciprocal Rank Fusion kringgår problemet genom att kasta bort de råa poängen och bara behålla den rangordning varje motor tilldelade. Ett resultats bidrag från ett hämtningsläge är:
contribution = weight × 1 / (k + rank + 1)
där rank är dess position i det hämtningslägets lista och k är en utjämningskonstant. Bidrag summeras över hämtningslägen för varje resultat som mer än en motor hittade — samstämmighet mellan hämtare flyter naturligt upp till toppen. Vi använder k = 40 i standardinställningen och 60 för thorough (quick körs enbart semantiskt, så fusion aktiveras aldrig där). Det ursprungliga RRF-arbetet landade på k = 60 för generell hämtning; vi har som standard något skarpare, vilket ger topprankad samstämmighet mellan hämtningslägen lite mer tyngd — och vi rekommenderar inte att man finjusterar den för hand.
Utöver det bär resultaten en grafviktighetsboost. Ett nav — en funktion hela kodbasen lutar sig mot — bör rankas högre än ett obskyrt löv även vid samma textuella relevans, så vi multiplicerar varje bidrag med:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
Centralitet kommer från pipelinens förberäknade PageRank-/gradmått, och boosten är begränsad till 1,5× så att en populär funktion inte helt kan begrava en mer relevant men obskyr sådan. Slutligen degraderar vi resultat från vendor-, build- och arkivsökvägar, och dedupar till den bästa styckningen per fil.
Vad som fortfarande är ofullständigt
Avsiktsklassificeraren är en stapel av reguljära uttryck, inte en tränad modell. Den täcker de vanliga formerna av en sökfråga väl — beslutet som introducerade den registrerade att andelen nollresultat sjönk från cirka 15 % till under 5 % — men den är heuristisk, och en genuint tvetydig sökfråga faller igenom till understand_code och en semantiskt lutande blandning. Det är ett säkert standardval, inte ett smart ett. Vi har inte ersatt den med en tränad klassificerare eftersom den billiga versionen är snabb och tillräckligt bra, och eftersom en fel-men-säker klassificerare är värre än en ärlig fallback. Vikterna själva är handvalda a priori-värden, inte inlärda från klickdata vi inte samlar in.
Ställ frågan, inte verktyget
En agent ska inte behöva veta om den ska ta till grep eller inbäddningar eller anropsgrafen — den ska ställa sin fråga i vanliga ord och få rätt svar. Multimodal fusion är det som låter find_symbol, semantisk sökning och beroendeanalys ligga bakom en och samma sökyta: systemet läser frågans form och sätter tyst ihop rätt hämtare för den. Modellen som poängsätter inbäddningarna spelar roll, men det gör även att veta när man inte ska använda dem. Att välja rätt verktyg för varje sökfråga är sin egen sorts kvalitet, och det är en vi hellre äger själva än skjuter över på anroparen.
// you bring the question. it brings the tools.
Relaterad läsning
Mer från byggloggen för Maguyva
Varför vi uppgraderade kodsökningen till voyage-4-large_
Vi flyttade våra kodinbäddningar till voyage-4-large — för närvarande etta på den offentliga RTEB-topplistan för kodhämtning. Den ärliga versionen: avvägningen vi gör, vad vi faktiskt indexerar, och varför vi betalar för premiuminbäddningar.
Rekursiv självförbättring för språk: att slita fram kodintelligens över ~280 språk_
Vi stöder kodintelligens för cirka 280 språk. Ingen människa kan granska det för hand. Så vi byggde en rekursiv självförbättringsloop för språk — stickprov, LLM som domare, fixa en sak, omvalidera — och kör den med en flotta av isolerade agenter tills extraktionen faktiskt är korrekt, inte bara grön.
Agentobservabilitet: hooks, Alloy och Grafana_
Vi kopplade in Claude Code och Codex i en gemensam Grafana-stack med OpenTelemetry och Alloy, och använde sedan spårningar och loggar för att hitta och åtgärda agentbeteenden vid källan.