Phase 05 - Lesson 23
Estrategias de Chunking para RAG
A configuracao de chunking influencia a qualidade da recuperacao tanto quanto a escolha do modelo de embedding (Vectara NAACL 2025). Erre o chunking e nenhuma quantidade de reranking vai te salvar.
Tipo: Build Linguagens: Python Pre-requisitos: Fase 5 · 14 (Recuperacao de Informacao), Fase 5 · 22 (Modelos de Embedding) Tempo: ~60 minutos
O Problema
Voce coloca um contrato de 50 paginas em um sistema RAG. O usuario pergunta: "Qual e a clausula de rescisao?" O recuperador retorna a capa. Por que? Porque o modelo foi treinado com chunks de 512 tokens e a clausula de rescisao esta 20 paginas adiante, dividida por uma quebra de pagina, sem palavras-chave locais que a conectem a consulta.
A solucao nao e "comprar um modelo de embedding melhor". A solucao e chunking. De que tamanho? Com sobreposicao? Onde dividir? Com contexto ao redor?
Os benchmarks de fevereiro de 2026 mostram resultados surpreendentes:
- Estudo da Vectara de 2026: o chunking recursivo de 512 tokens superou o chunking semantico, 69% → 54% de acuracia.
- SPLADE + Mistral-8B no Natural Questions: a sobreposicao nao trouxe nenhum beneficio mensuravel.
- Penhasco de contexto: a qualidade da resposta cai bruscamente em torno de 2.500 tokens de contexto.
A resposta "obvia" (chunking semantico, 20% de sobreposicao, 1000 tokens) costuma estar errada. Esta licao constroi intuicao para seis estrategias e diz quando recorrer a cada uma.
O Conceito
Chunking fixo. Divide a cada N caracteres ou tokens. O baseline mais simples. Quebra no meio da frase. Boa compressao, ma coerencia.
Recursivo. O RecursiveCharacterTextSplitter da LangChain. Tenta dividir primeiro em \n\n, depois \n, depois ., depois espaco. Recua de forma limpa. O padrao de 2026.
Semantico. Faz o embedding de cada frase. Calcula a similaridade de cosseno entre frases adjacentes. Divide onde a similaridade cai abaixo de um limiar. Preserva a coerencia de topico. Mais lento; as vezes produz fragmentos minusculos de 40 tokens que prejudicam a recuperacao.
Por frase. Divide nos limites de frase. Uma frase por chunk ou uma janela de N frases. Iguala o chunking semantico ate ~5k tokens por uma fracao do custo.
Parent-document (documento-pai). Armazena pequenos chunks-filho para recuperacao e o chunk-pai maior para contexto. Recupera pelo filho; retorna o pai. Degrada de forma elegante: chunks-filho ruins ainda retornam pais razoaveis.
Late chunking (2024). Faz o embedding do documento inteiro no nivel de token primeiro, depois agrupa (pooling) os embeddings de token em embeddings de chunk. Preserva o contexto entre chunks. Funciona com embedders de contexto longo (BGE-M3, Jina v3). Maior custo computacional.
Recuperacao contextual (Anthropic, 2024). Antepoe a cada chunk um resumo gerado por LLM de sua posicao no documento ("Este chunk e a secao 3.2 das clausulas de rescisao..."). Melhora de 35-50% na recuperacao no benchmark da propria Anthropic. Caro para indexar.
A regra que supera todos os padroes
Ajuste o tamanho do chunk ao tipo de consulta:
| Tipo de consulta | Tamanho do chunk |
|---|---|
| Factual ("qual e o nome do CEO?") | 256-512 tokens |
| Analitica / multi-hop | 512-1024 tokens |
| Compreensao de secao inteira | 1024-2048 tokens |
Benchmark da NVIDIA de 2026. O chunk deve ser grande o suficiente para conter a resposta mais o contexto local, e pequeno o suficiente para que os top-K retornados pelo recuperador foquem na resposta em vez do ruido de contexto.
Construa
Passo 1: chunking fixo e recursivo
def chunk_fixed(text, size=512, overlap=0):
step = size - overlap
return [text[i:i + size] for i in range(0, len(text), step)]
def chunk_recursive(text, size=512, seps=("\n\n", "\n", ". ", " ")):
if len(text) <= size:
return [text]
for sep in seps:
if sep not in text:
continue
parts = text.split(sep)
chunks = []
buf = ""
for p in parts:
if len(p) > size:
if buf:
chunks.append(buf)
buf = ""
chunks.extend(chunk_recursive(p, size=size, seps=seps[1:] or (" ",)))
continue
candidate = buf + sep + p if buf else p
if len(candidate) <= size:
buf = candidate
else:
if buf:
chunks.append(buf)
buf = p
if buf:
chunks.append(buf)
return [c for c in chunks if c.strip()]
return chunk_fixed(text, size)
Passo 2: chunking semantico
def chunk_semantic(text, encoder, threshold=0.6, min_chars=200, max_chars=2048):
sentences = split_sentences(text)
if not sentences:
return []
embs = encoder.encode(sentences, normalize_embeddings=True)
chunks = [[sentences[0]]]
for i in range(1, len(sentences)):
sim = float(embs[i] @ embs[i - 1])
current_len = sum(len(s) for s in chunks[-1])
if sim < threshold and current_len >= min_chars:
chunks.append([sentences[i]])
else:
chunks[-1].append(sentences[i])
result = []
for group in chunks:
text_group = " ".join(group)
if len(text_group) > max_chars:
result.extend(chunk_recursive(text_group, size=max_chars))
else:
result.append(text_group)
return result
Ajuste o threshold ao seu dominio. Alto demais → fragmentos. Baixo demais → um unico chunk gigante.
Passo 3: parent-document
def chunk_parent_child(text, parent_size=2048, child_size=256):
parents = chunk_recursive(text, size=parent_size)
mapping = []
for p_idx, parent in enumerate(parents):
children = chunk_recursive(parent, size=child_size)
for child in children:
mapping.append({"child": child, "parent_idx": p_idx, "parent": parent})
return mapping
def retrieve_parent(child_query, mapping, encoder, top_k=3):
child_embs = encoder.encode([m["child"] for m in mapping], normalize_embeddings=True)
q_emb = encoder.encode([child_query], normalize_embeddings=True)[0]
scores = child_embs @ q_emb
top = np.argsort(-scores)[:top_k]
seen, parents = set(), []
for i in top:
if mapping[i]["parent_idx"] not in seen:
parents.append(mapping[i]["parent"])
seen.add(mapping[i]["parent_idx"])
return parents
Insight chave: deduplique os pais. Multiplos filhos podem mapear para o mesmo pai; retornar todos desperdicaria contexto.
Passo 4: recuperacao contextual (padrao Anthropic)
def contextualize_chunks(document, chunks, llm):
context_prompts = [
f"""<document>{document}</document>
Here is the chunk to situate: <chunk>{c}</chunk>
Write 50-100 words placing this chunk in the document's context."""
for c in chunks
]
contexts = llm.batch(context_prompts)
return [f"{ctx}\n\n{c}" for ctx, c in zip(contexts, chunks)]
Indexe os chunks contextualizados. No momento da consulta, a recuperacao se beneficia do sinal extra ao redor.
Passo 5: avalie
def recall_at_k(queries, corpus_chunks, encoder, k=5):
chunk_embs = encoder.encode(corpus_chunks, normalize_embeddings=True)
hits = 0
for q_text, gold_idxs in queries:
q_emb = encoder.encode([q_text], normalize_embeddings=True)[0]
top = np.argsort(-(chunk_embs @ q_emb))[:k]
if any(i in gold_idxs for i in top):
hits += 1
return hits / len(queries)
Sempre faca benchmark. A "melhor" estrategia para o seu corpus pode nao corresponder a nenhum post de blog.
Armadilhas
- Chunking avaliado apenas em consultas factuais. Consultas multi-hop revelam vencedores bem diferentes. Use um conjunto de avaliacao estratificado por tipo de consulta.
- Chunking semantico sem tamanho minimo. Produz fragmentos de 40 tokens que prejudicam a recuperacao. Sempre imponha
min_tokens. - Sobreposicao como culto cargo. Estudos de 2026 mostram que a sobreposicao muitas vezes nao traz beneficio e dobra o custo do indice. Meca, nao assuma.
- Sem imposicao de min/max. Chunks de 5 tokens ou de 5000 tokens, ambos quebram a recuperacao. Limite (clamp).
- Chunking entre documentos. Nunca deixe um chunk abranger dois documentos. Sempre faca o chunk por documento e depois mescle.
Use
A stack de 2026:
| Situacao | Estrategia |
|---|---|
| Primeira construcao, corpus desconhecido | Recursivo, 512 tokens, sem sobreposicao |
| QA factual | Recursivo, 256-512 tokens |
| Analitica / multi-hop | Recursivo, 512-1024 tokens + parent-document |
| Muita referencia cruzada (contratos, artigos) | Late chunking ou recuperacao contextual |
| Corpus conversacional / de dialogo | Chunks por turno + metadados do interlocutor |
| Enunciados curtos (tweets, avaliacoes) | Um documento = um chunk |
Comece com recursivo de 512. Meca recall@5 em um conjunto de avaliacao de 50 consultas. Ajuste a partir dai.
Entregue
Salve como outputs/skill-chunker.md:
---
name: chunker
description: Pick a chunking strategy, size, and overlap for a given corpus and query distribution.
version: 1.0.0
phase: 5
lesson: 23
tags: [nlp, rag, chunking]
---
Given a corpus (document types, avg length, domain) and query distribution (factoid / analytical / multi-hop), output:
1. Strategy. Recursive / sentence / semantic / parent-document / late / contextual. Reason.
2. Chunk size. Token count. Reason tied to query type.
3. Overlap. Default 0; justify if >0.
4. Min/max enforcement. `min_tokens`, `max_tokens` guards.
5. Evaluation plan. Recall@5 on 50-query stratified eval set (factoid, analytical, multi-hop).
Refuse any chunking strategy without min/max chunk size enforcement. Refuse overlap above 20% without an ablation showing it helps. Flag semantic chunking recommendations without a min-token floor.
Exercicios
- Facil. Faca o chunk de um documento de 20 paginas com fixed(512, 0), recursive(512, 0) e recursive(512, 100). Compare a contagem de chunks e a qualidade das fronteiras.
- Medio. Construa um conjunto de avaliacao de 30 consultas sobre 5 documentos. Meca recall@5 para recursivo, semantico e parent-document. Qual vence? Corresponde aos posts de blog?
- Dificil. Implemente a recuperacao contextual. Meca a melhoria de MRR em relacao ao recursivo baseline. Reporte o custo de indexacao (chamadas de LLM) versus o ganho de acuracia.
Termos-chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Chunk | Um pedaco de um documento | Unidade de subdocumento que recebe embedding, e indexada e recuperada. |
| Sobreposicao (overlap) | Margem de seguranca | N tokens compartilhados entre chunks adjacentes; muitas vezes inutil nos benchmarks de 2026. |
| Chunking semantico | Chunking inteligente | Divide onde a similaridade de embedding entre frases adjacentes cai. |
| Parent-document | Recuperacao em dois niveis | Recupera filhos pequenos, retorna pais maiores. |
| Late chunking | Chunk depois do embedding | Faz o embedding do documento inteiro no nivel de token e agrupa em vetores de chunk. |
| Recuperacao contextual | O truque da Anthropic | Resumo gerado por LLM anteposto a cada chunk antes da indexacao. |
| Penhasco de contexto | A barreira dos 2500 tokens | Queda de qualidade observada em torno de 2,5k tokens de contexto em RAG (jan 2026). |
Leituras Adicionais
- Yepes et al. / LangChain — Recursive Character Splitting docs — o padrao em producao.
- Vectara (2024, NAACL 2025). Chunking configurations analysis — o chunking importa tanto quanto a escolha do embedding.
- Jina AI — Late Chunking in Long-Context Embedding Models (2024) — o artigo sobre late chunking.
- Anthropic — Contextual Retrieval — melhora de 35-50% na recuperacao com prefixos de contexto gerados por LLM.
- NVIDIA 2026 chunk-size benchmark — Premai summary — tamanho do chunk por tipo de consulta.