← Voltar a AI Engineer — DEUS.ai

🔴 A — RAG (Retrieval Augmented Generation)

AI Engineer — DEUS.ai

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, LangChain RecursiveCharacterTextSplitter com separators=["\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

TamanhoRetrievalContexto no LLM
Pequeno (128–256)Mais preciso, mais hitsMenos contexto por chunk
Médio (512)EquilíbrioEquilíbrio
Grande (1024+)Menos precisoMais contexto, risco de dilution

Regra prática: 256–512 tokens para retrieval; expandir com parent se necessário.


3. Embeddings

3.1 Modelos comuns

ModeloDimensõesMultilingualLatênciaUso típico
text-embedding-3-small1536SimBaixaProdução, custo
text-embedding-3-large3072SimMédiaQualidade
BGE, E5, GTE (open)768–1024VariaBaixaSelf-hosted
Cohere embed-v31024SimMédiaProduçã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)) onde rank_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-encoderCross-encoder
InputQuery e doc separadosPar (query, doc) concatenado
UsoRetrieval (rápido)Reranking (lento, preciso)
Custo1 embedding por doc1 forward por par (query, doc)

6.2 Pipeline típico

  1. Retrieve 50–100 chunks (bi-encoder ou hybrid).
  2. Rerank com cross-encoder → top 5–10.
  3. 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étricaDescrição
Recall@k% de docs relevantes no top-k
MRRMean Reciprocal Rank do primeiro relevante
NDCGNormalized DCG — considera posição e relevância

9.2 Métricas de geração

MétricaDescrição
FaithfulnessA resposta está suportada pelo contexto?
GroundednessAs citações correspondem ao contexto?
Answer relevanceA 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_search ajustado; 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

  1. Retrieval: Os chunks certos estão no top-k? → Query expansion, hybrid, chunk size.
  2. Chunking: Info cortada nos limites? → Overlap, semantic chunking.
  3. Context dilution: Muitos chunks irrelevantes? → Reranking, reduzir k.
  4. 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ágioO que fazLatência típica
1. HybridBM25 + vector em paralelo25–80 ms
2. RRFFusion das listas<5 ms
3. RerankingCross-encoder nos top-12030–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").