Phase 11 - Lesson 07
RAG Avançado (Chunking, Reranking, Busca Híbrida)
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
O RAG básico recupera os top-k chunks mais semelhantes. Isso funciona para perguntas simples. No entanto, falha para raciocínio multi-hop (múltiplas etapas), consultas ambíguas e grandes corpora. O RAG avançado é a diferença entre uma demonstração que funciona em 10 documentos e um sistema que funciona em 10 milhões.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 11, Lição 06 (RAG) Tempo: ~90 minutos Relacionado: A Fase 5 · 23 (Estratégias de Chunking para RAG) cobre todos os seis algoritmos de chunking — recursivo, semântico, sentença, documento-pai, late chunking, recuperação contextual — com benchmarks da Vectara/Anthropic. Esta lição constrói sobre essa base: busca híbrida, reranking, transformação de consultas.
Objetivos de Aprendizado
- Implementar estratégias avançadas de chunking (semântico, recursivo, pai-filho) que preservem a estrutura e o contexto do documento
- Construir um pipeline de busca híbrida combinando a correspondência de palavras-chave do BM25 com busca vetorial semântica e um reranker cross-encoder
- Aplicar técnicas de transformação de consultas (HyDE, multi-query, step-back) para melhorar a recuperação em perguntas ambíguas ou complexas
- Diagnosticar e corrigir falhas comuns de RAG: chunk incorreto recuperado, resposta ausente no contexto, quebra no raciocínio multi-hop
O Problema
Você construiu um pipeline RAG básico na Lição 06. Ele funciona para perguntas diretas em um corpus pequeno. Agora tente estes casos:
Consulta ambígua: "What was revenue last quarter?" A busca semântica retorna chunks sobre estratégia de receita, projeções de receita e os pensamentos do CFO sobre o crescimento da receita. Todos semanticamente semelhantes à palavra "receita". Nenhum contendo o número real. O chunk correto diz "$47.2M em Q3 2025", mas usa a palavra "lucros" (earnings) em vez de "receita". O modelo de embedding acha que "estratégia de receita" está mais próximo da consulta do que "os lucros do Q3 foram de $47.2M".
Pergunta multi-hop: "Which team had the highest customer satisfaction score improvement?" Isso requer encontrar as pontuações de satisfação de cada equipe, compará-las e identificar a máxima. Nenhum chunk sozinho contém a resposta. As informações estão espalhadas pelos relatórios das equipes.
Problema de corpus grande: Você tem 2 milhões de chunks. A resposta correta está no chunk nº 1.847.293. Sua recuperação top-5 traz os chunks nº 14, nº 89.201, nº 1.200.000, nº 44 e nº 901.333. Próximos no espaço de embedding, mas nenhum contém a resposta. Nessa escala, a busca de vizinhos mais próximos aproximados (ANN) introduz erro suficiente para que os resultados relevantes sejam empurrados para fora do top-k.
O RAG básico falha porque a similaridade vetorial não é o mesmo que relevância. Um chunk pode ser semanticamente semelhante a uma consulta sem ser útil para respondê-la. O RAG avançado resolve isso com quatro técnicas: busca híbrida (adiciona correspondência de palavras-chave), reranking (pontua os candidatos com mais critério), transformação de consultas (corrige a consulta antes da busca) e melhor chunking (recupera na granularidade certa).
O Conceito
Busca Híbrida: Semântica + Palavra-chave
A busca semântica (similaridade vetorial) é boa para entender o significado. "How do I cancel my subscription?" corresponde a "Passos para encerrar seu plano", mesmo que não compartilhem nenhuma palavra. Mas ela perde correspondências exatas. O "código de erro E-4021" pode não corresponder a um chunk contendo "E-4021" se o modelo de embedding tratá-lo como ruído.
A busca por palavra-chave (BM25) é o oposto. Ela se destaca em correspondências exatas. "E-4021" corresponde perfeitamente. Mas "cancelar minha assinatura" retorna zero resultados se o documento disser "encerrar seu plano".
A busca híbrida executa ambas e depois combina os resultados.
O BM25 (Best Matching 25) é o algoritmo padrão de busca por palavra-chave. Tem sido a espinha dorsal dos motores de busca desde os anos 1990. A fórmula:
BM25(q, d) = sum over terms t in q:
IDF(t) * (tf(t,d) * (k1 + 1)) / (tf(t,d) + k1 * (1 - b + b * |d| / avgdl))
Onde tf(t,d) é a frequência do termo t no documento d, IDF(t) é a frequência inversa do documento, |d| é o comprimento do documento, avgdl é o comprimento médio do documento, k1 controla a saturação da frequência do termo (padrão 1.2) e b controla a normalização de comprimento (padrão 0.75).
Em termos simples: o BM25 pontua os documentos mais alto quando eles contêm termos da consulta (especialmente os raros), mas com retornos decrescentes para termos repetidos. Um documento com a palavra "receita" 50 vezes não é 50 vezes mais relevante do que um que a contém apenas uma vez.
Fusão de Ranks Recíprocos (RRF)
Você tem duas listas ranqueadas: uma da busca vetorial e outra do BM25. Como você as combina? A Fusão de Ranks Recíprocos (Reciprocal Rank Fusion) é a abordagem padrão.
RRF_score(d) = sum over rankings R:
1 / (k + rank_R(d))
Onde k é uma constante (geralmente 60) que impede que o resultado melhor posicionado domine.
Um documento ranqueado em 1º lugar na busca vetorial e em 5º lugar no BM25 recebe: 1/(60+1) + 1/(60+5) = 0,0164 + 0,0154 = 0,0318
Um documento ranqueado em 3º lugar na busca vetorial e em 2º lugar no BM25 recebe: 1/(60+3) + 1/(60+2) = 0,0159 + 0,0161 = 0,0320
O RRF equilibra naturalmente os dois sinais. Um documento que ranqueia bem em ambas as listas obtém a melhor pontuação. Um documento que ranqueia em 1º lugar em uma lista, mas está ausente na outra, recebe uma pontuação moderada. Isso é robusto porque utiliza posições (ranks), não as pontuações brutas (raw scores), fazendo com que as diferenças nas distribuições de pontuação entre os dois sistemas não importem.
Reranking
A recuperação (seja vetorial, por palavra-chave ou híbrida) é rápida, mas imprecisa. Ela usa bi-encoders: a consulta e cada documento são embutidos (embedded) de forma independente e depois comparados. Os embeddings são computados uma vez e armazenados em cache. Isso escala para milhões de documentos.
O reranking usa cross-encoders: a consulta e um documento candidato são alimentados juntos em um modelo que gera uma pontuação de relevância. O modelo analisa ambos os textos simultaneamente e consegue capturar interações detalhadas entre eles. Um cross-encoder consegue entender que "Quais foram os lucros do Q3?" é altamente relevante para um chunk contendo "$47.2M no Q3", mesmo que um bi-encoder tenha perdido essa conexão.
O trade-off: os cross-encoders são de 100 a 1000 vezes mais lentos que os bi-encoders porque processam o par consulta-documento de forma conjunta. Não é possível pré-computar pontuações de cross-encoder para um milhão de documentos. A solução: recuperar um conjunto maior de candidatos (top-50 da busca híbrida) e depois reranquear com um cross-encoder para obter o top-5 final.
graph LR
Q["Consulta"] --> H["Busca Híbrida"]
H --> C50["Top 50 candidatos"]
C50 --> RR["Reranker Cross-Encoder"]
RR --> C5["Top 5 resultados finais"]
C5 --> P["Construir prompt"]
P --> LLM["Gerar resposta"]
Modelos comuns de reranking (portfólio de 2026):
- Cohere Rerank 3.5: API gerenciada, multilíngue, melhor ganho de recall em corpora mistos
- Voyage rerank-2.5: API gerenciada, menor latência entre as opções hospedadas
- Jina-Reranker-v2 Multilingual: pesos abertos (open-weight), mais de 100 idiomas
- bge-reranker-v2-m3: pesos abertos, linha de base (baseline) sólida
- cross-encoder/ms-marco-MiniLM-L-6-v2: pesos abertos, roda em CPU para prototipagem
- ColBERTv2 / Jina-ColBERT-v2: rerankers multi-vetor de interação tardia (late-interaction) — O(tokens) e não O(docs) no momento da pontuação
Transformação de Consultas
Às vezes, o problema não é a recuperação, mas a própria consulta. "Qual era aquela coisa sobre a nova mudança de política?" é uma péssima consulta de busca. Não contém termos específicos. O embedding é vago. Nenhum sistema de recuperação consegue encontrar os documentos certos a partir disso.
Reescrita de consulta: reformular a consulta do usuário em uma consulta de busca melhor. Um LLM pode fazer isso:
User: "What was that thing about the new policy change?"
Rewritten: "Recent policy changes and updates"
HyDE (Hypothetical Document Embeddings): em vez de buscar com a consulta, gera-se uma resposta hipotética, faz-se o embedding dela e busca-se por documentos reais semelhantes.
Query: "What is the refund policy for enterprise?"
Hypothetical answer: "Enterprise customers are eligible for a full refund
within 60 days of purchase. Refunds are pro-rated based on the remaining
subscription period and processed within 5-7 business days."
Faça o embedding da resposta hipotética e busque por documentos reais semelhantes a ela. A intuição: a resposta hipotética vive mais próxima no espaço de embedding da resposta real do que a pergunta original. Perguntas e respostas têm estruturas linguísticas diferentes. Ao gerar uma resposta hipotética, você faz a ponte entre o "espaço da pergunta" e o "espaço da resposta" no embedding.
O HyDE adiciona uma chamada de LLM antes da recuperação. Isso aumenta a latência em 500-2000ms. Vale a pena quando a qualidade da recuperação é ruim em consultas brutas.
Fatiamento Pai-Filho (Parent-Child Chunking)
O chunking padrão força um trade-off: chunks pequenos para recuperação precisa, chunks grandes para contexto suficiente. O chunking pai-filho elimina esse trade-off.
Indexe chunks pequenos (128 tokens) para recuperação. Quando um chunk pequeno for recuperado, retorne seu chunk pai (512 tokens) para o prompt. O chunk pequeno corresponde à consulta com precisão. O chunk pai fornece contexto suficiente para o LLM gerar uma boa resposta.
graph TD
P["Chunk pai (512 tokens)<br/>Seção completa sobre a política de reembolso"]
C1["Chunk filho (128 tokens)<br/>Plano padrão: reembolso de 30 dias"]
C2["Chunk filho (128 tokens)<br/>Enterprise: 60 dias pro-rata"]
C3["Chunk filho (128 tokens)<br/>Tempo de processamento: 5-7 dias"]
C4["Chunk filho (128 tokens)<br/>Como enviar uma solicitação"]
P --> C1
P --> C2
P --> C3
P --> C4
Q["Consulta: reembolso enterprise?"] -.->|"corresponde ao filho"| C2
C2 -.->|"retorna o pai"| P
O termo de busca "reembolso enterprise?" corresponde exatamente ao chunk filho C2. Mas o prompt recebe o chunk pai P completo, que inclui o contexto ao redor sobre o tempo de processamento e o processo de envio.
Filtragem por Metadados
Antes de executar a busca vetorial, filtre o corpus por metadados: data, fonte, categoria, autor, idioma. Isso reduz o espaço de busca e evita resultados irrelevantes.
"O que mudou na política de segurança no mês passado?" deve buscar apenas documentos dos últimos 30 dias na categoria de segurança. Sem a filtragem por metadados, você busca em todo o corpus e pode recuperar um documento de segurança de 2 anos atrás que por acaso é semanticamente semelhante.
Sistemas RAG em produção armazenam metadados ao lado de cada chunk: documento de origem, data de criação, categoria, autor, versão. Os bancos de dados vetoriais suportam pré-filtragem por metadados antes da busca por segurança, o que é crítico para o desempenho em escala.
Avaliação
Você construiu um sistema RAG. Como saber se ele funciona? Três métricas:
Relevância da recuperação (Recall@k): para um conjunto de perguntas de teste com documentos relevantes conhecidos, qual porcentagem de documentos relevantes aparece nos top-k resultados? Se a resposta para uma pergunta está no chunk nº 47, o chunk nº 47 aparece no top-5?
Fidelidade (Faithfulness): a resposta gerada é fundamentada nos documentos recuperados? Se os chunks recuperados dizem "janela de reembolso de 60 dias" e o modelo diz "janela de reembolso de 90 dias", isso é uma falha de fidelidade. O modelo alucinou apesar de ter o contexto correto.
Correção da resposta (Answer correctness): a resposta gerada corresponde à resposta esperada? Esta é a métrica ponta a ponta. Ela combina a qualidade da recuperação e a qualidade da geração.
Uma verificação simples de fidelidade: pegue cada afirmação na resposta gerada e verifique se ela aparece (em substância) nos chunks recuperados. Se a resposta contiver um fato que não está em nenhum chunk recuperado, provavelmente foi alucinado.
graph TD
subgraph "Framework de Avaliação"
Q["Perguntas de teste<br/>+ respostas esperadas<br/>+ IDs de docs relevantes"]
Q --> Ret["Avaliação de recuperação<br/>Recall@k: os docs corretos<br/>foram recuperados?"]
Q --> Faith["Avaliação de fidelidade<br/>A resposta é fundamentada<br/>nos docs recuperados?"]
Q --> Correct["Avaliação de correção<br/>A resposta corresponde<br/>à resposta esperada?"]
end
Mão na Massa
Passo 1: Implementação do BM25
import math
from collections import Counter
class BM25:
def __init__(self, k1=1.2, b=0.75):
self.k1 = k1
self.b = b
self.docs = []
self.doc_lengths = []
self.avg_dl = 0
self.doc_freqs = {}
self.n_docs = 0
def index(self, documents):
self.docs = documents
self.n_docs = len(documents)
self.doc_lengths = []
self.doc_freqs = {}
for doc in documents:
words = doc.lower().split()
self.doc_lengths.append(len(words))
unique_words = set(words)
for word in unique_words:
self.doc_freqs[word] = self.doc_freqs.get(word, 0) + 1
self.avg_dl = sum(self.doc_lengths) / self.n_docs if self.n_docs else 1
def score(self, query, doc_idx):
query_words = query.lower().split()
doc_words = self.docs[doc_idx].lower().split()
doc_len = self.doc_lengths[doc_idx]
word_counts = Counter(doc_words)
score = 0.0
for term in query_words:
if term not in word_counts:
continue
tf = word_counts[term]
df = self.doc_freqs.get(term, 0)
idf = math.log((self.n_docs - df + 0.5) / (df + 0.5) + 1)
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avg_dl)
score += idf * numerator / denominator
return score
def search(self, query, top_k=10):
scores = [(i, self.score(query, i)) for i in range(self.n_docs)]
scores.sort(key=lambda x: x[1], reverse=True)
return scores[:top_k]
Passo 2: Fusão de Ranks Recíprocos
def reciprocal_rank_fusion(ranked_lists, k=60):
scores = {}
for ranked_list in ranked_lists:
for rank, (doc_id, _) in enumerate(ranked_list):
if doc_id not in scores:
scores[doc_id] = 0.0
scores[doc_id] += 1.0 / (k + rank + 1)
fused = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return fused
Passo 3: Pipeline de Busca Híbrida
def hybrid_search(query, chunks, vector_embeddings, vocab, idf, bm25_index, top_k=5, fusion_k=60):
query_emb = tfidf_embed(query, vocab, idf)
vector_results = search(query_emb, vector_embeddings, top_k=top_k * 3)
bm25_results = bm25_index.search(query, top_k=top_k * 3)
fused = reciprocal_rank_fusion([vector_results, bm25_results], k=fusion_k)
return fused[:top_k]
Passo 4: Reranker Simples
Em produção, você usaria um modelo cross-encoder. Aqui, construímos um reranker que pontua a relevância consulta-documento usando sobreposição de palavras, importância de termos e correspondência de frases.
def rerank(query, candidates, chunks):
query_words = set(query.lower().split())
stop_words = {"the", "a", "an", "is", "are", "was", "were", "what", "how",
"why", "when", "where", "do", "does", "for", "of", "in", "to",
"and", "or", "on", "at", "by", "it", "its", "this", "that",
"with", "from", "be", "has", "have", "had", "not", "but"}
query_terms = query_words - stop_words
scored = []
for doc_id, initial_score in candidates:
chunk = chunks[doc_id].lower()
chunk_words = set(chunk.split())
term_overlap = len(query_terms & chunk_words)
query_bigrams = set()
q_list = [w for w in query.lower().split() if w not in stop_words]
for i in range(len(q_list) - 1):
query_bigrams.add(q_list[i] + " " + q_list[i + 1])
bigram_matches = sum(1 for bg in query_bigrams if bg in chunk)
position_boost = 0
for term in query_terms:
pos = chunk.find(term)
if pos != -1 and pos < len(chunk) // 3:
position_boost += 0.5
rerank_score = (
term_overlap * 1.0
+ bigram_matches * 2.0
+ position_boost
+ initial_score * 5.0
)
scored.append((doc_id, rerank_score))
scored.sort(key=lambda x: x[1], reverse=True)
return scored
Passo 5: HyDE (Hypothetical Document Embeddings)
def hyde_generate_hypothesis(query):
templates = {
"what": "The answer to '{query}' is as follows: Based on our documentation, {topic} involves specific policies and procedures that define how the process works.",
"how": "To address '{query}': The process involves several steps. First, you need to initiate the request. Then, the system processes it according to the defined rules.",
"default": "Regarding '{query}': Our records indicate specific details and policies related to this topic that provide a comprehensive answer."
}
query_lower = query.lower()
if query_lower.startswith("what"):
template = templates["what"]
elif query_lower.startswith("how"):
template = templates["how"]
else:
template = templates["default"]
topic_words = [w for w in query.lower().split()
if w not in {"what", "is", "the", "how", "do", "does", "a", "an",
"for", "of", "to", "in", "on", "at", "by", "and", "or"}]
topic = " ".join(topic_words) if topic_words else "this topic"
return template.format(query=query, topic=topic)
def hyde_search(query, chunks, vector_embeddings, vocab, idf, top_k=5):
hypothesis = hyde_generate_hypothesis(query)
hypothesis_emb = tfidf_embed(hypothesis, vocab, idf)
results = search(hypothesis_emb, vector_embeddings, top_k)
return results, hypothesis
Passo 6: Fatiamento Pai-Filho
def create_parent_child_chunks(text, parent_size=200, child_size=50):
words = text.split()
parents = []
children = []
child_to_parent = {}
parent_idx = 0
start = 0
while start < len(words):
parent_end = min(start + parent_size, len(words))
parent_text = " ".join(words[start:parent_end])
parents.append(parent_text)
child_start = start
while child_start < parent_end:
child_end = min(child_start + child_size, parent_end)
child_text = " ".join(words[child_start:child_end])
child_idx = len(children)
children.append(child_text)
child_to_parent[child_idx] = parent_idx
child_start += child_size
parent_idx += 1
start += parent_size
return parents, children, child_to_parent
Passo 7: Avaliação de Fidelidade
def evaluate_faithfulness(answer, retrieved_chunks):
answer_sentences = [s.strip() for s in answer.split(".") if len(s.strip()) > 10]
if not answer_sentences:
return 1.0, []
grounded = 0
ungrounded = []
context = " ".join(retrieved_chunks).lower()
for sentence in answer_sentences:
words = set(sentence.lower().split())
stop_words = {"the", "a", "an", "is", "are", "was", "were", "and", "or",
"to", "of", "in", "for", "on", "at", "by", "it", "this", "that"}
content_words = words - stop_words
if not content_words:
grounded += 1
continue
matched = sum(1 for w in content_words if w in context)
ratio = matched / len(content_words) if content_words else 0
if ratio >= 0.5:
grounded += 1
else:
ungrounded.append(sentence)
score = grounded / len(answer_sentences) if answer_sentences else 1.0
return score, ungrounded
def evaluate_retrieval_recall(queries_with_relevant, retrieval_fn, k=5):
total_recall = 0.0
results = []
for query, relevant_indices in queries_with_relevant:
retrieved = retrieval_fn(query, k)
retrieved_indices = set(idx for idx, _ in retrieved)
relevant_set = set(relevant_indices)
hits = len(retrieved_indices & relevant_set)
recall = hits / len(relevant_set) if relevant_set else 1.0
total_recall += recall
results.append({
"query": query,
"recall": recall,
"hits": hits,
"total_relevant": len(relevant_set)
})
avg_recall = total_recall / len(queries_with_relevant) if queries_with_relevant else 0
return avg_recall, results
Como Usar
Com um cross-encoder real para reranking:
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank_with_cross_encoder(query, candidates, chunks, top_k=5):
pairs = [(query, chunks[doc_id]) for doc_id, _ in candidates]
scores = reranker.predict(pairs)
scored = list(zip([doc_id for doc_id, _ in candidates], scores))
scored.sort(key=lambda x: x[1], reverse=True)
return scored[:top_k]
Com o reranker gerenciado da Cohere:
import cohere
co = cohere.Client()
def rerank_with_cohere(query, candidates, chunks, top_k=5):
docs = [chunks[doc_id] for doc_id, _ in candidates]
response = co.rerank(
model="rerank-english-v3.0",
query=query,
documents=docs,
top_n=top_k
)
return [(candidates[r.index][0], r.relevance_score) for r in response.results]
Para HyDE com um LLM real:
import anthropic
client = anthropic.Anthropic()
def hyde_with_llm(query):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=256,
messages=[{
"role": "user",
"content": f"Write a short paragraph that would be a good answer to this question. Do not say you don't know. Just write what the answer would look like.\n\nQuestion: {query}"
}]
)
return response.content[0].text
Para busca híbrida em produção com Weaviate:
import weaviate
client = weaviate.connect_to_local()
collection = client.collections.get("Documents")
response = collection.query.hybrid(
query="enterprise refund policy",
alpha=0.5,
limit=10
)
O parâmetro alpha controla o equilíbrio: 0.0 = palavra-chave pura (BM25), 1.0 = vetor puro, 0.5 = peso igual. A maioria dos sistemas em produção usa alpha entre 0.3 e 0.7.
Entregáveis
Esta lição produz:
outputs/prompt-advanced-rag-debugger.md-- um prompt para diagnosticar e corrigir problemas de qualidade de RAGoutputs/skill-advanced-rag.md-- uma skill para construir RAG de nível de produção com busca híbrida e reranking
Exercícios
Compare BM25 vs busca vetorial vs busca híbrida nos documentos de exemplo. Para cada uma das 5 consultas de teste, registre qual abordagem retorna o chunk mais relevante na posição nº 1. A busca híbrida deve vencer em pelo menos 3 de 5.
Implemente um filtro de metadados. Adicione um campo "category" (categoria) a cada documento (security, billing, api, product). Antes de executar a busca vetorial, filtre os chunks apenas para a categoria relevante. Teste com "What encryption is used?" e verifique se ele pesquisa apenas chunks da categoria security.
Construa um pipeline HyDE completo usando a função simples de geração da Lição 06. Compare a qualidade de recuperação (relevância top-3) entre a busca direta por consulta e a busca HyDE em todas as 5 consultas de teste. O HyDE deve melhorar os resultados para consultas vagas.
Implemente a estratégia de chunking pai-filho nos documentos de exemplo. Use child_size=30 e parent_size=100. Busque com chunks filhos, mas retorne os chunks pais no prompt. Compare as respostas geradas com o chunking padrão com chunk_size=50.
Crie um conjunto de dados de avaliação: 10 perguntas com chunks de respostas conhecidos. Meça Recall@3, Recall@5 e Recall@10 para (a) apenas busca vetorial, (b) apenas BM25, (c) busca híbrida, (d) híbrida + reranking. Plote os resultados e identifique onde o reranking ajuda mais.
Termos-Chave
| Termo | Lo que diz a gente | O que realmente significa |
|---|---|---|
| BM25 | "Busca por palavra-chave" | Um algoritmo probabilístico de ranqueamento que pontua documentos com base na frequência do termo, frequência inversa do documento e normalização do comprimento do documento |
| Busca híbrida | "O melhor dos dois mundos" | Execução paralela de busca semântica (vetorial) e por palavra-chave (BM25), fundindo os resultados com fusão de ranks |
| Fusão de Ranks Recíprocos | "Mesclar listas ranqueadas" | Combinação de múltiplas listas ranqueadas somando 1/(k + rank) para cada documento em todas as listas |
| Reranking | "Pontuação de segunda passagem" | Uso de um modelo cross-encoder mais caro para repontuar um conjunto de candidatos da recuperação inicial |
| Cross-encoder | "Modelo conjunto de consulta-documento" | Um modelo que recebe uma consulta e um documento como entrada única, gerando uma pontuação de relevância; mais preciso que bi-encoders, mas lento demais para busca em todo o corpus |
| Bi-encoder | "Modelo de embedding independente" | Um modelo que gera embeddings de consultas e documentos de forma independente; rápido porque os embeddings são pré-computados, mas menos preciso que cross-encoders |
| HyDE | "Buscar com uma resposta falsa" | Geração de uma resposta hipotética para a consulta, geração de seu embedding e busca por documentos reais semelhantes a ela |
| Chunking pai-filho | "Busca pequena, contexto grande" | Indexação de chunks pequenos para recuperação precisa, mas retorno do chunk pai maior para fornecer contexto suficiente |
| Filtragem por metadados | "Estreitar antes de buscar" | Filtragem de documentos por atributos (data, fonte, categoria) antes de executar a busca vetorial para reduzir o espaço de busca |
| Fidelidade | "Permaneceu fundamentado" | Se a resposta gerada é apoiada pelos documentos recuperados, em oposição a ser alucinada a partir dos dados de treinamento do modelo |
Leituras Adicionais
- Robertson & Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" (2009) -- a referência definitiva para BM25, explicando os fundamentos probabilísticos por trás da fórmula
- Cormack et al., "Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods" (2009) -- o artigo original sobre RRF mostrando que ele supera métodos de fusão mais complexos
- Gao et al., "Precise Zero-Shot Dense Retrieval without Relevance Labels" (2022) -- o artigo sobre HyDE demonstrando que embeddings de documentos hipotéticos melhoram a recuperação sem nenhum dado de treinamento
- Nogueira & Cho, "Passage Re-ranking with BERT" (2019) -- mostrou que o reranking com cross-encoders sobre o BM25 melhora significativamente a qualidade da recuperação
- Khattab et al., "DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines" (2023) -- trata a construção de prompts e a seleção de pesos como um problema de otimização sobre pipelines de recuperação; leia isto para "programar LLMs" em vez de "criar prompts para LLMs".
- Edge et al., "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" (Microsoft Research 2024) -- artigo sobre GraphRAG: extração de entidade-relação + detecção de comunidade Leiden para sumarização focada em consultas; a distinção entre recuperação global vs local.
- Asai et al., "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection" (ICLR 2024) -- RAG autoavaliativo com tokens de reflexão; a fronteira dos agentes além do modelo estático recuperar-depois-gerar.
- LangChain Query Construction blog -- como traduzir consultas de linguagem natural em consultas estruturadas de banco de dados (Text-to-SQL, Cypher) como uma etapa prévia de recuperação.