pgvector no Postgres: onde guardar a memória do seu agente
Você abriu a documentação de mais um serviço gerenciado de memória para o seu agente. Plano por mês, SDK próprio, mais um vendor no seu diagrama de arquitetura. E aí bate a pergunta honesta: o Postgres que já roda o seu Laravel não dá conta disso?
Quase sempre dá. A memória de um agente — as conversas passadas, os documentos que ele consulta, o histórico que ele precisa "lembrar" — no fundo é busca por similaridade em cima de embeddings. E o Postgres faz isso nativamente com uma extensão chamada pgvector. Mesmo banco, mesma transação, mesma query que você já sabe debugar.
Neste post você vai instalar o pgvector, guardar e buscar vetores, plugar isso num model Eloquent e — o mais importante — entender quando o Postgres resolve e quando faz sentido pagar por um serviço gerenciado. Sem hype. Engenharia.
TL;DR
- O que é: extensão open-source que adiciona o tipo
vectore busca por similaridade ao PostgreSQL. - Stack: PostgreSQL 12+, extensão
pgvector0.8+, Laravel/PHP viapgvector/pgvector-php. - Custo/Acesso: gratuito e open-source. Já vem disponível em RDS, Azure Database for PostgreSQL, Cloud SQL, Supabase e Neon.
- Repositório: pgvector/pgvector e pgvector/pgvector-php.
O contexto — por que memória de agente é, no fundo, busca vetorial
Quando a gente fala em "dar memória" para um agente, parece coisa mágica. Não é. É arquitetura.
Você pega um pedaço de texto — uma mensagem antiga, um trecho de documento, um fato sobre o usuário — e transforma em um vetor de números via embedding. Esse vetor é uma representação matemática do significado daquele texto. Textos parecidos viram vetores próximos no espaço. Aí, quando o agente precisa "lembrar" de algo relevante para a conversa atual, você embeda a pergunta, procura os vetores mais próximos e injeta esses trechos no contexto do modelo. Isso é RAG. Isso é memória de agente. É o mesmo mecanismo.
O ponto: guardar e buscar esses vetores não exige um banco vetorial dedicado. O pgvector adiciona um tipo vector ao Postgres e operadores de distância que rodam dentro de uma query SQL normal — você combina similaridade semântica com WHERE, JOIN e transação no mesmo lugar onde já moram os dados da sua aplicação (pgvector).
E não é brinquedo de protótipo. Benchmarks mostram busca abaixo de 20ms em 1 milhão de vetores com índice HNSW, mantendo recall acima de 95% (Northflank). Para a esmagadora maioria dos agentes que você vai colocar em produção, isso sobra.
Pré-requisitos
- PostgreSQL 12 ou superior (RDS, Supabase, Neon e Azure já suportam
pgvector). - Permissão para rodar
CREATE EXTENSIONno banco. - Laravel 10+ com a conexão
pgsqlconfigurada. - Um provider de embeddings (OpenAI, Cohere, Gemini, Voyage — tanto faz; o que importa é a dimensão do vetor).
Mão na massa — pgvector do zero ao Eloquent
Passo 1: ativar a extensão e criar a tabela
Conecta no banco e liga a extensão. Uma linha:
CREATE EXTENSION IF NOT EXISTS vector;
Agora a tabela de memória. O número entre parênteses é a dimensão do embedding — tem que bater exatamente com o que o seu modelo produz (o text-embedding-3-small da OpenAI, por exemplo, gera 1536):
CREATE TABLE agent_memories (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL,
content text NOT NULL,
embedding vector(1536),
created_at timestamptz DEFAULT now()
);
O pgvector aguenta até 16.000 dimensões, e cada vetor ocupa 4 * dimensões + 8 bytes em disco (pgvector). Guarde esse número — ele volta na hora de decidir entre Postgres e serviço gerenciado.
Passo 2: inserir e buscar por similaridade
Inserir é só passar o array como texto:
INSERT INTO agent_memories (user_id, content, embedding)
VALUES (42, 'cliente prefere contato por email', '[0.012, -0.034, ...]');
A busca é o coração de tudo. Você ordena pela distância entre o vetor da pergunta e os vetores guardados:
SELECT content
FROM agent_memories
WHERE user_id = 42
ORDER BY embedding <-> '[0.011, -0.030, ...]'
LIMIT 5;
Aquele <-> é o operador de distância L2 (euclidiana). O pgvector também tem <=> para distância de cosseno e <#> para inner product negativo — para embeddings normalizados de LLM, cosseno costuma ser a escolha (pgvector). Repare uma coisa que banco vetorial nenhum te dá tão fácil: o WHERE user_id = 42 e a busca semântica acontecem na mesma query, na mesma transação. Memória isolada por usuário, sem gambiarra.
Passo 3: criar índice (e escolher o certo)
Sem índice, o Postgres faz scan exato — perfeito até uns milhares de linhas, lento quando a tabela cresce. Aí você cria um índice aproximado. São dois:
-- HNSW: mais rápido na busca, melhor recall, insert barato
CREATE INDEX ON agent_memories USING hnsw (embedding vector_cosine_ops);
-- IVFFlat: usa menos memória, exige escolher o número de listas
CREATE INDEX ON agent_memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
A regra prática: comece com HNSW. Ele ganha em velocidade de busca e recall, e o insert é uma operação de custo constante no grafo. O preço é memória — o grafo HNSW pode ocupar de 2 a 5 vezes o tamanho dos vetores crus, enquanto o IVFFlat fica perto de 1x (BigDataBoutique). Se RAM for o seu gargalo, IVFFlat. Caso contrário, HNSW e segue o jogo.
Passo 4: plugar no Eloquent
Aqui o Laravel brilha. O pacote pgvector/pgvector-php dá um cast e um trait que escondem o SQL:
composer require pgvector/pgvector
Na migration, o tipo vector vira um método de coluna:
Schema::create('agent_memories', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id');
$table->text('content');
$table->vector('embedding', 1536);
$table->timestamps();
});
No model, o cast Vector e o trait HasNeighbors:
use Pgvector\Laravel\Vector;
use Pgvector\Laravel\HasNeighbors;
class AgentMemory extends Model
{
use HasNeighbors;
protected $fillable = ['user_id', 'content', 'embedding'];
protected $casts = ['embedding' => Vector::class];
}
E a busca vira PHP que você lê e debuga:
use Pgvector\Laravel\Distance;
$memory = AgentMemory::find($id);
$relevantes = $memory->nearestNeighbors('embedding', Distance::Cosine)
->where('user_id', $userId)
->take(5)
->get();
Os métodos de distância cobrem Distance::L2, Distance::Cosine, Distance::InnerProduct, além de L1, Hamming e Jaccard (pgvector-php). E se você usa o Laravel AI SDK mais novo, dá pra gerar o embedding com Str::of($text)->toEmbeddings() e buscar com whereVectorSimilarTo, fechando o ciclo sem sair do framework (Laravel News).
Limitações e pontos de atenção
O pgvector não é bala de prata. Onde você vai se queimar:
- Índice consome RAM. HNSW grande exige memória. Se você tem dezenas de milhões de vetores em dimensão alta, faça a conta de
4 * dimensões + 8bytes por linha antes de prometer prazo. Para dimensões acima de 2000,halfveccorta cerca de 50% do consumo de armazenamento com qualidade de busca parecida (dbi-services). - Filtro + ANN pode "subfiltrar". Quando você combina índice aproximado com
WHERErestritivo, às vezes voltam menos resultados do que oLIMITpede. Opgvector0.8.0 resolveu isso com iterative scans (hnsw.iterative_scan) e melhorou a escolha de índice na presença de filtros (PostgreSQL). Use 0.8+. - Memória de agente é mais que busca vetorial. Resumo de conversas longas, decaimento por tempo, deduplicação de fatos, extração de entidades — isso é lógica de aplicação que você escreve. O
pgvectorte dá o armazenamento e a recuperação. Os outros 20% são seus.
FAQ rápido
Preciso trocar de banco para usar pgvector?
Não. É uma extensão do Postgres que você já tem. CREATE EXTENSION vector e pronto. Em RDS, Supabase, Neon e Azure já vem disponível, é só habilitar.
HNSW ou IVFFlat? Comece com HNSW: melhor recall e busca mais rápida, insert barato. Migre para IVFFlat só se a memória do índice virar gargalo, já que ele ocupa perto de 1x o tamanho dos vetores contra 2-5x do HNSW.
Aguenta produção de verdade? Sim. Há benchmarks com busca abaixo de 20ms em 1 milhão de vetores e recall acima de 95% com HNSW. O limite prático aparece na casa das dezenas de milhões de vetores em dimensão alta — aí a conversa muda.
Qual dimensão uso na coluna?
A que o seu modelo de embedding gera. text-embedding-3-small da OpenAI = 1536. A coluna vector(n) precisa bater com esse número, senão o insert falha.
Conclusão — quando o Postgres resolve e quando não
Volta para a pergunta do começo. Antes de assinar o Mem0 ou qualquer serviço gerenciado de memória, faça três perguntas honestas.
Volume. Está na casa de milhares a poucos milhões de vetores? Postgres resolve com folga. Dezenas de milhões em dimensão alta, com latência agressiva e sharding? Aí um serviço dedicado começa a pagar o próprio preço.
Compliance. Se o dado da memória do agente não pode sair da sua infraestrutura, manter tudo no seu Postgres é mais simples do que auditar mais um processador de dados. Um vendor a menos no contrato.
Time. Serviço gerenciado vende conveniência: resumo automático, decaimento, deduplicação prontos. Se o seu time é pequeno e esses 20% custam caro pra construir, pagar pode fazer sentido. Mas comece sabendo que os 80% — guardar e recuperar por similaridade — o seu banco já faz.
Na prática, a decisão raramente é técnica. É de maturidade de produto. E entender onde a fronteira está — o que o seu Postgres resolve e o que justifica um serviço externo — é exatamente o tipo de decisão de arquitetura que separa quem usa IA de quem constrói produto com IA. É isso que a gente coloca na mesa, com código rodando, no Workshop Arquitetando Soluções de IA.
Agora que você tem memória vetorial no seu banco, o próximo passo é montar o pipeline de RAG do zero em cima dela: chunking, embedding em lote e reranking dos resultados.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Você também pode gostar
Guia de RAG para devs backend: do zero ao pgvector em Laravel
Tutorial completo de RAG em Laravel com PostgreSQL e pgvector: ingestion assíncrono, busca híbrida BM25 + embeddings com RRF, tool use no Claude API e as três métricas que separam protótipo de produto (recall@5, faithfulness e latência p95).
Agente que pesquisa antes de agir: multi-tool + RAG em Laravel com pgvector
Como construir um agente em Laravel que decide quando buscar e quando responder direto. Arquitetura completa com Prism PHP, pgvector e a lógica de orquestração que separa demo de produto.
Ferramentas de IA para dev backend: top 12 testadas em 90 dias de Laravel real
Review longa e opinada de 12 ferramentas de IA pra dev backend, depois de 90 dias rodando num Laravel real em produção. Nota 0-10, veredito em uma linha, e 5 que viralizaram mas não cumpriram.
RAG do zero: chunking, embeddings e busca que funciona
RAG não é mágica: é quebrar texto, virar vetor e buscar bem. O passo a passo de um RAG do zero — chunking recursive com overlap, embeddings com text-embedding-3-small e busca por similaridade no Postgres com pgvector e índice HNSW. Errar o chunking é onde 80% dos RAGs nascem ruins.