Capítulo 2: Estratégias de Divisão de Texto
Introdução
A divisão de texto (chunking) é crucial para o desempenho do RAG. O tamanho e a estratégia corretos dos chunks impactam significativamente a qualidade da recuperação e a precisão da geração.
Por que o Chunking é Importante
Problemas sem chunking:
- Documentos inteiros são grandes demais
- A recuperação retorna conteúdo irrelevante
- Desperdiça a janela de contexto do LLM
Parâmetros Principais
Tamanho do Chunk
O número de caracteres/tokens por chunk:
| Tamanho do Chunk | Prós | Contras |
|---|---|---|
| Pequeno (256) | Recuperação precisa | Pode perder contexto |
| Médio (512-1024) | Equilibrado | Bom para a maioria dos casos |
| Grande (2000+) | Mais contexto | Recuperação menos precisa |
Sobreposição de Chunk
Texto compartilhado entre chunks consecutivos:
Chunk 1: [_______________]
Chunk 2: [_______________]
^overlap^
Por que sobrepor?
- Evita cortar frases no meio
- Preserva o contexto entre os limites dos chunks
- Típico: 10-20% do tamanho do chunk
Estratégias de Divisão
1. Divisão Recursiva por Caracteres
A abordagem mais comum – divide por hierarquia de separadores:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(documents)
Hierarquia de separadores:
- Duas quebras de linha (parágrafos)
- Uma quebra de linha
- Pontos finais (frases)
- Espaços (palavras)
- Caracteres (último recurso)
2. Divisão Baseada em Tokens
Utiliza a contagem real de tokens (importante para limites do LLM):
from langchain.text_splitter import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=500, # tokens, não caracteres
chunk_overlap=50,
encoding_name="cl100k_base" # Tokenizer do GPT-4
)
3. Divisão por Frases
Preserva os limites das frases:
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=50,
tokens_per_chunk=256
)
4. Divisão Semântica
Divide com base na similaridade semântica:
from langchain_experimental.text_splitter import SemanticChunker
from langchain.embeddings import OpenAIEmbeddings
splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile"
)
# Chunks baseados em similaridade semântica
semantic_chunks = splitter.split_documents(documents)
5. Divisão por Cabeçalhos Markdown
Respeita a estrutura do documento:
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)
Como Escolher o Tamanho do Chunk
def optimal_chunk_size(embedding_model, llm_context_window, top_k):
"""
Calculate optimal chunk size
"""
# Leave room for prompt template and response
available_tokens = llm_context_window * 0.6
# Divide by number of chunks to retrieve
chunk_tokens = available_tokens / top_k
# Convert to characters (roughly 4 chars per token)
return int(chunk_tokens * 4)
# Example: GPT-4 with 8K context, retrieving 5 chunks
optimal = optimal_chunk_size(
embedding_model="text-embedding-3-small",
llm_context_window=8192,
top_k=5
)
# Result: ~3900 characters per chunk
Tabela Comparativa
| Estratégia | Melhor Uso | Qualidade do Chunk |
|---|---|---|
| Recursiva por Caracteres | Uso geral | Boa |
| Baseada em Tokens | Gerenciamento de contexto do LLM | Excelente |
| Por Frases | Sistemas de perguntas e respostas | Boa |
| Semântica | Conteúdo variado | Excelente |
| Cabeçalhos Markdown | Documentos estruturados | Excelente |
Boas Práticas
- Comece com os padrões – 1000 caracteres, 200 de sobreposição
- Teste tamanhos diferentes – Avalie a qualidade da recuperação
- Adapte ao conteúdo – Documentação técnica pode precisar de chunks maiores
- Considere os embeddings – Alguns modelos têm limite máximo de tokens
- Preserve metadados – Rastreie a origem e posição do chunk
# Pipeline de chunking para produção
def chunk_documents(documents, chunk_size=1000, overlap=200):
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=overlap,
length_function=len,
add_start_index=True # Rastrear posição
)
chunks = splitter.split_documents(documents)
# Adiciona índice do chunk aos metadados
for i, chunk in enumerate(chunks):
chunk.metadata["chunk_index"] = i
chunk.metadata["total_chunks"] = len(chunks)
return chunks
Resumo
- Tamanho do chunk afeta precisão da recuperação vs contexto
- Sobreposição previne perda de informação nos limites
- Estratégia deve se adequar ao tipo de documento
- Teste e itere para encontrar as configurações ideais
Próximos Passos
Continue para Capítulo 3: Tipos de Arquivo para aprender sobre o tratamento de documentos complexos.