Apresentação
📚 RAG — Retrieval Augmented Generation (Guia Aprofundado)
O problema que RAG resolve
LLMs têm conhecimento "congelado" no treino. Não sabem factos recentes, dados internos da empresa, ou documentos específicos. RAG resolve isto: em vez de depender só dos parâmetros do modelo, o sistema recupera documentos relevantes de uma base externa e injeta no prompt antes da geração.
Benefícios: reduz hallucinations, mantém respostas atualizadas, permite conhecimento privado sem fine-tuning.
1. Pipeline completo: Ingestão vs Query
Pipeline de Ingestão (offline/batch)
Documentos (PDF, HTML, Markdown, DB)
→ Parsing (extrair texto, tabelas, código)
→ Limpeza (normalização, remoção de ruído)
→ Chunking (dividir em pedaços)
→ Embedding (text → vector)
→ Vector DB (indexar com metadata)
Pipeline de Query (online)
Query do utilizador
→ (opcional) Query expansion / HyDE
→ Embedding da query
→ Retrieval (vector search, BM25, ou híbrido)
→ (opcional) Reranking
→ Context builder (formatar chunks para prompt)
→ LLM (gerar resposta)
2. Chunking — Técnicas detalhadas
2.1 Fixed-size com overlap (sliding window)
- Como: Dividir por número de caracteres/tokens com sobreposição (ex: 512 tokens, overlap 50).
- Prós: Simples, previsível, funciona bem para texto homogéneo.
- Contras: Pode cortar frases/parágrafos; informação importante nos limites perde-se.
- Overlap: 10–20% do chunk size evita perda de contexto nas fronteiras.
2.2 Semantic chunking
- Como: Usar delimitadores semânticos (parágrafos, secções, sentenças). Ou: embeddings para detectar mudanças de tópico — quando a similaridade entre sentenças consecutivas cai, novo chunk.
- Prós: Chunks coerentes, respeitam estrutura do documento.
- Contras: Tamanhos variáveis; mais complexo de implementar.
- Ferramentas: LlamaIndex
SentenceSplitter, LangChainRecursiveCharacterTextSplittercomseparators=["\n\n", "\n", ". ", " "].
2.3 Recursive character splitter
- Como: Tentar dividir por
\n\n, depois\n, depois., depois. - Prós: Adapta-se ao tipo de documento; mantém estrutura quando possível.
- Contras: Pode produzir chunks muito pequenos ou grandes em edge cases.
2.4 Parent-child (small-to-large)
- Como: Criar chunks pequenos para retrieval (ex: 256 tokens) e chunks maiores como "parent" (ex: 1024 tokens). Retrieval usa small chunks; devolve parent ao LLM.
- Prós: Retrieval mais preciso (chunks pequenos); contexto mais rico (parent).
- Contras: Duplicação de embeddings; mais armazenamento.
2.5 Chunk size — trade-offs
| Tamanho | Retrieval | Contexto no LLM |
|---|---|---|
| Pequeno (128–256) | Mais preciso, mais hits | Menos contexto por chunk |
| Médio (512) | Equilíbrio | Equilíbrio |
| Grande (1024+) | Menos preciso | Mais contexto, risco de dilution |
Regra prática: 256–512 tokens para retrieval; expandir com parent se necessário.
3. Embeddings
3.1 Modelos comuns
| Modelo | Dimensões | Multilingual | Latência | Uso típico |
|---|---|---|---|---|
| text-embedding-3-small | 1536 | Sim | Baixa | Produção, custo |
| text-embedding-3-large | 3072 | Sim | Média | Qualidade |
| BGE, E5, GTE (open) | 768–1024 | Varia | Baixa | Self-hosted |
| Cohere embed-v3 | 1024 | Sim | Média | Produção |
3.2 Normalização
- Cosine similarity: Embeddings devem ser normalizados (L2) para cosine = dot product.
- Sem normalização: Usar dot product ou inner product; alguns modelos já vêm normalizados.
3.3 Dimensão e compressão
- Dimensões maiores = mais expressividade, mais custo e armazenamento.
- Matryoshka embeddings: Treinar com dimensão alta, usar truncado (ex: 1024→256) para retrieval mais rápido com pouca perda.
4. Vector Search — ANN e algoritmos
4.1 Exact vs Approximate (ANN)
- Exact (brute force): Compara query com todos os vetores. O(n). Só para <100k vetores.
- ANN (Approximate Nearest Neighbour): Sacrifica precisão por velocidade. O(log n) ou O(√n).
4.2 HNSW (Hierarchical Navigable Small World)
- Grafos com múltiplas camadas; busca em camada superior (poucos nós), depois desce.
- Parâmetros:
ef_construction(qualidade do índice),ef_search(qualidade da busca). - Prós: Muito rápido, boa recall. Usado em Qdrant, Weaviate, Pinecone, Milvus.
- Contras: Memória; índices estáticos (reindex para updates).
4.3 IVF (Inverted File Index)
- K-means para agrupar vetores em clusters. Query busca nos clusters mais próximos.
- Prós: Escalável, eficiente.
- Contras: Recall pode ser inferior a HNSW para mesmo tempo.
4.4 Quantização
- Scalar (PQ): Comprimir vetores para menos bits. Reduz memória e acelera.
- Trade-off: 10–20% de recall por 4–8× menos memória.
5. Hybrid Search — BM25 + Dense
5.1 BM25 (sparse retrieval)
- TF-IDF evoluído: Pondera term frequency e inverse document frequency.
score = IDF * (TF * (k1+1)) / (TF + k1 * (1-b + b*|D|/avgdl)). - Prós: Excelente para exact match (nomes, IDs, termos técnicos). Não precisa de embeddings.
- Contras: Zero para synonyms; não entende semântica.
5.2 Reciprocal Rank Fusion (RRF)
- Como:
score_RRF(d) = Σ 1/(k + rank_i(d))onderank_ié o rank em cada lista (dense, BM25). - k típico: 60. Combina rankings sem normalizar scores.
- Prós: Simples, robusto, funciona bem na prática.
5.3 Quando usar hybrid
- Só dense: Domínio muito semântico, queries em linguagem natural.
- Hybrid: Nomes próprios, IDs, termos técnicos (ex: "HNSW", "ReAct"), documentação com keywords.
6. Reranking
6.1 Cross-encoder vs Bi-encoder
| Bi-encoder | Cross-encoder | |
|---|---|---|
| Input | Query e doc separados | Par (query, doc) concatenado |
| Uso | Retrieval (rápido) | Reranking (lento, preciso) |
| Custo | 1 embedding por doc | 1 forward por par (query, doc) |
6.2 Pipeline típico
- Retrieve 50–100 chunks (bi-encoder ou hybrid).
- Rerank com cross-encoder → top 5–10.
- Passar ao LLM.
6.3 Modelos de reranking
- Cohere rerank, BGE reranker, ms-marco-MiniLM (open).
- Latência: 10–50 ms por doc. Só rerankear top-k para manter latência aceitável.
6.4 Quando reranking vale a pena
- Retrieval inicial com recall alto mas precisão média.
- Quando o LLM tem contexto limitado — melhor 5 chunks excelentes que 20 medianos.
7. Query expansion e reformulação
7.1 Multi-query
- Gerar 3–5 variações da query com LLM; buscar para cada; unir resultados (RRF).
- Prós: Aumenta recall; cobre diferentes formulações.
- Contras: Custo (embeddings × N); latência.
7.2 HyDE (Hypothetical Document Embeddings)
- Como: LLM gera um "documento hipotético" que responderia à query; embed desse doc; buscar documentos similares ao hipotético.
- Ideia: Documentos relevantes são mais parecidos com uma resposta ideal que com a query.
- Prós: Melhora retrieval em muitos benchmarks.
- Contras: Custo extra (1 chamada LLM + embedding).
7.3 Query rewriting
- Reformular query para ser mais específica ou expandir abreviações.
- Ex: "RAG" → "Retrieval Augmented Generation, document retrieval, vector search".
8. Arquiteturas RAG — Tipos detalhados
8.1 Naive RAG
- Query → embed → vector search → top-k → prompt → LLM.
- Quando: Protótipo, POC, dados simples.
8.2 Hybrid RAG
- BM25 + dense; RRF para combinar.
- Quando: Produção com termos técnicos ou exact match.
8.3 RAG com Reranking
- Retrieve 50–100 → rerank com cross-encoder → top 5–10 → LLM.
- Quando: Qualidade crítica; latência aceitável (~100–200 ms extra).
8.4 Parent-child RAG
- Small chunks para retrieval; devolver parent (chunk maior) ao LLM.
- Quando: Documentos longos; precisão de retrieval + contexto rico.
8.5 Hierarchical RAG
- Document → Section → Chunk. Buscar primeiro documento, depois secção, depois chunk.
- Quando: Livros, documentação muito longa; reduz ruído.
8.6 Corrective RAG (CRAG)
- Avaliar qualidade do retrieval (ex: score médio, dispersão).
- Se baixa: não passar ao LLM; responder "não encontrei" ou fazer web search.
- Se média: query expansion e retry.
- Se alta: passar ao LLM normalmente.
- Objetivo: Evitar context dilution; não inventar com chunks irrelevantes.
8.7 Self-RAG
- LLM emite tokens especiais: [Retrieve], [IsRel], [IsSup], [IsUse].
- Retrieve quando precisa de mais info; IsRel avalia relevância dos chunks; IsSup verifica se a resposta é suportada; IsUse se é útil.
- Prós: Adaptativo; avalia própria geração.
- Contras: Requer modelo fine-tuned ou prompting complexo.
8.8 FLARE (Forward-Looking Active Retrieval)
- Durante a geração, quando o modelo emite token de baixa confiança, pausa e faz retrieval.
- Prós: Retrieval sob demanda; evita retrieval desnecessário.
- Contras: Latência variável; implementação mais complexa.
8.9 Agentic RAG
- Agente decide quando e o quê buscar. Pode fazer múltiplas queries, iterar.
- Fluxo: Thought → Retrieve? → Action (search) → Observation → Repeat.
- Quando: Queries complexas, multi-hop, necessidade de raciocínio antes de buscar.
9. Avaliação de RAG
9.1 Métricas de retrieval
| Métrica | Descrição |
|---|---|
| Recall@k | % de docs relevantes no top-k |
| MRR | Mean Reciprocal Rank do primeiro relevante |
| NDCG | Normalized DCG — considera posição e relevância |
9.2 Métricas de geração
| Métrica | Descrição |
|---|---|
| Faithfulness | A resposta está suportada pelo contexto? |
| Groundedness | As citações correspondem ao contexto? |
| Answer relevance | A resposta aborda a pergunta? |
9.3 RAGAS (framework)
- Faithfulness: LLM judge — extrair claims da resposta, verificar se cada claim está no contexto.
- Answer relevance: Similaridade entre pergunta e resposta.
- Context precision/relevance: Relevância dos chunks recuperados.
- Ferramentas: ragas library, LangSmith/Langfuse evals.
9.4 Golden dataset
- Perguntas + respostas ideais + docs relevantes.
- Avaliar retrieval (recall) e pipeline completo (faithfulness, relevance) em cada deploy.
10. Produção e otimização
10.1 Latência
- Embedding: Cache de embeddings de queries repetidas.
- Vector search: HNSW com
ef_searchajustado; considerar quantização. - Reranking: Só top-20 ou top-50; modelos pequenos (MiniLM).
- LLM: Streaming; prompt caching (OpenAI) para contexto longo.
10.2 Custo
- Embedding cache: Queries similares não re-embedam.
- Semantic cache: Resposta cached para query semanticamente similar.
- Model routing: RAG simples → modelo pequeno; RAG complexo → modelo maior.
10.3 Observability
- Trace: Query → chunks recuperados (ids, scores) → prompt → resposta.
- Métricas: Latência por etapa, recall@k, faithfulness (amostragem).
- Ferramentas: Langfuse, LangSmith, Phoenix.
10.4 Debugging quando RAG falha
- Retrieval: Os chunks certos estão no top-k? → Query expansion, hybrid, chunk size.
- Chunking: Info cortada nos limites? → Overlap, semantic chunking.
- Context dilution: Muitos chunks irrelevantes? → Reranking, reduzir k.
- Prompt: Instruções claras? "Responde só com base no contexto. Se não souberes, diz que não sabes."
11. Resposta forte para entrevista
Retrieval Augmented Generation combina LLM com fonte de conhecimento externa. A query é convertida em embedding, documentos relevantes são recuperados do vector DB e injetados no prompt antes da geração. Técnicas que impressionam: semantic chunking com overlap, hybrid search (BM25 + dense com RRF), reranking com cross-encoder, parent-child e hierarchical para documentos longos, Corrective RAG para evitar context dilution, e avaliação com RAGAS (faithfulness, recall). Em produção: caching de embeddings, semantic cache de respostas, e observability com tracing de query → chunks → resposta.
Recomendação para ti
Com base em benchmarks e práticas de produção (2024–2025), a abordagem com melhores resultados é o pipeline híbrido em três estágios. Abaixo explico cada passo em detalhe.
Passo 1 — Hybrid Search (gerar candidatos)
Executas duas buscas em paralelo com a mesma query:
-
Busca vetorial (dense): A query é convertida em embedding e comparada com os embeddings dos chunks. O vector DB devolve os top-100 mais similares. Funciona bem para: paráfrases, sinónimos, perguntas em linguagem natural. Falha quando: a query tem tokens exatos (IDs, códigos de erro como ORA-00942, nomes técnicos como HNSW).
-
Busca BM25 (lexical): Conta a frequência das palavras da query nos documentos. Pondera termos raros (IDF) e a frequência no doc (TF). Funciona bem para: exact match, IDs, códigos, negações ("não suportado"). Falha quando: a query usa sinónimos ou linguagem diferente do documento.
Resultado: Duas listas (ex: 100 da dense + 100 da BM25). Com fusão, ficas com ~120–150 candidatos únicos. Cobres ambos os casos — semântico e lexical.
Passo 2 — RRF (Reciprocal Rank Fusion)
As duas listas têm scores em escalas diferentes (similaridade vetorial vs score BM25). Em vez de comparar números diretamente, usas RRF para combinar pelos ranks:
- Cada documento recebe um score:
1/(k + posição)em cada lista. Ex:k=60, documento na posição 1 →1/61; na posição 10 →1/70. - Os scores das duas listas são somados. Um doc que aparece no top-5 de ambas fica com score alto; um que só aparece numa lista fica mais baixo.
- Ordenas por score total e deduplicas os candidatos.
Resultado: Uma única lista ordenada, sem precisar de normalizar scores. Simples e robusto.
Passo 3 — Reranking com cross-encoder
Tens ~120 candidatos, mas o LLM só precisa de 5–10. Um cross-encoder avalia cada par (query, chunk) com um único forward pass — mais preciso que o bi-encoder usado na busca inicial:
- Input:
[query] [SEP] [chunk]concatenados. - Output: score de relevância (0–1).
- Ordenas por score e ficas com os top-5 ou top-10.
Resultado: Chunks de alta precisão para o LLM. Eliminas falsos positivos que passaram por BM25 ou dense.
Exemplo de código (Python)
Exemplo mínimo que podes executar — simula BM25 com contagem de termos e "embeddings" com overlap de palavras. Em produção usarias rank_bm25, sentence-transformers e um cross-encoder real.
from collections import Counter
# --- 1. BM25 (simplificado: TF * IDF aproximado)
def bm25_search(query: str, docs: list[str], top_k: int = 5) -> list[tuple[int, float]]:
"""Busca lexical: score por frequência de termos."""
q_terms = query.lower().split()
n_docs = len(docs)
results = []
for i, doc in enumerate(docs):
doc_terms = doc.lower().split()
tf = sum(1 for t in q_terms if t in doc_terms)
# IDF simplificado: termos raros valem mais
idf = 1.0 # em produção: log((n_docs - df + 1) / (df + 1))
score = tf * idf if tf > 0 else 0
results.append((i, score))
results.sort(key=lambda x: x[1], reverse=True)
return results[:top_k]
# --- 2. Dense (simulado: overlap de palavras como proxy de similaridade)
def dense_search(query: str, docs: list[str], top_k: int = 5) -> list[tuple[int, float]]:
"""Busca semântica simulada: overlap normalizado."""
q_set = set(query.lower().split())
results = []
for i, doc in enumerate(docs):
doc_set = set(doc.lower().split())
overlap = len(q_set & doc_set) / max(len(q_set), 1)
results.append((i, overlap))
results.sort(key=lambda x: x[1], reverse=True)
return results[:top_k]
# --- 3. RRF (Reciprocal Rank Fusion)
def rrf_fuse(ranked_lists: list[list[tuple[int, float]]], k: int = 60) -> list[tuple[int, float]]:
"""Combina listas pelo rank: score = sum(1/(k + rank))."""
scores = {}
for lst in ranked_lists:
for rank, (doc_id, _) in enumerate(lst, start=1):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
# --- 4. Reranking (simulado: média ponderada dos scores originais)
def rerank(query: str, doc_ids: list[int], docs: list[str], top_k: int = 3) -> list[int]:
"""Em produção: cross-encoder. Aqui: devolve os primeiros top_k."""
return doc_ids[:top_k]
# --- Pipeline completo
docs = [
"RAG combina retrieval com geração. Query vai a embedding, vector search devolve chunks.",
"O erro ORA-00942 indica que a tabela ou vista não existe na base de dados Oracle.",
"BM25 é um algoritmo de busca lexical baseado em TF-IDF. Funciona bem para exact match.",
"Hybrid search junta BM25 e embeddings. RRF combina as duas listas de resultados.",
]
query = "ORA-00942 erro Oracle"
# Passo 1: Hybrid (BM25 + Dense em paralelo)
bm25_results = bm25_search(query, docs, top_k=3) # [(1, 2.0), ...]
dense_results = dense_search(query, docs, top_k=3) # [(1, 0.5), ...]
# Passo 2: RRF
fused = rrf_fuse([bm25_results, dense_results], k=60)
# Passo 3: Reranking (top 3)
doc_ids = [d[0] for d in fused]
top_docs = rerank(query, doc_ids, docs, top_k=3)
print("Query:", query)
print("Top chunks:", [docs[i] for i in top_docs])
Output esperado: O chunk com "ORA-00942" deve aparecer no topo — o BM25 capta o exact match que a busca "semântica" simulada pode perder.
Resumo
| Estágio | O que faz | Latência típica |
|---|---|---|
| 1. Hybrid | BM25 + vector em paralelo | 25–80 ms |
| 2. RRF | Fusion das listas | <5 ms |
| 3. Reranking | Cross-encoder nos top-120 | 30–80 ms |
Total: ~60–150 ms. Ganhos de 20–35% em precisão face a RAG naive. Para ti, que preparas uma entrevista de AI Engineer: este é o sweet spot entre qualidade, latência aceitável e complexidade. Recomendado pela indústria.
Zona de prática
Sem perguntas. Clica em Editar para adicionar.
Desafios de código
Hybrid RAG — BM25 + RRF + Reranking
Executa o pipeline híbrido recomendado: duas buscas em paralelo, RRF para fusion, reranking. Experimenta mudar a query (ex: "BM25" ou "hybrid search").