Chunking e Indexação para RAG: Tamanho, Overlap e Recall

Como definir tamanho de chunk, overlap e indexação dual (embedding + tsvector) para maximizar recall em pipelines RAG de produção — com código Python e tradeoffs reais.

Renan Moraes

Chunking e Indexação para RAG: Tamanho, Overlap e Recall

Antes de tunar embeddings ou rerankers, o gargalo silencioso de um pipeline RAG é como você fatia o conhecimento. Chunk grande demais dilui relevância; chunk pequeno demais quebra contexto. Indexação só semântica perde termos exatos. Este artigo cobre chunking consciente de estrutura e indexação dual na ingestão.

Por que chunking domina o recall

O retriever só encontra o que foi indexado. Se a resposta certa está dividida entre dois chunks sem overlap, ou enterrada num bloco de 4.000 tokens com ruído de TOC, o LLM nunca recebe contexto útil. Chip Huyen enfatiza em AI Engineering (p. 118) que retrieval quality — e chunking faz parte disso — limita a performance end-to-end do RAG.

Tamanho de chunk: regra prática

Não existe número mágico universal. Para livros técnicos em inglês com ~500 tokens por parágrafo denso:

Estratégia Chunk size Quando usar
Precisão 800–1200 chars Queries com termos exatos, APIs, siglas
Balanceado 1500–2000 chars Tutoriais, capítulos mistos
Recall amplo 2500–3000 chars Conceitos que cruzam seções

Em produção, 2000 chars com overlap 200 é um ponto de partida sólido para PDFs técnicos — alinhado ao que Unlocking Data with Generative AI and RAG (p. 307) discute sobre preservar unidades semânticas.

Overlap: evite cortes no meio da ideia

Overlap de 10–15% do chunk size reduz perda de contexto nas bordas. Sem overlap, uma definição que termina no chunk N e continua no N+1 pode não rankear nenhum dos dois para a query completa.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=2000,
    chunk_overlap=200,
    separators=["\n## ", "\n### ", "\n\n", "\n", " ", ""],
)
chunks = splitter.split_text(markdown_section)

A ordem dos separadores prioriza quebras em headings Markdown antes de quebrar parágrafos — structure-aware chunking.

Chunking por seção vs. token-blind

Dividir o PDF inteiro em janelas fixas ignora capítulos. Pipeline melhor:

  1. Extrair Markdown estruturado (headings #, ##, ###).
  2. Agrupar por capítulo/seção.
  3. Subdividir seções longas com splitter token-aware.
  4. Anexar metadados: book_title, chapter, page.

Metadados alimentam rerank por capítulo e citações (p. N) no gerador — padrão descrito em LLM Design Patterns (p. 414) para RAG com proveniência.

def build_chunks(pages: list[dict]) -> list[dict]:
    """Split by section; track page number per chunk."""
    chunks = []
    for section in detect_sections(pages):
        for piece in splitter.split_text(section["text"]):
            if len(piece.strip()) < 120:
                continue
            chunks.append({
                "content": piece,
                "chapter": section["heading"],
                "page": section["page"],
            })
    return chunks

Indexação dual na ingestão

Cada chunk precisa de dois índices complementares:

  1. Embedding (E5 multilingual, prefixo passage:) → HNSW para busca semântica.
  2. tsvector (config english para livros EN) → GIN para keyword search.
async def upsert_chunk(session, chunk, embedding):
    tsv = await session.execute(
        text("SELECT to_tsvector('english', :content)"),
        {"content": chunk["content"]},
    )
    row = BookChunk(
        content=chunk["content"],
        chapter=chunk["chapter"],
        page=chunk["page"],
        embedding=embedding,
        tsv=tsv.scalar(),
    )
    session.add(row)

Sem tsvector na ingestão, busca híbrida na query fica impossível — você depende só de paráfrase semântica e perde siglas.

Filtros de ruído antes de indexar

Chunks de TOC, índice remissivo e copyright poluem o índice. Gates heurísticos na ingestão (regex de .... em linhas de TOC, linhas de índice termo, 42) e no runtime (filter_book_noise) evitam que lixo domine o top-k.

Medindo impacto

Golden set com queries técnicas + keywords esperadas. Métricas:

  • Precision@K: keyword aparece nos top-K chunks?
  • Context recall: termos do chunk aparecem no draft gerado?

Altere chunk size em ±500 chars, reingira, compare MRR. Chip Huyen (p. 285) trata indexação e eval de retrieval como ciclo iterativo — não one-shot.

Conclusão

Chunking não é detalhe de ingestão — é decisão de arquitetura. Comece com seções Markdown + 2000/200, indexe embedding e tsvector juntos, filtre ruído, meça recall. Só depois suba para hybrid fusion e rerank.

Referências técnicas

  • Chip Huyen, AI Engineering (p. 118) — retrieval quality e chunking como gargalo de RAG.
  • Chip Huyen, AI Engineering (p. 285) — indexação e avaliação iterativa de retrieval.
  • Unlocking Data with Generative AI and RAG (p. 307) — unidades semânticas e estratégias de chunk.
  • LLM Design Patterns (p. 414) — RAG com metadados de proveniência e rerank.