Home · Blog · RAG do zero
Tutorial

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).

15 de Maio, 202614 min de leituraProfessor Neural

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

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

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 →