Estratégias de Segmentação na Comparação de RAG: Alternativas, Compensações e Exemplos
Comparação de Estratégias de Chunking em RAG
Chunking é o hiperparâmetro mais subestimado na Geração Aumentada por Recuperação (RAG): ele determina silenciosamente o que seu LLM “vê”, o quão cara se torna a ingestão, e quanto da janela de contexto do LLM você queima por resposta.
Este artigo trata o chunking como um problema de otimização de engenharia: defina objetivos, escolha uma estratégia, meça e, em seguida, itere.
Se você é novo na arquitetura RAG, comece pelo 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 (fragmentos), 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 do RAG, a recuperação fornece passagens para a geração; os limites dessas passagens são efetivamente seus limites de chunk.
Uma boa estratégia de chunking busca uma fronteira de Pareto entre: qualidade de recuperação (recall/precisão de evidências), 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 rotineiramente misturam estratégias (por exemplo, chunking consciente da estrutura para PDFs + divisões conscientes do significado semântico para prosa + chunking AST para código).
Para a maioria dos casos de “QA de documentação” e bases de conhecimento internas, um padrão seguro é um splitter recursivo que respeita a estrutura com sobreposição moderada (para reduzir a perda de borda), apoiado por um
vector store
com filtragem de metadados e reranking opcional. O RecursiveCharacterTextSplitter do 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 bordas.
Quando os documentos têm estrutura forte (PDFs com títulos, tabelas, listas, legendas), o chunking baseado em elementos / consciente da estrutura pode superar o fatiamento por contagem de tokens, produzindo menos chunks. Um estudo de 2024 sobre documentos da SEC descobriu que o chunking baseado em tipos de elementos melhorou os resultados do RAG e também reduziu o número de chunks (e, portanto, vetores) pela metade em comparação com métodos agnósticos à estrutura — cortando o custo de indexação e potencialmente melhorando a latência de consulta.
Se você puder computar mais recursos pré-existentes, o chunking semântico (dividir em mudanças de tópico usando similaridade de embedding) pode melhorar materialmente a fidelidade de recuperação para texto narrativo e páginas de tópicos mistos. Algoritmos de segmentação de tópicos mais antigos, como TextTiling, mostram o princípio geral: mudanças fortes de vocabulário/semântica são bons candidatos a limites.
Para materiais muito longos e com referências cruzadas internas (políticas, RFCs, padrões, manuais grandes), o chunking hierárquico + recuperação/fusão hierárquica (nós pai/filho) pode recuperar contexto contíguo maior sob demanda. O analisador de nós hierárquicos do LlamaIndex produz hierarquias de chunk de “grão grosso para fino”, e o AutoMergingRetriever pode fundir nós filhos em nós pais no momento da recuperação quando filhos relacionados suficientes são recuperados.
Objetivos e trade-offs do Chunking
O chunking não é apenas “dividir texto para que caiba em um modelo de embedding”. Ele controla múltiplos comportamentos operacionais e downstream.
Granularidade de recuperação vs ruído de recuperação. Chunks menores aumentam a chance de que a sentença exata contendo uma resposta seja recuperável (recall potencial maior em top‑k fixo). Mas eles também produzem mais vetores, aumentando o tamanho do índice e às vezes trazendo “quase correspondências” que são semanticamente similares, mas não realmente evidentes (precisão menor). Recuperadores densos como o DPR foram construídos em torno de recuperar passagens efetivamente para QA, destacando que os limites de passagens importam para o desempenho de QA de ponta a ponta.
Coerência de contexto vs perda de borda. Chunks coerentes ajudam o LLM a raciocinar corretamente e reduzem alucinações ao fornecer contexto local completo (definições, restrições, pré-requisitos). A sobreposição reduz a perda de borda, mas cria texto duplicado, o que pode levar a resultados de recuperação redundantes e inflação do comprimento do prompt se você não desduplicar/fundir.
Custo de embedding e indexação. O custo de embedding é tipicamente proporcional aos tokens embutidos, e o tempo de ingestão escala com o número de chunks (mais sobrecarga de escrita do DB vetorial). Para embeddings da OpenAI, as solicitações têm um limite máximo de tokens por entrada (8192 tokens para todos os modelos de embedding) e um limite máximo total de tokens somados por entrada por solicitação (300.000 tokens). Para corpora grandes, a API de Lote (Batch) pode reduzir custos em ~50% com um tempo de resposta assíncrono de 24 horas — útil para backfills e reindexações periódicas.
Tamanho do índice vetorial, RAM e latência. Mais chunks significam mais vetores e potencialmente mais memória e consultas mais lentas (dependendo do tipo de índice). O FAISS enquadra explicitamente o design de índice como um conjunto de trade-offs entre tempo de busca, qualidade de busca e memória por vetor indexado; ele também oferece implementações GPU para busca exata e aproximada rápida.
Comprimento do prompt LLM downstream / uso da janela de contexto. A saída do recuperador torna-se o orçamento de prompt. Uma estratégia de chunking que recupera consistentemente “apenas o necessário” pode melhorar a qualidade da resposta e reduzir custos. Por outro lado, sobreposição e chunks muito grandes inflacionam o comprimento do prompt. Na prática, você frequentemente ajusta: (tamanho do chunk, sobreposição, top‑k, reranking/fusão) juntos.
Custo de atualização/ingestão e desduplicação. O chunking afeta o quão caro é atualizar dados. Chunks menores tornam atualizações parciais mais baratas (você pode re-embed apenas a seção alterada), mas também tornam a desduplicação mais difícil se chunks sobrepostos ou quase duplicados proliferarem.
Onde o chunking se situa no fluxo de trabalho RAG

Estratégias e alternativas de Chunking
Abaixo estão as principais famílias de chunking que você encontrará no RAG moderno. Na prática, você frequentemente misturará duas: chunking primeiro-estrutura (respeitando limites de documento) mais forçamento de orçamento de tokens (garantir que os chunks se encaixem em seus orçamentos de embedding e prompt).
Chunking de tamanho fixo
O que é. Divide 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 em streaming onde você não tem contexto de documento completo.
Onde falha. Ignora limites (sentenças, 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. Menor complexidade de ingestão; contagem de chunk previsível; cache mais fácil. Mas você geralmente precisa de sobreposição (abaixo) para evitar perda de borda.
Chunking com sobreposição (Overlap)
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 “borda suave” — permite que a recuperação capture um fato que atravessa uma borda.
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ê desduplica no momento da recuperação (por exemplo, fundir por offsets de origem ou usar MMR).
Chunking baseado em sentenças e parágrafos
O que é. Divide o texto nos limites de sentenças ou parágrafos, depois empacota sentenças/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 detecção de limites de sentença do Punkt por padrão, e o spaCy oferece ferramentas de limites de sentença baseadas em regras como Sentencizer (útil quando você quer divisões de sentença sem um modelo de dependência completo).
Modos de falha. Pontuação não padrão (logs, transcrições de chat), tabelas, código e listas com marcadores podem quebrar pressupostos de segmentação de sentenças.
Chunking de janela deslizante (Sliding window)
O que é. Cria chunks usando um tamanho de janela fixo e um passo (stride). Esta é a versão de “sobreposição sistemática” do chunking.
Quando é bom. Texto de séries temporais, transcrições, logs de chat, atas de reunião — qualquer coisa onde fatos relevantes podem aparecer em vizinhanças locais e você quer recall robusto.
Quando é ruim. Amplifica redundância e pode ser caro em escala. Também tende a recuperar janelas redundantes a menos que desduplicado.
Chunking recursivo / hierarquia de separadores
O que é. Começa com separadores “naturais” grandes (por exemplo, \n\n para parágrafos) e divide recursivamente em unidades menores (sentenças, espaços) apenas quando necessário para permanecer abaixo de um orçamento de tamanho. A documentação do LangChain descreve explicitamente este comportamento: um splitter recursivo tenta manter unidades maiores intactas e só recorre a separadores menores quando uma unidade ainda excede o tamanho do chunk.
Por que é um padrão forte. Respeita a estrutura sem exigir parsing de documento complexo. É um ponto ideal pragmático para Markdown, HTML-como-texto e documentação.
Parâmetros chave de ajuste. chunk_size, chunk_overlap e a length_function (caracteres vs tokens), além de separadores personalizados para bases de código multilingues.
Chunking semântico (consciente de embedding)
O que é. Detecta mudanças de tópico usando representações semânticas (embeddings) e divide 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 o chunking baseado em tamanho. Você para de dividir em contagens arbitrárias de tokens e, em vez disso, alinha chunks com limites de tópicos — frequentemente melhorando a precisão de recuperação para documentos de múltiplos tópicos (blogs, docs de design, tickets, relatórios de incidentes).
Custos. Você pode precisar de embeddings adicionais durante o chunking (embeddings de nível de sentença ou parágrafo) antes dos embeddings finais do chunk. Isso pode dobrar ou triplicar chamadas de embedding a menos que você reutilize embeddings intermediários.
Truque prático. “Empacotamento consciente de semântica”: calcule embeddings de sentença uma vez, agrupe sentenças em segmentos coerentes de tópico, depois embed cada segmento final.
Chunking hierárquico (pai/filho)
O que é. Constrói uma representação de múltipla granularidade: chunks pais grosseiros (por exemplo, tamanho de seção) com chunks filhos mais finos (por exemplo, tamanho de parágrafo). O parsing de nós hierárquicos do LlamaIndex produz hierarquias de “grosso-para-fino” por padrão (por exemplo, escalas de token 2048 → 512 → 128), e o AutoMergingRetriever pode fundir nós filhos em pais no momento da recuperação quando filhos relacionados suficientes são recuperados.
Por que ajuda. Evita escolher entre “chunks pequenos para recall” e “chunks grandes para coerência” ao armazenar ambos e selecionar no momento da consulta.
Custos. Lógica de ingestão e recuperação mais complexa, além de potencialmente mais armazenamento (porque você armazena múltiplas granularidades).
Chunking adaptativo / baseado em LLM
O que é. Usa um LLM para decidir limites de chunk (e opcionalmente gerar resumos ou cabeçalhos contextuais). O Weaviate descreve explicitamente o chunking baseado em LLM como tendo o LLM criar chunks semanticamente coerentes, em vez de confiar em regras fixas ou similaridade de embedding.
Quando vale a pena. Corpora de alto valor onde a correção domina o custo (legal, compliance, runbooks de suporte), e onde documentos são bagunçados, heterogêneos e mal segmentados.
Riscos. Custo, latência e não-determinismo. Você quererá cache, decodificação determinística e testes de regressão (veja seção de avaliação).
Chunking baseado em estrutura e elementos (documentos não são texto puro)
O que é. Parseia o documento em elementos (títulos, parágrafos, listas, tabelas, legendas) usando uma camada de compreensão de documentos, depois chunka 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 de documento detectado e anexa metadados estruturais como cabeçalhos/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; relata resultados RAG melhorados e menos chunks/vetores do que abordagens agnósticas à estrutura.
Por que importa para multimodal. Tabelas, figuras e legendas frequentemente contêm a verdade fundamental. “Achatar” (flatten) em texto puro pode destruir sinais que a recuperação exploraria.
Chunking consciente de código (AST/estrutura)
O que é. Chunka código por unidades sintáticas (funções, classes, módulos), opcionalmente incluindo docstrings e comentários.
Por que importa. Divisões de token de tamanho fixo tendem a cortar funções pela metade e separar docstrings de implementações — ruim para busca de código e casos de uso RAG de “explique esta função”.
Opções de implementação. Para Python, o módulo ast embutido é frequentemente suficiente. Para repositórios multilingues, 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 de sistema.
Métricas de qualidade de recuperação
Use métricas IR padrão para a camada de recuperação:
- Recall@k / Precisão@k: O top‑k continha a evidência de ouro?
- MRR / nDCG: A evidência de ouro ranqueou alto?
O BEIR é um benchmark heterogêneo amplamente usado para avaliação IR através de tarefas/domínios e destaca trade-offs entre abordagens esparsas, densas, de interação tardia e reranking.
O chunking afeta essas métricas porque define o que conta como “um item recuperado relevante”.
Métricas de qualidade de resposta RAG de ponta a ponta
Se você está construindo QA ou assistentes, métricas de recuperação são necessárias, mas não suficientes. Você também precisa:
- Recall / Precisão de contexto: se os contextos recuperados contêm evidências relevantes e evitam 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 essas alavancas:
Latência (p50/p95). A latência de consulta geralmente aumenta com mais vetores e mais pós-processamento. Seu índice vetorial também importa: os tipos de índice FAISS fazem trade-offs entre tempo de busca, qualidade, memória e tempo de treinamento/adicionar.[^faiss]
Custo e throughput de embedding. Embeddings da OpenAI são cobrados por tokens; a API de embeddings tem limites explícitos por entrada e por solicitação.[^openai_embed_create] Para ingestão offline, a API de Lote reduz custos e oferece cota maior em troca de tempo de resposta não em tempo real.[^openai_batch]
Tamanho e memória do índice. Aproximadamente, armazenar N vetores float32 de dimensão d custa ~4 * N * d bytes apenas para os vetores brutos (mais metadados + sobrecarga de índice). O chunking impacta N. A dimensionalidade de embedding impacta d, e a API de embeddings da OpenAI permite controlar a dimensionalidade de saída via o parâmetro dimensions.[^openai_embed_create]
Orçamento de prompt LLM. Chunks maiores e sobreposição inflacionam tokens de prompt. Isso pode aumentar latência e custo, e aumentar modos de falha do tipo “perdido no meio” onde modelos prestam menos atenção a algum contexto. Na prática, você frequentemente:
- recupera chunks pequenos,
- funde/desduplica,
- opcionalmente resume,
- envia um conjunto de evidências compacto para o LLM.
Custo de atualização/ingestão. Chunks menores permitem re-embedding parcial, mas aumentam a contabilidade. Para ingestão em streaming, prefira chunking determinístico e incremental (tamanho fixo ou janela deslizante) e anexe IDs estáveis (document_id, faixas de offset, hash).
Design experimental: um loop de benchmark pragmático
Um benchmark de chunking reprodutível tipicamente tem:
- Um snapshot de corpus fixo + conjunto fixo de consultas com evidência de ouro (ou pelo menos faixas de resposta esperada).
- Um modelo de embedding fixo e configuração de índice vetorial.
- Uma avaliação “apenas recuperação” (recall@k, nDCG) mais avaliação “RAG” (fidelidade, relevância de resposta).
- Telemetria de custo: #chunks, tokens embutidos, $/mês de armazenamento, latência de consulta p95, tokens de prompt.
O artigo do Unstructured sobre documentos da SEC é um bom exemplo de avaliar estratégias de chunking com métricas orientadas a recuperação e medidas de precisão de QA.
Diretrizes práticas, matriz de decisão e padrões recomendados
Padrões recomendados que funcionam surpreendentemente bem
Se você precisa de uma estratégia “dia 1” robusta para QA de documentação geral:
- Parse levemente: preserve títulos e metadados básicos (fonte, título da seção, URL/caminho, carimbo de tempo).
- Chunk com um splitter de separador recursivo (parágrafo → sentença → palavra), com sobreposição modesta.
- Embed com um modelo de embedding geral forte.
- Indexe com metadados (doc id, seção, ACLs) e desduplica durante a recuperação.
- Adicione reranking ou fusão hierárquica apenas se sua avaliação mostrar uma lacuna.
Isso alinha com como frameworks RAG comuns descrevem sobreposição de chunk e splitting que respeita estrutura.
Qual método de chunking usar - Matriz de decisão

| Caso de uso | Chunking padrão recomendado | Parâmetros chave para ajustar | Modo de falha comum | Caminho de upgrade |
|---|---|---|---|---|
| QA de curta duração sobre docs (FAQs, wiki interna) | Chunking recursivo/separador + sobreposição | chunk_size, sobreposição, top‑k |
Evidência cruzada entre sentenças faltando na borda | Adicionar chunking semântico ou reranker |
| QA de longa duração (políticas, padrões, manuais) | Chunking hierárquico + recuperador de fusão | tamanhos pai/filho, limiar de fusão | Recupera pequenos fragmentos; LLM falta contexto completo | Auto-fusão/recuperação hierárquica |
| Sumarização (por doc / por seção) | Chunks conscientes da estrutura (seções) | detecção de seção, max tokens | Resumos perdem links cruzados de seção | Sumarização hierárquica + grafo de seção |
| Busca de código & “explique esta função” | Chunks de nível AST/função | incluir docstring/comentários, max tokens | Função dividida; perde assinatura/uso | Hierarquia consciente de 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, fusão de legenda | Conteúdo de tabela perdido ou corrompido | Usar Docling/Unstructured + serializadores estruturados |
| Ingestão em streaming (logs, chats, tickets) | Janela deslizante ou tokens de tamanho fixo | janela, stride, desduplicação | Super-recuperação de janelas redundantes | Adicionar detecção de borda 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 borda prejudica |
| Empacotamento por sentença/parágrafo | Alto (prosa) | Alto | Médio | Médio | Médio | Médio | docs, artigos, prosa limpa |
| Janela deslizante | Recall alto | 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 | RAG de docs “padrão” |
| Chunking semântico | Alto–Muito alto | Alto | Alto | Médio | Alto | Médio | páginas de múltiplos tópicos, 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 | corpora 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ório |
Notas de DevOps e hardware (frequentemente negligenciadas)
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 backados em disco.
- Se você embed localmente, o throughput de embedding torna-se um problema de agendamento de GPU; se você embed via API, o volume de tokens torna-se um problema de FinOps (a API de Lote é sua amiga para backfills).
- Alguns engines (FAISS) fornecem busca acelerada por GPU; isso pode mudar custo de CPU limitado por RAM para memória GPU e throughput PCIe.
- Parsing consciente de estrutura (layout de PDF, OCR, extração de tabela) é frequentemente limitado por CPU e pode superar o custo de embedding para docs escaneados; orçene separadamente.
Implementações de referência em Python - Chunking
Todos os exemplos são projetados para serem legíveis e executáveis. Se você precisar de uma chave de API ou um DB em execução, fica claro pelo código.
Utilitários compartilhados: contagem de token 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 à sua tokenização 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 de token de tamanho fixo
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, "stride deve estar dentro de (0, window]"
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 sentenças (NLTK) com empacotamento de 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 sentenças
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 sentença exceder o orçamento, voltar para chunking de tamanho fixo nesse span
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 sentenças (spaCy) quando você quer SBD baseado em regras ou modelo
# 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 parse de dependência), use sentencizer.
nlp = spacy.blank("en")
nlp.add_pipe("sentencizer") # detecção de limites de sentença 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 de separador (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 consciente de token
separators=["\n\n", "\n", ". ", " ", ""], # personalize para 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 OpenAI)
Esta abordagem calcula embeddings para unidades candidatas (sentenças/parágrafos), depois encontra “pontos de ruptura” 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 lotes grandes, respeite limites de token de solicitação e tamanho de 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 de candidatos de sentença
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 sentença
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 ruptura
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
for i, s in enumerate(sents):
# Adicione sentença
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 ponto de ruptura após a sentença i (baseado 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 + recuperação de fusã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ão para tamanhos de grosso-para-fino
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 de parsing 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 conteúdo de chunk serializado; c.meta carrega info de estrutura
out.append(Chunk(text=c.text, meta={"strategy": "docling_hierarchical", **dict(c.meta)}))
return out
Chunking consciente 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ínima, rápida
# pip install faiss-cpu numpy
import numpy as np
import faiss
def build_faiss_index(vectors: np.ndarray) -> faiss.Index:
# vectors: shape (N, d), dtype float32
d = vectors.shape[1]
index = faiss.IndexFlatIP(d) # produto interno; cos sim se vetores 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 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) — traga seus próprios vetores
# 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 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 Origem
- 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 OpenAI: Criar embeddings (
/v1/embeddings) — limites de token (8192 por entrada; 300k total por solicitação) e parâmetrodimensions. - Guia da API de Lote OpenAI + página de preços da API OpenAI (Lote economiza ~50% com turnaround de 24h).
- Docs do LangChain: RecursiveCharacterTextSplitter e guia de integração de splitters (tamanho/sobreposição de chunk, hierarquia de separador recursivo).
- Docs do LlamaIndex: HierarchicalNodeParser e AutoMergingRetriever (nós de grosso-para-fino; fusão no momento da recuperação).
- Blog do Weaviate: “Estratégias de Chunking para Melhorar o Desempenho do Pipeline RAG de LLM” (descrição de chunking baseado em LLM e trade-offs).
- Docs do Docling: HierarchicalChunker cria chunks de estrutura de elemento de documento e anexa metadados de cabeçalhos/legendas.
- Jimeno Yepes et al., “Financial Report Chunking for Effective Retrieval Augmented Generation” (arXiv:2402.05131v3, 2024).
- Marti A. Hearst, “TextTiling: Segmenting Text into Multi-Paragraph Subtopic Passages”, Computational Linguistics, 1997 (ACL Anthology: J97-1003).
- Documentação do FAISS / repositório GitHub: trade-offs entre tempo de busca, qualidade, memória; suporte GPU opcional.
- Nandan Thakur et al., “BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models”, NeurIPS 2021; arXiv:2104.08663.
- Documentação do RAGAS: métrica “Fidelidade” e métricas de avaliação RAG relacionadas.
- Documentação do NLTK:
nltk.tokenize.sent_tokenize- tokenizador de sentença recomendado baseado em Punkt. - Docs da API do spaCy:
Sentencizer- detecção de limites de sentença baseada em regras sem parsing de dependência.
Outros Links Úteis
- RAG Avançado: LongRAG, Self-RAG e GraphRAG Explicados
- Comparação de Vector Stores 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 Reranker Qwen3 - em Go
- Auto-hospedagem Cognee (Memória LLM): Testes de Desempenho LLM
- Hospedagem LLM: Local, Auto-hospedado e Infraestrutura Nuvem Comparados
- Desempenho LLM: Benchmarks, Gargalos & Otimização
- Tutorial de Geração Aumentada por Recuperação (RAG): Arquitetura, Implementação e Guia de Produção