Salta al contenuto
Torna alla home
2024-12-2012 min di lettura
RAGRetrievalLLMProduction

RAG: chunking e re-ranking prima degli embedding

La maggior parte delle pipeline RAG fallisce nel chunking o nel re-ranking prima della qualità degli embedding: un framework diagnostico.

Perché conta

La maggior 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.

Ho dedicato gran parte del 2024 alla costruzione e al debugging di pipeline RAG per settori regolamentati — Q&A su documenti finanziari, revisione di contratti per compliance, analisi di memorie legali. La lezione che ho imparato, ripetutamente, è che l'embedding model raramente è il primo collo di bottiglia. Nei sistemi su cui ho lavorato, il chunking è stato spesso il primo problema strutturale, e il chunking insieme al re-ranking ha spiegato molti dei failure di retrieval che ho diagnosticato.

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, stabilisco una baseline. "I retrieval sembrano sbagliati" non è una diagnosi. È un sintomo. Uso il framework RAGAS per separare tre failure mode prima di modificare una config:

  • 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 è basata sul contesto recuperato? Una faithfulness bassa significa che l'LLM sta allucinando nonostante un retrieval corretto — un problema completamente diverso.

Eseguo RAGAS su un set di query rappresentativo prima di fare qualsiasi altra cosa. La diagnosi mi dice dove guardare:

  • Precisione bassa → probabile problema di re-ranking. La pipeline recupera chunk rilevanti ma li sommerge nel rumore.
  • Recall basso → probabile problema di chunking. La risposta esiste nel corpus ma non arriva mai nella context window.
  • Entrambi bassi → di solito correggo prima il chunking, poi aggiungo il re-ranking.
  • Buona precisione e recall, faithfulness bassa → problema di prompting o di model, non un problema di retrieval.

Questo passaggio è facile da saltare, e lo vedo saltare spesso. Cerco di non farlo. Senza un punteggio RAGAS di baseline, ogni ottimizzazione che faccio è un'ipotesi.

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 che uso di solito è 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-L-6-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-L-6-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, di solito uso un bi-encoder più forte al retrieval time, come bge-large-en-v1.5, e salto il cross-encoder. 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 lo scoring dense rispetto a quello sparse, 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 il cambiamento con il benchmark RAGAS.

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 metadata progettato al tempo di indicizzazione per i filtri di cui avrò bisogno al query time. Lo progetto prima che mi serva. Se indicizzo documenti con una data created_at ma il tipo di query più frequente è "mostrami le normative attuali", mi serve anche un concetto di policy status come effective_through o un campo equivalente. Senza di esso, finisco per filtrare per data quando dovrei filtrare per status, e la correzione richiede di solito una reindicizzazione completa.

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, eseguo RAGAS dopo chunking e re-ranking. Se il recall è ancora basso, cerco prima un problema strutturale. Non mi aspetto che il fine-tuning risolva confini dei documenti difettosi o metadata mancanti. Se il recall è già alto e i failure rimanenti sono circoscritti, il fine-tuning potrebbe non valere il costo. Il caso in cui lo considero seriamente è una pipeline ben strutturata con un gap di retrieval specifico del dominio documentato.

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.

FAQ

Come dovrei diagnosticare un retrieval RAG scarso prima di modificare le config?

Misuro prima con RAGAS su un set di query rappresentativo. Separo Context Precision, Context Recall e Faithfulness prima di modificare una config. Una precisione bassa indica il re-ranking, un recall basso indica il chunking, entrambi bassi significano che di solito correggo prima il chunking e poi applico il re-ranking, mentre una faithfulness bassa con buon retrieval indica prompting o comportamento del model.

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.

Condividi questo articolo

Articoli correlati

  • Cinque pattern di function calling che hanno retto in produzione

    L'uso dei tool è dove i sistemi LLM falliscono più spesso: cinque pattern robusti in produzione e gli anti-pattern sostituiti.

    25 nov 202413 min di lettura
    #LLM#Function Calling#OpenAI#Production
  • Perché agenti autonomi allucinano: ciclo critico

    Gli agenti planner-executor falliscono sulla verificabilità. Un critico con accesso alle fonti individua affermazioni non supportate e lacune di copertura.

    15 nov 202410 min di lettura
    #AI Agents#Research#LLM#Production
  • Dove CrewAI fallisce in produzione e alternative

    L’astrazione dei ruoli in CrewAI regge nelle demo ma cede in produzione: quattro failure mode e i pattern LangGraph che li sostituiscono.

    15 gen 202512 min di lettura
    #CrewAI#Multi-Agent#LangGraph#Production