Multi-modální fúzní vyhledávání: pro každý dotaz ten správný retriever
> Dotaz jako „kde je definovaný parseConfig“ chce jiné vyhledávání než „jak funguje autentizace“. Maguyva klasifikuje záměr, podle toho zváží čtyři vyhledávací modality a výsledky sloučí pomocí vážené Reciprocal Rank Fusion.
Vyhledávací dotaz není jedna věc.
„kde je definovaný parseConfig“ chce přesný symbol — jedno přesné místo, rychle. „jak funguje autentizace“ chce význam — rozptýlený související kód, který vysvětluje koncept. „co se rozbije, když změním tuhle funkci“ chce graf závislostí. „najdi řetězec ECONNREFUSED“ chce doslovnou shodu, nic chytrého.
Grep je výborný na doslovné shody a užitečný pro některé pátrání po referencích, ale není to graf závislostí a nerozumí významu. Embeddingy pokrývají sémantickou stránku, ale jsou špatný nástroj na přesné řetězce a analýzu dopadu. Většina nástrojů pro vyhledávání v kódu si vybere jeden engine a nechá s tou volbou žít každý dotaz. Maguyva si nevybírá. Zjistí, jaký typ otázky jste položili, a pak smísí čtyři retrievery v poměru, jaký si daná otázka zaslouží.
Čtyři modality
Pod kapotou existují čtyři nezávislé způsoby, jak najít kód:
- sémantický — vektorové vyhledávání nad binárními Voyage embeddingy; hledá kód podle významu.
- textový — trigramová shoda; hledá literály, chybové řetězce, přesné identifikátory.
- strukturální — dotazy AST; hledá definice, signatury a jazykové konstrukce.
- grafový — graf závislostí; hledá volající, volané a poloměr dopadu.
Každý je silný na jiné třídě otázek. Trik je rozhodnout, nakolik každému z nich věřit pro dotaz, který máte před sebou.
Klasifikace záměru
Než proběhne jakékoliv vyhledávání, odlehčený klasifikátor zařadí dotaz do jednoho ze šesti záměrů, s mírou jistoty. Je záměrně levný — seřazené heuristiky, vyhrává první shoda — protože běží na hot path a přidává jen milisekundu nebo dvě:
- začíná na
def,class,func,import… → find_definition (jistota 0,95) - „kdo volá“, „použití“, „reference na“ → find_references (0,90)
- „dopad“, „poloměr dopadu“, „co na tomhle závisí“ → impact_analysis (0,90)
"string"v uvozovkách nebo chybový token jakotraceback→ exact_match (0,85–0,90)- identifikátor ve tvaru
CamelCasenebosnake_case→ find_definition (0,60–0,80) - „jak“, „proč“, „vysvětli“, „architektura“ → understand_code (0,75)
- nic nesedí → understand_code, nízká jistota (0,40)
Každý záměr nese profil vah napříč čtyřmi modalitami. Toto jsou skutečná čísla:
| Záměr | sémantický | textový | strukturální (AST) | grafový |
|---|---|---|---|---|
| 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 |
Takže „kde je definovaný parseConfig“ se výrazně opírá o AST (0,6). „jak funguje autentizace“ se opírá o sémantické vektory (0,5). „co na tomhle závisí“ je skoro celé grafové (0,7). „najdi ECONNREFUSED“ je skoro celé trigramové (0,9), embedding model je úplně vypnutý — protože sémantická podobnost je přesně ten špatný nástroj na přesný řetězec.
Rychlá cesta a fúzovaná cesta
Když si je klasifikátor jistý — skóre ≥ 0,85 — a dotaz je běžný, Maguyva fúzi úplně přeskočí a routuje přímo na jednu dominantní modalitu. „kde je definované X“ nepotřebuje čtyři retrievery; potřebuje AST index, hned. Tahle přímá cesta se hlásí zpátky jako fusion_strategy: "direct".
Všechno nejednoznačné prochází fúzí. Čtyři (nebo tři, na výchozím presetu) modality běží paralelně, každá vrací svůj vlastní seřazený seznam, a my je kombinujeme.
Vážená Reciprocal Rank Fusion
Fúzovat heterogenní retrievery je těžší, než to zní: kosinová podobnost 0,82, trigramové skóre 137 a grafová centralita 0,004 nejsou na stejné škále, takže je nemůžete jen sečíst. Reciprocal Rank Fusion tenhle problém obchází tím, že zahodí syrová skóre a ponechá si jen pořadí, které danému výsledku přidělil každý engine. Příspěvek výsledku z jedné modality je:
contribution = weight × 1 / (k + rank + 1)
kde rank je jeho pozice v seznamu dané modality a k je vyhlazovací konstanta. Příspěvky se sčítají napříč modalitami pro každý výsledek, který našel víc než jeden engine — shoda mezi retrievery se přirozeně vyplave nahoru. Používáme k = 40 na výchozím presetu a 60 na thorough (quick běží čistě sémanticky, takže se tam fúze nikdy nezapojí). Původní práce o RRF přistála na k = 60 pro obecné vyhledávání; my ve výchozím stavu jdeme mírně ostřeji, což dává shodě mezi modalitami na nejvyšších pozicích trochu větší váhu — a nedoporučujeme to ručně dolaďovat.
Nad tím vším výsledky nesou ještě boost za grafovou důležitost. Hub — funkce, o kterou se opírá celá kódová základna — by měla porazit obskurní list i při stejné textové relevanci, takže každý příspěvek násobíme:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
Centralita pochází z předpočítaných metrik PageRank/stupně z pipeline a boost je oříznutý na 1,5×, aby populární funkce úplně nepohřbila relevantnější, ale obskurnější výsledek. Nakonec degradujeme výsledky z cest vendor, build a archive a deduplikujeme na nejlepší chunk na soubor.
Co je pořád nedokonalé
Klasifikátor záměru je hromada regexů, ne naučený model. Běžné tvary dotazu pokrývá dobře — rozhodnutí, které ho zavedlo, zaznamenalo, že míra nulových výsledků klesla zhruba z 15 % pod 5 % — ale je heuristický, a opravdu nejednoznačný dotaz propadne do understand_code a semanticky nakloněné směsi. To je bezpečný výchozí stav, ne chytrý. Nenahradili jsme ho natrénovaným klasifikátorem, protože levná verze je rychlá a dost dobrá, a protože klasifikátor, který se plete, ale je si jistý, je horší než upřímný fallback. Samotné váhy jsou ručně zvolené priory, ne naučené z dat o kliknutích, která nesbíráme.
Ptejte se na otázku, ne na nástroj
Agent by neměl muset vědět, jestli sáhnout po grepu, embeddingech, nebo grafu volání — měl by položit svou otázku prostými slovy a dostat správnou odpověď. Multi-modální fúze je to, co umožňuje find_symbol, sémantickému vyhledávání a analýze závislostí sedět za jedním dotazovacím rozhraním: systém přečte tvar otázky a tiše sestaví ten správný retriever. Na modelu, který embeddingy skóruje, záleží, ale záleží i na tom, kdy je nepoužít. Vybrat pro každý dotaz správný nástroj je svůj vlastní druh kvality, a raději ho vlastníme sami, než abychom ho přehazovali na volajícího.
// you bring the question. it brings the tools.
Související čtení
Další ze stavebního deníku Maguyva
Proč jsme upgradovali vyhledávání v kódu na voyage-4-large_
Přesunuli jsme naše embeddingy kódu na voyage-4-large — aktuálně na špici veřejného žebříčku RTEB pro retrieval kódu. Upřímná verze: kompromis, který děláme, co skutečně indexujeme a proč platíme za prémiové embeddingy.
Jazykové rekurzivní sebezlepšování: brousíme code intelligence napříč ~280 jazyky_
Podporujeme code intelligence pro ~280 jazyků. Ručně to žádný člověk zaudituje. Postavili jsme proto jazykovou smyčku rekurzivního sebezlepšování — namátková kontrola, LLM jako rozhodčí, oprav jednu věc, znovu ověř — a necháme ji běžet na flotile izolovaných agentů, dokud extrakce není skutečně správná, ne jen zelená.
Observabilita agentů: hooks, Alloy a Grafana_
Zapojili jsme Claude Code a Codex do jednoho Grafana stacku pomocí OpenTelemetry a Alloy a pak jsme pomocí trace a logů dohledávali a opravovali problémy v chování agentů přímo u zdroje.