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

Seis estrategias de chunking visualizadas em uma unica passagem

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

  1. 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.
  2. 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?
  3. 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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).