Quando il Markdown peggiora un sistema RAG: shallow chunks come falsi positivi nella dense retrieval
Uno studio completo su 20 PDF scientifici mostra che la conversione in Markdown prima dell'indicizzazione RAG genera shallow structural chunks — frammenti strutturali vuoti che rubano posti nella top-k e degradano la qualità della risposta.
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 locale e riproducibile che è cresciuto fino a includere venti PDF scientifici — tutti paper noti, da "Attention Is All You Need" al survey sulla valutazione degli LLM — come corpus. Settantacinque 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 usa un fuzzy word-overlap con soglia 0.4, validata anche tramite LLM-as-judge.
Quattro configurazioni modello-provider sono state testate: Gemma 3 4B via Ollama Cloud, Nemotron 3 Super Free via OpenCode Zen, DeepSeek V4 Flash via OpenCode Go e Gemma 4 26B via Google Gemini. Il quinto modello della fase preliminare (Qwen 0.8B via LM Studio) è stato sostituito perché troppo lento (~5 minuti per domanda) e con risultati anomali attribuiti a debole instruction-following.
I risultati
Ecco i risultati principali del confronto tra Pipeline A (Raw) e Pipeline B (Markdown). Definisco Δ come la differenza:
Δ = score(Markdown) − score(Raw)
Se Δ > 0, Markdown ha fatto meglio; se Δ < 0, Raw ha fatto meglio.
| Modello | Raw | Markdown | Δ |
|---|---|---|---|
| Gemma 3 4B (Ollama Cloud) | 2.29 | 1.99 | −0.30 (−6.0%) |
| Nemotron 3 Super Free (OpenCode Zen) | — | — | −3.6% |
| DeepSeek V4 Flash (OpenCode Go) | — | — | −4.4% |
| Gemma 4 26B (Google Gemini) | — | — | −5.2% |
Tutti e quattro i modelli preferiscono il testo grezzo. Il dato più importante è l'universalità del pattern: nessun modello, in questa versione del benchmark, trae beneficio dalla conversione in Markdown. Il risultato anomalo di Qwen 0.8B (che nella v0.1 mostrava un +1.2% a favore del Markdown) è stato ricondotto a debole instruction-following del modello più che a una genuina interazione formato-modello.
Inoltre, per ogni modello è stata eseguita anche una Pipeline C (Markdown con filtro shallow chunk), di cui parlo nella sezione dedicata.
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.3% dei chunk Markdown recuperati sono shallow. Nella Pipeline A, con testo grezzo, questa percentuale è zero.
I limiti di questa analisi
Questa è una nota empirica, non una legge generale sui sistemi RAG. I limiti sono esplicitamente documentati nel paper.
Il benchmark è cresciuto ma resta piccolo: venti documenti, settantacinque domande. Lo scoring automatico basato su word-overlap è stato validato tramite LLM-as-judge (Nemotron 3 Super Free, rubric 0-5), che ha mostrato un Δ sistematico da −0.21 a −1.20 rispetto allo scoring fuzzy — il che significa che i punteggi assoluti vanno interpretati con cautela, ma il ranking relativo delle pipeline è stabile. I documenti sono paper scientifici noti, quindi un modello con forte conoscenza parametrica potrebbe rispondere correttamente anche senza usare i chunk recuperati (le domande negative mitigano parzialmente questo problema).
Non sto dicendo che Markdown sia dannoso in generale. Sto dicendo che, in questo setup specifico, la pipeline Markdown introduce un failure mode misurabile, riproducibile e coerente attraverso tutte le fasi sperimentali.
Pipeline C: conferma del meccanismo
La domanda cruciale era: il Markdown peggiora perché è Markdown, o perché il chunker produce chunk superficiali?
Per rispondere ho implementato la Pipeline C: stessa Pipeline B (PDF → Markdown → chunk), ma con un filtro prima dell'indicizzazione. Per ogni chunk Markdown calcolo informative_len — rimuovo la sintassi e conto i caratteri di testo reale. Se il testo informativo è sotto i 200 caratteri, il chunk viene scartato.
| Pipeline | Gemma 3 4B |
|---|---|
| A (Raw) | 2.29 |
| B (Markdown) | 1.99 |
| C (MD filtrato) | 2.16 |
Risultato: la Pipeline C recupera +0.17 rispetto alla B, confermando che il meccanismo causale sono proprio gli shallow chunk. Una sensitivity analysis su soglie da 100 a 300 caratteri conferma T=200 come valore ottimale. Con questa soglia, solo l'1.6% dei chunk Markdown viene filtrato — 9 chunk su ~560 — ma il loro impatto sulla qualità della retrieval è misurabile.
La Pipeline C si avvicina alla Pipeline A ma non la raggiunge. Questo suggerisce che gli shallow chunk non sono l'unica fonte di degrado: anche il modo in cui il Markdown altera i confini dei paragrafi e l'interazione tra markup e modello di embedding giocano un ruolo.
Bootstrap e significatività statistica
Per ogni coppia di pipeline (A vs B, B vs C, A vs C) ho eseguito un bootstrap test con 10.000 resample e un permutation test. Il risultato è onesto: nessun confronto raggiunge p < 0.05 sulle 50 domande condivise. Le differenze osservate sono consistenti nella direzione ma non raggiungono la significatività statistica individuale.
Questo non invalida il risultato — la coerenza del pattern attraverso 4 modelli, 3 pipeline e 2 embedder è un'evidenza più forte di un singolo p-value — ma impone onestà intellettuale: le differenze sono piccole e servono più dati per raggiungere significatività classica.
Robustezza dell'embedder
Per verificare che il fenomeno non dipenda dalla scelta di all-MiniLM-L6-v2, ho replicato l'intero esperimento con BAAI/bge-small-en-v1.5 (stessa dimensionalità 384d). Il Δ medio assoluto tra i due embedder è di soli 0.12 su una scala 0-5.
L'embedder scelto non altera il ranking delle pipeline né le conclusioni dello studio. Il fenomeno shallow chunk è indipendente dal modello di embedding.
Espansione del corpus
Nella fase finale ho espanso il corpus da 10 a 20 documenti e le domande da 50 a 75. L'effetto è interessante: il vantaggio del testo grezzo si attenua, passando da un Δ medio di circa −0.30 a circa −0.13. Con più documenti, il numero di chunk complessivi aumenta e la probabilità che uno shallow chunk finisca nella top-k si riduce — ma il fenomeno non scompare del tutto.
Questo dato è importante perché suggerisce che il failure mode è più pronunciato in corpora piccoli e diventa meno critico (ma non nullo) man mano che il corpus cresce.
Validazione LLM-as-judge
Per superare i limiti dello scoring automatico basato su word-overlap, ho introdotto un giudice LLM (Nemotron 3 Super Free) con una rubric esplicita 0-5. Il confronto ha mostrato uno scostamento sistematico: il LLM judge assegna punteggi da −0.21 a −1.20 inferiori rispetto al fuzzy word-overlap, a seconda della pipeline. Il ranking relativo delle pipeline, però, rimane invariato.
Il progetto
Il valore del lavoro non è dire che Markdown sia peggiore del testo grezzo. Il valore è aver isolato un failure mode specifico — gli shallow structural chunks — e averlo validato attraverso 8 fasi sperimentali: confronto A/B, Pipeline C, bootstrap test, embedder robustness, LLM-as-judge, espansione del corpus e infine paper completo.
La v0.2-final è il rilascio conclusivo. Il paper (16 pagine, 10 tabelle, 4 figure, 3 appendici) è disponibile nel repository. Non è marketing sul RAG. È un tentativo onesto e riproducibile di capire come si comportano i sistemi RAG locali quando si incontrano decisioni di preprocessing che sembrano innocue ma non lo sono.
Il repository è pubblico, il codice è riproducibile e ogni risultato è tracciabile: github.com/cioffiAI/rag-vs-markdown.