RAG in produzione: prima chunking e re-ranking, poi gli embedding
Quando il retrieval è debole, cambiare gli embedding raramente lo sistema. Diagnostico prima chunking e re-ranking.
Ho tradotto questo articolo dall’inglese con un modello linguistico. Leggi l’originale in inglese
Aggiornamento, ottobre 2026. I risultati legali, finanziari e di compliance qui sotto vengono da lavoro privato che non posso pubblicare, quindi leggili come un mio resoconto, non come evidenza. La parte applicabile è l’ordine diagnostico: misuro, poi controllo chunking e ranking prima di cambiare il modello di embedding. I punteggi privati non sono una prova. Il testo è invariato. Il motivo è nella sezione di aggiornamento verso la fine.
Perché conta
Gran parte del debugging RAG che vedo segue lo stesso schema: la qualità del retrieval è scarsa, qualcuno prova un embedding model diverso, la qualità migliora un po’ e il problema viene considerato chiuso. Tre mesi dopo, la qualità peggiora di nuovo su una nuova classe di query e il ciclo si ripete.
Su una pipeline di Q&A legale che ho diagnosticato, cambiare l’embedding model ha mosso a malapena i punteggi RAGAS. I chunk recuperati erano incoerenti: clausole spezzate a metà frase, tabelle senza le loro righe, e i passaggi rilevanti restavano sotto un mucchio di near-match. Chunking e un re-ranker cross-encoder sono state le prime leve che hanno mosso i numeri. Ho poi visto lo stesso ordine nel Q&A su documenti finanziari e nella revisione di contratti per compliance: l’embedding model raramente era il primo collo di bottiglia.
L’ordine conta perché gli interventi hanno curve di costo diverse. Cambiare un embedding model significa reindicizzare il corpus, operazione che può richiedere ore o giorni a seconda della scala. Cambiare il chunking significa ripreprocessare e riapplicare gli embedding, quindi il costo è simile. Il tuning di un re-ranker avviene al query time e non richiede una ricostruzione dell’indice. Quando ricorro prima all’embedding model, spesso sostengo un costo elevato prima di sapere se il guadagno atteso sia probabile.
Ecco l’ordine di debugging che seguo ora e perché.
1. La diagnosi prima di qualsiasi ottimizzazione
Prima di cambiare qualsiasi cosa, fisso una baseline. "I risultati del retrieval sembrano sbagliati" non è una diagnosi: è un sintomo. Quando ho risposte e contesti di riferimento adatti, uso il framework RAGAS, insieme all’analisi di latenza, costi e casi di errore, per distinguere tre tipi di guasto prima di toccare una configurazione:
- Context Precision: dei chunk recuperati, quale frazione è effettivamente rilevante? Una precisione bassa significa rumore nella context window.
- Context Recall: delle informazioni rilevanti nel corpus, quale frazione ho effettivamente recuperato? Un recall basso significa che la risposta non viene trovata.
- Faithfulness: la risposta generata si basa sul contesto recuperato? Una faithfulness bassa significa che la risposta non è sufficientemente supportata dal contesto recuperato, e indica problemi di generazione, di prompt o di costruzione del contesto.
Quando ho dati di riferimento adatti, eseguo RAGAS su un insieme rappresentativo di query prima di qualsiasi altra cosa. La diagnosi mi dice dove indagare:
- Precisione bassa → indago su re-ranking, generazione dei candidati e chunking.
- Recall basso → indago su chunking, copertura delle query e modello di embedding.
- Entrambi bassi → di solito indago prima sul chunking, poi valuto se serve il re-ranking.
- Buona precisione e buon recall, faithfulness bassa → indago su prompt, comportamento del modello e costruzione del contesto, non solo sul retrieval.
Questo passaggio si salta facilmente, e lo vedo saltare spesso. Io cerco di non farlo. Senza una baseline che includa RAGAS dove ha senso, più l’analisi di latenza, costi e casi di errore, ogni ottimizzazione è un tentativo alla cieca.
2. Il chunking è di solito il primo intervento strutturale
I tutorial RAG più semplici dividono i documenti a conteggi fissi di token — 512 token, sovrapposizione di 50 token, fatto. Questo può funzionare per prosa uniforme. Nella mia esperienza, fallisce rapidamente quando la struttura del documento porta significato.
Un chunk di 512 token in un documento finanziario può essere disordinato. Potrebbe iniziare a metà frase in una clausola, includere un’intestazione di tabella ma nessuna riga e terminare prima della definizione che attribuisce significato alla clausola. L’embedding di quel chunk è semanticamente incoerente. Non mi aspetto che un embedding model riesca a recuperare un retrieval affidabile da input incoerenti.
# Naive: fixed-size chunks regardless of document structure
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=512 , chunk_overlap=50 )
chunks = splitter.split_text(document_text) # splits anywhere — mid-sentence, mid-table
# Better: structure-aware splitting respects semantic boundaries
from langchain_text_splitters import MarkdownHeaderTextSplitter
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[("##" , "section" ), ("###" , "subsection" )]
)
chunks = splitter.split_text(markdown_text) # splits at heading boundaries
La correzione non consiste in una libreria di splitter migliore — consiste nel comprendere la struttura del documento e scegliere una strategia che preservi le unità semantiche. Per i contratti finanziari, di solito divido a livello di clausola. Per la documentazione API, preferisco una funzione per chunk. Per le trascrizioni di riunioni, i turni dei parlanti hanno funzionato meglio nei sistemi che ho gestito. Per la prosa densa, uso chunk a livello di paragrafo con sovrapposizione di frasi.
Il secondo problema strutturale è il metadata. Un chunk senza provenienza — ID documento, titolo della sezione, data di creazione, tipo di fonte — è solo testo sospeso. Se il retrieval restituisce un chunk da una policy sostituita datata 2022, ho bisogno di metadata per filtrarlo al query time. Nelle mie pipeline, aggiungo source, date, section e document type a ogni chunk. Filtro prima della similarity search quando posso e dopo la similarity search quando devo.
Dopo aver corretto il chunking in un sistema legale Q&A che avevo costruito, il context recall è aumentato sul benchmark RAGAS che stavo usando. Non avevo modificato l’embedding model. Considero questo un risultato di benchmark per quel sistema, non una garanzia generale.
3. Re-ranking: il passaggio di retrieval che molte pipeline saltano
La vector similarity search recupera documenti i cui embedding sono vicini all’embedding della query nello spazio ad alta dimensionalità. È un’approssimazione utile della rilevanza semantica. Non è un ranker di cui mi fidi da solo.
Il problema è semplice: la similarità basata su embedding viene calcolata indipendentemente per ciascun documento. Il model vede la query e un documento alla volta. Un cross-encoder re-ranker vede insieme la query e un documento candidato, quindi può modellare la loro interazione. È più lento, ma nei miei benchmark ha spesso prodotto un segnale di rilevanza migliore per il set di contesto finale.
La pipeline di esempio è semplice: recuperare i primi 50 candidati con vector search, quindi riordinare quei 50 con un cross-encoder per ottenere i primi 5. I 5 finali entrano nella context window dell’LLM.
from sentence_transformers import CrossEncoder
# Stage 1: fast retrieval — top 50 candidates via vector search
candidates = vectorstore.similarity_search(query, k=50 )
# Stage 2: cross-encoder re-ranking — top 5 from those 50
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2" )
scores = reranker.predict([(query, doc.page_content) for doc in candidates])
ranked = sorted (zip (scores, candidates), key=lambda x: x[0 ], reverse=True )
top_5 = [doc for _, doc in ranked[:5 ]]
Costo: inferenza cross-encoder su 50 coppie di candidati al query time. Con ms-marco-MiniLM-L6-v2, ho visto la latenza CPU diventare evidente sui miei workload e hardware. Tratto questo come una misurazione specifica del workload, non come un’affermazione generale sulla latenza. Per i workload Q&A su cui ho lavorato, questo può essere accettabile. Non è accettabile per pipeline con target di latenza rigorosi inferiori a 100ms.
Per use case real-time in cui la latenza del re-ranking è inaccettabile, posso usare un bi-encoder più forte al retrieval time, come bge-large-en-v1.5, quando il suo compromesso qualità-latenza misurato lo giustifica, e saltare il cross-encoder; verifico la modifica sul set di valutazione. Questo scambia una parte della precisione con la latenza. Preferisco rendere questo tradeoff esplicito e misurabile anziché accettare la perdita di precisione nascosta di un retrieval debole senza re-ranking.
Dopo aver aggiunto il re-ranking allo stesso sistema legale Q&A, la context precision è aumentata sul mio benchmark RAGAS. Combinato con la correzione del chunking, i failure di retrieval misurati erano più allineati ai failure riportati dagli utenti.
4. Hybrid search: quando BM25 aiuta e quando no
La vector search pura può avere prestazioni inferiori sulle query exact-match — SKU di prodotto, numeri di caso, nomi di persone, stringhe di versione specifiche. Quando vedo un corpus ricco di identificatori, testo una componente keyword.
La hybrid search combina dense retrieval e sparse retrieval. Il dense retrieval usa la vector similarity. Lo sparse retrieval usa lo scoring keyword BM25. Le liste di risultati vengono quindi unite, spesso con Reciprocal Rank Fusion. In pratica, molti vector database (Qdrant, Weaviate, Pinecone) supportano questo in modo nativo. Preferisco l’implementazione nativa quando disponibile invece di mantenerne una personalizzata.
I casi di retrieval fallito mi dicono quando aggiungere BM25. Se le mancate corrispondenze si concentrano attorno a identificatori specifici — nomi, numeri, abbreviazioni — testo la hybrid search. Se le mancate corrispondenze sono semantiche, il passaggio successivo è diverso. Per esempio, un utente può chiedere una "termination clause" mentre il documento dice "cessation of obligations". Nei sistemi che ho valutato, BM25 di solito non ha aiutato molto con questa classe di failure, quindi guardo di nuovo al chunking o alla copertura degli embedding.
Il parametro alpha, che pondera il punteggio denso rispetto a quello sparso, deve essere ottimizzato sul dataset effettivo. Inizio da 0,5. Nelle valutazioni che ho eseguito, ho talvolta visto corpus finanziari e legali spostarsi verso alpha 0,3–0,4, con maggiore peso alle keyword. Ho anche visto knowledge base general-purpose spostarsi verso 0,6–0,7, con maggiore peso semantico. Prima del rilascio misuro la modifica con il benchmark RAGAS, quando ho dati di riferimento adatti.
Un errore che vedo spesso: la hybrid search viene aggiunta prima che il chunking sia corretto, perché sembra più sofisticata e richiede meno modifiche all’indice. Non corregge un problema di chunking. Se i chunk sono semanticamente incoerenti, aggiungere BM25 mi fornisce chunk incoerenti recuperati con due metodi invece che con uno.
5. Metadata filtering prima di scalare
Un failure mode che appare solo con la scala: l’indice vettoriale cresce fino a milioni di documenti, la latenza delle query aumenta e la prima risposta è aggiungere più compute. Nei miei workload, di solito cerco prima un problema di filtering.
Il metadata filtering — filtrare un sottoinsieme di documenti prima o dopo la similarity search — può essere meno costoso di hardware migliore. Risolve inoltre una classe di problemi che i miglioramenti degli embedding non possono affrontare.
Esempi di pre-filtering: "recupera solo documenti del Q3 2024", "recupera solo documenti di policy per il customer tier Premium". Questi riducono la dimensione effettiva dell’indice prima dell’operazione di similarity costosa. Esempi di post-filtering: "filtra qualsiasi chunk il cui documento abbia superseded: true", "filtra i chunk con un confidence score sotto la soglia". Questi ripuliscono i risultati rumorosi dopo il retrieval.
Entrambi richiedono uno schema di metadati progettato al momento dell’indicizzazione per i filtri che mi serviranno in fase di query. Lo progetto prima che serva. Se indicizzo i documenti con una data created_at ma la query più frequente è "mostrami le normative in vigore", mi serve anche un concetto di stato di validità, come effective_through o un campo equivalente. Senza, finisco per filtrare per data quando dovrei filtrare per stato, e la correzione di solito richiede di reindicizzare tutto.
6. Selezione dell’embedding model: l’ultima leva
Dopo chunking, re-ranking e, opzionalmente, hybrid search, la qualità del retrieval è di solito stata superiore alla baseline nei sistemi che valuto. A quel punto, l’embedding model diventa l’ultima leva su cui intervengo.
Nel mio lavoro, la scelta dell’embedding model ha contato maggiormente in due casi. Il primo riguarda vocabolario specifico di dominio che i model generalisti potrebbero non aver visto con un volume di training sufficiente: note cliniche, terminologia legale di nicchia, strumenti finanziari specializzati. Il secondo riguarda il retrieval multilingue, dove il fine-tuning specifico per lingua può contare.
Per il testo general-purpose in lingua inglese, il divario di qualità tra i model principali sui benchmark pubblici di embedding è spesso minore del divario che vedo tra un corpus ben suddiviso in chunk e uno con chunking scadente. Cambiare embedding model può aiutare, ma non mi aspetto che compensi un chunking difettoso.
Quando vale la pena fare fine-tuning: considero il fine-tuning solo quando dispongo di un dataset di retrieval etichettato, come coppie query → documento rilevante, con abbastanza esempi per valutare in modo affidabile i cambiamenti. Il fine-tuning di un bi-encoder sul dominio può migliorare il recall, ma aggiunge costi reali: raccolta del dataset, infrastruttura di training e reindicizzazione dell’intero corpus a ogni aggiornamento del model.
Prima di investire nel fine-tuning, quando ho risposte e contesti di riferimento adatti, eseguo RAGAS dopo chunking e re-ranking. Se il recall resta basso, indago su problemi strutturali, copertura delle query e modello di embedding. Non mi aspetto che il fine-tuning ripari confini dei documenti sbagliati o metadati mancanti. Se il recall è già alto e gli errori residui sono circoscritti, il fine-tuning potrebbe non valere il costo. Lo considero seriamente solo in una pipeline ben strutturata con un divario di retrieval documentato e specifico del dominio.
Il quadro della categoria
Retrieval-Augmented Generation viene spesso trattato come un unico monolite. In pratica, lo tratto come uno stack di scelte indipendenti: strategia di chunking, embedding model, meccanismo di retrieval (vector, keyword, hybrid) e re-ranker. Ciascuno ha i propri failure mode, struttura dei costi e leve di tuning.
L’ordine di debugging che seguo è stabile nel mio lavoro: misuro prima, correggo il chunking, aggiungo il re-ranking, considero la hybrid search se le query keyword stanno fallendo e ottimizzo l’embedding model per ultimo, se necessario. Questa sequenza riserva gli interventi ad alto costo ai problemi che le diagnosi precedenti non hanno risolto.
Se posso aiutarti a fare debugging di una pipeline RAG in produzione quando i numeri RAGAS non corrispondono all’esperienza utente — oppure a progettarne una con eval riproducibili fin dall’inizio — scrivimi.
Aggiornamento — ottobre 2026
A settembre 2026 ho adottato per questo sito una regola sulle prove: un risultato conta come evidenza solo se chi legge può ispezionarlo. I risultati di questo articolo non rispettano quella regola. La pipeline di Q&A legale, i suoi punteggi RAGAS e le pipeline finanziarie e di compliance vengono da lavoro privato, e non posso pubblicarne dati o benchmark. Lascio il testo invariato come traccia di come sono arrivato all’ordine diagnostico, ma è un mio resoconto, non un’evidenza.
L’ordine in sé non dipende da quei casi. Misura prima precisione, recall e faithfulness sul tuo insieme di query, correggi chunking e re-ranking prima di cambiare il modello di embedding, e lascia che i tuoi numeri confermino o smentiscano l’ordine.
FAQ
Come dovrei diagnosticare un retrieval RAG scarso prima di modificare le config?
Quando ho risposte e contesti di riferimento adatti, misuro con RAGAS su un insieme rappresentativo di query, insieme all’analisi di latenza, costi e casi di errore. Separo Context Precision, Context Recall e Faithfulness prima di toccare una configurazione. Con una precisione bassa indago su re-ranking, generazione dei candidati e chunking. Con un recall basso indago su chunking, copertura delle query e modello di embedding. Se sono bassi entrambi, di solito parto dal chunking e poi valuto il re-ranking. Con una faithfulness bassa e buone metriche di retrieval indago su prompt, comportamento del modello e costruzione del contesto.
Perché correggo il chunking prima di cambiare embedding model?
Non mi aspetto che un embedding model riesca a recuperare un retrieval affidabile da input incoerenti. I chunk a dimensione fissa possono dividere clausole, tabelle, definizioni o altre unità semantiche. Scelgo una strategia che preservi la struttura del documento, come chunk a livello di clausola per i contratti, chunk a livello di funzione per la documentazione API o chunk di paragrafo con sovrapposizione di frasi per la prosa densa.
Quando dovrei aggiungere un cross-encoder re-ranker a una pipeline RAG?
Aggiungo il re-ranking quando la precisione è bassa e il contesto recuperato contiene troppo rumore. La mia pipeline abituale recupera i primi 50 candidati con vector search, quindi usa un cross-encoder per selezionare i primi 5 per la context window dell’LLM. Tratto la latenza aggiunta al query time come specifica del workload e la misuro prima del rilascio.
Quando la hybrid search aiuta più della vector search pura?
Testo la hybrid search quando i casi di retrieval fallito si concentrano attorno a identificatori esatti come SKU di prodotto, numeri di caso, nomi di persone o stringhe di versione. Se i failure sono incomprensioni semantiche, BM25 di solito non ha aiutato nei sistemi che ho valutato, e guardo di nuovo al chunking o alla copertura degli embedding.
Quando vale la pena considerare il tuning dell’embedding model?
Tratto l’embedding model come l’ultima leva dopo chunking, re-ranking e hybrid search opzionale. Considero il fine-tuning solo quando dispongo di un dataset di retrieval etichettato, abbastanza esempi per una valutazione affidabile e una pipeline ben strutturata con un gap di retrieval specifico del dominio documentato.
Articoli correlati
Una ricevuta di retrieval è il JSON che la query ha scritto
Conservo il JSON pubblico della query RAGFS come ricevuta di retrieval: query, file, score, content e intervallo di righe. Non invento un benchmark di qualità.
4 ott 20265 min di lettura#RAG#FUSE#Linux#RetrievalCinque pattern di function calling che uso in produzione
Cinque pattern di function calling che uso in sistemi in produzione, e gli anti-pattern che ho sostituito con questi.
25 nov 202413 min di lettura#LLM#Function Calling#OpenAI#ProduzionePerché gli agenti di ricerca autonomi allucinano, e come un ciclo di critica fa emergere le affermazioni senza fonte
Gli agenti di ricerca possono sembrare autorevoli e inventare citazioni. Un critico con fonti intercetta le affermazioni non supportate: non azzera il rischio.
15 nov 202410 min di lettura#Agenti AI#Ricerca#LLM#Produzione