Pergunte a um LLM genérico qual é a política de reembolso da sua empresa ou o que diziam os tickets de suporte da semana passada, e ele vai dizer que não sabe ou vai inventar algo plausível. O conhecimento dele para no que estava nos dados de treinamento. Ele nunca viu os seus documentos e não pode ir consultá-los no meio da conversa. A retrieval-augmented generation (RAG) resolve exatamente isso: antes de o modelo responder, uma etapa separada encontra o texto relevante nos seus próprios dados e o entrega ao modelo como parte do prompt.

Como a retrieval-augmented generation realmente funciona

Uma chamada comum a um LLM é uma única etapa: prompt entra, resposta sai, usando só o que o modelo aprendeu durante o treinamento. O RAG divide isso em duas: primeiro recupera, depois gera.

  1. Seus documentos são divididos em chunks, convertidos em vetores (embeddings) e armazenados antecipadamente em um banco de dados vetorial.
  2. No momento da consulta, a pergunta do usuário é transformada em embedding da mesma forma e comparada com os chunks armazenados para encontrar as correspondências mais próximas.
  3. As melhores correspondências são inseridas no prompt como contexto, e só então o LLM gera uma resposta.

Pense nisso como uma prova de consulta aberta contra uma de consulta fechada. Um LLM de consulta fechada responde só de memória, e é exatamente por isso que inventa coisas quando a pergunta foge do que ele memorizou. O RAG entrega a página certa antes, então ele responde a partir de algo que tem na frente, não de algo que está reconstruindo.

O modelo em si nunca muda. Os pesos dele continuam exatamente os mesmos de antes de adicionar o RAG; o retrieval é a única peça nova. É isso também que torna o RAG barato de manter atualizado: você edita um documento e a próxima consulta já vê a mudança.

Construindo uma pipeline RAG: chunking e embedding dos seus dados

A ingestão é a metade da pipeline que roda antes de alguém fazer uma pergunta. Ela transforma seus documentos em algo que uma busca por similaridade consegue consultar.

Documentos são longos demais para virar embedding como uma unidade só. Um PDF de 20 páginas transformado em um único vetor acaba correspondendo um pouco a cada consulta e bem a nenhuma, então você o divide em chunks primeiro. Um divisor de tamanho fixo com sobreposição já basta para começar:

1
2
3
4
5
6
7
8
def chunk_text(text, chunk_size=500, overlap=50):
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start = end - overlap
    return chunks

500 caracteres com 50 de sobreposição é um ponto de partida razoável. Pipelines em produção costumam dividir por número de tokens em vez de caracteres, usando um divisor que conhece o tokenizador para que o corte de um chunk não caia no meio de uma palavra. A sobreposição existe para que uma frase dividida entre dois chunks apareça inteira em pelo menos um deles.

Cada chunk é então transformado em embedding e armazenado. O Chroma é um bom primeiro banco de dados vetorial porque roda no mesmo processo, sem precisar subir um servidor, e sua função de embedding padrão (all-MiniLM-L6-v2) não precisa de API key:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import chromadb

client = chromadb.Client()
collection = client.create_collection(name="support-docs")

collection.add(
    ids=["doc1", "doc2", "doc3"],
    documents=[
        "The refund window is 30 days from the delivery date.",
        "Refunds go back to the original payment method within 5 business days.",
        "Store credit never expires and can be used on any future order.",
    ],
)

Essa é toda a fase de ingestão: dividir, transformar em embedding, armazenar. Roda uma vez por documento, e de novo sempre que um documento muda.

Se você preferir não instalar um banco de dados vetorial na sua máquina, a maioria deles (Chroma, Weaviate, Milvus) distribui uma imagem oficial e roda bem em um container; veja o que o Docker realmente faz se você nunca usou um antes.

Como o retrieval funciona: embedding da consulta e ranking dos resultados

No momento da consulta, o mesmo modelo de embedding transforma a pergunta do usuário em um vetor, e o banco de dados devolve os chunks armazenados mais próximos dele, usando a métrica de distância configurada — cosine similarity e L2 ao quadrado são as duas mais comuns:

1
2
3
4
5
6
7
8
results = collection.query(
    query_texts=["How long do I have to return something?"],
    n_results=2,
)

print(results["documents"])
# [['The refund window is 30 days from the delivery date.',
#   'Refunds go back to the original payment method within 5 business days.']]

A consulta nunca usa a palavra “reembolso” (refund) e mesmo assim encontra a correspondência certa. Esse é o motivo inteiro para usar embedding em vez de grep: dois textos ficam próximos no espaço vetorial quando têm significados parecidos, não quando compartilham as mesmas palavras.

A última etapa junta os chunks recuperados no prompt:

1
2
3
4
5
6
7
8
context = "\n".join(results["documents"][0])

prompt = f"""Answer the question using only the context below. If the context doesn't contain the answer, say so.

Context:
{context}

Question: How long do I have to return something?"""

O prompt vai para qualquer API de chat completion que você já esteja usando. O RAG não exige um modelo ou provedor específico; é um padrão para decidir o que colocar no prompt antes de enviá-lo. Se essa chamada de API precisa de uma chave, mantenha-a fora da sua imagem como faria com qualquer outra credencial: como variável de ambiente ou secret montado, nunca como build argument. Variáveis de Ambiente e Secrets no Docker explica a diferença.

RAG contra fine-tuning: qual resolve o seu problema

Os dois buscam deixar um LLM melhor no seu caso de uso específico, mas mudam coisas diferentes.

RAGFine-tuning
O que mudaNada no modelo; você adiciona uma etapa de retrievalOs próprios pesos do modelo, por meio de mais treinamento
Atualizar o conhecimentoEdita ou adiciona um documento, a próxima consulta já vêRetreinar (ou rodar outra rodada de fine-tuning)
Bom paraFatos, dados atuais, tudo que muda com frequênciaTom, formato de saída, padrões de raciocínio específicos da tarefa
Custo de atualizarBaixo — sem rodada de treinamentoAlto — precisa de uma rodada de treinamento e um dataset
Falha quandoO retrieval erra ou classifica mal o chunkO comportamento necessário não se reduz a exemplos de entrada-saída

Um bot de suporte cuja política de reembolso muda todo trimestre é um problema de RAG: faça fine-tuning com a política do trimestre passado e ele estará confiante e errado sobre a nova. Um modelo que precisa emitir um schema JSON específico toda vez, ou manter um estilo de raciocínio particular, está mais perto de um problema de fine-tuning: nenhuma quantidade de contexto recuperado muda como um modelo formata sua saída. Muitos sistemas em produção usam os dois: um modelo com fine-tuning para tom e estrutura, alimentado com fatos vindos do RAG.

Onde a retrieval-augmented generation falha

Demos de RAG parecem fáceis porque a demo tem três documentos e uma resposta óbvia. Sistemas em produção têm milhares de documentos, e cada um dos pontos a seguir pode arruinar uma resposta silenciosamente:

  • Chunking que ignora a estrutura. Um divisor de tamanho fixo que corta uma tabela ao meio, ou separa um título do parágrafo que ele introduz, produz chunks que fora de contexto parecem sem sentido — e o que não faz sentido vira embedding e é recuperado mal.
  • Um índice desatualizado. A busca vetorial não sabe se o que ela devolve está desatualizado. Se um documento é atualizado mas nunca reprocessado em embedding, a versão antiga continua sendo a recuperada, e nada na pipeline avisa que ela está errada.
  • O chunk certo existe mas não fica bem posicionado. O retrieval top-k só devolve as k correspondências mais próximas; se a resposta real é o 15º chunk mais próximo e você recupera 5, o modelo nunca a vê.
  • Contexto demais, mal posicionado. Enfiar dez chunks recuperados no prompt não significa que o modelo os pesa igualmente. Modelos são mensuravelmente piores usando informação que está no meio de um contexto longo do que no início ou no fim.
  • Nenhuma avaliação além de “a resposta da demo parecia certa”. Sem um conjunto de perguntas e respostas esperadas, uma regressão de retrieval causada por uma mudança de chunking ou um bug de reindexação passa despercebida.

A maioria desses são falhas de retrieval, não do modelo, e cada uma delas é visível se você inspecionar os chunks recuperados em vez de só a resposta final.

Como construir sua primeira pipeline RAG

Construa primeiro a versão de três etapas: divida em chunks um punhado de documentos reais, transforme-os em embedding no Chroma ou em outro armazenamento local, e conecte os chunks recuperados a um prompt exatamente como mostrado acima. Depois faça as 10 perguntas que você espera que usuários reais façam. Confira se os chunks recuperados contêm a resposta antes de julgar a resposta gerada — um retrieval errado torna uma resposta errada inevitável, não importa como o prompt esteja escrito. Só depois que esse ciclo se mostrar sólido em perguntas reais vale a pena ajustar o tamanho dos chunks, trocar o modelo de embedding ou adicionar um reranker.

Artigos relacionados