Una domanda che il generatore non può risolvere da solo
Un assistente cerca la procedura giusta in un archivio tecnico. La ricerca per parole mette in testa un documento con il codice esatto del componente; quella per significato preferisce una spiegazione con termini diversi. Entrambe sono plausibili, ma restituiscono punteggi su scale diverse. Sommarli può far decidere la scala numerica invece della pertinenza. In questo articolo capiremo come fondere le posizioni con Reciprocal Rank Fusion, RRF, e soprattutto quale informazione perdiamo quando scegliamo questa scorciatoia. La domanda finale non è se possiamo ottenere un ordine, ma se abbiamo motivi per fidarci di quello ottenuto.
Abstract. Costruiamo due graduatorie sintetiche di cinque documenti e calcoliamo RRF con frazioni esatte. A parità di contenuti, il parametro k cambia la posizione relativa di A e C oltre una soglia derivata analiticamente, circa 4,4641. Limitare ogni lista ai primi due risultati cambia ancora l’ordine; contare quattro volte lo stesso motore porta in testa un documento non rilevante secondo le etichette dichiarate. Misuriamo la precisione media sul singolo caso, non su un benchmark. Il risultato mostra che RRF evita il confronto diretto delle scale, ma non elimina le scelte su candidati, correlazione fra motori e valutazione.
Che cosa combina una graduatoria
Un documento è un oggetto identificabile, non soltanto un testo simile a un altro. Nel nostro esempio usiamo gli identificatori A, B, C, D ed E; rappresentano cinque oggetti distinti. Una graduatoria è una lista ordinata in cui la posizione 1 è la migliore. Se un sistema restituisce lo stesso identificatore due volte, non deve ottenere due voti nella stessa lista: il codice lo rifiuta. Deduplicare per identificatore non significa eliminare documenti diversi che raccontano la stessa cosa. Quella seconda ridondanza richiede una decisione sul contenuto, estranea alla formula qui studiata.
La prima lista, L1, è A-B-C-D-E. La seconda, L2, è B-C-D-E-A. Possiamo immaginarle come esiti di ricerca lessicale e semantica, ma non abbiamo eseguito questi motori: sono input costruiti per isolare il meccanismo. Per la successiva valutazione dichiariamo rilevanti soltanto B e C. Non deduciamo queste etichette dalle posizioni; le fissiamo separatamente, come farebbe un protocollo con giudizi di pertinenza indipendenti. In un archivio reale sarebbe necessario spiegare chi definisce la pertinenza, rispetto a quale domanda e con quali documenti mancanti dal campione.
In un sistema RAG, recupero aumentato per la generazione, il generatore riceve una selezione di documenti o frammenti prima di formulare la risposta. La fusione agisce prima di quel passaggio: decide quali candidati proporre e con quale priorità. Non controlla che una frase generata sia sostenuta dal documento né che la versione del documento sia valida. Questa separazione conta: migliorare il recupero può aiutare il contesto disponibile, ma un ordine convincente non è una prova della correttezza della risposta finale.
Una somma di posizioni, non di confidenze
Se un motore assegna 12 e un altro 0,8, la somma non ha automaticamente un significato comune. Anche riscalare tutti i punteggi del primo senza cambiarne l’ordine può cambiare il vincitore di una fusione ingenua. RRF rinuncia alle distanze numeriche fra documenti e usa soltanto le posizioni. Questo lo rende invariato rispetto a trasformazioni strettamente crescenti dei punteggi di ciascun motore, finché candidati e ordine restano gli stessi. Il prezzo è perdere anche distanze che potevano essere informative: una vittoria schiacciante e una vittoria minima producono lo stesso primo posto.
S(d) è il punteggio fuso del documento d. L’indice j percorre le liste, r_j(d) è la posizione del documento nella lista j e k è una costante non negativa, priva di unità fisica. Sommiamo soltanto le liste in cui d è presente: nel nostro protocollo un assente contribuisce zero, non una posizione stimata. I punteggi sono quindi numeri senza unità, non probabilità di pertinenza. Il rango parte da 1; farlo partire da zero cambierebbe i denominatori e con k=0 produrrebbe una divisione impossibile.
Il riferimento fondativo è il lavoro di Cormack, Clarke e Büttcher, pubblicato a SIGIR nel 2009, pagine 758–759. Abbiamo letto formula, protocollo, tabelle e discussione, non solo l’abstract. Gli autori fissano k=60 dopo un’indagine pilota e confrontano fusioni su raccolte TREC e LETOR 3. Il documento stesso riporta eccezioni a una superiorità assoluta, fra cui risultati di CombMNZ. Qui non riproduciamo quegli esperimenti, non usiamo i loro dati e non trasferiamo le prestazioni dichiarate al nostro archivio ipotetico: studiamo con input nuovi il comportamento della regola.
Cinque documenti, tutti i contributi visibili
Partiamo da k=0 per rendere leggibili i conti. B è secondo in L1 e primo in L2: ottiene 1/2+1=1,5. A è primo in L1 e quinto in L2: ottiene 1+1/5=1,2. C è terzo e secondo: ottiene 1/3+1/2=5/6, circa 0,833333. D riceve 1/4+1/3=7/12 ed E 1/5+1/4=9/20. Ordinando dal maggiore al minore otteniamo B-A-C-D-E. La prima posizione condivisa indirettamente da B nelle due liste batte l’unica vittoria di A, ma C resta dietro A.
Rifacciamo esattamente lo stesso esperimento con k=60. B ottiene 1/62+1/61=0,032522475; C ottiene 1/63+1/62=0,032002048; A ottiene 1/61+1/65=0,031778058. D ed E seguono rispettivamente con circa 0,031498016 e 0,031009615. Ora l’ordine è B-C-A-D-E. Non è cambiato un documento e nessuno ha rietichettato la pertinenza. È cambiata la funzione che trasforma una posizione in un contributo. Per questo k fa parte del metodo sperimentale e deve essere registrato, anziché essere trattato come dettaglio invisibile dell’implementazione.
| Documento | r in L1 | r in L2 | S(k=0) | S(k=60) |
|---|---|---|---|---|
| A | 1 | 5 | 1.200000000 | 0.031778058 |
| B | 2 | 1 | 1.500000000 | 0.032522475 |
| C | 3 | 2 | 0.833333333 | 0.032002048 |
| D | 4 | 3 | 0.583333333 | 0.031498016 |
| E | 5 | 4 | 0.450000000 | 0.031009615 |
Nella tabella conviene osservare C e A, non la dimensione assoluta del punteggio. Passare da circa 1 a circa 0,03 non significa che i documenti siano diventati meno pertinenti. Le due scale appartengono a due regole diverse, e il valore serve soltanto a ordinare candidati sotto la stessa regola. Anche con un parametro fissato, aggiungere una lista alza i punteggi e può cambiare l’ordine. Una soglia operativa come “accetta se S supera 0,03” non è trasferibile fra configurazioni senza una validazione specifica.
Derivare il punto in cui cambia la preferenza
Possiamo spiegare il cambio fra A e C senza limitarci a provare valori. Sottraiamo il punteggio di C da quello di A. Per k non negativo tutti i denominatori sono positivi, quindi il segno della differenza dipende soltanto dal numeratore comune. Riunendo le quattro frazioni otteniamo un polinomio quadratico. Questo passaggio rende la sensibilità una proprietà comprensibile del nostro caso, non una coincidenza del grafico. Non stiamo derivando un valore ottimale universale di k: stiamo individuando il punto di inversione fra due specifici profili di rango.
Il numeratore si annulla in 1 più o meno radice di 12. La radice negativa non appartiene al dominio scelto. Per 0≤k<4,4641 il numeratore è positivo e A precede C; oltre quella soglia è negativo e C precede A. Nel programma usiamo valori interi: a k=4 vale ancora la prima preferenza, a k=5 la seconda. Alla soglia esatta i due documenti sarebbero pari, perciò occorrerebbe una regola esplicita di spareggio. Nel calcolo ordinario risolviamo eventuali parità con l’identificatore, scelta deterministica ma priva di significato di pertinenza.
Il contributo del primo posto rispetto al quinto è (k+5)/(k+1). Vale 5 con k=0 e circa 1,0656 con k=60: aumentando k, i contributi delle posizioni vicine si assomigliano. Per ranghi piccoli rispetto a k possiamo leggere 1/(k+r) come circa 1/k−r/k². Se un documento compare in m liste, la prima parte del punteggio è allora circa m/k. È un’intuizione asintotica, non un’identità: aiuta a capire perché numero di presenze e profondità delle liste diventano importanti, ma il programma usa sempre le frazioni complete.
Troncare la lista cambia il problema
Un sistema reale raramente restituisce tutti i documenti. Ripetiamo quindi il calcolo a k=60 usando soltanto A-B da L1 e B-C da L2. B conserva due contributi e lo stesso punteggio precedente; A perde la presenza al quinto posto e resta con 1/61; C perde la presenza al terzo posto e resta con 1/62. La graduatoria diventa B-A-C. Non abbiamo approssimato soltanto la coda dei risultati: abbiamo modificato la preferenza fra due candidati presenti. Dire “fondiamo i primi due” descrive un altro algoritmo operativo rispetto a fondere entrambe le liste complete.
L’assenza ha quindi due significati che non vanno confusi. Un documento può non appartenere alla raccolta accessibile a un motore, oppure può trovarsi oltre la profondità richiesta. Il nostro zero non distingue i due casi e non equivale a un giudizio negativo di pertinenza. Questa scelta evita di inventare un rango non osservato, ma rende il risultato dipendente dal budget di recupero. Per confrontare due configurazioni bisogna registrare quanti risultati sono stati richiesti a ciascuna, quali filtri erano attivi e se i motori vedevano la stessa versione del corpus.
Se un documento rilevante manca da tutte le liste, nessuna fusione di quei candidati potrà recuperarlo. È un limite di richiamo, cioè della quota di documenti rilevanti presenti fra i candidati, prima ancora dell’ordinamento. Nel nostro caso troncato B e C sono entrambi disponibili, quindi il danno osservato riguarda la posizione, non il richiamo dei rilevanti. Distinguere le due situazioni permette di scegliere il rimedio: ampliare i candidati se manca copertura, modificare la fusione o aggiungere un riordinatore se i documenti giusti ci sono ma finiscono troppo in basso.
Quattro copie non sono quattro prove indipendenti
Ora lasciamo le liste complete, ma inseriamo quattro copie di L1 e una sola di L2. È una scelta volutamente estrema che rappresenta quattro configurazioni quasi identiche, oppure un errore di integrazione che conta più volte lo stesso risultato. A k=60 A ottiene 4/61+1/65=0,080958386, mentre B ottiene 4/62+1/61=0,080909572. A precede B per un margine piccolo ma positivo. La graduatoria torna A-B-C-D-E. La duplicazione non ha aggiunto informazioni sui documenti: ha aumentato il peso di una preferenza già presente.
La formula RRF non richiede indipendenza statistica delle liste per essere calcolata. Il problema è l’interpretazione del consenso: cinque contributi non significano cinque verifiche indipendenti. Una fusione pesata può rendere esplicita la scelta, assegnando per esempio un peso totale a una famiglia di motori invece di un peso pieno a ogni variante. Non dimostriamo che questa strategia migliori la qualità; illustriamo quale scelta andrebbe confrontata. Pesi e famiglie devono essere decisi senza guardare opportunisticamente il risultato del test finale, altrimenti la valutazione perde la propria funzione.
I pesi w_j sono numeri non negativi, senza unità; qui la versione pesata è una variante discussa, non il sistema di cui presentiamo risultati. Con quattro copie identiche assegnare 1/4 a ciascuna ripristinerebbe esattamente il contributo totale di una L1: è una conseguenza algebrica, non un nuovo esperimento su motori reali. Se le quattro liste fossero diverse, quella equivalenza non varrebbe. La diversità utile non si conta dal numero dei servizi chiamati: richiede osservare quali errori e quali documenti aggiuntivi producono.
Misurare ciò che è davvero migliorato
Per valutare l’esempio usiamo average precision, precisione media per una domanda. Quando incontriamo un documento rilevante, calcoliamo la precisione fino a quel punto: rilevanti già incontrati diviso numero di posizioni lette. Sommiamo questi valori e dividiamo per il numero totale di documenti rilevanti dichiarati, qui due. I rilevanti assenti dalla lista contribuiscono zero. Questa metrica premia la disponibilità precoce dei documenti utili, ma non misura la correttezza delle frasi generate. Specificare il denominatore evita di premiare artificialmente una lista che ha perso un rilevante.
Nell’ordine B-C-A-D-E i rilevanti occupano le prime due posizioni: AP=(1+1)/2=1. Nell’ordine B-A-C-D-E occupano le posizioni 1 e 3: AP=(1+2/3)/2=5/6, circa 0,833333. Quando le copie di L1 riportano A in testa, i rilevanti sono alle posizioni 2 e 3: AP=(1/2+2/3)/2=7/12, circa 0,583333. Le etichette sintetiche sono rimaste identiche. Non sono percentuali di affidabilità dell’assistente: sono tre misure di ordinamento per una sola domanda costruita.
Il confronto indispensabile è con le liste originali. L1 ha AP=7/12, mentre L2 ha già AP=1. La fusione completa a k=60 eguaglia L2: non la migliora. A k=0 è persino peggiore di L2. Omettere questa baseline permetterebbe una frase fuorviante come “la fusione aumenta la precisione da 0,583 a 1”, vera soltanto se scegliessimo di nascosto il confronto favorevole con L1. In un test reale servono molte domande, giudizi documentati e variazione fra domande; un solo caso dimostra possibilità e meccanismi, non una superiorità media.

Un’implementazione piccola, ipotesi esplicite
La funzione fuse riceve liste ordinate e un intero k non negativo. Per ogni identificatore accumula 1/(k+r) usando Fraction della libreria standard Python, cioè rapporti di interi mantenuti esatti. Soltanto il JSON di presentazione converte i risultati in numeri decimali; conserva anche le frazioni come stringhe. Ordinare con le frazioni evita che un arrotondamento decida una quasi-parità nel nostro piccolo caso. Questo non significa che ogni servizio debba usare aritmetica razionale: con molti documenti può essere costosa, e il comportamento numerico della propria implementazione va verificato.
Se leggiamo in totale M posizioni e incontriamo U documenti distinti, l’accumulo con una mappa richiede M aggiornamenti e memoria proporzionale a U. L’ordinamento completo richiede O(U log U) confronti. Questa è una descrizione del numero di operazioni su chiavi e confronti, non una misura di latenza: il costo delle frazioni dipende anche dalla lunghezza degli interi. Se servono soltanto i primi K risultati si può usare una selezione parziale, ma qui ordiniamo tutto per mantenere verificabili tutti i punteggi. Non abbiamo eseguito un benchmark di velocità.
Il file experiment.py ricostruisce tutti gli input, verifica unicità nelle singole liste, controlla gli ordini attesi e calcola la sensibilità per k da 0 a 100. I valori scelti sono deterministici, quindi il seed non è applicabile. Lo snippet seguente usa le stesse funzioni del file allegato: non è un secondo algoritmo abbreviato con regole differenti. Per riprodurre la tabella basta Python; per i grafici serve Matplotlib. Versioni e output sono conservati nell’archivio. Nessun motore di ricerca o modello generativo è stato addestrato o interrogato per produrre questi numeri.
Come trasferire il ragionamento a un archivio reale
Una verifica utile separerebbe tre domande. Primo: i candidati contengono i documenti necessari, sotto gli stessi permessi e la stessa versione del corpus? Secondo: la fusione li rende disponibili entro il budget di contesto, contando anche lunghezza e ridondanza dei frammenti? Terzo: il generatore usa quelle prove correttamente? La nostra AP riguarda solo una parte della seconda domanda, assumendo documenti di pari ruolo. Un documento molto lungo può consumare il contesto prima degli altri; ordinare documenti e selezionare token non sono operazioni equivalenti.
Fra le alternative, una normalizzazione dei punteggi conserva informazioni sulle distanze ma introduce assunzioni sulla comparabilità; un riordinatore valuta nuovamente i candidati ma richiede risorse e un proprio protocollo; pesare famiglie di motori affronta esplicitamente la ridondanza ma richiede scegliere e validare i pesi. Qui non attribuiamo risultati a queste alternative, perché non le abbiamo implementate e misurate sul medesimo insieme di domande. Il confronto onesto consiste nel dichiarare quale informazione ciascuna usa e quale costo o ipotesi aggiunge, prima di formulare una classifica di qualità.
La risposta: una fusione semplice, scelte tutt’altro che neutre
Possiamo unire due ricerche senza sommare punteggi incompatibili usando le posizioni e una regola come RRF. Ma la semplicità del calcolo non rende neutrale il risultato. Nel nostro caso un parametro cambia una preferenza con una soglia dimostrabile, la profondità richiesta modifica i contributi e copiare un motore cambia il vincitore senza aggiungere conoscenza. La fusione migliore fra quelle eseguite eguaglia già una lista originale: nessun miracolo è nascosto nei numeri. Per fidarci del recupero dobbiamo conservare provenienza delle liste, parametri, candidati e giudizi indipendenti, poi verificare separatamente ciò che il generatore afferma.
from experiment import fuse, ap, L1, L2
for name, lists, k in [('full', [L1,L2],60),('top2',[L1[:2],L2[:2]],60),('duplicates',[L1]*4+[L2],60)]:
result = fuse(lists,k)
print(name,result['order'],ap(result['order']))
Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 16 ottobre 2026.

