MÓDULO 3.2 GA

🔍 Recuperação, reranking e contextual retrieval

Do índice à resposta: top-k retrieval, rerankers cross-encoder, contextual retrieval (Anthropic 2024) e citações obrigatórias.

6
Tópicos
60
Minutos
Intermediário
Nível
Prático
Tipo

Retrieval ingênuo recupera 50 docs e despeja na janela. RAG bom recupera 100, rerankeia para 5, e mostra citação. Aqui você aprende a fechar o ciclo: top-k, reranker, contextual retrieval, e como forçar o modelo a citar a fonte.

🎯 Pipeline de retrieval em 5 passos

Cada passo tem decisão clara e métrica de saúde. Sem instrumentação, você não sabe onde o pipeline degrada.

📊 Anthropic Contextual Retrieval (2024)

1

🎯 Top-k retrieval: quanto recuperar

Trade-off recall vs. ruído

k é o tamanho do top recuperado que vai pra geração. Trade-off: k baixo (3-5) tem alta precisão mas perde recall; k alto (20-50) capta tudo mas enche a janela e dispara lost-in-middle. Default: k=5 para gerar, k=50 para reranker. Otimize empiricamente no seu golden set.

trade-off k em RAG (cenário ilustrativo)
k para gerarRecall@kGroundednessCusto input
30.710.93
50.840.911.6×
100.920.85
200.960.78
500.990.6215×
📑 Resumo navegável
O que é: Pegar os k chunks mais similares à query. k=3-10 é o range típico para passar à geração.
Por que aprender: k baixo perde recall; k alto enche a janela de ruído (vide lost-in-the-middle). Otimize empiricamente.
Conceitos-chave: Recall@k, precision@k, k-tuning, MRR (mean reciprocal rank).
2

🏆 Reranker cross-encoder: precisão alta

BGE-reranker, Cohere Rerank

Retriever inicial (dual encoder) é rápido mas grosseiro. Reranker cross-encoder recebe (query, chunk) JUNTOS e produz score com mais precisão. Padrão: top-50 do retriever → reranker → top-5 final. Custo: 50-200ms a mais. Ganho: groundedness +5-15 pp consistente.

rerank com BGE-reranker
from sentence_transformers import CrossEncoder
rer = CrossEncoder('BAAI/bge-reranker-large')

candidatos = retriever.search(query, top_k=50)
pairs = [(query, c.text) for c in candidatos]
scores = rer.predict(pairs)

top5 = sorted(zip(candidatos, scores), key=lambda x: -x[1])[:5]
📑 Resumo navegável
O que é: Modelo cross-encoder que recebe (query, chunk) e retorna score. Mais preciso que dual encoder, mas mais lento.
Por que aprender: Padrão: recupera top-50 com encoder rápido, rankeia para top-5 com cross-encoder. Ganho consistente em recall.
Conceitos-chave: Cross-encoder vs dual encoder, BGE reranker, Cohere Rerank, latency budget.
3

🪄 Contextual retrieval (Anthropic 2024)

Embed com contexto do documento

Contextual retrieval (Anthropic, 2024): antes de embedar cada chunk, o LLM gera uma frase curta de contexto sobre o documento e prefixa no chunk. Resultado: chunks isolados deixam de perder referência. Anthropic reporta -49% em failure rate (recall@20) com contextual retrieval + reranker. Custo: 1 chamada LLM por chunk no index time (mitigado por prompt caching).

contextualizar antes de embedar
for doc in corpus:
    chunks = chunk_by_paragraph(doc.text)
    for chunk in chunks:
        contexto = llm_describe(
            f'<doc>{doc.text}</doc>\n<chunk>{chunk}</chunk>\n'
            'Em 1 frase: como este chunk se situa no doc?'
        )
        chunk_aumentado = f'{contexto}\n\n{chunk}'
        index.add(embed(chunk_aumentado), metadata={...})
📑 Resumo navegável
O que é: Antes de embedar cada chunk, injeta uma descrição curta do documento de onde veio. Reduz miss em 35-50% (Anthropic 2024).
Por que aprender: Chunk isolado perde contexto; com contexto, embedding fica mais informativo. Custo: chamada extra ao LLM por chunk no index time.
Conceitos-chave: Contextual retrieval, document-level prefix, prompt caching no index, late chunking.
4

📌 Citações obrigatórias na geração

Rastreabilidade da resposta

Sem citação explícita, você não sabe se o modelo grounded a resposta ou alucinou. Padrão: incluir IDs visíveis no contexto (<chunk id=42>...</chunk>) e instruir 'cite o id após cada afirmação'. Eval de groundedness depende disso para ser objetivo.

prompt com citação obrigatória
system = '''Responda APENAS com base no contexto fornecido.
Cite o id de cada chunk usado, no formato [chunk:42].
Se a resposta não está no contexto, diga: 'Não tenho informação suficiente'.
Não invente fatos.'''

ctx = '\n'.join(f'<chunk id={c.id}>{c.text}</chunk>' for c in top5)
📑 Resumo navegável
O que é: Padrão de incluir IDs/URLs dos chunks recuperados no prompt e exigir que a resposta cite a fonte de cada afirmação.
Por que aprender: Sem citação, você não sabe se o modelo grounded a resposta ou alucinou. Eval (T6) precisa de citação para medir groundedness.
Conceitos-chave: Citation patterns, source tracking, groundedness, attribution.
5

🚫 Saber dizer 'não sei'

Quando não há contexto suficiente

Modelos preferem responder algo a admitir ignorância — viés conhecido. Combate: instrução explícita 'se a resposta não está no contexto, diga não sei' + few-shot mostrando casos de abstenção. Sem isso, alucinação em RAG é a regra, não a exceção.

few-shot de abstenção
<exemplos>
  <ex>
    <ctx><chunk id=1>O céu é azul.</chunk></ctx>
    <q>Qual a cor do mar?</q>
    <a>Não tenho informação suficiente no contexto.</a>
  </ex>
  <ex>
    <ctx><chunk id=2>O mar é azul.</chunk></ctx>
    <q>Qual a cor do mar?</q>
    <a>O mar é azul [chunk:2].</a>
  </ex>
</exemplos>
📑 Resumo navegável
O que é: System prompt instrui: 'se a resposta não está no contexto, diga não sei'. Reduz alucinação.
Por que aprender: Modelos preferem responder algo a admitir ignorância. Instrução explícita + few-shot de 'não sei' equilibra.
Conceitos-chave: Abstention, calibration, hallucination, RAG-fail-safe.
6

♻️ Re-rankeio adaptativo: query rewriting

Query expansion e HyDE

Query do usuário é tipicamente curta e ambígua. Query rewriting expande sinônimos ou decompõe em sub-perguntas; HyDE (Gao et al. 2022) gera resposta hipotética com LLM e usa essa resposta como query — surpreendentemente eficaz para queries factuais. Custo: 1 chamada LLM extra; ganho: +5-10% em recall.

HyDE em uma chamada
def hyde_search(query: str, retriever) -> list:
    # 1. gera resposta hipotética (sem contexto)
    hipotetica = llm.generate(
        f'Responda em 2 frases (mesmo que invente): {query}'
    )
    # 2. usa a resposta como query (semanticamente mais rica)
    return retriever.search(hipotetica, top_k=10)
📑 Resumo navegável
O que é: Antes de buscar, o LLM reescreve/expande a query (sinônimos, sub-perguntas) ou gera resposta hipotética (HyDE) usada como query.
Por que aprender: Query do usuário é frequentemente curta e ambígua. Reescrita melhora recall sem custar muito.
Conceitos-chave: Query rewriting, HyDE (Gao et al. 2022), multi-query retrieval, query decomposition.

📑 Resumo navegável dos tópicos

1 🎯 Top-k retrieval: quanto recuperar — Trade-off recall vs. ruído
O que é: Pegar os k chunks mais similares à query. k=3-10 é o range típico para passar à geração.
Por que aprender: k baixo perde recall; k alto enche a janela de ruído (vide lost-in-the-middle). Otimize empiricamente.
Conceitos-chave: Recall@k, precision@k, k-tuning, MRR (mean reciprocal rank).
2 🏆 Reranker cross-encoder: precisão alta — BGE-reranker, Cohere Rerank
O que é: Modelo cross-encoder que recebe (query, chunk) e retorna score. Mais preciso que dual encoder, mas mais lento.
Por que aprender: Padrão: recupera top-50 com encoder rápido, rankeia para top-5 com cross-encoder. Ganho consistente em recall.
Conceitos-chave: Cross-encoder vs dual encoder, BGE reranker, Cohere Rerank, latency budget.
3 🪄 Contextual retrieval (Anthropic 2024) — Embed com contexto do documento
O que é: Antes de embedar cada chunk, injeta uma descrição curta do documento de onde veio. Reduz miss em 35-50% (Anthropic 2024).
Por que aprender: Chunk isolado perde contexto; com contexto, embedding fica mais informativo. Custo: chamada extra ao LLM por chunk no index time.
Conceitos-chave: Contextual retrieval, document-level prefix, prompt caching no index, late chunking.
4 📌 Citações obrigatórias na geração — Rastreabilidade da resposta
O que é: Padrão de incluir IDs/URLs dos chunks recuperados no prompt e exigir que a resposta cite a fonte de cada afirmação.
Por que aprender: Sem citação, você não sabe se o modelo grounded a resposta ou alucinou. Eval (T6) precisa de citação para medir groundedness.
Conceitos-chave: Citation patterns, source tracking, groundedness, attribution.
5 🚫 Saber dizer 'não sei' — Quando não há contexto suficiente
O que é: System prompt instrui: 'se a resposta não está no contexto, diga não sei'. Reduz alucinação.
Por que aprender: Modelos preferem responder algo a admitir ignorância. Instrução explícita + few-shot de 'não sei' equilibra.
Conceitos-chave: Abstention, calibration, hallucination, RAG-fail-safe.
6 ♻️ Re-rankeio adaptativo: query rewriting — Query expansion e HyDE
O que é: Antes de buscar, o LLM reescreve/expande a query (sinônimos, sub-perguntas) ou gera resposta hipotética (HyDE) usada como query.
Por que aprender: Query do usuário é frequentemente curta e ambígua. Reescrita melhora recall sem custar muito.
Conceitos-chave: Query rewriting, HyDE (Gao et al. 2022), multi-query retrieval, query decomposition.

✓ O que FAZER

  • Reranker cross-encoder após top-50 inicial
  • Citação obrigatória + ID visível no prompt
  • Instrução 'diga não sei' + few-shot de abstenção
  • Eval de groundedness no harness

✗ O que NÃO fazer

  • Passar top-50 direto à geração
  • Esperar que o modelo 'use' o contexto sem citar
  • Sempre forçar uma resposta
  • Confiar que 'a resposta parece boa'

🚫 Quando NÃO usar

💻 Exemplo de código

from fec_sdk import Message, MessageRole
from fec_sdk.adapters import get_adapter
from fec_sdk.retrieval import HybridRetriever, CrossEncoderReranker  # pseudo

retriever = HybridRetriever(vector_store, bm25)
reranker = CrossEncoderReranker(model="bge-reranker-large")

def responder(query: str) -> dict:
    candidatos = retriever.search(query, k=50)        # top-50
    top5 = reranker.rerank(query, candidatos, k=5)    # top-5

    contexto = "\n\n".join(
        f"<chunk id={c.id}>{c.text}</chunk>" for c in top5
    )
    system = (
        "Responda APENAS com base no contexto. "
        "Cite o id de cada chunk usado, ex.: [chunk:42]. "
        "Se a resposta não está no contexto, diga 'não sei'."
    )
    client = get_adapter("mock")
    resp = client.chat([
        Message(role=MessageRole.SYSTEM, content=system),
        Message(role=MessageRole.USER, content=f"{contexto}\n\n{query}"),
    ])
    return {"answer": resp.content, "citations": [c.id for c in top5]}

🏋️ Exercício hands-on

Use o índice de 3.1 + reranker para responder 30 perguntas do golden FEC-GS-RAG-v1 com groundedness ≥0.85. Implementação em exercicios/modulo-3-2/.

📚 Bibliografia

🎯 Resumo do Módulo

  • Top-k: 3-10 é o range; meça empiricamente.
  • Reranker cross-encoder após top-50 dá ganho consistente.
  • Contextual retrieval (Anthropic 2024): -35-49% em failure rate.
  • Citação obrigatória é pré-requisito de eval de groundedness.
  • Saber dizer 'não sei' é parte do design, não exceção.

Próximo Módulo:

RAG agêntico e self-RAG (beta)