04-04-03 — Contextual retrieval e late chunking
TL;DR
Contextual retrieval (Anthropic, 2024): antes de embeddar cada chunk, peça ao LLM para gerar um resumo de contexto que situe o chunk dentro do documento. Resultado: -49% de retrieval failures em benchmarks. Late chunking (JinaAI, 2024): embedda o documento inteiro com atenção ao contexto completo, depois recorta os embeddings. Ambas as técnicas atacam o mesmo problema: chunks sem contexto do documento pai.
O problema dos chunks sem contexto
Quando você corta um documento em chunks e embeda cada um isoladamente, informações de contexto se perdem. Um chunk pode dizer "O valor previsto aumentou 15% neste trimestre" — mas o embedding não sabe que se refere à Vale, ao contrato X, ou ao exercício fiscal 2023. Esse chunk isolado vai ter uma representação vetorial vaga demais para ser encontrado por queries específicas.
Contextual Retrieval (Anthropic)
Publicado pela Anthropic em setembro de 2024, o método é elegantemente simples: para cada chunk, use o LLM para gerar um mini-contexto de 1-2 frases que situe o chunk no documento. Esse contexto é prepended ao chunk antes de embeddar.
def generate_contextual_chunk(document: str, chunk: str, llm_client) -> str:
"""
Gera contexto situacional para o chunk dentro do documento.
Use um modelo barato (gpt-4o-mini, claude-haiku) para reduzir custo.
"""
prompt = f"""Você receberá um documento e um trecho específico desse documento.
Gere uma frase curta (máximo 2 sentenças) que contextualize esse trecho
dentro do documento, mencionando: (1) o assunto geral do documento,
(2) como o trecho se relaciona com o contexto maior.
Não repita informação que já está no trecho.
{document[:3000]}
{chunk}
Contexto situacional (máximo 2 frases):"""
response = llm_client.chat.completions.create(
model="gpt-4o-mini", # modelo barato — escala para muitos chunks
messages=[{"role": "user", "content": prompt}],
max_tokens=100
)
context = response.choices[0].message.content.strip()
# Prepend o contexto ao chunk antes de embeddar
return f"{context}\n\n{chunk}"
# Pipeline de indexação com contextual retrieval
def index_with_context(document_text: str, chunks: list[str]) -> list[dict]:
contextualized_chunks = []
for chunk in chunks:
contextual = generate_contextual_chunk(document_text, chunk, client)
embedding = embed(contextual) # embeda o chunk COM contexto
contextualized_chunks.append({
"text": chunk, # armazena o chunk original para exibição
"contextual_text": contextual, # armazena o texto contextualizado
"embedding": embedding
})
return contextualized_chunks
Anthropic reportou, em testes com 20 bases de conhecimento de diferentes domínios: contextual retrieval sozinho reduziu retrieval failures em 35%. Combinado com BM25 (hybrid search): -49%. Com reranking adicionado: -67% de falhas comparado a RAG vanilla. São números significativos para qualquer sistema de produção.
Custo de contextual retrieval
O custo extra vem de gerar contexto para cada chunk durante a indexação. Para um corpus de 10.000 chunks usando gpt-4o-mini:
- ~200 tokens por chamada (documento resumido + chunk + prompt)
- 10.000 × 200 tokens = 2M tokens de input
- Custo: ~$0.30 (gpt-4o-mini a $0.15/1M tokens input)
É um investimento único na indexação, não por query. Vale muito a pena.
Use prompt caching (disponível no Azure OpenAI) para reduzir ainda mais o custo: o documento completo é enviado em todas as chamadas de contextualização dos seus chunks. Com caching, o documento é processado apenas uma vez por sessão — economia de até 90% do custo de tokens do documento.
Late Chunking (JinaAI)
Abordagem diferente para o mesmo problema. Em vez de adicionar contexto textualmente, a ideia é aproveitar a atenção do modelo de embedding sobre o documento completo antes de chunkar.
Processo:
- Tokenize o documento completo
- Rode o encoder transformer sobre o documento inteiro — cada token obtém representação com atenção ao documento todo
- Faça mean pooling por chunk (média dos embeddings dos tokens do chunk)
Resultado: cada chunk tem embedding que "viu" o documento inteiro, não só seus próprios tokens.
from transformers import AutoTokenizer, AutoModel
import torch
model_name = "jinaai/jina-embeddings-v2-base-en"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
def late_chunking(document: str, chunk_boundaries: list[tuple[int,int]]) -> list[list[float]]:
"""
chunk_boundaries: lista de (start_char, end_char) para cada chunk
retorna: lista de embeddings, um por chunk
"""
# 1. Tokenizar documento completo
inputs = tokenizer(document, return_tensors="pt", truncation=True, max_length=8192)
# 2. Encoder sobre o documento inteiro
with torch.no_grad():
outputs = model(**inputs)
token_embeddings = outputs.last_hidden_state[0] # shape: [seq_len, hidden_size]
# 3. Mean pooling por chunk
chunk_embeddings = []
for start_char, end_char in chunk_boundaries:
# Mapear caracteres para tokens
start_token = inputs.char_to_token(start_char) or 0
end_token = inputs.char_to_token(end_char - 1) or -1
chunk_emb = token_embeddings[start_token:end_token+1].mean(dim=0)
chunk_embeddings.append(chunk_emb.tolist())
return chunk_embeddings
Late chunking requer que o documento inteiro caiba no contexto do encoder (geralmente 8k tokens). Para documentos maiores, você precisa dividir em janelas antes — o que parcialmente anula a vantagem. Contextual retrieval escala melhor para documentos longos.
Comparativo das duas técnicas
| Técnica | Abordagem | Custo extra | Complexidade | Limitação |
|---|---|---|---|---|
| Contextual retrieval | LLM gera contexto textual | ~$0.30 / 10k chunks | Baixa | +1 chamada LLM por chunk |
| Late chunking | Embedding sobre doc completo | Compute de embedding | Média | Limite de contexto do encoder |
Para a maioria dos casos, contextual retrieval é mais prático por ser independente do modelo de embedding e escalar para documentos longos.
Como isso se conecta
- 04-03-01 — Chunking — contextual retrieval é aplicado após o chunking tradicional
- 04-03-03 — Hybrid search — Anthropic recomenda combinar contextual retrieval + BM25 para -49% de falhas
- 04-05-01 — Métricas de RAG — medir o ganho de contextual retrieval via context recall
Fontes
- Anthropic. Contextual Retrieval (2024). anthropic.com/news/contextual-retrieval
- JinaAI. Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models (2024). arxiv.org/abs/2409.04701
- JinaAI. jina-embeddings-v2 model card. huggingface.co
- Microsoft. Prompt caching in Azure OpenAI. learn.microsoft.com