Quando il Markdown può peggiorare un sistema RAG: uno studio preliminare sugli shallow chunks
Un piccolo benchmark locale su PDF scientifici mostra un possibile failure mode: la conversione in Markdown può generare chunk strutturali poveri di contenuto che disturbano la dense retrieval.
Il presupposto da testare
Nei sistemi RAG locali si dà spesso per scontato che convertire un PDF in Markdown prima di indicizzarlo migliori sempre la qualità della retrieval. L'intuizione è sensata: il Markdown rende il documento più ordinato, preserva la struttura delle tabelle, separa le sezioni in modo esplicito e rimuove gli artefatti di impaginazione. Un documento più strutturato, si pensa, produce chunk migliori, e chunk migliori portano a una retrieval più precisa.
Ma questa trasformazione aiuta sempre? Oppure può anche introdurre rumore?
La domanda
La domanda che guida questo lavoro è semplice da formulare:
«Does compiling PDFs into structured Markdown before indexing improve RAG quality?»
In pratica: prima di indicizzare un PDF in un vector database, conviene trasformarlo in Markdown oppure è meglio usare il testo grezzo estratto direttamente dal PDF? Non è una domanda retorica. La risposta, come spesso accade quando si fanno esperimenti, non è scontata.
Setup sperimentale
Per rispondere ho costruito un benchmark piccolo, locale e riproducibile. Dieci PDF scientifici — tutti paper noti, da "Attention Is All You Need" al survey sulla valutazione degli LLM — fanno da corpus. Cinquanta domande in italiano, scritte a mano, coprono cinque tipologie: fatti semplici, ragionamento locale, confronto multi-documento, estrazione da tabella e domande "trappola" a cui il corpus non può rispondere.
Due pipeline vengono messe a confronto. La Pipeline A è la più diretta: PDF, testo grezzo via PyMuPDF, chunking a paragrafi, embedding con all-MiniLM-L6-v2, indicizzazione in ChromaDB con distanza coseno, retrieval a top_k=3 e generazione finale via LLM. La Pipeline B è identica, tranne per un passaggio in più: dopo l'estrazione, il testo grezzo viene compilato in Markdown strutturato prima del chunking e dell'embedding.
Entrambe le pipeline condividono lo stesso chunker — un algoritmo che accumula paragrafi interi e chiude il chunk quando il prossimo paragrafo farebbe superare i 1500 caratteri —, lo stesso modello di embedding, lo stesso vector database e gli stessi parametri di generazione: temperatura 0.1, max_tokens 256. La valutazione automatica preliminare usa un fuzzy word-overlap con soglia 0.4.
Quattro configurazioni modello-provider sono state testate: Qwen 0.8B via LM Studio, Nemotron 3 Super Free via OpenCode Zen, DeepSeek V4 Flash via OpenCode Go e Gemma 4 26B via Google Gemini.
I risultati
Ecco i risultati, riportati con la cautela che un setup di queste dimensioni richiede. Definisco Δ come la differenza tra il punteggio medio della Pipeline B (Markdown) e quello della Pipeline A (Raw), su una scala 0-5:
Δ = score(Markdown) − score(Raw)
Se Δ > 0, Markdown ha fatto meglio; se Δ < 0, Raw ha fatto meglio.
| Modello | Raw | Markdown | Δ |
|---|---|---|---|
| Qwen 0.8B | 2.50 | 2.56 | +0.06 |
| Nemotron 3 | 2.46 | 2.28 | −0.18 |
| DeepSeek V4 Flash | 2.98 | 2.76 | −0.22 |
| Gemma 4 26B | 3.12 | 2.86 | −0.26 |
Il pattern è visibile: il modello più piccolo trae un piccolo beneficio dal Markdown, mentre le altre tre configurazioni ottengono risultati migliori con il testo grezzo. Il Δ cresce in valore assoluto passando da Qwen a Gemma, ma attenzione: non si può attribuire questo gradiente causalmente alla dimensione del modello. Nelle quattro configurazioni testate cambiano simultaneamente provider, API, tokenizer e formato del prompt. È quello che nel paper chiamo un gradiente confounded: osservato, coerente, ma non isolato sperimentalmente.
Shallow chunks: il meccanismo
Qui arriva la parte che mi interessa di più. Il punto non è stabilire se il Markdown sia peggiore del testo grezzo — sarebbe una conclusione affrettata e probabilmente sbagliata fuori da questo setup. Il punto è più preciso.
Una pipeline Markdown può produrre quelli che chiamo shallow structural chunks.
Cosa sono? Pezzi di documento brevi, molto strutturali, spesso fatti di titoli, separatori di tabella, intestazioni di sezione o frammenti con poco contenuto informativo reale. Dopo aver rimosso la sintassi Markdown (#, *, |, --- e così via), ciò che resta è un testo inferiore ai 200 caratteri.
Il meccanismo con cui questi chunk possono danneggiare la retrieval è sottile ma concreto. La dense retrieval seleziona i chunk più vicini alla domanda nello spazio vettoriale, usando la similarità del coseno. Se un chunk strutturale contiene parole come "Transformer", "Attention" o "Model" — parole che compaiono anche nella query — il modello di embedding può assegnargli un punteggio di similarità alto, anche se il chunk non contiene alcuna evidenza utile per rispondere.
Il risultato è che questi chunk vuoti entrano nella finestra di contesto del modello. Se top_k = 3, e uno dei tre chunk recuperati è uno shallow chunk, il modello riceve meno informazione utile di quanta ne avrebbe ricevuta con tre chunk sostanziosi. Lo shallow chunk ruba letteralmente un posto nella top-k.
Nei dati del benchmark, circa il 25% dei chunk Markdown recuperati (38 su 150) sono shallow. Nella Pipeline A, con testo grezzo, questa percentuale è zero.
I limiti di questa analisi
Questa è una nota empirica preliminare, non una legge generale sui sistemi RAG.
Il benchmark v0.1 è piccolo: dieci documenti, cinquanta domande. Lo scoring è automatico e basato su word-overlap, non su valutazione umana. I modelli e i provider non sono controllati in modo perfetto — il gradiente osservato è confounded, come ho spiegato sopra. I documenti sono paper scientifici noti, e un modello con forte conoscenza parametrica potrebbe rispondere correttamente anche senza usare i chunk recuperati. Le domande negative servono proprio a mitigare questo problema, ma non lo eliminano del tutto.
Non sto dicendo che Markdown sia dannoso in generale. Sto dicendo che, in questo setup specifico, la pipeline Markdown introduce un failure mode misurabile e che questo failure mode merita attenzione.
Prossimo passo: Pipeline C
La domanda vera a cui voglio rispondere con i prossimi esperimenti è: il Markdown peggiora perché è Markdown, o perché il chunker produce chunk superficiali?
Per separare le due ipotesi sto preparando una Pipeline C. Il concetto è semplice: stessa pipeline B (PDF → Markdown → chunk), ma prima di indicizzare filtro i chunk shallow. Per ogni chunk Markdown calcolo una stima del contenuto informativo — la chiamo informative_len — rimuovendo la sintassi e contando i caratteri di testo reale. Se il chunk ha troppo poco contenuto, lo escludo.
keep chunk if
informative_len > threshold
Il threshold di default previsto è 200 caratteri, ma farò una sensitivity analysis con valori da 100 a 300 per capire quanto il risultato dipende dalla soglia.
Se la Pipeline C performa meglio della Pipeline B e si avvicina alla Pipeline A, avremo un'evidenza a favore dell'ipotesi shallow chunks. Se invece il gap rimane, il problema potrebbe stare altrove — nel modo in cui il Markdown altera i confini dei paragrafi, per esempio, o nell'interazione tra markup e embedding model.
Il progetto
Il valore del lavoro non è dire che Markdown è peggio del testo grezzo. Il valore è isolare un possibile failure mode: la creazione di chunk strutturali poveri di contenuto che possono comportarsi come falsi positivi nella dense retrieval.
La v0.1 è una baseline preliminare. Le prossime versioni integreranno la Pipeline C, un bootstrap/permutation test per quantificare l'incertezza statistica e possibilmente un secondo embedding model per verificare se l'effetto shallow chunks dipende dall'embedder scelto.
L'obiettivo di questo progetto non è fare marketing sul RAG. È capire, in modo riproducibile e onesto, quali sono i failure mode pratici dei sistemi RAG locali — quelli che chiunque può incontrare quando prova a costruirne uno sul proprio computer.
Il repository è pubblico, il codice è riproducibile e ogni risultato è tracciabile: github.com/cioffiAI/rag-vs-markdown.