Salve salve, Pythonista! ![]()
Você já perguntou algo específico do seu negócio para o ChatGPT e levou uma resposta inventada com a maior cara de certeza? Modelo de linguagem não conhece os seus documentos: ele só sabe o que viu no treino. Quando você precisa de respostas sobre o seu manual, suas políticas ou a sua base de conhecimento, ele chuta. E chute em produção vira dor de cabeça.
A solução tem nome: RAG (Retrieval-Augmented Generation). É a técnica que dá ao LLM acesso aos seus dados na hora de responder. E o melhor: dá pra montar um RAG funcional em Python com pouco código.
Neste artigo você vai construir, do zero, um chatbot que responde apenas com base nos documentos que você fornecer.
Vá Direto ao Assunto…
- O que é RAG?
- Como o RAG funciona por baixo dos panos
- Preparando o ambiente
- Passo 1: a base de conhecimento
- Passo 2: dividir em chunks
- Passo 3: gerar embeddings
- Passo 4: indexar no banco vetorial
- Passo 5: buscar os trechos relevantes (Retrieval)
- Passo 6: gerar a resposta (Generation)
- Juntando tudo e rodando
- Quando NÃO usar RAG
- Perguntas frequentes (FAQ)
- Conclusão
O que é RAG?
RAG é uma técnica que conecta um LLM à sua base de conhecimento: antes de responder, o sistema busca os trechos mais relevantes dos seus documentos e os entrega ao modelo como contexto. Em vez de confiar na memória do treino, o LLM responde olhando para os dados que você deu.
Isso resolve dois problemas de uma vez:
- Alucinação: o modelo para de inventar, porque a resposta vem do contexto fornecido.
- Dados privados e atuais: ele passa a responder sobre o que ele nunca viu no treino - o seu manual, a documentação interna, os tickets de suporte.
A sigla traduz bem o fluxo: Retrieval (buscar os trechos certos) + Augmented Generation (gerar a resposta enriquecida com esses trechos).
Como o RAG funciona por baixo dos panos
O processo tem duas fases. Uma acontece uma vez (preparar os dados); a outra, a cada pergunta:
1
2
3
4
5
6
Indexação (uma vez):
documentos → chunks → embeddings → banco vetorial
Consulta (a cada pergunta):
pergunta → embedding → busca por similaridade → chunks relevantes
→ LLM (pergunta + chunks) → resposta fundamentada
A peça-chave são os embeddings: vetores de números que representam o significado de um texto. Textos com sentido parecido geram vetores próximos. É assim que o sistema acha o trecho certo mesmo quando a pergunta usa palavras diferentes das do documento.
| Etapa | O que faz |
|---|---|
| Chunking | Quebra documentos longos em pedaços menores |
| Embedding | Converte cada pedaço em um vetor numérico |
| Indexação | Guarda os vetores em um banco vetorial |
| Retrieval | Busca os pedaços mais parecidos com a pergunta |
| Generation | O LLM responde usando os pedaços como contexto |
Preparando o ambiente
Vamos usar duas bibliotecas: a openai (para embeddings e geração) e a chromadb (um banco vetorial local, que roda na sua máquina sem servidor).
Se ainda não usa ambientes virtuais, vale ler este artigo sobre virtualenv antes - mantém o projeto isolado e organizado.
1
pip install openai chromadb
Configure sua chave da OpenAI como variável de ambiente:
1
export OPENAI_API_KEY="sua-chave-aqui"
Quer rodar 100% local, sem custo de API? Dá pra trocar a OpenAI por modelos via Ollama - a arquitetura do RAG é a mesma. Fica de dica para o próximo passo.
Passo 1: a base de conhecimento
Na vida real, os documentos viriam de PDFs, páginas web ou um banco de dados. Para o exemplo ser executável de ponta a ponta, vamos usar uma pequena base de suporte em memória:
1
2
3
4
5
6
7
8
9
10
# Na prática, isto viria de PDFs, sites, um banco de dados...
DOCUMENTOS = [
"""Política de devolução: o cliente pode devolver qualquer produto em até
30 dias corridos após o recebimento. O reembolso é feito no mesmo meio de
pagamento em até 10 dias úteis.""",
"""Prazos de entrega: pedidos para a região Sudeste chegam em 3 a 5 dias
úteis. Para as demais regiões, o prazo é de 7 a 12 dias úteis.""",
"""Formas de pagamento: aceitamos cartão de crédito em até 12x, Pix com 5%
de desconto e boleto bancário. O Pix é aprovado na hora.""",
]
Passo 2: dividir em chunks
Documentos longos não cabem (e não devem caber) inteiros no contexto do LLM. Quebramos cada um em chunks menores. A sobreposição (overlap) evita perder o sentido de uma frase cortada na borda de um pedaço:
1
2
3
4
5
6
7
8
def dividir_em_chunks(texto: str, tamanho: int = 120, sobreposicao: int = 20) -> list[str]:
"""Quebra um texto em pedaços menores (em palavras), com sobreposição entre eles."""
palavras = texto.split()
passo = tamanho - sobreposicao
return [
" ".join(palavras[inicio:inicio + tamanho])
for inicio in range(0, len(palavras), passo)
]
Como nossos documentos de exemplo são curtos, cada um vira um único chunk. Para um manual de 50 páginas, esse mesma função produziria dezenas deles.
Passo 3: gerar embeddings
Aqui transformamos texto em vetor. O modelo text-embedding-3-small é barato e mais que suficiente:
1
2
3
4
5
6
7
8
9
10
11
from openai import OpenAI
cliente = OpenAI() # lê a chave automaticamente de OPENAI_API_KEY
def gerar_embeddings(textos: list[str]) -> list[list[float]]:
"""Converte uma lista de textos em uma lista de vetores (embeddings)."""
resposta = cliente.embeddings.create(
model="text-embedding-3-small",
input=textos,
)
return [item.embedding for item in resposta.data]
Passo 4: indexar no banco vetorial
O ChromaDB guarda os vetores e cuida da busca por similaridade. Usamos upsert para a indexação ser idempotente - rodar de novo não duplica os dados:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import chromadb
chroma = chromadb.PersistentClient(path="./base_vetorial")
colecao = chroma.get_or_create_collection("conhecimento")
def indexar(documentos: list[str]) -> None:
"""Quebra os documentos em chunks, gera os embeddings e salva no banco vetorial."""
chunks = [chunk for doc in documentos for chunk in dividir_em_chunks(doc)]
colecao.upsert(
ids=[f"chunk-{i}" for i in range(len(chunks))],
documents=chunks,
embeddings=gerar_embeddings(chunks),
)
print(f"{len(chunks)} chunks indexados.")
Repare no list comprehension aninhado: ele percorre cada documento e cada chunk dele numa linha só. É o jeito pythônico de achatar essa lista de listas.
Construir uma base sólida em Python - de comprehensions a integrações com IA como esta - é o que separa quem só copia código de quem entende o que está fazendo. É exatamente isso que ensinamos na Jornada Python:
Passo 5: buscar os trechos relevantes (Retrieval)
Dada uma pergunta, geramos o embedding dela e pedimos ao Chroma os k chunks mais próximos:
1
2
3
4
5
def buscar(pergunta: str, k: int = 3) -> list[str]:
"""Retorna os k chunks mais relevantes para a pergunta."""
embedding_pergunta = gerar_embeddings([pergunta])[0]
resultado = colecao.query(query_embeddings=[embedding_pergunta], n_results=k)
return resultado["documents"][0]
Passo 6: gerar a resposta (Generation)
A última peça: montamos um prompt que entrega o contexto e proíbe o modelo de inventar. temperature=0 deixa a resposta mais determinística e fiel aos dados:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PROMPT = """Você é um assistente de suporte. Responda à pergunta usando APENAS o
contexto abaixo. Se a informação não estiver no contexto, diga que não a possui -
não invente.
Contexto:
{contexto}
Pergunta: {pergunta}
Resposta:"""
def responder(pergunta: str) -> str:
contexto = "\n\n".join(buscar(pergunta))
resposta = cliente.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": PROMPT.format(contexto=contexto, pergunta=pergunta)}],
temperature=0,
)
return resposta.choices[0].message.content
Juntando tudo e rodando
Some os blocos acima em um arquivo rag.py e adicione o ponto de entrada:
1
2
3
4
5
6
if __name__ == "__main__":
indexar(DOCUMENTOS)
pergunta = "Posso devolver um produto depois de 2 semanas?"
print(f"\nPergunta: {pergunta}")
print(f"Resposta: {responder(pergunta)}")
Rodando com python rag.py, a saída é parecida com:
1
2
3
4
5
6
3 chunks indexados.
Pergunta: Posso devolver um produto depois de 2 semanas?
Resposta: Sim. Você pode devolver qualquer produto em até 30 dias corridos após o
recebimento, então duas semanas está dentro do prazo. O reembolso é feito no mesmo
meio de pagamento em até 10 dias úteis.
Repare no detalhe: a pergunta fala em “2 semanas”, o documento fala em “30 dias”, e o modelo conectou os dois - usando o seu dado, não a memória dele. Pergunte algo fora da base (“vocês têm loja física?”) e ele vai dizer que não tem essa informação, em vez de inventar. Isso é RAG funcionando.
Quando NÃO usar RAG
RAG é poderoso, mas não é bala de prata. Evite quando:
- A resposta cabe no prompt: se a sua base é um parágrafo, mande direto no contexto. Não precisa de banco vetorial.
- Você precisa de raciocínio, não de fatos: RAG busca trechos existentes; ele não substitui lógica ou cálculo.
- Os dados mudam a cada segundo: para informação em tempo real (preço de ação, estoque ao vivo), uma chamada de API direta serve melhor.
- Conhecimento muito específico e estável: aí fine-tuning pode valer mais que recuperar contexto toda vez.
Perguntas frequentes (FAQ)
Qual a diferença entre RAG e fine-tuning?
Fine-tuning re-treina o modelo para mudar o comportamento dele (estilo, formato). RAG não treina nada: ele injeta conhecimento atualizado no contexto na hora da pergunta. Para responder sobre documentos que mudam, RAG costuma ser mais barato e flexível.
Preciso pagar pela API da OpenAI?
Não obrigatoriamente. Aqui usamos a OpenAI por simplicidade, mas dá pra trocar os embeddings e a geração por modelos locais (via Ollama ou sentence-transformers). A arquitetura do RAG continua idêntica.
Qual o tamanho ideal de chunk?
Depende do conteúdo, mas algo entre 200 e 500 palavras com 10-20% de sobreposição é um bom ponto de partida. Chunk grande demais traz ruído; pequeno demais perde contexto. Vale testar com os seus dados.
Dá pra usar com PostgreSQL em vez do Chroma?
Sim. A extensão pgvector transforma o Postgres em banco vetorial - ótimo se você já usa Django/Postgres e não quer mais uma peça na infraestrutura.
Conclusão
Você acabou de construir um RAG funcional: indexação, busca por similaridade e geração fundamentada, com um punhado de funções pythônicas. Esse mesmo esqueleto - trocando a base de exemplo pelos seus PDFs e o Chroma por um banco vetorial mais robusto - é o que está por trás da maioria dos “chatbots que conhecem a empresa”.
E é exatamente esse tipo de arquitetura que move o Ebookr.ai, minha plataforma que gera ebooks profissionais com IA: recuperar o contexto certo e gerar conteúdo fiel a ele, em escala. Se curte ver IA aplicada de verdade, dá uma conferida:
E você, vai plugar esse RAG em qual base de conhecimento? Conta aqui nos comentários! ![]()
Nos vemos no próximo artigo! ![]()
"Porque o Senhor dá a sabedoria, e da sua boca vem a inteligência e o entendimento" Pv 2:6