🧙 Maestro Yoda Cap. 22 · Agenti, tool use, RAG

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.

un panettone tagliato in 4 modi diversi affiancati. Primo taglio: a fettine sottilissime (tutte uguali, ma pezzettini casuali). Secondo: a quarti enormi (perdi precisione). Terzo: per ingrediente (canditi, uvetta, impasto) — quello “perfetto”. Quarto: a casaccio. Sotto: “stesso panettone, gusto diverso secondo come lo tagli”

Perché il chunking è così critico

Tre motivi strutturali:

  1. 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.
  2. 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.
  3. 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=it o doc_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

PDF

  • 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:

  1. Costruisci un piccolo dataset di (query, risposta_attesa, chunk_id_che_la_contiene).
  2. Indexizza con strategia A.
  3. Lancia retrieval per le query, misura recall@5.
  4. Cambia strategia, re-indexizza, ripeti.
  5. La strategia con recall più alto vince (a parità di costo accettabile).

Esempio realistico (corpus legale italiano, 1,000 chunks, 100 query):

StrategiaChunk sizeRecall@5Index cost
Fixed 5005000.68
Recursive 500/505000.79
Markdown-header500 (variabile)0.86
Semantic~5000.84
Hierarchical (300+1500)300/15000.89
Agenticvariabile0.91100×

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

ToolForzaQuando
LangChain text_splittersModulare, completoDefault per la maggior parte
LlamaIndex NodeParsersIdem, con sintassi più dichiarativaStack LlamaIndex
unstructured.ioParser PDF/HTML/docx ottimoDocumenti eterogenei
Docling (IBM)Best-in-class PDF → MD layout-awarePDF complessi
Marker (Datalab)Alternativa a Docling, GPU-friendlyPDF scientifici
ChonkieLibrary Python dedicata solo a chunkingNeed fine control
markdownify + customPer HTML → MD pulitoWeb 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.