Ricerca a fusione multi-modale: scegliere il retriever giusto per ogni query
> Una query come 'dove è definito parseConfig' vuole una ricerca diversa da 'come funziona l'auth'. Maguyva classifica l'intento, pesa di conseguenza quattro modalità di retrieval, e fonde i risultati con una Reciprocal Rank Fusion pesata.
Nota sul design storico: questo articolo dell’aprile 2026 descrive il design del routing di allora, non il comportamento predefinito attuale. La correzione di settembre riportata sotto riguarda esclusivamente un’affermazione su una misurazione. Per il routing attuale, i preset e i controlli espliciti delle modalità, si veda Come funziona.
Una query di ricerca non è una cosa sola.
“dove è definito parseConfig” vuole un simbolo esatto — una posizione precisa, veloce. “come funziona l’autenticazione” vuole significato — la distesa di codice correlato che spiega un concetto. “cosa si rompe se cambio questa funzione” vuole il grafo delle dipendenze. “trova la stringa ECONNREFUSED” vuole un match letterale, niente di furbo.
Grep è eccellente per i match letterali e utile per alcune cacce ai riferimenti, ma non è un grafo delle dipendenze e non capisce il significato. Gli embedding coprono il lato semantico, ma sono lo strumento sbagliato per stringhe esatte e analisi d’impatto. La maggior parte degli strumenti di ricerca sul codice sceglie un solo motore e impone quella scelta a ogni query. Maguyva non sceglie. Capisce che tipo di domanda hai fatto, poi combina quattro retriever nella proporzione che quella domanda merita.
Quattro modalità
Sotto il cofano ci sono quattro modi indipendenti per trovare codice:
- semantica — ricerca vettoriale su embedding binari Voyage; trova codice per significato.
- testuale — matching a trigrammi; trova letterali, stringhe di errore, identificatori esatti.
- strutturale — query AST; trova definizioni, firme, e costrutti del linguaggio.
- a grafo — il grafo delle dipendenze; trova chiamanti, chiamati, e raggio d’impatto.
Ognuna eccelle su una classe diversa di domanda. Il trucco sta nel decidere quanto fidarsi di ciascuna per la query che hai davanti.
Classificazione dell’intento
Prima che parta qualsiasi retrieval, un classificatore leggero smista la query in una di sei categorie di intento, con un punteggio di confidenza. È deliberatamente economico — euristiche ordinate, vince il primo match — perché gira sul percorso critico e aggiunge solo un millisecondo o due:
- inizia con
def,class,func,import… → find_definition (confidenza 0,95) - “who calls”, “usages of”, “references to” (chi chiama, usi di, riferimenti a) → find_references (0,90)
- “impact”, “blast radius”, “what depends on” (impatto, raggio d’impatto, cosa dipende da) → impact_analysis (0,90)
- una
"string"tra virgolette o un token di errore cometraceback→ exact_match (0,85–0,90) - un identificatore
CamelCaseosnake_case→ find_definition (0,60–0,80) - “how”, “why”, “explain”, “architecture” (come, perché, spiega, architettura) → understand_code (0,75)
- niente corrisponde → understand_code, bassa confidenza (0,40)
Ogni intento porta un profilo di peso sulle quattro modalità. Questi sono i numeri reali:
| Intento | semantica | testuale | strutturale (AST) | a grafo |
|---|---|---|---|---|
| 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 |
Quindi “dove è definito parseConfig” pesa forte sull’AST (0,6). “come funziona l’auth” pesa sui vettori semantici (0,5). “cosa dipende da questo” è quasi tutto grafo (0,7). “trova ECONNREFUSED” è quasi tutto trigrammi (0,9), con il modello di embedding completamente spento — perché la similarità semantica è esattamente lo strumento sbagliato per una stringa esatta.
Il percorso rapido, e il percorso fuso
Quando il classificatore è sicuro — punteggio ≥ 0,85 — e la query è di tipo normale, Maguyva salta del tutto la fusione e instrada direttamente verso la singola modalità dominante. “dove è definito X” non ha bisogno di quattro retriever; ha bisogno dell’indice AST, subito. Quel percorso diretto viene riportato indietro come fusion_strategy: "direct".
Tutto ciò che è ambiguo passa attraverso la fusione. Le quattro modalità (o tre, sul preset predefinito) girano in parallelo, ciascuna restituendo la propria lista ordinata, e le combiniamo.
Reciprocal Rank Fusion pesata
Fondere retriever eterogenei è più difficile di quanto sembri: una similarità coseno di 0,82 e un punteggio a trigrammi di 137 e una centralità di grafo di 0,004 non sono sulla stessa scala, quindi non puoi semplicemente sommarli. La Reciprocal Rank Fusion aggira il problema scartando i punteggi grezzi e mantenendo solo il rank che ogni motore ha assegnato. Il contributo di un risultato da una modalità è:
contribution = weight × 1 / (k + rank + 1)
dove rank è la sua posizione nella lista di quella modalità e k è una costante di smoothing. I contributi vengono sommati tra le modalità per qualsiasi risultato che più di un motore ha trovato — l’accordo tra i retriever sale naturalmente in cima. Usiamo k = 40 sul preset predefinito e 60 su thorough (quick gira solo in modalità semantica, quindi la fusione lì non entra mai in gioco). Il lavoro originale su RRF è arrivato a k = 60 per il retrieval general-purpose; il nostro default è leggermente più aggressivo, il che dà un po’ più peso all’accordo tra le modalità nei risultati meglio classificati — e non consigliamo di regolarlo a mano.
Oltre a questo, i risultati portano un boost di importanza del grafo. Un hub — una funzione su cui si appoggia tutto il codebase — dovrebbe superare in classifica un nodo foglia poco noto anche a parità di rilevanza testuale, quindi moltiplichiamo ogni contributo per:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
La centralità arriva dalle metriche PageRank/degree precalcolate dalla pipeline, e il boost è limitato a un massimo di 1,5× così che una funzione popolare non possa seppellire completamente una più rilevante ma oscura. Infine retrocediamo i risultati da percorsi vendor, build, e archive, e deduplichiamo al miglior chunk per file.
Cosa resta imperfetto
Il classificatore dell’intento è un insieme di regex, non un modello addestrato. Gestisce le forme di query comuni con euristiche, mentre una query ambigua ricade su understand_code e su una combinazione orientata alla semantica. Una versione precedente di questo articolo descriveva come raggiunta una riduzione del tasso di ricerche senza risultati da circa il 15% a meno del 5%. La proposta del dicembre 2025 riportava in realtà una stima iniziale e un obiettivo; non viene fornito alcun insieme di query o resoconto delle misurazioni a supporto. Ritiriamo quell’affermazione di miglioramento raggiunto; si vedano le evidenze della fonte e la correzione. Non abbiamo sostituito il classificatore con un modello addestrato perché la versione economica è veloce e sufficientemente valida per il ruolo di routing previsto, e perché un classificatore sicuro di sé ma in errore è peggiore di un fallback onesto. I pesi stessi sono prior scelti a mano, non appresi da dati sui clic che non raccogliamo.
Fai la domanda, senza dover scegliere lo strumento
Un agente non dovrebbe dover sapere se ricorrere a grep o agli embedding o al call graph — dovrebbe fare la sua domanda in termini semplici e ottenere la risposta giusta. La fusione multi-modale è ciò che permette a find_symbol, alla ricerca semantica, e all’analisi delle dipendenze di stare dietro un’unica interfaccia di query: il sistema legge la forma della domanda e assembla silenziosamente il retriever giusto per essa. Il modello che valuta gli embedding conta, ma conta anche sapere quando non usarli. Scegliere lo strumento giusto per ogni query è un tipo di qualità a sé, ed è uno che preferiamo possedere noi piuttosto che scaricare su chi chiama.
// you bring the question. it brings the tools.
Letture correlate
Altro dal diario di sviluppo di Maguyva
Perché abbiamo aggiornato la ricerca sul codice a voyage-4-large_
Abbiamo spostato i nostri embedding del codice su voyage-4-large — attualmente in cima alla classifica pubblica RTEB per il retrieval di codice. La versione onesta: il compromesso che facciamo, cosa indicizziamo davvero, e perché paghiamo per embedding premium.
Osservabilità degli agenti: hook, Alloy e Grafana_
Abbiamo collegato Claude Code e Codex a un unico stack Grafana con OpenTelemetry e Alloy, poi abbiamo usato trace e log per trovare e correggere i problemi di comportamento degli agenti alla fonte.
Estrarre conoscenza per chiudere il loop: come le modifiche diventano memoria istituzionale_
I commit git diventano voci di changelog strutturate e record di decisioni architetturali, poi rientrano negli agenti IA come memoria istituzionale interrogabile.