Estratégias de Chunking no Comparativo de RAG: Alternativas, Compromissos e Exemplos
Comparação de Estratégias de Chunking no RAG
Chunking é o hiperparâmetro mais subestimado em Geração Aumentada por Recuperação (RAG): ele determina silenciosamente o que o seu LLM “vê”, quão cara se torna a ingestão, e quanta parte da janela de contexto do LLM você consome por resposta.
Este artigo trata o chunking como um problema de otimização de engenharia: defina objetivos, escolha uma estratégia, meça e, depois, itere.
Se você é novo na arquitetura RAG, comece com o principal Tutorial de Geração Aumentada por Recuperação (RAG): Arquitetura, Implementação e Guia de Produção.

TL;DR (Resumo executivo)
Sistemas RAG recuperam chunks, não documentos. Portanto, o chunking define a unidade de recuperação, a unidade de custo de embedding e a unidade de evidência que você pode mostrar ou citar. Na formulação original de RAG, a recuperação fornece passagens à geração; as fronteiras das passagens são efetivamente as fronteiras dos chunks.
Uma boa estratégia de chunking busca uma fronteira de Pareto entre: qualidade de recuperação (recall/precision da evidência), coerência (os chunks devem ser interpretáveis) e custo (embedding, armazenamento e latência de consulta). Não existe um tamanho ou método de chunk globalmente ótimo, e sistemas de produção costumam misturar estratégias (por exemplo, chunking estruturado para PDFs + divisões semânticas para prosa + chunking AST para código).
Para a maioria das perguntas sobre documentação e bases de conhecimento internas, um padrão seguro é um separador recursivo que respeita a estrutura com sobreposição modesta (para reduzir a perda de fronteira), apoiado por um
armazenamento vetorial
com filtragem de metadados e reclassificação opcional. O RecursiveCharacterTextSplitter da LangChain é uma implementação comum dessa ideia de separador hierárquico; a sobreposição existe especificamente para mitigar a perda de informação quando o contexto relevante é cortado nas fronteiras.
Quando os documentos têm uma estrutura forte (PDFs com títulos, tabelas, listas, legendas), o chunking baseado em elementos / estrutura-aware pode superar a divisão por contagem de tokens, produzindo menos chunks. Um estudo de 2024 sobre documentos da SEC encontrou que o chunking baseado em tipo de elemento melhorou os resultados de RAG e também reduziu o número de chunks (e, portanto, vetores) aproximadamente pela metade em comparação com métodos indiferentes à estrutura — reduzindo o custo de indexação e potencialmente melhorando a latência de consulta.
Se você puder pagar mais custo computacional inicial, o chunking semântico (dividir em mudanças de tópico usando similaridade de embedding) pode melhorar significativamente a fidelidade de recuperação para textos narrativos e páginas com tópicos mistos. Algoritmos mais antigos de segmentação de tópicos, como TextTiling, mostram o princípio geral: mudanças fortes no vocabulário/semântica são bons candidatos a fronteiras.
Para materiais muito longos, internamente referenciados (políticas, RFCs, padrões, manuais grandes), o chunking hierárquico + recuperação/merge hierárquico (nós pais/nós filhos) pode recuperar contexto contíguo maior sob demanda. O analisador de nós hierárquico do LlamaIndex produz hierarquias de chunk de grosso para fino, e o AutoMergingRetriever pode mesclar nós folha em nós pais no momento da recuperação quando suficientes filhos relacionados forem recuperados.
Objetivos do chunking e trade-offs
O chunking não é apenas “dividir o texto para que ele caiba em um modelo de embedding”. Ele controla vários comportamentos downstream e operacionais.
Granularidade de recuperação vs. ruído de recuperação. Chunks menores aumentam a chance de que a frase exata contendo uma resposta seja recuperável (maior potencial de recall com top-k fixo). Mas também produzem mais vetores, aumentando o tamanho do índice e, às vezes, apresentando “matches próximos” que são semanticamente similares, mas não realmente evidenciais (menor precisão). Recuperadores densos como DPR foram construídos ao redor da recuperação eficaz de passagens para QA, destacando que as fronteiras das passagens importam para o desempenho de QA end-to-end.
Coerência do contexto vs. perda de fronteira. Chunks coerentes ajudam o LLM a raciocinar corretamente e reduzem a geração de alucinações ao fornecer contexto local completo (definições, restrições, pré-requisitos). A sobreposição reduz a perda de fronteira, mas cria texto duplicado, que pode levar a resultados de recuperação redundantes e ao alongamento do prompt se você não deduplicar/merge.
Custo de embedding e indexação. O custo de embedding é normalmente proporcional aos tokens embutidos, e o tempo de ingestão escala com o número de chunks (mais a sobrecarga de escrita do banco de vetores). Para embeddings da OpenAI, as solicitações têm um limite máximo de token por entrada (8192 tokens para todos os modelos de embedding) e um limite máximo de tokens somados por entrada por solicitação (300.000 tokens). Para grandes corporas, a API em lote pode reduzir os custos em cerca de 50% com um tempo de turnaround assíncrono de 24 horas — útil para preenchimento de dados e reindexação periódica.
Tamanho do índice vetorial, RAM e latência. Mais chunks significa mais vetores e potencialmente mais memória e consultas mais lentas (dependendo do tipo de índice). O FAISS explicitamente enquadra o design do índice como um conjunto de trade-offs entre tempo de busca, qualidade de busca e memória por vetor indexado; também oferece implementações de GPU para busca exata e aproximada rápida.
Comprimento do prompt downstream / uso da janela de contexto do LLM. A saída do recuperador torna-se orçamento de prompt. Uma estratégia de chunking que consistentemente recupera “apenas o suficiente” de contexto pode melhorar a qualidade da resposta e reduzir o custo. Por outro lado, sobreposição e chunks muito grandes inflamam o comprimento do prompt. Na prática, você geralmente ajusta: (tamanho do chunk, sobreposição, top-k, reclassificação/merge) juntos.
Custo de atualização/ingestão e deduplicação. O chunking afeta quanto custa atualizar dados. Chunks menores tornam atualizações parciais mais baratas (você pode re-embed apenas a seção alterada), mas também tornam a deduplicação mais difícil se chunks sobrepostos ou quase duplicados proliferarem.
Onde o chunking se encaixa no fluxo de trabalho RAG

Estratégias de chunking e alternativas
Abaixo estão as principais famílias de chunking que você encontrará em RAG moderno. Na prática, você frequentemente combina duas: chunking estruturado primeiro (respeitar limites de documento) mais enforcement de orçamento de token (garantir que chunks se encaixem nos orçamentos de embedding e prompt).
Chunking de tamanho fixo
O que é. Dividir o texto em blocos de tamanho igual por caracteres ou tokens.
Por que existe. É simples, rápido, previsível e fácil de paralelizar. É também a estratégia mais fácil para ingestão de streaming onde você não tem o contexto completo do documento.
Onde falha. Ignora limites (frases, seções, blocos de código), então pode quebrar definições ou dividir “pares pergunta/resposta” entre chunks, aumentando o erro de recuperação.
Perfil operacional. Complexidade de ingestão mais baixa; contagem de chunk previsível; mais fácil de armazenar em cache. Mas você normalmente precisa de sobreposição (abaixo) para evitar perda de fronteira.
Chunking com sobreposição
O que é. Qualquer estratégia onde chunks consecutivos compartilham uma região de sobreposição fixa (por exemplo, 10–20% do tamanho do chunk). A sobreposição é padrão em muitos frameworks porque reduz a perda de informação quando o contexto é dividido.
Por que importa. A sobreposição é efetivamente uma “fronteira suave” — permite que a recuperação capture um fato que atravessa uma fronteira.
Custos e armadilhas. Mais tokens embutidos; mais texto duplicado no índice; maior risco de recuperar múltiplos chunks quase idênticos a menos que você deduplique no momento da recuperação (por exemplo, mesclar por deslocamentos de origem ou usar MMR).
Chunking baseado em frase e parágrafo
O que é. Dividir o texto em limites de frase ou parágrafo, depois empacotar frases/parágrafos em chunks até um orçamento de token.
Por que engenheiros gostam. Melhora a coerência para linguagem natural e é robusto para documentos com pontuação e espaçamento convencionais.
Ferramentas. O sent_tokenize() do NLTK usa por padrão a detecção de limites de frase Punkt, e o spaCy oferece ferramentas baseadas em regras para limites de frase, como Sentencizer (útil quando você quer divisões de frase sem um modelo de dependência completo).
Modos de falha. Pontuação não convencional (registros, transcrições de chat), tabelas, código e listas de itens podem quebrar as suposições de segmentação de frase.
Chunking com janela deslizante
O que é. Criar chunks usando um tamanho de janela fixo e um passo (passo). É a versão “sobreposição sistemática” do chunking.
Quando é bom. Texto de séries temporais, transcrições, logs de chat, minutos de reuniões — qualquer coisa onde fatos relevantes podem aparecer em vizinhanças locais e você deseja recall robusto.
Quando é ruim. Amplifica a redundância e pode ser caro em escala. Também tende a recuperar janelas redundantes a menos que sejam deduplicadas.
Chunking recursivo / hierárquico de separador
O que é. Comece com grandes “separadores naturais” (por exemplo, \n\n para parágrafos) e divida recursivamente em unidades menores (frases, espaços) apenas quando necessário para ficar abaixo de um orçamento de tamanho. O LangChain documenta esse comportamento explicitamente: um separador recursivo tenta manter unidades maiores intactas e só recorre a separadores menores quando uma unidade ainda excede o tamanho do chunk.
Por que é um bom padrão. Respeita a estrutura sem exigir análise complexa de documentos. É um ponto de equilíbrio prático para Markdown, HTML como texto e documentação.
Parâmetros principais de ajuste. chunk_size, chunk_overlap, e a length_function (caracteres vs tokens), mais separadores personalizados para bases de código multilíngues.
Chunking semântico (consciente de embedding)
O que é. Detectar mudanças de tópico usando representações semânticas (embeddings) e dividir onde a similaridade cai. Isso espelha ideias clássicas de segmentação como TextTiling, que usa mudanças na coesão lexical para encontrar limites de subtópicos.
Por que pode superar chunking baseado em tamanho. Você para de dividir em contagens de token arbitrárias e alinha chunks com limites de tópico — frequentemente melhorando a precisão de recuperação para documentos multitópicos (blogs, documentos de design, tickets, relatórios de incidentes).
Custos. Pode ser necessário embeddings adicionais durante o chunking (embeddings de nível de frase ou parágrafo) antes dos embeddings finais do chunk. Isso pode dobrar ou triplicar os chamados de embedding a menos que você reutilize embeddings intermediários.
Truque prático. “Empacotamento semântico consciente”: calcule embeddings de frase uma vez, agrupe frases em segmentos coerentes com tópico, depois embute cada segmento final.
Chunking hierárquico (pai/filho)
O que é. Construa uma representação de múltiplas granularidades: chunks grossos (por exemplo, tamanho de seção) com chunks mais finos (por exemplo, tamanho de parágrafo). O parsing hierárquico do LlamaIndex produz hierarquias “grossas para finas” por padrão (por exemplo, escalas de 2048 → 512 → 128 tokens), e o AutoMergingRetriever pode mesclar nós filhos em pais no momento da recuperação quando suficientes filhos relacionados forem recuperados.
Por que ajuda. Evita escolher entre “chunks pequenos para recall” e “chunks grandes para coerência” armazenando ambos e selecionando no momento da consulta.
Custos. Lógica de ingestão e recuperação mais complexa, mais armazenamento potencialmente (porque você armazena múltiplas granularidades).
Chunking adaptativo / baseado em LLM
O que é. Use um LLM para decidir as fronteiras do chunk (e opcionalmente gerar resumos ou cabeçalhos contextuais). O Weaviate descreve explicitamente o chunking baseado em LLM como ter o LLM criar chunks semânticamente coerentes, em vez de depender de regras fixas ou similaridade de embedding.
Quando vale a pena. Corporas de alto valor onde a precisão domina o custo (jurídico, conformidade, runbooks de suporte), e onde os documentos são desordenados, heterogêneos e mal segmentados.
Riscos. Custo, latência e não determinismo. Você vai querer cache, decodificação determinística e testes de regressão (veja seção de avaliação).
Chunking baseado em estrutura e elemento (documentos não são apenas texto plano)
O que é. Parse o documento em elementos (títulos, parágrafos, listas, tabelas, legendas) usando uma camada de compreensão de documento, depois chunk usando esses elementos. As funções de chunking do Unstructured usam explicitamente metadados e elementos de documento (produzidos por particionamento) para produzir chunks para RAG. O HierarchicalChunker do Docling cria chunks por elemento detectado de documento e anexa metadados estruturais como títulos/legendas.
Evidência de trabalhos recentes. Um estudo de 2024 sobre documentos da SEC argumenta que o chunking apenas por parágrafos negligencia a estrutura do documento e propõe chunking por elementos estruturais; ele relata resultados melhorados de RAG e menos chunks/vetores do que abordagens indiferentes à estrutura.
Por que importa para multimodal. Tabelas, figuras e legendas frequentemente contêm a verdade factual. “Aplanar” os mesmos em texto plano pode destruir sinais que a recuperação de outra forma exploraria.
Chunking consciente de código (AST/estrutura)
O que é. Chunk de código por unidades sintáticas (funções, classes, módulos), opcionalmente incluindo docstrings e comentários.
Por que importa. Divisões de tamanho fixo de token tendem a cortar funções pela metade e separar docstrings da implementação — ruim para busca de código e casos de uso RAG “explique esta função”.
Opções de implementação. Para Python, o módulo embutido ast é frequentemente suficiente. Para repositórios multilíngues, chunkers baseados em tree-sitter são comuns.
Dimensões de avaliação e como comparar estratégias de chunking
O chunking deve ser benchmarkado como um componente do sistema.
Métricas de qualidade de recuperação
Use métricas padrão de IR para a camada de recuperação:
- Recall@k / Precision@k: A top-k contém a evidência dourada?
- MRR / nDCG: A evidência dourada tem ranque alto?
O BEIR é um benchmark heterogêneo amplamente usado para avaliação de IR em tarefas e domínios, e destaca trade-offs entre abordagens esparsas, densas, late-interaction e reclassificação.
O chunking afeta essas métricas porque define o que conta como “um item recuperado relevante”.
Métricas de qualidade de resposta de RAG end-to-end
Se você estiver construindo QA ou assistentes, métricas de recuperação são necessárias, mas não suficientes. Você também precisa de:
- Recall / precision de contexto: se os contextos recuperados contêm evidência relevante e evitam o ruído.
- Fidelidade: se a resposta gerada é suportada pelo contexto recuperado.
O RAGAS fornece definições concretas e implementações para “fidelidade” e outras métricas orientadas a RAG.
Dimensões de custo e desempenho do sistema
O chunking muda esses levers:
Latência (p50/p95). A latência da consulta normalmente aumenta com mais vetores e mais pós-processamento. Seu índice vetorial também importa: os tipos de índice FAISS trocam tempo de busca, qualidade, memória e tempo de treinamento/adicionar.[^faiss]
Custo e throughput de embedding. Embeddings da OpenAI são cobrados por token; a API de embedding tem limites explícitos por entrada e por solicitação.[^openai_embed_create] Para ingestão offline, a API em lote reduz o custo e oferece maior quota em troca de turnaround não em tempo real.[^openai_batch]
Tamanho do índice e memória. Aproximadamente, armazenar N vetores float32 de dimensão d custa ~4 * N * d bytes apenas para os vetores brutos (mais metadados + sobrecarga do índice). O chunking impacta N. A dimensionalidade de embedding impacta d, e a API de embedding da OpenAI permite controlar a dimensionalidade de saída via o parâmetro dimensions.[^openai_embed_create]
Orçamento de prompt do LLM. Chunks maiores e sobreposição inflamam os tokens do prompt. Isso pode aumentar a latência e o custo, e aumentar modos de falha “perdido no meio” onde os modelos prestam menos atenção a algum contexto. Na prática você frequentemente:
- recupera chunks pequenos,
- mescla/deduplica,
- opcionalmente resumir,
- envia um conjunto compacto de evidências para o LLM.
Custo de atualização/ingestão. Chunks menores permitem re-embedding parcial, mas aumentam a contabilidade. Para ingestão de streaming, prefira chunking determinístico, incremental (fixo ou janela deslizante) e anexe IDs estáveis (document_id, intervalos de deslocamento, hash).
Design experimental: um ciclo de benchmark prático
Um benchmark de chunking reprodutível tipicamente tem:
- Um instantâneo fixo do corpus + um conjunto fixo de consultas com evidência dourada (ou ao menos spans de resposta esperados).
- Um modelo de embedding fixo e uma configuração fixa de índice vetorial.
- Uma avaliação “apenas de recuperação” (recall@k, nDCG) mais uma avaliação “RAG” (fidelidade, relevância da resposta).
- Telemetria de custo: #chunks, tokens embutidos, $/mês de armazenamento, latência p95 de consulta, tokens de prompt.
O artigo Unstructured SEC-filings é um bom exemplo de avaliar estratégias de chunking com métricas orientadas à recuperação e medidas de precisão de QA.
Diretrizes práticas, matriz de decisão e recomendações padrão
Recomendações padrão que funcionam surpreendentemente bem
Se você precisar de uma estratégia robusta “dia 1” para QA de documentação geral:
- Parse levemente: preserve títulos e metadados básicos (fonte, título da seção, URL/caminho, timestamp).
- Chunk com um separador recursivo (parágrafo → frase → palavra), com sobreposição modesta.
- Embute com um modelo de embedding forte geral.
- Indice com metadados (ID do documento, seção, ACLs) e deduplique durante a recuperação.
- Adicione reclassificação ou merge hierárquico apenas se sua avaliação mostrar um gap.
Isso se alinha com como os frameworks comuns de RAG descrevem sobreposição de chunk e divisão respeitando a estrutura.
Qual método de chunking usar - Matriz de decisão

| Caso de uso | Recomendação padrão de chunking | Parâmetros principais para ajustar | Modo de falha comum | Caminho de upgrade |
|---|---|---|---|---|
| QA de curto prazo sobre documentos (FAQs, wiki interno) | Chunking recursivo/separador + sobreposição | chunk_size, sobreposição, top-k |
Falta de evidência trans-frase nas fronteiras | Adicione chunking semântico ou reclassificador |
| QA de longo prazo (políticas, padrões, manuais) | Chunking hierárquico + recuperador de merge | tamanho de pai/filho, limiar de merge | Recupera fragmentos pequenos; LLM falta contexto completo | Merge automático/recuperação hierárquica |
| Resumo (por documento / por seção) | Chunking estruturado (seções) | detecção de seção, max tokens | Resumos perdem links inter-seções | Resumo hierárquico + gráfico de seção |
| Busca de código & “explique esta função” | Chunking de nível AST/função | incluir docstring/comentários, max tokens | Função dividida; perde assinatura/usagem | Hierarquia consciente do repositório (módulo→classe→função) |
| PDFs multimodais (tabelas/figuras) | Chunking baseado em elementos (consciente de título/tabela/legenda) | serialização de tabela, mesclagem de legenda | Conteúdo da tabela perdido ou corrompido | Use Docling/Unstructured + serializadores estruturados |
| Ingestão de streaming (registros, chats, tickets) | Janela deslizante ou tokens de tamanho fixo | janela, passo, dedup | Recuperação excessiva de janelas redundantes | Adicione detecção de fronteira semântica em lotes |
chunking - Comparação qualitativa de desempenho
Trate isso como “direção esperada de mudança” (valide com seus próprios dados).
| Estratégia | Potencial de precisão de recuperação | Coerência do contexto recuperado | Complexidade de ingestão | Contagem de vetores / tamanho do índice | Custo de embedding | Impacto na latência de consulta | Melhor para |
|---|---|---|---|---|---|---|---|
| Tamanho fixo (sem sobreposição) | Médio | Baixo | Baixo | Médio | Baixo | Médio | protótipos rápidos, texto homogêneo |
| Tamanho fixo + sobreposição | Médio–Alto | Médio | Baixo | Alto | Médio–Alto | Médio–Alto | QA onde perda de fronteira prejudica |
| Empacotamento de frase/parágrafo | Alto (prosa) | Alto | Médio | Médio | Médio | Médio | documentos, artigos, prosa limpa |
| Janela deslizante | Alto recall | Médio | Médio | Muito alto | Muito alto | Alto | transcrições, logs, chat |
| Recursivo/separador | Alto | Alto | Médio | Médio | Médio | Médio | “padrão” RAG de documentos |
| Chunking semântico | Alto–Muito alto | Alto | Alto | Médio | Alto | Médio | páginas multitémicas, texto narrativo |
| Hierárquico (pai/filho) | Muito alto | Muito alto | Alto | Alto | Alto | Médio | manuais longos / padrões |
| Baseado em LLM/Adaptativo | Muito alto | Muito alto | Muito alto | Médio | Muito alto | Médio–Alto | corporas de alto risco |
| Baseado em elemento/estrutura | Alto–Muito alto | Alto | Alto | Baixo–Médio | Médio | Médio | PDFs, relatórios, tabelas, layouts mistos |
| Consciente de código (AST) | Alto (código) | Alto | Médio | Médio | Médio | Médio | busca de código, assistentes de repositórios |
Notas de DevOps e hardware (frequentemente ignoradas)
Escolhas de chunking afetam quanta infraestrutura você precisa:
- Chunks menores → mais vetores → índices maiores e mais RAM/disco. Para FAISS auto-hospedado, isso pode forçar sharding ou índices em disco.
- Se você embute localmente, a throughput de embedding torna-se um problema de agendamento de GPU; se embute via API, o volume de token torna-se um problema de FinOps (API em lote é seu amigo para preenchimentos).
- Alguns motores (FAISS) oferecem busca acelerada por GPU; isso pode transferir custo de CPU limitada por RAM para memória de GPU e throughput de PCIe.
- Parsing estruturado (layout de PDF, OCR, extração de tabelas) é frequentemente limitado por CPU e pode superar o custo de embedding para documentos escaneados; orçamento separadamente.
Chunking - Implementações de referência em Python
Todos os exemplos foram projetados para serem legíveis e executáveis. Se você precisar de uma chave de API ou de um banco de dados em execução, isso está claro no código.
Utilitários compartilhados: contagem de tokens e IDs de chunk estáveis
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Any, Iterable, Optional
from transformers import AutoTokenizer # pip install transformers
@dataclass(frozen=True)
class Chunk:
text: str
meta: dict[str, Any]
def sha1_id(*parts: str) -> str:
h = hashlib.sha1()
for p in parts:
h.update(p.encode("utf-8"))
h.update(b"\x1e")
return h.hexdigest()
# Use qualquer tokenizer que corresponda aproximadamente à tokenização do seu LLM/embedding.
TOKENIZER = AutoTokenizer.from_pretrained("bert-base-uncased")
def token_len(text: str) -> int:
return len(TOKENIZER.encode(text, add_special_tokens=False))
Chunking com tamanho fixo de token
def chunk_fixed_tokens(
text: str,
*,
chunk_size: int = 512,
) -> list[Chunk]:
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
for i in range(0, len(token_ids), chunk_size):
window = token_ids[i : i + chunk_size]
chunk_text = TOKENIZER.decode(window)
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "fixed_tokens", "start_token": i, "end_token": i + len(window)},
)
)
return out
Tamanho fixo com sobreposição + janela deslizante
def chunk_sliding_window(
text: str,
*,
window_tokens: int = 512,
stride_tokens: int = 384, # stride menor = mais sobreposição
) -> list[Chunk]:
assert 1 <= stride_tokens <= window_tokens, "o stride deve estar dentro de (0, janela]"
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
start = 0
while start < len(token_ids):
end = min(start + window_tokens, len(token_ids))
window = token_ids[start:end]
out.append(
Chunk(
text=TOKENIZER.decode(window),
meta={"strategy": "sliding_window", "start_token": start, "end_token": end},
)
)
if end == len(token_ids):
break
start += stride_tokens
return out
Chunking baseado em frases (NLTK) com empacotamento com orçamento de token
# pip install nltk
import nltk
from nltk.tokenize import sent_tokenize
nltk.download("punkt", quiet=True)
def chunk_by_sentences_nltk(
text: str,
*,
max_tokens: int = 512,
overlap_sentences: int = 1,
) -> list[Chunk]:
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
def flush():
nonlocal buf, buf_tokens
if not buf:
return
chunk_text = " ".join(buf).strip()
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "sentences_nltk", "sent_count": len(buf)},
)
)
# Sobreposição mantendo as últimas N frases
if overlap_sentences > 0:
buf = buf[-overlap_sentences:]
buf_tokens = token_len(" ".join(buf))
else:
buf, buf_tokens = [], 0
for s in sents:
s_tokens = token_len(s)
# Se uma única frase exceder o orçamento, recuar para chunking de tamanho fixo nesse intervalo
if s_tokens > max_tokens:
flush()
out.extend(chunk_fixed_tokens(s, chunk_size=max_tokens))
continue
if buf_tokens + s_tokens > max_tokens and buf:
flush()
buf.append(s)
buf_tokens += s_tokens
flush()
return out
Chunking baseado em frases (spaCy) quando quiser SBD baseado em regras ou modelos
# pip install spacy
# python -m spacy download en_core_web_sm
import spacy
def chunk_by_sentences_spacy(
text: str,
*,
max_tokens: int = 512,
) -> list[Chunk]:
# Para divisões leves baseadas em regras (sem análise de dependência), use o sentencizer.
nlp = spacy.blank("en")
nlp.add_pipe("sentencizer") # detecção de limites de frase baseada em regras
doc = nlp(text)
sents = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
return chunk_by_sentences_nltk(" ".join(sents), max_tokens=max_tokens, overlap_sentences=1)
Chunking recursivo com separadores (LangChain)
# pip install langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter
def chunk_recursive_langchain(
text: str,
*,
chunk_size: int = 1200,
chunk_overlap: int = 150,
) -> list[Chunk]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=token_len, # orçamento baseado em tokens
separators=["\n\n", "\n", ". ", " ", ""], # personalise conforme seu conteúdo (ex: código)
)
pieces = splitter.split_text(text)
return [
Chunk(text=p, meta={"strategy": "recursive_langchain", "chunk_size": chunk_size, "overlap": chunk_overlap})
for p in pieces
]
Chunking semântico com similaridade de embedding (embeddings da OpenAI)
Este método calcula embeddings para unidades candidatas (frases/parágrafos), depois encontra “pontos de quebra” semânticos.
# pip install openai numpy
import os
import numpy as np
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY"))
def embed_texts_openai(texts: list[str], *, model: str = "text-embedding-3-small") -> np.ndarray:
# NOTA: para grandes lotes, respeite os limites de token de solicitação e os limites de tamanho do lote.
resp = client.embeddings.create(model=model, input=texts)
embs = np.array([d.embedding for d in resp.data], dtype=np.float32)
return embs
def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
denom = (np.linalg.norm(a) * np.linalg.norm(b)) + 1e-8
return float(np.dot(a, b) / denom)
def chunk_semantic(
text: str,
*,
max_tokens: int = 800,
breakpoint_threshold: float = 0.70,
) -> list[Chunk]:
# 1) Comece com candidatos a frases
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
if len(sents) <= 1:
return [Chunk(text=text, meta={"strategy": "semantic", "note": "no_split"})]
# 2) Embed cada frase
embs = embed_texts_openai(sents)
# 3) Calcule quedas de similaridade
sims = [cosine_sim(embs[i], embs[i + 1]) for i in range(len(sents) - 1)]
# 4) Crie segmentos em pontos de quebra
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
for i, s in enumerate(sents):
# Adicione a frase
s_tok = token_len(s)
if buf and buf_tokens + s_tok > max_tokens:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "max_tokens"}))
buf, buf_tokens = [], 0
buf.append(s)
buf_tokens += s_tok
# Decida sobre o ponto de quebra após a frase i (com base na similaridade com a próxima)
if i < len(sims) and sims[i] < breakpoint_threshold:
out.append(
Chunk(
text=" ".join(buf),
meta={"strategy": "semantic", "reason": "sim_drop", "sim_to_next": sims[i]},
)
)
buf, buf_tokens = [], 0
if buf:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "final"}))
return out
Chunking hierárquico + mesclagem de recuperação (LlamaIndex)
# pip install llama-index llama-index-llms-openai
from llama_index.core import Document, StorageContext, VectorStoreIndex
from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes
from llama_index.core.retrievers import AutoMergingRetriever
from llama_index.core.storage.docstore import SimpleDocumentStore
def build_hierarchical_index(text: str):
docs = [Document(text=text)]
node_parser = HierarchicalNodeParser.from_defaults() # padrões para tamanhos de grossa a fina
nodes = node_parser.get_nodes_from_documents(docs)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)
leaf_nodes = get_leaf_nodes(nodes)
base_index = VectorStoreIndex(leaf_nodes, storage_context=storage_context)
base_retriever = base_index.as_retriever(similarity_top_k=6)
retriever = AutoMergingRetriever(base_retriever, storage_context, verbose=True)
return retriever
Chunking baseado em elementos para PDFs (Docling)
# pip install docling
# NOTA: a qualidade da análise de PDF depende do seu ambiente (fontes, OCR, etc.).
from docling.document_converter import DocumentConverter
from docling.transforms.chunker.hierarchical_chunker import HierarchicalChunker
def chunk_pdf_docling(pdf_path: str) -> list[Chunk]:
converter = DocumentConverter()
doc = converter.convert(pdf_path).document # DoclingDocument
chunker = HierarchicalChunker()
doc_chunks = list(chunker.chunk(doc))
out: list[Chunk] = []
for c in doc_chunks:
# c.text contém o conteúdo serializado do chunk; c.meta carrega informações de estrutura
out.append(Chunk(text=c.text, meta={"strategy": "docling_hierarchical", **dict(c.meta)}))
return out
Chunking com consciência de código para Python (AST)
import ast
def chunk_python_by_ast(code: str, *, filepath: str = "<memory>") -> list[Chunk]:
tree = ast.parse(code)
out: list[Chunk] = []
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
start = getattr(node, "lineno", None)
end = getattr(node, "end_lineno", None)
if start is None or end is None:
continue
lines = code.splitlines()
snippet = "\n".join(lines[start - 1 : end])
kind = "class" if isinstance(node, ast.ClassDef) else "function"
name = getattr(node, "name", "<anon>")
out.append(
Chunk(
text=snippet,
meta={
"strategy": "python_ast",
"kind": kind,
"name": name,
"filepath": filepath,
"start_line": start,
"end_line": end,
},
)
)
return out
Exemplos de indexação
FAISS (local) — baseline mínimo, rápido
# pip install faiss-cpu numpy
import numpy as np
import faiss
def build_faiss_index(vectors: np.ndarray) -> faiss.Index:
# vetores: forma (N, d), tipo float32
d = vectors.shape[1]
index = faiss.IndexFlatIP(d) # produto interno; similaridade cos se os vetores forem normalizados
faiss.normalize_L2(vectors)
index.add(vectors)
return index
def faiss_search(index: faiss.Index, query_vec: np.ndarray, k: int = 5):
q = query_vec.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(q)
scores, ids = index.search(q, k)
return ids[0].tolist(), scores[0].tolist()
Chroma (local) — persistência simples para prototipagem de RAG
# pip install chromadb
import chromadb
def build_chroma_collection(chunks: list[Chunk], embeddings: np.ndarray, *, path: str = "./chroma_store"):
client = chromadb.PersistentClient(path=path)
col = client.get_or_create_collection(name="docs")
ids = [sha1_id(c.meta.get("strategy", "chunk"), str(i), c.text[:50]) for i, c in enumerate(chunks)]
col.upsert(
ids=ids,
documents=[c.text for c in chunks],
metadatas=[c.meta for c in chunks],
embeddings=embeddings.tolist(),
)
return col
Weaviate (auto-hospedado / nuvem) — vetores próprios
# pip install weaviate-client
import weaviate
from weaviate.classes.config import Configure
def weaviate_upsert_self_provided(
chunks: list[Chunk],
embeddings: np.ndarray,
):
client = weaviate.connect_to_local() # ou connect_to_weaviate_cloud(...)
try:
collection = client.collections.create(
name="Chunk",
vector_config=Configure.Vectors.self_provided(), # você fornece os vetores
)
with collection.batch.dynamic() as batch:
for c, v in zip(chunks, embeddings):
batch.add_object(
properties={"text": c.text, **{f"m_{k}": str(vv) for k, vv in c.meta.items()}},
vector=v.tolist(),
)
# Consulta por near-vector
q = embeddings[0]
res = collection.query.near_vector(near_vector=q.tolist(), limit=5)
for obj in res.objects:
print(obj.properties.get("text")[:200])
finally:
client.close()
Alguns Documentos de Fonte
- Patrick Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, NeurIPS 2020; arXiv:2005.11401.- Vladimir Karpukhin et al., “Dense Passage Retrieval for Open-Domain Question Answering”, EMNLP 2020; arXiv:2004.04906.
- Referência da API da OpenAI: Criar embeddings (
/v1/embeddings) — limites de token (8192 por entrada; 300k totais por solicitação) e parâmetrodimensions. - Guia da API em lote da OpenAI + página de preços da API da OpenAI (economia de ~50% com turnaround de 24h).
- Documentação da LangChain: RecursiveCharacterTextSplitter e guia de integração de splitters (tamanho do chunk/sobreposição, hierarquia de separadores recursivos).
- Documentação da LlamaIndex: HierarchicalNodeParser e AutoMergingRetriever (nós de grossa a fina; mesclagem durante a recuperação).
- Blog da Weaviate: “Estratégias de Chunking para Melhorar o Desempenho do Pipeline RAG do LLM” (descrição e trade-offs do chunking baseado em LLM).
- Documentação do Docling: HierarchicalChunker cria chunks a partir da estrutura de elementos do documento e anexa metadados de cabeçalhos/capturas.
- Jimeno Yepes et al., “Chunking de Relatórios Financeiros para Geração Aumentada de Recuperação Eficiente” (arXiv:2402.05131v3, 2024). Marti A. Hearst, “TextTiling: Segmentação de Texto em Subtópicos de Múltiplos Parágrafos”, Computational Linguistics, 1997 (ACL Anthology: J97-1003).
- Documentação / repositório do GitHub do FAISS: trade-offs entre tempo de busca, qualidade, memória; suporte opcional a GPU.
- Nandan Thakur et al., “BEIR: Um Benchmark Heterogêneo para Avaliação de Zero-shot de Modelos de Recuperação de Informação”, NeurIPS 2021; arXiv:2104.08663.
- Documentação do RAGAS: Métrica “Faithfulness” e métricas relacionadas de avaliação de RAG.
- Documentação do NLTK:
nltk.tokenize.sent_tokenize- tokenizer de frase recomendado baseado em Punkt. - Documentação da API do spaCy:
Sentencizer- detecção de limites de frase baseada em regras sem análise de dependência.
Outros Links Úteis
- RAG Avançado: LongRAG, Self-RAG e GraphRAG Explicados
- Comparação de Armazenamentos Vetoriais para RAG
- Reranking com modelos de embedding
- Reranking de documentos de texto com Ollama e modelo de embedding Qwen3 - em Go
- Reranking de documentos de texto com Ollama e modelo de reranking Qwen3 - em Go
- Auto-Hospedagem do Cognee (Memória do LLM): Testes de Desempenho do LLM
- Hospedagem de LLM: Comparação de Infraestrutura Local, Auto-Hospedada e em Nuvem
- Desempenho de LLM: Benchmarks, Bottlenecks e Otimização
- Tutorial de Geração Aumentada de Recuperação (RAG): Arquitetura, Implementação e Guia de Produção