Última notícia há desconhecido
Tutorialavancado· Avançado· PT50M

O que é RAG e como implementar em Python: Guia Avançado

Guia técnico completo sobre RAG (Retrieval-Augmented Generation) em Python. Aprenda a arquitetura, como gerar embeddings, escolher um vector store e construir um pipeline de perguntas e respostas sobre documentos próprios.

13 de maio de 2026· Atualizado em 14 de maio de 2026por Redação SWEN.AI

O Que é RAG? O Problema que Ele Resolve

Imagine que você contratou o funcionário mais inteligente do mundo. Ele leu praticamente tudo que foi publicado até 2023, fala com fluência sobre qualquer assunto técnico. O problema? Quando você pergunta sobre procedimentos internos da sua empresa, ele inventa uma resposta que parece convincente, mas está completamente errada.

Esse é o problema central dos Large Language Models (LLMs): alucinação combinada com conhecimento desatualizado.

RAG — Retrieval Augmented Generation — é a solução arquitetural para esse problema. Em vez de depender apenas do conhecimento treinado, o modelo consulta uma base de conhecimento externa no momento da pergunta, recupera os trechos mais relevantes e usa esse contexto para formular a resposta.

O fluxo de um sistema RAG:

1. Pergunta do usuário chega ao sistema

2. O sistema busca os documentos mais relevantes na base de conhecimento

3. Os documentos recuperados são injetados no prompt como contexto

4. O LLM gera a resposta baseado no contexto fornecido, não apenas no que memorizou

Isso muda fundamentalmente a dinâmica: o modelo agora tem evidência concreta para citar. Se o documento não contém a informação, um sistema RAG bem construído responde "não encontrei informação sobre isso" em vez de inventar.

---

RAG vs Fine-Tuning: Quando Usar Cada Abordagem

| Critério | RAG | Fine-Tuning |

|---|---|---|

| Atualização de dados | Tempo real | Requer retreinamento |

| Custo inicial | Baixo | Alto (GPU horas) |

| Transparência | Alta (fontes rastreáveis) | Baixa (conhecimento implícito) |

| Volume de dados necessário | Qualquer quantidade | Mínimo ~1000 exemplos |

| Alucinação | Reduzida significativamente | Depende da qualidade dos dados |

| Privacidade dos dados | Dados ficam no seu servidor | Dados passam pelo treinamento |

| Complexidade de manutenção | Média | Alta |

Use RAG quando: seus dados mudam frequentemente, você precisa de rastreabilidade, o volume de documentos é grande ou quer começar rápido com orçamento limitado.

Use Fine-Tuning quando: quer mudar o comportamento e tom do modelo, seus dados são estáticos e bem curados, ou latência é crítica e você não pode adicionar o overhead de busca.

---

Arquitetura de um Sistema RAG: Os 5 Componentes

Todo sistema RAG é construído sobre cinco componentes fundamentais:

1. Document Loader — ingere dados brutos (PDFs, páginas web, bancos de dados)

2. Text Splitter — divide documentos em chunks menores para busca precisa

3. Embedding Model — converte cada chunk em vetor numérico de alta dimensão

4. Vector Store — armazena e indexa vetores para busca eficiente por similaridade

5. Retriever + LLM Chain — busca chunks similares e gera a resposta final

---

Vector Databases: Chroma, FAISS e Pinecone Comparados

| Característica | FAISS | Chroma | Pinecone |

|---|---|---|---|

| Tipo | Biblioteca in-memory | Banco local/servidor | SaaS gerenciado |

| Persistência | Requer serialização manual | Nativa em disco | Nativa na nuvem |

| Escalabilidade | Limitada (RAM) | Média | Alta (bilhões de vetores) |

| Custo | Gratuito | Gratuito | Freemium + pago |

| Ideal para | Prototipagem, pesquisa | Dev/staging | Produção em escala |

FAISS (Facebook AI Similarity Search): velocidade máxima, funciona inteiramente em memória. Para datasets grandes precisa gerenciar particionamento manualmente. Ótimo para provas de conceito.

Chroma: ponto de entrada mais amigável para developers. Roda localmente, persiste em SQLite, API Python limpa e integração nativa com LangChain.

Pinecone: para quando você precisa de SLA, escala e não quer gerenciar infraestrutura. Free tier suporta 100k vetores.

---

Embeddings na Prática: Como Transformar Texto em Vetores

Um embedding é uma representação numérica de texto em um espaço de alta dimensão (tipicamente 768 a 3072 dimensões). O modelo aprende a posicionar textos semanticamente similares próximos uns dos outros.

from openai import OpenAI
import numpy as np

client = OpenAI()

def get_embedding(text: str, model: str = "text-embedding-3-small") -> list:
    response = client.embeddings.create(input=[text.replace("\n", " ")], model=model)
    return response.data[0].embedding

def cosine_similarity(v1: list, v2: list) -> float:
    a, b = np.array(v1), np.array(v2)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Textos semanticamente similares ficam próximos no espaço vetorial
v1 = get_embedding("O cachorro correu pelo parque")
v2 = get_embedding("O cão estava correndo no jardim")
v3 = get_embedding("A bolsa de valores fechou em alta")

print(f"cachorro vs cão: {cosine_similarity(v1, v2):.4f}")    # ~0.93
print(f"cachorro vs bolsa: {cosine_similarity(v1, v3):.4f}")  # ~0.12

Comparação de Modelos de Embedding

| Modelo | Dimensões | Custo/1M tokens | Qualidade |

|---|---|---|---|

| text-embedding-3-small | 1536 | $0.02 | Boa |

| text-embedding-3-large | 3072 | $0.13 | Excelente |

| paraphrase-multilingual-MiniLM-L12-v2 | 384 | Gratuito (local) | Média |

---

Implementando RAG do Zero com Python e LangChain

Instalação e Configuração

pip install langchain langchain-openai langchain-community chromadb pypdf tiktoken python-dotenv

Carregando e Dividindo Documentos (Chunking)

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

def load_and_split(path: str, chunk_size: int = 1000, chunk_overlap: int = 200):
    loader = PyPDFLoader(path)
    documents = loader.load()

    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        separators=["\n\n", "\n", ". ", " ", ""],
        add_start_index=True
    )

    chunks = splitter.split_documents(documents)
    print(f"{len(documents)} docs -> {len(chunks)} chunks")
    return chunks

Criando o Vector Store

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

def create_vectorstore(chunks, persist_dir="./chroma_db"):
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=persist_dir,
        collection_metadata={"hnsw:space": "cosine"}
    )

    print(f"Vector store criado com {vectorstore._collection.count()} vetores")
    return vectorstore

Implementando o Retriever com MMR

def get_mmr_retriever(vectorstore, k: int = 4, fetch_k: int = 20):
    """
    MMR (Maximal Marginal Relevance) balanceia relevância e diversidade.
    Evita recuperar 4 chunks que dizem exatamente a mesma coisa.

    lambda_mult: 0=maxima diversidade, 1=maxima relevancia
    """
    return vectorstore.as_retriever(
        search_type="mmr",
        search_kwargs={
            "k": k,
            "fetch_k": fetch_k,
            "lambda_mult": 0.7
        }
    )

Montando a Chain Completa

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser

SYSTEM_PROMPT = (
    "Voce e um assistente que responde perguntas baseando-se "
    "EXCLUSIVAMENTE no contexto fornecido. Se a resposta nao "
    "estiver no contexto, diga que nao encontrou informacoes.\n\n"
    "Contexto:\n{context}"
)

def format_docs(docs) -> str:
    return "\n\n---\n\n".join([
        f"[Fonte: {doc.metadata.get('source', 'N/A')}]\n{doc.page_content}"
        for doc in docs
    ])

def create_rag_chain(vectorstore):
    retriever = get_mmr_retriever(vectorstore)
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=1500)

    prompt = ChatPromptTemplate.from_messages([
        ("system", SYSTEM_PROMPT),
        ("human", "{question}")
    ])

    chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )

    return chain

# Uso completo
if __name__ == "__main__":
    chunks = load_and_split("./documentos/politica_rh.pdf")
    vectorstore = create_vectorstore(chunks)
    chain = create_rag_chain(vectorstore)

    resposta = chain.invoke("Quantos dias de ferias o colaborador tem direito?")
    print(resposta)

---

Estratégias de Chunking: Como Evitar Perda de Contexto

| Estratégia | Velocidade | Qualidade | Custo | Melhor para |

|---|---|---|---|---|

| Recursive Character | Muito alta | Boa | Mínimo | Textos gerais, início rápido |

| Markdown Headers | Alta | Muito boa | Mínimo | Documentação técnica, wikis |

| Token-based | Alta | Boa | Mínimo | Quando precisão de tokens importa |

| Semantic Chunking | Baixa | Excelente | Médio | Textos narrativos, artigos |

Regra prática: comece com RecursiveCharacterTextSplitter com chunk_size=800 e chunk_overlap=150. Só invista em chunking semântico se os resultados iniciais forem insatisfatórios.

Armadilha mais comum: chunk overlap insuficiente. Sem overlap, uma informação que cai na fronteira entre dois chunks fica fragmentada. Use pelo menos 15-20% do chunk_size como overlap.

---

Avaliando a Qualidade do Seu RAG

from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_recall, context_precision
from datasets import Dataset

def avaliar_rag(perguntas, respostas, contextos, gabaritos):
    dataset = Dataset.from_dict({
        "question": perguntas,
        "answer": respostas,
        "contexts": contextos,
        "ground_truth": gabaritos
    })

    return evaluate(
        dataset,
        metrics=[faithfulness, answer_relevancy, context_recall, context_precision]
    )

As 4 métricas principais:

| Métrica | O que mede | Score ideal | Sinal de problema |

|---|---|---|---|

| Faithfulness | A resposta é suportada pelo contexto? | > 0.85 | LLM alucinando além do contexto |

| Answer Relevancy | A resposta endereça a pergunta? | > 0.80 | Sistema retorna informação irrelevante |

| Context Recall | O contexto cobre o gabarito? | > 0.75 | Retriever não busca os chunks certos |

| Context Precision | Contexto recuperado é de qualidade? | > 0.70 | Muitos chunks irrelevantes |

---

RAG em Produção: Boas Práticas e Armadilhas

1. Indexação Incremental

Nunca recrie o vector store inteiro para adicionar documentos novos. Use hash dos chunks para detectar e indexar apenas o que é novo — economiza tempo e custo de embeddings.

2. Query Expansion

Gere 2-3 variações da pergunta usando um LLM e faça retrieve com todas elas. Uma pergunta mal formulada pode não recuperar os chunks certos. A união dos resultados aumenta significativamente o recall.

3. Cache para Perguntas Frequentes

Implemente cache por MD5 da pergunta normalizada. Perguntas idênticas não devem gerar chamadas redundantes ao LLM. Economiza 40-60% do custo em sistemas com perguntas repetitivas.

4. Limpeza de Texto de PDF

PDFs mal formatados geram texto com OCR ruim, quebras de linha no meio de frases, cabeçalhos repetidos em todo chunk. Sempre limpe o texto extraído antes de indexar.

5. Quando NÃO usar RAG

Se seus dados cabem inteiramente na janela de contexto do modelo (ex: um documento de 50 páginas com Gemini 1.5 Pro), envie tudo direto. RAG adiciona latência e complexidade desnecessária para volumes pequenos.

---

Perguntas Frequentes sobre RAG

Qual o tamanho ideal de chunk?

Não existe resposta universal. 512-1024 caracteres funciona bem para a maioria dos casos. Teste com seu dataset específico e meça as métricas RAGAS.

Posso usar RAG sem a OpenAI para reduzir custos?

Sim. Use HuggingFaceEmbeddings com paraphrase-multilingual-MiniLM-L12-v2 (gratuito, multilingual) e Llama 3 via Ollama como LLM. Custo zero, qualidade inferior mas suficiente para muitos casos.

Como lidar com documentos em múltiplos idiomas?

Use modelos de embedding multilingual desde o início. text-embedding-3-small da OpenAI funciona bem com português, inglês e espanhol no mesmo vector store.

Como evitar que o LLM invente além do contexto?

Seja explícito no prompt: "Responda APENAS com base no contexto fornecido. Se a informação não estiver no contexto, diga que não sabe." E use temperatura 0 no LLM.

Quantos documentos meu RAG consegue suportar?

FAISS e Chroma locais escalam bem até dezenas de milhões de vetores com hardware adequado. Para escala corporativa, Pinecone ou Weaviate gerenciados são a escolha certa. O gargalo raramente é o vector store — geralmente é o custo de embeddings na ingestão.

Tutoriais relacionados