RAG do zero: tutorial completo em português
Da ingestão ao reranking, com código rodável em Python, OpenAI e Pinecone. Os erros que você vai cometer (e como evitá-los).
RAG (Retrieval-Augmented Generation) é a arquitetura mais utilizada em IA corporativa hoje. A ideia é simples: em vez de fazer fine-tuning do modelo nos seus dados, você guarda esses dados em um banco vetorial, busca os trechos relevantes a cada pergunta, e injeta no prompt. Mais barato, mais auditável, atualiza em tempo real.
Este tutorial monta um RAG funcional do zero. Sem framework mágico — usamos OpenAI direto e Pinecone como vector store, porque entender o que está acontecendo importa mais do que escrever menos código.
O que você precisa antes de começar
- Python 3.10+
- Conta na OpenAI com créditos (vai gastar uns $0.50 para rodar este tutorial inteiro)
- Conta no Pinecone (tier gratuito serve)
- Variáveis
OPENAI_API_KEYePINECONE_API_KEYsetadas
Instale as dependências:
pip install openai pinecone-client tiktoken python-dotenv
Passo 1: Chunking — dividir os documentos
Você não pode embedar um PDF de 200 páginas inteiro. Embedding tem limite de tokens (8.192 para o text-embedding-3-large), e mesmo se coubesse, o vetor resultante seria genérico demais — perderia o sinal de cada parágrafo. Solução: dividir em pedaços (chunks).
A regra prática: chunks de 500 a 1.000 tokens, com overlap de 10-20% entre eles. Overlap evita cortar frase no meio sem contexto.
import tiktoken
def chunk_text(text: str, chunk_size: int = 800, overlap: int = 150) -> list[str]:
encoder = tiktoken.get_encoding("cl100k_base")
tokens = encoder.encode(text)
chunks = []
start = 0
while start < len(tokens):
end = start + chunk_size
chunk_tokens = tokens[start:end]
chunks.append(encoder.decode(chunk_tokens))
start += chunk_size - overlap
return chunks
Gotcha #1: chunk pequeno demais (200 tokens) perde contexto, chunk grande demais (2.000) traz ruído junto com sinal. 800 é o ponto doce para a maioria dos documentos técnicos em português.
Para conteúdo estruturado (markdown, código), considere chunking semântico: dividir por seção, função, parágrafo. Bibliotecas como LangChain têm RecursiveCharacterTextSplitter que faz isso, mas você pode escrever com 30 linhas.
Passo 2: Embeddings — transformar chunks em vetores
Embedding é um vetor de números (1.536 dimensões no text-embedding-3-small, 3.072 no large) que representa o significado semântico do texto. Textos parecidos viram vetores próximos no espaço.
from openai import OpenAI
client = OpenAI()
def embed(texts: list[str]) -> list[list[float]]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
return [item.embedding for item in response.data]
# Embeda em batch (até 2048 por chamada, mas 100 é mais seguro)
chunks = chunk_text(open("meu_manual.txt").read())
batches = [chunks[i:i+100] for i in range(0, len(chunks), 100)]
vectors = []
for batch in batches:
vectors.extend(embed(batch))
Custo: text-embedding-3-small custa $0.02 por milhão de tokens em 2026. Embeddar 10 mil chunks de 800 tokens (= 8M tokens) sai por $0.16. Praticamente grátis.
Passo 3: Indexação no Pinecone
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone()
# Cria o índice (uma vez só)
pc.create_index(
name="meu-rag",
dimension=1536,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index("meu-rag")
# Upsert dos vetores
to_upsert = [
{
"id": f"chunk-{i}",
"values": vec,
"metadata": {"text": chunks[i], "source": "meu_manual.txt"}
}
for i, vec in enumerate(vectors)
]
index.upsert(vectors=to_upsert)
Sempre guarde o texto original no metadata. É ele que você vai injetar no prompt depois — o vetor só serve pra busca.
Passo 4: Retrieval — buscar chunks relevantes
Na hora da query, embeda a pergunta do usuário e busca os top-K vizinhos mais próximos por similaridade de cosseno.
def retrieve(query: str, top_k: int = 10) -> list[dict]:
query_vec = embed([query])[0]
results = index.query(
vector=query_vec,
top_k=top_k,
include_metadata=True
)
return [
{
"text": match["metadata"]["text"],
"score": match["score"]
}
for match in results["matches"]
]
Gotcha #2: similarity threshold. Score abaixo de 0.7 (cosseno) geralmente é ruído. Filtre antes de injetar no prompt — chunk irrelevante distrai o modelo e gera alucinação.
Passo 5: Reranking — refinar a ordem
Busca vetorial é boa em recall, fraca em precision. Você pega 10 candidatos, mas a ordem entre eles é ruim. Reranking resolve: passa os 10 candidatos por um modelo cross-encoder que reordena pela relevância real à query.
import cohere
co = cohere.Client()
def rerank(query: str, candidates: list[dict], top_n: int = 3) -> list[dict]:
docs = [c["text"] for c in candidates]
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=docs,
top_n=top_n
)
return [candidates[r.index] for r in response.results]
Sem reranking, o sistema responde com chunk só semanticamente parecido. Com reranking, responde com o chunk que responde a pergunta. Diferença gigante em qualidade percebida.
Passo 6: Prompt augmentation e geração
def answer(query: str) -> str:
candidates = retrieve(query, top_k=10)
top = rerank(query, candidates, top_n=3)
context = "\n\n---\n\n".join(c["text"] for c in top)
prompt = f"""Você é assistente técnico. Responda APENAS com base no contexto.
Se a resposta não estiver no contexto, diga "Não encontrei isso na base".
CONTEXTO:
{context}
PERGUNTA: {query}
RESPOSTA:"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
return response.choices[0].message.content
print(answer("Qual o prazo de garantia do produto X?"))
Temperatura baixa (0.0 a 0.2) reduz alucinação. A instrução "responda APENAS com base no contexto" é crítica — sem ela, o modelo completa lacunas com conhecimento geral, e você perde a auditabilidade.
Os gotchas que aparecem em produção
- Português brasileiro funciona, mas testa. Embeddings da OpenAI são multilíngues, mas alguns termos técnicos em PT-BR têm performance pior que inglês. Avalie no seu corpus.
- Documentos atualizam. Você precisa de pipeline pra detectar mudanças (hash do conteúdo) e reembedar só o que mudou. Caso contrário, o RAG vira mentira.
- Citação da fonte é não-negociável. Sempre devolva qual chunk veio de qual documento. Usuários não confiam em resposta sem fonte — e com razão.
- Avaliação automática. Use ferramentas como Ragas ou Phoenix para medir faithfulness e answer relevance. Sem métrica, você não sabe se piorou.
Conclusão prática
RAG bem feito é menos sobre o vector database e mais sobre chunking inteligente, reranking, e prompt disciplinado. O código aqui roda em produção com algumas adições (cache, retry, observabilidade), mas a espinha é essa. Comece simples, meça qualidade com perguntas reais dos seus usuários, e itere. 90% dos casos de "RAG não funciona" são chunking ruim ou ausência de reranking — não a tecnologia em si.
Quer aplicar isso na prática?
O Nível 1 (IA do Zero ao Especialista) é 100% grátis · 14h de conteúdo.
Começar grátis →