# Quando o RAG erra, quase nunca é o LLM: 4 falhas de recuperação e como logar cada uma

> Source: <https://dev.to/wildrik/quando-o-rag-erra-quase-nunca-e-o-llm-4-falhas-de-recuperacao-e-como-logar-cada-uma-3ljm>
> Published: 2026-08-15 02:09:44+00:00

Todo problema de RAG parece problema do modelo. Quase nunca é.

Demorei pra aceitar isso. Ficava trocando modelo de embedding, mexendo no prompt, achando que o Claude tava inventando coisa. O que estava quebrado era o que eu entregava pra ele, e só ficou óbvio quando comecei a logar o que voltava da recuperação.

Depois que instrumentei, os erros se separaram em quatro tipos bem diferentes.

A pergunta simplesmente não tem resposta na base. Quase ninguém checa. Um piso resolve:

```
q_emb = vo.embed([question], model="voyage-3.5", input_type="query").embeddings[0]

cur.execute(
    "SELECT content, 1 - (embedding <=> %s) AS score "
    "FROM documents ORDER BY embedding <=> %s LIMIT %s",
    (q_emb, q_emb, top_k)
)
rows = cur.fetchall()

if not rows or rows[0][1] < 0.7:
    return "Não tenho essa informação na base."
```

Feio, mas corta a maior parte das respostas inventadas com cara de certeza. O 0.7 não é sagrado: é o ponto onde, na minha base, o chunk deixava de ter relação com a pergunta. Loga o score por uma semana e você acha o teu.

"como resetar senha" trazendo "como resetar produto". Vetorialmente colado, na prática inútil.

Esse foi o que mais me irritou, porque parece bug do modelo e não é: busca vetorial pura não separa as duas coisas. Os dois chunks falam de resetar algo, com estrutura de frase quase idêntica, e a diferença inteira mora numa palavra que o vetor dilui.

Hybrid search (vetor + BM25) rendeu mais aqui do que qualquer troca de embedding que eu tinha tentado antes, porque o BM25 dá peso ao token literal "senha" — justamente o que o vetor perdeu. Reranker com cross-encoder na frente ataca o mesmo problema por outro lado.

O retrieval acertou e o modelo extrapolou mesmo assim. Isso é instrução, não recuperação: o system prompt precisa proibir na cara dura e pedir citação do trecho.

```
SYSTEM = (
    "Responda APENAS com base no contexto fornecido. "
    "Cite o trecho que embasa cada afirmação. "
    "Se a resposta não estiver no contexto, diga que não sabe."
)
```

Óbvio depois de escrito. Passei semanas culpando o retrieval por isso.

Tabela partida, bloco de código sem fechamento. Chunking por contagem de token faz isso o tempo todo:

``` python
def chunk_text(text, size=500, overlap=50):
    words = text.split()
    return [" ".join(words[i:i + size])
            for i in range(0, len(words), size - overlap)]
```

Funciona pra prosa corrida e destrói documentação técnica. E o estrago é silencioso: o modelo responde bonito em cima de um pedaço mutilado, sem sinal nenhum de que faltou metade da tabela. Respeitar a estrutura do documento (header de markdown, seção do PDF) dá mais trabalho pra escrever e se paga na primeira semana.

Se fosse começar de novo, a primeira coisa que eu escreveria não era o pipeline, era o log. Score de cada chunk, quais entraram, tamanho de cada um. Sem isso você fica trocando peça no escuro, e trocar modelo de embedding é caro, demorado e quase nunca era o problema.

Escrevi o passo a passo completo no blog, com o pipeline em Python usando pgvector, Voyage e Claude, as 4 estratégias de chunking e a comparação com fine-tuning: [https://www.techknow.com.br/post/o-que-e-rag](https://www.techknow.com.br/post/o-que-e-rag)

Quem já tem isso em produção: o ganho maior veio de mexer no chunking ou de botar um reranker na frente?
