
RAG na prática: busca semântica com seus dados
LLMs como GPT-4o e Claude têm um problema fundamental quando usados de forma isolada: eles não conhecem seus dados. Eles foram treinados com informações até uma certa data, não têm acesso aos seus documentos internos, políticas da empresa, histórico de clientes ou qualquer dado privado. Quando um usuário pergunta algo sobre sua empresa específica, o modelo ou inventa uma resposta plausível ou admite que não sabe.
RAG — Retrieval-Augmented Generation — é a solução padrão para esse problema. Em vez de confiar apenas no conhecimento paramétrico do modelo, você recupera documentos relevantes da sua base de dados e os injeta no contexto antes de gerar a resposta. O modelo passa a responder com seus dados, de forma atualizada e rastreável.
Este artigo mostra como implementar RAG do zero, desde os conceitos até o pipeline funcionando.
Embeddings: Transformando Texto em Vetores
O coração do RAG é a busca semântica, e busca semântica depende de embeddings. Um embedding é uma representação numérica de texto em um espaço vetorial de alta dimensão. Textos semanticamente similares ficam próximos nesse espaço — "cachorro" e "cão" terão vetores próximos, mesmo sendo palavras diferentes.
Modelos de embedding transformam qualquer texto em um vetor de centenas ou milhares de dimensões. A OpenAI oferece o text-embedding-3-small e o text-embedding-3-large. A Sentence Transformers oferece modelos open-source excelentes para português, como o rufimelo/bert-large-portuguese-cased-sts.
from openai import OpenAI
client = OpenAI()
def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
text = text.replace("\n", " ")
response = client.embeddings.create(input=[text], model=model)
return response.data[0].embedding
# Exemplo
vetor = get_embedding("Qual é a política de devolução?")
print(f"Dimensões: {len(vetor)}") # 1536 para text-embedding-3-small
A escolha do modelo de embedding impacta diretamente a qualidade da busca. Modelos maiores capturam nuances semânticas mais complexas, mas são mais caros e lentos. Para a maioria das aplicações em português, text-embedding-3-small ou um modelo BERT fine-tuned para português oferecem boa relação custo-benefício.
Vector Stores: Pinecone, Chroma e pgvector
Com os embeddings gerados, você precisa de um lugar para armazená-los e fazer buscas por similaridade de forma eficiente. Bancos de dados relacionais tradicionais não são otimizados para isso — comparar um vetor de 1536 dimensões contra milhões de outros vetores exige algoritmos especializados como HNSW (Hierarchical Navigable Small World).
As principais opções são:
| Opção | Tipo | Melhor para |
|---|---|---|
| Pinecone | Gerenciado (cloud) | Produção sem ops, escala grande |
| Weaviate | Self-hosted / cloud | Filtragem híbrida, GraphQL |
| Chroma | Local / self-hosted | Prototipagem, projetos menores |
| pgvector | Extensão PostgreSQL | Já usa Postgres, quer simplicidade |
| Qdrant | Self-hosted / cloud | Performance, filtragem avançada |
Para começar com Chroma (ideal para desenvolvimento):
import chromadb
from chromadb.utils import embedding_functions
client = chromadb.PersistentClient(path="./chroma_db")
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key="sua-chave-aqui",
model_name="text-embedding-3-small"
)
collection = client.get_or_create_collection(
name="documentos_empresa",
embedding_function=openai_ef
)
# Adicionar documentos
collection.add(
documents=["Nossa política de devolução permite troca em até 30 dias..."],
metadatas=[{"fonte": "politica-devolucao.pdf", "pagina": 1}],
ids=["doc_001"]
)
# Buscar
resultados = collection.query(
query_texts=["posso trocar um produto?"],
n_results=3
)
Em produção com dados sensíveis ou grandes volumes, migrar para pgvector (se já usa PostgreSQL) ou Pinecone é a decisão mais pragmática.
Chunking: Como Dividir Documentos para RAG Eficiente
O chunking — divisão de documentos em pedaços menores — é onde muitos projetos de RAG falham silenciosamente. Se os chunks são muito grandes, você inclui contexto desnecessário no prompt e paga mais tokens. Se são muito pequenos, o contexto fica fragmentado e sem sentido.
Não existe resposta universal, mas algumas diretrizes ajudam:
Chunk size: 256-512 tokens é um bom ponto de partida para documentos técnicos. Para documentos jurídicos com cláusulas longas, 512-1024 tokens pode ser necessário.
Overlap: sobreposição entre chunks consecutivos (50-100 tokens) garante que informações no limite entre dois chunks não se percam.
Chunking semântico: em vez de dividir por tamanho fixo, dividir por parágrafos ou seções semânticas produz chunks com sentido completo.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", ".", "!", "?", " ", ""],
length_function=len,
)
with open("manual_produto.txt") as f:
texto = f.read()
chunks = splitter.split_text(texto)
print(f"Total de chunks: {len(chunks)}")
print(f"Primeiro chunk: {chunks[0][:200]}...")
O RecursiveCharacterTextSplitter do LangChain tenta dividir primeiro por parágrafos (\n\n), depois por linhas, depois por sentenças — preservando ao máximo a coerência semântica.
Montando o Pipeline Completo com LangChain
Com embeddings, vector store e chunking configurados, o pipeline RAG completo funciona assim:
- Ingestão: carregar documentos, dividir em chunks, gerar embeddings e salvar no vector store
- Recuperação: receber a pergunta do usuário, gerar embedding da pergunta, buscar os N chunks mais similares
- Geração: montar um prompt com os chunks recuperados e a pergunta original, enviar ao LLM
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# Configurar retriever
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# Prompt customizado
template = """Use apenas o contexto abaixo para responder à pergunta.
Se a informação não estiver no contexto, diga "Não encontrei essa informação na nossa base de dados."
Contexto:
{context}
Pergunta: {question}
Resposta:"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
# Pipeline RAG
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": prompt},
return_source_documents=True,
)
# Uso
resultado = qa_chain.invoke({"query": "Qual o prazo de garantia dos produtos?"})
print(resultado["result"])
print("Fontes:", [doc.metadata["fonte"] for doc in resultado["source_documents"]])
Incluir as fontes na resposta é essencial: permite ao usuário verificar a informação e aumenta a confiança no sistema.
Um detalhe crítico no template: a instrução "Se a informação não estiver no contexto, diga X" reduz drasticamente as alucinações. O modelo tem permissão explícita para admitir que não sabe, em vez de inventar.
Conclusão com CTA
RAG transformou o que é possível fazer com LLMs em sistemas empresariais. Em vez de um modelo genérico que não conhece seu negócio, você passa a ter um assistente que responde com seus documentos, suas políticas e seus dados — de forma rastreável e atualizada.
Mas implementar RAG bem vai além de conectar uma API. Escolha de embedding, estratégia de chunking, configuração do retriever e avaliação de qualidade são decisões que impactam diretamente a utilidade do sistema.
No SystemForge, construímos pipelines RAG para empresas que precisam de IA que fala com seus dados de verdade — não com dados inventados. Se você tem documentos internos, manuais, contratos ou históricos que precisam estar acessíveis via linguagem natural, entre em contato.
Quer Automatizar com IA?
Implementamos soluções de IA e automação para empresas de todos os tamanhos.
Saiba mais →Precisa de ajuda?


