Chunking: semântico, hierárquico e por estrutura

Chunking é a decisão mais subestimada em projetos RAG. A maioria dos tutoriais usa "divida em chunks de 500 tokens com 50 de overlap" e segue em frente. Mas é aqui que 80% dos problemas de qualidade têm origem. Um chunk ruim gera um embedding ruim, que recupera o trecho errado, que leva o LLM a uma resposta errada ou vazia — e o culpado parece ser o modelo, não a estratégia de chunking.

Por que chunking ingênuo arruína o RAG

Chunking (ou fragmentação) é o processo de dividir documentos em pedaços menores antes de gerar embeddings e indexar no vector store. O objetivo é criar unidades de texto que sejam:

  1. Semanticamente coesas: cada chunk deve tratar de um único tópico ou responder a um tipo de pergunta.
  2. Completas o suficiente: devem conter contexto suficiente para que a resposta faça sentido isoladamente.
  3. Pequenas o suficiente: para não diluir o embedding com múltiplos tópicos não relacionados.

O chunking por tamanho fixo viola os três critérios com frequência. Considere um manual de procedimentos:

# Texto original (simplificado):
"...concluída a verificação de segurança (etapa 2.3), prossiga para a etapa 3.
3. PROCEDIMENTO DE REINICIALIZAÇÃO
3.1 Desligue o equipamento pressionando o botão vermelho por 5 segundos.
3.2 Aguarde 30 segundos antes de religar.
3.3 Ao religar, verifique o painel de status — indicadores verdes confirmam..."

# Chunking fixo de 100 tokens pode cortar assim:
Chunk A: "...concluída a verificação de segurança (etapa 2.3), prossiga para
a etapa 3. 3. PROCEDIMENTO DE REINICIALIZAÇÃO 3.1 Desligue o equipamento
pressionando o botão vermelho por 5 segundos. 3.2 Aguarde 30 segundos"

Chunk B: "antes de religar. 3.3 Ao religar, verifique o painel de status
— indicadores verdes confirmam..."

# Resultado: a pergunta "como verificar se o equipamento reiniciou com sucesso?"
# pode recuperar apenas o Chunk B, que não tem contexto sobre o que veio antes.

As 5 estratégias de chunking: comparação completa

Estratégias de chunking para RAG: parâmetros, indicações e limitações
Estratégia Tamanho típico Overlap recomendado Quando usar Limitações
Fixo (por tokens/caracteres) 256–512 tokens 10–15% do chunk (25–75 tokens) Corpus homogêneo, prototipagem rápida, documentos sem estrutura clara Corta frases e cláusulas ao meio; desconsidera estrutura semântica; ruim para documentos heterogêneos
Por sentença 1–5 sentenças (~100–300 tokens) 1–2 sentenças de overlap Textos narrativos, FAQs simples, artigos de blog, textos corridos sem estrutura hierárquica Sentenças longas ou muito curtas criam chunks desequilibrados; problemas com listas e tabelas
Semântico Variável (~200–800 tokens) Baseado em similaridade, não tamanho fixo Documentos com mudanças de tópico internas; textos de múltiplos assuntos; corpus misto Custo computacional maior (requer modelo de embedding na etapa de chunking); chunks de tamanho muito variável complica indexação uniforme
Hierárquico (parent-child) Pai: 512–2048 tokens / Filho: 128–256 tokens Overlap no nível filho; pai usa fronteiras naturais (seção) Manuais, contratos, documentos técnicos com estrutura de capítulos/seções; quando precisão de busca E contexto rico são necessários Dobra o volume de chunks indexados; mais complexidade no pipeline de retrieval; requer metadado de parentesco
Por estrutura de documento Variável (por unidade lógica: cláusula, seção, item de lista) Contexto do título pai adicionado como prefixo Documentos com estrutura formal definida (contratos, normas, especificações técnicas, documentação de API); Markdown; HTML estruturado Requer parsing específico por tipo de documento; documentos sem estrutura explícita falham; mais trabalho de pré-processamento

Chunking semântico: como funciona na prática

O chunking semântico detecta mudanças de tópico no texto usando embeddings, em vez de cortar por tamanho fixo. O algoritmo básico:

  1. Dividir o texto em sentenças.
  2. Gerar embedding para cada sentença (ou grupo de 3 sentenças — "janela deslizante").
  3. Calcular similaridade coseno entre sentenças adjacentes.
  4. Identificar "pontos de corte" onde a similaridade cai abaixo de um threshold (mudança de tópico).
  5. Criar chunks agrupando sentenças entre os pontos de corte.
# Exemplo conceitual com LangChain SemanticChunker
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings

splitter = SemanticChunker(
    embeddings=OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",  # ou "standard_deviation"
    breakpoint_threshold_amount=95           # percentil 95 de dissimilaridade
)

chunks = splitter.create_documents([texto_completo])
# Resultado: chunks de tamanho variável, cada um tratando de um tópico coeso

O custo extra: gerar embeddings na etapa de chunking (não apenas na ingestão) aumenta o tempo e custo de processamento em ~30%. Para um corpus de 10.000 documentos, isso é geralmente aceitável.

Parent-child chunking: o melhor de dois mundos

O dilema clássico do chunk size: chunks pequenos têm embeddings mais precisos e são melhores para retrieval; chunks grandes fornecem mais contexto ao LLM para gerar respostas completas. Parent-child resolve isso usando ambos:

# Estrutura de parent-child
Documento: "Manual de Segurança — Capítulo 3: Procedimentos de Emergência"

CHUNK PAI (para contexto no LLM, ~1000 tokens):
  "3. Procedimentos de Emergência
   3.1 Em caso de incêndio, acione o alarme...
   3.2 Evacue pelo corredor principal...
   3.3 Ponto de encontro: estacionamento...
   3.4 Nunca use elevadores durante a evacuação..."

CHUNKS FILHOS (para busca, ~100-150 tokens cada):
  Filho 3.1: "Em caso de incêndio, acione o alarme vermelho no corredor..."
  Filho 3.2: "Evacue pelo corredor principal em direção à saída norte..."
  Filho 3.3: "O ponto de encontro para evacuação é o estacionamento..."
  Filho 3.4: "Durante evacuação de emergência, nunca utilize os elevadores..."

# Na query: "onde fica o ponto de encontro de evacuação?"
# → Busca encontra: Filho 3.3 (alta similaridade)
# → Sistema envia ao LLM: CHUNK PAI (contexto completo)
# → LLM responde com contexto rico, não apenas a frase do filho

Metadata-aware chunking: o contexto que o chunk carrega

Além do texto, cada chunk pode carregar metadados contextuais que melhoram tanto o retrieval quanto a geração. Uma técnica poderosa é o "contextual retrieval" (popularizado pela Anthropic): adicionar ao início de cada chunk um resumo de onde ele está no documento.

# Chunk ingênuo:
"O prazo de pagamento é de 30 dias corridos após a emissão da nota fiscal."

# Chunk com prefixo contextual:
"[Contexto: Contrato de Fornecimento n.º 2024-0891, Cláusula 6 — Condições de Pagamento]
O prazo de pagamento é de 30 dias corridos após a emissão da nota fiscal."

O prefixo contextual custa tokens extras (20–50 tokens por chunk), mas melhora significativamente a qualidade do embedding e a precisão do retrieval — especialmente para perguntas que se referem ao documento ou seção pelo nome. Benchmarks da Anthropic mostram melhoria de 49% na taxa de recuperação de chunks relevantes com contextual retrieval.

Chunk size vs. qualidade do retrieval: o trade-off em números

Não existe resposta universal, mas existe uma framework de decisão:

Uma forma prática de calibrar: pegue 20 perguntas típicas do seu domínio, gere a resposta esperada e meça a quantidade de tokens necessária. O chunk size deve ser pelo menos 1,5× essa quantidade para garantir que a resposta esteja completa no chunk.

Chunking por estrutura de documento: o mais poderoso para conteúdo formal

Para documentos com estrutura explícita (contratos, normas, manuais estruturados, Markdown, HTML), o chunking por estrutura usa as fronteiras naturais do documento — títulos, cláusulas, itens de lista — em vez de critérios artificiais de tamanho ou similaridade.

# Para Markdown com estrutura de headers:
from langchain.text_splitter import MarkdownHeaderTextSplitter

headers_para_dividir = [
    ("#", "titulo"),
    ("##", "secao"),
    ("###", "subsecao"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_para_dividir)
chunks = splitter.split_text(texto_markdown)

# Cada chunk contém automaticamente os metadados de hierarquia:
# chunk.metadata = {"titulo": "3. Procedimentos", "secao": "3.2 Evacuação"}

# Para contratos em texto plano, regex por cláusula:
import re
clausulas = re.split(r'\n(?=\d+\.\s+[A-Z])', texto_contrato)
# Divide em cada novo número de cláusula seguido de texto em maiúsculas

Qual estratégia escolher: guia de decisão

Use este fluxo de decisão:

  1. O documento tem estrutura formal (headers, cláusulas, seções numeradas)? → Chunking por estrutura de documento. Se sim e os chunks resultantes são grandes, adicione hierarquia (parent-child).
  2. O corpus é heterogêneo (documentos de tipos muito diferentes misturados)? → Chunking semântico, com estratégia específica por tipo se possível.
  3. As perguntas são simples e o corpus é homogêneo (ex.: FAQ, base de conhecimento)? → Chunking por sentença ou fixo com overlap adequado. Simples e eficiente.
  4. Você precisa de alta precisão de retrieval E respostas detalhadas? → Parent-child obrigatório.
  5. Está prototipando ou em POC? → Fixo com 512 tokens e 10% de overlap. Ajuste depois com métricas reais.

Independente da estratégia, valide com métricas objetivas. Veja como avaliar RAG com faithfulness e context recall para saber se sua estratégia de chunking está funcionando. E entenda como o retrieval usa os chunks em busca híbrida e reranking no RAG.

Para uma visão completa do que é RAG e como o chunking se encaixa no pipeline, veja o guia prático de RAG para empresas.