1. O que é RAG (e o que não é)
RAG (Retrieval-Augmented Generation) combina busca + geração. Em vez de depender só do conhecimento treinado no modelo, o sistema:
- Recebe a pergunta do usuário
- Busca trechos relevantes em uma base de documentos (PDFs, Markdown, tickets, etc.)
- Injeta esses trechos no contexto do prompt
- Pede ao modelo para responder usando apenas o material recuperado, citando fontes
Não é fine-tuning. Não é treinar um modelo do zero. É um padrão de arquitetura que reduz alucinações e permite atualizar a base sem re-treinar nada.
2. Arquitetura mínima
Para um protótipo sério, quatro peças bastam:
- Store de documentos — arquivos + metadados (título, origem, data)
- Índice vetorial — embeddings dos chunks (aqui: pgvector)
- Retriever — busca por similaridade + filtros opcionais
- Gerador — LLM com prompt que força uso das fontes e citações
PostgreSQL + extensão pgvector é uma escolha sólida: você já tem o banco, ACID, backups e SQL. Para muitos casos (até dezenas/centenas de milhares de chunks) não precisa de Pinecone ou similar.
3. Preparar o PostgreSQL + pgvector
No Postgres 16+ (ou 15 com a extensão instalada):
-- Habilitar a extensão
CREATE EXTENSION IF NOT EXISTS vector;
-- Tabela de documentos (metadados)
CREATE TABLE documents (
id bigserial PRIMARY KEY,
title text NOT NULL,
source text, -- caminho do arquivo, URL, etc.
created_at timestamptz DEFAULT now()
);
-- Chunks com embedding
-- dimension 1536 = text-embedding-3-small (OpenAI)
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
document_id bigint REFERENCES documents(id) ON DELETE CASCADE,
content text NOT NULL,
embedding vector(1536),
chunk_index int,
token_count int
);
-- Índice HNSW para busca aproximada (bom equilíbrio qualidade/velocidade)
CREATE INDEX ON chunks
USING hnsw (embedding vector_cosine_ops);
Ajuste a dimensão conforme o modelo de embedding que você usar (OpenAI, Voyage, nomic, etc.).
4. Chunking e embeddings
Documentos longos precisam ser divididos. Regras práticas:
- Tamanho típico: 400–800 tokens por chunk
- Overlap de 10–20% para não cortar frases no meio
- Preferir quebrar em títulos/parágrafos (não no meio de tabelas ou código)
- Guardar
chunk_indexe referência ao documento original
Exemplo mínimo em Python (usando a API oficial da OpenAI):
from openai import OpenAI
import psycopg2
client = OpenAI()
def embed(texts: list[str]) -> list[list[float]]:
res = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
return [d.embedding for d in res.data]
# após chunkar o PDF/Markdown:
embeddings = embed([c["content"] for c in chunks])
# inserir no Postgres com psycopg2 ou SQLAlchemy
5. Retrieval + geração
Fluxo de uma pergunta:
-- 1. Embedding da pergunta
-- (feito na aplicação)
-- 2. Busca dos k chunks mais próximos
SELECT content, document_id, 1 - (embedding <=> $1) AS score
FROM chunks
ORDER BY embedding <=> $1
LIMIT 6;
O operador <=> é distância de cosseno no pgvector. Depois monte o prompt:
Você é um assistente técnico. Responda APENAS com base nos trechos abaixo.
Se a informação não estiver nos trechos, diga que não encontrou.
Cite a fonte no formato [doc:ID].
Trechos:
---
[doc:42] ...conteúdo do chunk...
[doc:17] ...conteúdo do chunk...
---
Pergunta do usuário: {pergunta}
Envie para o modelo (GPT-4o-mini, Claude, Gemini, etc.) com temperatura baixa (0–0.3) e, se possível, response format estruturado.
6. Quando RAG não é a solução certa
- Perguntas que exigem raciocínio multi-hop complexo sem evidência direta nos docs
- Base muito pequena e estável — às vezes um prompt com o texto inteiro basta
- Necessidade de respostas em tempo real com dados transacionais (aí é melhor function calling + SQL)
- Documentos extremamente ruidosos ou mal estruturados (priorize limpeza antes de indexar)
7. Próximos passos
- Adicionar hybrid search (BM25 + vetor) para termos exatos (códigos de erro, nomes de serviços)
- Filtros por metadados (departamento, data, produto)
- Avaliação: conjunto de perguntas + respostas esperadas e métricas de retrieval (recall@k, MRR)
- Observabilidade: logar quais chunks foram usados e se o usuário aceitou a resposta
Com esse esqueleto você já tem um assistente interno utilizável. O resto é iteração sobre qualidade dos chunks, modelo de embedding e prompt de geração.
8. O SQL e o código explicados
| Trecho | O que faz |
|---|---|
CREATE EXTENSION IF NOT EXISTS vector; | Ativa a extensão pgvector no banco. O IF NOT EXISTS evita erro se ela já estiver ativa. |
id bigserial PRIMARY KEY | Identificador inteiro que se auto-incrementa e é a chave primária. |
text NOT NULL | Coluna de texto obrigatória. |
created_at timestamptz DEFAULT now() | Data e hora com fuso, preenchidas automaticamente. |
document_id bigint REFERENCES documents(id) ON DELETE CASCADE | Liga cada chunk ao seu documento. Se o documento for apagado, os chunks vão junto. |
embedding vector(1536) | Coluna de vetor com 1536 dimensões, o tamanho do modelo text-embedding-3-small. |
CREATE INDEX ... USING hnsw (embedding vector_cosine_ops) | Índice HNSW para busca aproximada dos vizinhos mais próximos, usando distância de cosseno. |
1 - (embedding <=> $1) AS score | Converte a distância de cosseno em similaridade: quanto mais perto de 1, mais parecido. |
ORDER BY embedding <=> $1 LIMIT 6 | Devolve os 6 chunks mais próximos da pergunta. O $1 é o vetor da pergunta, enviado pela aplicação. |
| Operador | Distância | Classe do índice |
|---|---|---|
<-> | euclidiana (L2) | vector_l2_ops |
<=> | cosseno | vector_cosine_ops |
<#> | produto interno (negativo) | vector_ip_ops |
| Modelo de embedding | Dimensão |
|---|---|
text-embedding-3-small (OpenAI) | 1536 |
text-embedding-3-large (OpenAI) | 3072 |
nomic-embed-text | 768 |
No Python, OpenAI() lê a chave da variável de ambiente OPENAI_API_KEY. A chamada client.embeddings.create(model=..., input=texts) aceita uma lista de textos e devolve um vetor por texto em res.data, o que permite processar em lote.
9. Erros comuns e como resolver
| Sintoma | Causa provável | Solução |
|---|---|---|
type "vector" does not exist | A extensão não está instalada ou ativada neste banco. | Instale o pacote do pgvector no servidor e rode CREATE EXTENSION vector; no banco certo. |
expected 1536 dimensions, not 768 | O modelo de embedding gera uma dimensão diferente da coluna. | Use o mesmo modelo na indexação e na busca, ou recrie a coluna e reindexe. |
| A busca fica lenta com muitos chunks | Não existe índice HNSW ou a consulta não usa ORDER BY ... LIMIT. | Crie o índice e mantenha o LIMIT na consulta. |
| Os trechos recuperados fogem do tema | Chunks grandes ou pequenos demais. | Ajuste o tamanho e a sobreposição e meça com um conjunto de perguntas de teste. |
| O modelo inventa informação | O prompt permite usar conhecimento fora dos trechos. | Mantenha a instrução de responder apenas com os trechos, a citação de fonte e a temperatura baixa. |