Puntata 241
Puntata 241 — Chunking strategies: l’arte sottile della frammentazione
Livello: 🧙 Maestro Yoda · Capitolo 22 · Agenti, tool use, RAG
Hai scelto il miglior vector DB, il miglior embedding model, hai pure messo un reranker. Le risposte fanno ancora schifo. 80% delle volte: chunking sbagliato. È il problema più sottovalutato del RAG — e quello che separa pipeline che funzionano da quelle che vanno solo nella demo.
Perché il chunking è così critico
Tre motivi strutturali:
- Granularità del retrieval: il vector DB ti restituisce chunk interi, non frammenti. Se il chunk contiene la risposta + 5 pagine di rumore: rumore nel context.
- Granularità dell’embedding: un chunk troppo lungo (es. 4k token) ha un embedding “medio” che dilui i concetti. Un chunk troppo corto (50 token) perde context.
- Boundary semantici: tagliare a metà una frase, una tabella, un’idea = perdi info critica.
Risultato: stesso modello, stesso DB, stesso reranker → cambiare chunking può cambiare recall del 20-50%.
La domanda di base: quanto grandi?
Trade-off:
- Chunk piccoli (100-300 token): alta precisione, ma serve recuperarne molti per coprire il contesto. Embedding più “focalizzati”.
- Chunk medi (400-800 token): sweet spot per la maggior parte degli use case.
- Chunk grandi (1000-2000 token): meno recall hit, ma embedding più “diluiti”, più rumore nel context.
Regola operativa 2026: inizia con 500 token + 50-100 overlap. Itera sul tuo eval set.
Le 7 strategie principali
1. Fixed-size chunking
Il più semplice: spezza ogni N caratteri o token. Con overlap (es. 10-20%) per evitare di tagliare a metà concetti.
def chunk_fixed(text, size=500, overlap=50):
chunks = []
for i in range(0, len(text), size - overlap):
chunks.append(text[i:i+size])
return chunks
Pro: facile, deterministico. Contro: tagli ovunque, anche a metà frase / tabella / codice. Quando: prototipo veloce, testi omogenei e narrativi.
2. Sentence/paragraph-aware
Spezzi su confini naturali: punto, newline, paragrafo. Aggreghi finché non superi N token.
Tool: RecursiveCharacterTextSplitter (LangChain), SentenceSplitter (LlamaIndex).
Pro: rispetta confini sintattici basilari. Contro: paragrafi enormi diventano chunk enormi; brevi diventano chunk minuscoli. Quando: testi narrativi standard (articoli, manuali).
3. Recursive chunking (default LangChain)
Provi a splittare su separator gerarchici: prima \n\n, poi \n, poi . , poi . Quando il pezzo è ancora troppo grande, scendi al separator successivo.
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(text)
Pro: bilancia size e boundary semantici. Contro: ancora oblivious al significato. Quando: 80% degli use case nel 2026.
4. Document-structure-aware (Markdown, HTML, code)
Sfrutti la struttura del documento: headers H1/H2, sezioni, code blocks, tabelle.
Tool: MarkdownHeaderTextSplitter, HTMLHeaderTextSplitter, PythonCodeTextSplitter.
Pattern: prima split per header (mantenendo il path nel chunk), poi recursive dentro ogni sezione se troppo grande.
Chunk:
> Section: "Articolo 7 — Condizioni del consenso"
> Subsection: "Comma 3"
> Content: "Il titolare deve essere in grado di dimostrare ..."
Pro: chunk hanno context naturale dal header path. Recall significativamente migliore. Contro: dipende dalla qualità del markup originale. Quando: docs strutturati (Markdown, Notion, Confluence, codice).
5. Semantic chunking
Spezzi quando il significato cambia, non quando finisce il paragrafo. Misura embedding similarity tra frasi consecutive: dove c’è un “salto semantico”, chiudi il chunk.
Tool: SemanticChunker (LangChain), SemanticSplitterNodeParser (LlamaIndex).
Pro: chunk semanticamente coerenti, retrieval più preciso. Contro: 5-20× più costoso in indexing (un embedding per frase + analisi). Risultati non sempre dominano recursive ben tunato. Quando: dataset stabili in cui pay one-time costo indexing, e domain non rispetta bene paragrafi (es. trascrizioni, dialoghi).
6. Agentic chunking
Un LLM legge il documento e decide dove tagliare, scrivendo titoli/sommari per ogni chunk.
Pro: massima qualità potenziale. Contro: $$$$ in indexing (LLM call per ogni 2-3k token), 100-1000× più costoso di recursive. Quando: corpus piccolo e ad altissimo valore (es. 100 contratti su cui basi tutta l’azienda).
7. Hierarchical / multi-granularity
Costruisci due (o più) livelli di chunk: piccoli (200 token) per retrieval preciso, grandi (2000 token) per context completo. Al match: recuperi piccoli, includi nel prompt i grandi che li contengono.
Pattern noto: Parent Document Retrieval (LangChain), Auto-Merging Retrieval (LlamaIndex), Recursive Retrieval.
Pro: precision del small + recall del big. Contro: complessità setup, storage 2-3×. Quando: docs lunghi narrativi (libri, report) dove chunk piccoli isolati perdono troppo context.
Cosa includere nel chunk (oltre al testo)
Best practice 2026: ogni chunk ha payload metadata:
{
"text": "Il consenso deve essere libero, specifico, informato...",
"source_doc": "GDPR_completo.pdf",
"page": 23,
"section": "Articolo 7",
"subsection": "Comma 1",
"lang": "it",
"date_added": "2026-01-15",
"doc_type": "normativa",
"version": "consolidated_2024"
}
Perché:
- Filtering: cerchi solo in
lang=itodoc_type=manuale. - Citations: rispondi citando “GDPR, art. 7, comma 1, pag. 23”.
- Versioning: filtri solo l’ultima versione.
- Hierarchical retrieval: trovi il chunk, recuperi il documento padre.
Includi anche, NEL TESTO del chunk, un’intestazione contestuale: “From: GDPR — Articolo 7 — Comma 1 — ”. Migliora embedding quality (il chunk si presenta).
Documenti speciali: gestione dedicata
- Layout-aware extraction: PyMuPDF, pdfplumber, unstructured.io, PyMuPDF4LLM, Marker (Datalab), Docling (IBM).
- I PDF scientifici/legali hanno colonne, footnote, header ripetuti: parser semplici fanno disastri.
- 2026: best in class open è Docling (IBM, OCR + layout + tables + Markdown out).
- A pagamento: AWS Textract, Azure Document Intelligence, Unstructured.io Enterprise.
Tabelle
- NON tagliare a metà. Serializza in Markdown o HTML, mantenila intera in un chunk dedicato.
- Aggiungi un “summary” della tabella nel chunk: “Tabella: vendite 2023-26 per regione”. Aiuta embedding.
Code
- Spezza per funzione/classe, non per linee.
- Include sempre signature + docstring nello stesso chunk.
- Tool:
tree-sitter+RecursiveCharacterTextSplitter.from_language(Language.PYTHON).
Trascrizioni audio
- Spezza per topic / silence detection, non per token fissi.
- Include timestamp nel payload (utile per linking).
Email / chat threads
- Mantieni il thread intero in un chunk se possibile.
- Se lungo: split per turno, ma includi sempre il subject e i 2-3 turni precedenti per context.
Test del tuo chunking
Process:
- Costruisci un piccolo dataset di (query, risposta_attesa, chunk_id_che_la_contiene).
- Indexizza con strategia A.
- Lancia retrieval per le query, misura recall@5.
- Cambia strategia, re-indexizza, ripeti.
- La strategia con recall più alto vince (a parità di costo accettabile).
Esempio realistico (corpus legale italiano, 1,000 chunks, 100 query):
| Strategia | Chunk size | Recall@5 | Index cost |
|---|---|---|---|
| Fixed 500 | 500 | 0.68 | 1× |
| Recursive 500/50 | 500 | 0.79 | 1× |
| Markdown-header | 500 (variabile) | 0.86 | 1× |
| Semantic | ~500 | 0.84 | 8× |
| Hierarchical (300+1500) | 300/1500 | 0.89 | 2× |
| Agentic | variabile | 0.91 | 100× |
Da queste 6: la strategia “Markdown-header” vince in cost/quality. Hierarchical se hai documenti lunghi.
Errori comuni
a) “One size fits all”
Stesso chunking per PDF, HTML, codice, trascrizioni. Sbagliato. Usa parser dedicati per ogni tipo.
b) No overlap
Tagliare nettamente perde context al boundary. 10-20% overlap è il default sano.
c) Chunk troppo piccoli per modelli a contesto lungo
Se hai context 200k, puoi permetterti chunk 1000-2000 token. Non costretto a 300.
d) Skip metadata
Chunk solo testo, niente metadata = niente filter, niente citation, niente versioning. Una metà del valore RAG persa.
e) No re-indexing quando cambi chunking
Cambi strategia ma vecchi vettori restano in DB. Mix di chunk size = retrieval inconsistente.
f) Ignore docs structure
Stai indicizzando Markdown ma usi un text splitter generico. Spreco grosso.
Tools specifici per chunking 2026
| Tool | Forza | Quando |
|---|---|---|
| LangChain text_splitters | Modulare, completo | Default per la maggior parte |
| LlamaIndex NodeParsers | Idem, con sintassi più dichiarativa | Stack LlamaIndex |
| unstructured.io | Parser PDF/HTML/docx ottimo | Documenti eterogenei |
| Docling (IBM) | Best-in-class PDF → MD layout-aware | PDF complessi |
| Marker (Datalab) | Alternativa a Docling, GPU-friendly | PDF scientifici |
| Chonkie | Library Python dedicata solo a chunking | Need fine control |
| markdownify + custom | Per HTML → MD pulito | Web scraping |
Hierarchical: pattern raccomandato 2026 in dettaglio
Per ogni documento:
small_chunks = recursive_split(doc, size=300, overlap=30)
big_chunks = recursive_split(doc, size=2000, overlap=200)
# Salva entrambi, con riferimento parent
for small in small_chunks:
small.parent_id = find_parent(small, big_chunks)
vector_db.insert(small.embed(), payload={...})
for big in big_chunks:
doc_store.insert(big) # non vector, key-value
# Al query time:
hits = vector_db.search(query.embed(), top_k=5)
parent_ids = set(h.parent_id for h in hits)
context = [doc_store.get(pid) for pid in parent_ids]
# Inietta context (i parent grandi) nel prompt, non gli small originali
Risultato: retrieval preciso (small), context completo (big). +10-20% answer quality su long-form questions.
Glossario lampo
- Chunk overlap — sovrapposizione tra chunk consecutivi (10-20% tipico) per non perdere context al boundary.
- Hierarchical chunking — due livelli di chunk: piccoli per retrieval, grandi per context iniettato.
- Semantic chunking — split basato sul “salto semantico” tra frasi (misurato via embedding similarity).
- Agentic chunking — un LLM legge il doc e decide dove tagliare. Massima qualità, massimo costo.
- Document-structure-aware — sfrutta header / sezioni / code blocks naturali del documento sorgente.
Take-away
Il chunking è la “guarnizione del rubinetto” del RAG: piccola, invisibile, ma se è male fatta tutto perde. Inizia con recursive 500/50, passa a markdown-header se i tuoi docs sono strutturati, hierarchical se sono lunghi narrativi. Testa sempre sul tuo eval set. Skipparlo è il modo numero uno per costruire un sistema RAG che “demo bene, prod no”.
➡️ Prossima puntata: MCP — Model Context Protocol, lo standard 2025/2026 per condividere tool tra agenti.