Pular para o conteúdo principal

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 ChunkPrósContras
Pequeno (256)Recuperação precisaPode perder contexto
Médio (512-1024)EquilibradoBom para a maioria dos casos
Grande (2000+)Mais contextoRecuperaçã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:

  1. Duas quebras de linha (parágrafos)
  2. Uma quebra de linha
  3. Pontos finais (frases)
  4. Espaços (palavras)
  5. 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égiaMelhor UsoQualidade do Chunk
Recursiva por CaracteresUso geralBoa
Baseada em TokensGerenciamento de contexto do LLMExcelente
Por FrasesSistemas de perguntas e respostasBoa
SemânticaConteúdo variadoExcelente
Cabeçalhos MarkdownDocumentos estruturadosExcelente

Boas Práticas​

  1. Comece com os padrões – 1000 caracteres, 200 de sobreposição
  2. Teste tamanhos diferentes – Avalie a qualidade da recuperação
  3. Adapte ao conteúdo – Documentação técnica pode precisar de chunks maiores
  4. Considere os embeddings – Alguns modelos têm limite máximo de tokens
  5. 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.