Retrieval-Augmented Generation (Geração Aumentada via Recuperação) é uma das técnicas fundamentais para quem quer construir aplicações com modelos de linguagem (LLMs). Neste artigo, o foco será em modelos gratuitos e de tamanho menor, disponíveis na plataforma Hugging Face. A principal vantagem desses modelos é o custo zero, sem necessidade de chave de API paga ou assinatura. A desvantagem é que eles tendem a ter menos conhecimento geral e conhecem menos detalhes do que modelos maiores e proprietários, como o GPT-4 ou o Claude. Vale dizer, porém, que a técnica de RAG que veremos aqui funciona igualmente bem com modelos grandes e privados, basta trocar o modelo configurado no final do código.

Modelos de linguagem como o GPT são treinados em enormes quantidades de texto coletados até uma certa data. Isso cria dois problemas práticos:

Conhecimento desatualizado. O modelo não sabe de eventos ocorridos após o seu treinamento.

Conhecimento específico ausente. Documentos internos da sua empresa, artigos científicos muito nichados, ou mesmo a biografia detalhada de uma figura histórica menos proeminente, o modelo provavelmente não conhece.

Os modelos de linguagem (grandes ou pequenos) podem não saber de informações específicas disponíveis em arquivos locais ou em livros. Vamos exemplificar com a biografia do ex-presidente brasileiro Juscelino Kubitschek, disponível em um livro eletrônico gratuito aqui.

Neste artigo, você vai entender o que é RAG e como implementar um sistema básico usando Python e LangChain.

Clique aqui para abrir o código deste artigo no Google Colab

A Ideia Central do RAG

RAG combina dois componentes que formam um sistema mais inteligente:

  • Retrieval (Recuperação): antes de gerar uma resposta, o sistema busca nos seus documentos os trechos mais relevantes para a pergunta do usuário.
  • Generation (Geração): esses trechos são entregues ao LLM como contexto, e o modelo gera uma resposta fundamentada neles.

Em vez de depender apenas do que o modelo “memorizou” durante o treinamento, você fornece o conhecimento relevante no momento da pergunta. É como fazer uma prova com consulta: o modelo não precisa saber tudo de cabeça, mas precisa saber usar bem o material disponível.

Visão Geral da Arquitetura

Um sistema RAG tem três etapas principais:

Indexação (feita uma vez)

Documentos → Divisão em chunks → Embeddings → Base de Dados Vetorial

Os documentos são divididos em pedaços menores (chunks), convertidos em vetores numéricos (embeddings) que capturam o significado semântico do texto, e armazenados em uma base de dados vetorial (vector store).

Recuperação (a cada pergunta)

Pergunta → Embedding da pergunta → Busca por similaridade → Chunks relevantes

Quando o usuário faz uma pergunta, ela também é convertida em um vetor. O sistema busca na base vetorial os chunks cujos vetores são mais próximos (semanticamente similares) ao da pergunta.

Geração (a cada pergunta)

Chunks relevantes + Pergunta → Prompt montado → LLM → Resposta

Os chunks recuperados são inseridos no prompt como contexto, e o LLM gera uma resposta baseada nesse material.

O Conceito de Embedding

Para entender RAG, é fundamental entender embeddings. Um modelo de embedding transforma um texto em um vetor de números (por exemplo, um vetor de 384 dimensões). Textos com significado semelhante ficam “próximos” nesse espaço vetorial.

Por exemplo, as frases “Qual é a capital do Brasil?” e “Onde fica Brasília?” ficariam próximas no espaço vetorial, mesmo usando palavras diferentes. Isso permite buscar por semântica (significado), não apenas por palavras-chave.

Implementação Prática com LangChain

Vamos construir um RAG simples usando como base o livro com a biografia de Juscelino Kubitschek, um exemplo propositalmente escolhido porque o material contém detalhes específicos (origem da família, traços de personalidade, início da carreira política) que os modelos dificilmente sabem com precisão.

Instalação das dependências

As versões abaixo foram testadas e confirmadas como compatíveis entre si (válidas até 19/fev/2026):

!pip install -q -U pymupdf

# versões compatíveis e funcionais até 19/fev/2026, pelo menos
!pip install -q \
  langchain==1.2.0 \
  langchain-core==1.2.4 \
  langchain-text-splitters==1.0.0 \
  langchain-community==0.4.1 \
  langchain-openai==1.1.0 \
  langchain-huggingface==1.1.0 \
  pypdf==4.3.1 \
  sentence-transformers==3.2.0

Importações

from langchain_community.document_loaders import PyPDFLoader, PyMuPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import OpenAIEmbeddings

from langchain_openai import ChatOpenAI
from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint

from langchain_core.prompts import PromptTemplate

1. Carregando o Documento

Vamos fazer o download do livro diretamente do endereço no qual está disponível gratuitamente.

# faz o download do livro
# se der erro, confira se o arquivo existe na seção "Arquivos" no menu lateral
!wget https://domainpublic.wordpress.com/wp-content/uploads/2022/10/jk_couto_2ed.pdf

# este é o nome do arquivo que deve ter sido baixado na célula anterior
arquivo = 'jk_couto_2ed.pdf'

loader = PyMuPDFLoader(f"/content/{arquivo}")
all_pages = loader.load()

print(f"Total de páginas carregadas: {len(all_pages)}")

Observação: Você pode usar qualquer outro documento, mas evite arquivos com mais de 150 ou 200 páginas, porque o processamento pode demorar muito. Você pode fazer o upload de um arquivo que esteja no seu computador, clicando no item em formato de pasta no menu lateral (à esquerda) no Colab; e, depois, clicando no botão de upload. Depois de fazer o upload, digite o nome correto do arquivo na variável acima.

Aqui fazemos um recorte para usar apenas os capítulos 1 a 11 (páginas 29 a 126 do PDF). Os índices da lista começam em 0, então vão de 28 a 125, mas é preciso indicar +1 no índice final:

# Vamos fazer um "recorte", para usar apenas os capítulos 1 a 11,
# que estão nas páginas de 29 a 126, na numeração do PDF
pages = all_pages[28:126]
print(f"Total de páginas após o recorte: {len(pages)}")

2. Dividindo em Chunks

Documentos longos precisam ser divididos em partes menores antes de serem transformados em vetores. O RecursiveCharacterTextSplitter faz isso de forma inteligente, tentando respeitar parágrafos e frases:

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=2000,      # Quantidade de caracteres por "chunk"
    chunk_overlap=200,    # Sobreposição de caracteres por "chunk"
    add_start_index=True, # Mapeia "chunk" no documento original
)

all_chunks = text_splitter.split_documents(pages)
print(f"Documento dividido em {len(all_chunks)} partes.")

O chunk_overlap é importante para não perder informações que estejam na fronteira entre dois chunks.

3. Criando a Base de Dados Vetorial

Para armazenar os chunks de forma que possamos buscar por significado, precisamos primeiro de um modelo de embedding — um modelo que converte qualquer texto em um vetor numérico que representa o seu significado. Textos parecidos geram vetores parecidos, o que permite a busca por similaridade semântica.

# Carrega um modelo de embedding gratuito do Hugging Face
# com bom suporte a português
embeddings_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)

O modelo paraphrase-multilingual-MiniLM-L12-v2 é uma boa escolha para texto em português: é gratuito, leve e funciona bem para busca por similaridade.

Com o modelo de embedding pronto, criamos o vector store: uma estrutura que converte cada chunk de texto para o seu embedding (vetor numérico) e mantém a associação entre o texto original e o vetor correspondente. Isso nos permite, depois, buscar pelos chunks mais relevantes para uma determinada pergunta.

# Cria o banco de dados vetorial (vector store) em memória
vector_store = InMemoryVectorStore(embeddings_model)

# Converte os "chunks" para vetores e os armazena no vector store
vector_store.add_documents(documents=all_chunks)

4. Fazendo uma Busca Semântica

Agora, vamos procurar os documentos que são mais similares (quanto ao significado) à pergunta indicada na variável query abaixo. O parâmetro k=2 indica que queremos recuperar os 2 chunks mais relevantes:

query = "Qual a origem da família de Juscelino Kubitschek? Eles vieram de qual país e eram de qual etnia?"
retrieved_docs = vector_store.similarity_search(query, k=2)

print(f"Chunks recuperados: {len(retrieved_docs)}")
print("Texto do 1o chunk recuperado (mais relevante):")
print(retrieved_docs[0].page_content[:300])

5. Definindo o Prompt

prompt = PromptTemplate(
    input_variables=["contexto", "pergunta"],
    template="""
Você é uma assistente de pesquisa que deve responder a pergunta informada,
de modo que sua resposta esteja bem fundamentada no contexto fornecido.
Se não for possível responder com base no contexto, diga que não achou informações
para responder.

# Contexto
{contexto}

# Pergunta
{pergunta}

# Instruções Resumidas
- Agora, responda a pergunta inicial com base no contexto informado.
- Não dê respostas com informações que não estejam no contexto.
- Não responda informações que não são relevantes para a pergunta acima.

# Resposta
"""
)

O prompt instrui o modelo a se limitar ao contexto fornecido — esse é um mecanismo importante para reduzir alucinações.

6. Configurando o LLM

# Aqui, é definido um modelo de linguagem para gerar as respostas
llm = HuggingFaceEndpoint(
    repo_id="HuggingFaceH4/zephyr-7b-beta",
    task="text-generation",
    max_new_tokens=512,
    do_sample=True,
)
# Instancia uma classe do LangChain que permite acessar modelos do HF
chat_model = ChatHuggingFace(llm=llm)

Este modelo roda remotamente nos servidores do Hugging Face — você não precisa baixar nada. Basta um token gratuito da plataforma.

7. Workflow Completo do RAG

def rag_workflow(pergunta_usuario, mostrar_chunks=False):
    # 1. Recuperação (busca semântica)
    docs = vector_store.similarity_search(pergunta_usuario, k=2)

    # (Extra): mostra o início dos chunks recuperados
    if mostrar_chunks:
        print("----")
        for i, doc in enumerate(docs):
            print(" => chunk", i+1, ":", doc.page_content[:80], "...")
        print("----")

    # 2. Montagem explícita do contexto - une todos os documentos em uma string
    full_context = "\n---\n".join(d.page_content for d in docs)

    # 3. Montagem explícita do prompt
    final_prompt = prompt.format(
        contexto=full_context,
        pergunta=pergunta_usuario
    )

    # 4. Chamada ao LLM
    response = chat_model.invoke(final_prompt)
    return response.content

8. Testando o Sistema

import textwrap

resposta1 = rag_workflow(
    "Qual a origem da família de Juscelino Kubitschek? Eles vieram de qual país e eram de qual etnia?",
    mostrar_chunks=True
)
print("Saída Final:\n")
print(textwrap.fill(resposta1, width=90))
resposta2 = rag_workflow(
    "Quais traços de personalidade Juscelino parece ter puxado dos pais (pai e mãe)?"
)
print("Saída Final:\n")
print(textwrap.fill(resposta2, width=90))
resposta3 = rag_workflow(
    "Como foi o início da vida política de Juscelino? Para qual cargo ele concorreu?"
)
print("Saída Final:")
print(textwrap.fill(resposta3, width=90))

Perguntas como essas são difíceis para um LLM sem contexto — os detalhes são específicos demais. Com o RAG funcionando corretamente, o modelo consegue responder com precisão porque o contexto relevante foi recuperado e inserido no prompt.

Clique aqui para abrir o código completo deste artigo no Google Colab

Conclusão

RAG é uma técnica que permite que LLMs respondam perguntas sobre documentos específicos, informações proprietárias ou conteúdos que não fazem parte do treinamento do modelo. O fluxo é simples: dividir documentos em chunks, converter em vetores, armazenar numa base vetorial e, a cada pergunta, recuperar os trechos mais relevantes para compor o contexto do LLM.