RAG na prática: adicione contexto privado ao seu LLM com pgvector
1. Fundamentos do RAG e a necessidade de contexto privado
Os modelos de linguagem de grande escala (LLMs) possuem uma limitação estrutural: seu conhecimento é estático, congelado na data de corte do treinamento. Um modelo treinado até 2023 não sabe sobre eventos de 2024, e, mais criticamente, não tem acesso a documentos internos da sua empresa, bases de conhecimento proprietárias ou dados sensíveis que nunca foram públicos. Isso gera dois problemas graves: alucinações (invenção de fatos) e incapacidade de responder sobre informações específicas do domínio.
O Retrieval-Augmented Generation (RAG) resolve essa limitação combinando dois componentes: um sistema de busca que recupera documentos relevantes de uma base privada e um LLM que gera respostas baseadas nesse contexto recuperado. Em vez de forçar o modelo a "saber tudo", o RAG permite que ele "pesquise" antes de responder. Os casos de uso são vastos: chatbots de RH que consultam políticas internas, assistentes de suporte técnico que acessam manuais de produtos, ou sistemas de compliance que verificam regulamentações atualizadas.
2. Arquitetura de um sistema RAG com banco vetorial
Um sistema RAG típico possui dois fluxos operacionais. O fluxo de indexação começa com a divisão dos documentos em chunks (pedaços gerenciáveis), seguido pela geração de embeddings vetoriais que capturam o significado semântico de cada chunk, e finalmente o armazenamento desses vetores em um banco vetorial. O fluxo de consulta recebe a pergunta do usuário, gera seu embedding, realiza uma busca por similaridade no banco vetorial para encontrar os chunks mais relevantes, e então monta um prompt aumentado que combina a instrução do sistema, o contexto recuperado e a pergunta original, enviando tudo ao LLM para geração da resposta.
Os componentes essenciais são três: um modelo de embedding (como text-embedding-3-small da OpenAI), um banco vetorial (pgvector neste artigo), e um LLM (GPT-4, Claude ou modelos locais via Ollama).
3. pgvector: o banco vetorial dentro do PostgreSQL
pgvector é uma extensão open-source que transforma o PostgreSQL em um banco vetorial completo. Sua principal vantagem é eliminar a necessidade de um banco separado para vetores, simplificando a arquitetura e aproveitando todos os recursos do PostgreSQL: transações ACID, replicação, backup e SQL avançado.
A instalação é direta:
-- No PostgreSQL (versão 14+)
CREATE EXTENSION vector;
Para criar uma tabela que armazena documentos com seus embeddings:
CREATE TABLE documentos (
id SERIAL PRIMARY KEY,
conteudo TEXT NOT NULL,
metadados JSONB DEFAULT '{}',
embedding vector(1536) -- dimensão do text-embedding-3-small
);
pgvector suporta três tipos de índices para busca eficiente:
- IVFFlat (Inverted File with Flat): rápido para construir, adequado para datasets médios
- HNSW (Hierarchical Navigable Small World): mais preciso, ideal para grandes volumes e alta precisão
-- Índice IVFFlat com 100 listas
CREATE INDEX idx_documentos_ivfflat ON documentos
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Índice HNSW (mais preciso)
CREATE INDEX idx_documentos_hnsw ON documentos
USING hnsw (embedding vector_cosine_ops);
4. Preparação dos dados: chunking e embeddings
A qualidade do RAG depende diretamente da estratégia de chunking. Três abordagens comuns:
- Tamanho fixo: divide o texto a cada N tokens (ex.: 500 tokens com sobreposição de 50)
- Semântico: quebra em parágrafos ou seções naturais
- Recursivo: combina separadores hierárquicos (dupla quebra de linha > quebra de linha > ponto final)
Exemplo de chunking com tamanho fixo em Python:
def chunk_text(texto, chunk_size=500, overlap=50):
palavras = texto.split()
chunks = []
for i in range(0, len(palavras), chunk_size - overlap):
chunk = ' '.join(palavras[i:i + chunk_size])
chunks.append(chunk)
return chunks
documento = """texto longo do documento corporativo..."""
chunks = chunk_text(documento)
print(f"Gerados {len(chunks)} chunks")
Para gerar embeddings, usamos o modelo text-embedding-3-small da OpenAI:
import openai
import psycopg2
from psycopg2.extras import execute_values
openai.api_key = "sua-chave-aqui"
def gerar_embedding(texto):
resposta = openai.embeddings.create(
model="text-embedding-3-small",
input=texto
)
return resposta.data[0].embedding
# Conectar ao PostgreSQL
conn = psycopg2.connect("dbname=ragdb user=postgres password=secret")
cur = conn.cursor()
# Inserir chunks com embeddings
for chunk in chunks:
embedding = gerar_embedding(chunk)
cur.execute(
"INSERT INTO documentos (conteudo, embedding) VALUES (%s, %s)",
(chunk, embedding)
)
conn.commit()
5. Implementação prática da busca vetorial
A busca por similaridade no pgvector usa operadores específicos:
- <=> distância cosseno (recomendado para embeddings normalizados)
- <-> distância L2 (euclidiana)
- <#> produto interno
Exemplo de consulta que retorna os 5 chunks mais similares:
import psycopg2
def buscar_chunks_relevantes(pergunta, top_k=5):
# Gerar embedding da pergunta
embedding_pergunta = gerar_embedding(pergunta)
conn = psycopg2.connect("dbname=ragdb user=postgres password=secret")
cur = conn.cursor()
# Busca por similaridade de cosseno com filtro de metadados
cur.execute("""
SELECT conteudo, metadados,
1 - (embedding <=> %s::vector) AS similaridade
FROM documentos
WHERE metadados->>'categoria' = 'politicas_internas'
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (embedding_pergunta, embedding_pergunta, top_k))
resultados = cur.fetchall()
cur.close()
conn.close()
return resultados
# Exemplo de uso
pergunta = "Qual é a política de férias para funcionários?"
chunks_relevantes = buscar_chunks_relevantes(pergunta)
for chunk, meta, score in chunks_relevantes:
print(f"Similaridade: {score:.3f}")
print(f"Conteúdo: {chunk[:200]}...")
print("---")
6. Integração com o LLM e construção do prompt aumentado
O prompt aumentado é a peça central do RAG. A estrutura segue este padrão:
def construir_prompt(pergunta, chunks):
contexto = "\n\n".join([f"[Documento {i+1}]: {chunk[0]}"
for i, chunk in enumerate(chunks)])
prompt = f"""Você é um assistente especializado em políticas internas da empresa.
Use APENAS as informações fornecidas abaixo para responder.
CONTEXTO:
{contexto}
PERGUNTA: {pergunta}
INSTRUÇÕES:
- Responda de forma clara e objetiva
- Se o contexto não contiver a resposta, diga que não sabe
- Cite os documentos relevantes quando apropriado
RESPOSTA:"""
return prompt
# Chamada ao LLM
def responder_com_rag(pergunta):
chunks = buscar_chunks_relevantes(pergunta)
prompt = construir_prompt(pergunta, chunks)
resposta = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Você é um assistente corporativo."},
{"role": "user", "content": prompt}
],
temperature=0.3 # baixo para respostas factuais
)
return resposta.choices[0].message.content
# Teste
resposta = responder_com_rag("Quantos dias de férias tenho direito?")
print(resposta)
Para modelos locais via Ollama:
import requests
def responder_local(pergunta):
chunks = buscar_chunks_relevantes(pergunta)
prompt = construir_prompt(pergunta, chunks)
resposta = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "llama3",
"prompt": prompt,
"stream": False
}
)
return resposta.json()["response"]
7. Otimizações e boas práticas para produção
Para levar o RAG a produção, considere estas otimizações:
Parâmetros críticos:
- top_k: entre 3 e 7 chunks (teste com seu domínio)
- Similaridade mínima: descarte chunks com score < 0.7
- temperature: mantenha entre 0.1 e 0.4 para respostas factuais
Performance:
- Use batch embedding para processar múltiplos chunks de uma vez
- Implemente cache Redis para consultas frequentes
- Ajuste o índice HNSW com parâmetros m=16 e ef_construction=200
Monitoramento de qualidade:
- Avalie a relevância dos chunks retornados (feedback do usuário)
- Monitore alucinações comparando respostas com o contexto
- Implemente logging de todas as consultas para auditoria
-- Exemplo de tabela para logging
CREATE TABLE logs_rag (
id SERIAL PRIMARY KEY,
pergunta TEXT,
chunks_retornados INTEGER,
similaridade_media FLOAT,
resposta TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
Referências
- Documentação oficial pgvector — Repositório oficial com instruções completas de instalação, tipos de dados e índices
- Guia de Embeddings da OpenAI — Documentação sobre modelos de embedding, parâmetros e melhores práticas
- Tutorial RAG com PostgreSQL pela Timescale — Artigo prático mostrando implementação completa de RAG com pgvector
- LangChain RAG Documentation — Framework popular que integra pgvector, chunking e LLMs em pipelines RAG
- Ollama: modelos locais para RAG — Tutorial sobre execução de modelos de embedding e LLMs localmente para RAG
- Estratégias de Chunking para RAG — Guia detalhado sobre diferentes abordagens de chunking e seus trade-offs
- Boas práticas de produção para RAG — Recomendações da LlamaIndex para deploy de sistemas RAG em produção