Busca semântica com IA no PostgreSQL com pgvector

- Published on

Aprenda a criar um mecanismo de busca semântica elegante e impulsionado por IA que opera diretamente no seu banco de dados existente. Veremos como armazenar embeddings vetoriais, executar consultas baseadas em similaridade e transformar buscas de texto comuns em recuperação sensível ao significado, utilizando apenas SQL padrão e uma extensão vetorial.
Imagine tentar descrever um filme para um amigo sem dizer o nome dele. Você poderia dizer: "é aquele filme sobre um astronauta preso em outro planeta que cultiva batatas para sobreviver" e, mesmo sem mencionar o título, seu amigo sabe imediatamente que você está falando de Perdido em Marte (The Martian).
Os seres humanos são excelentes em compreender o significado por trás das palavras, indo além da simples correspondência de palavras-chave exatas. Em contrapartida, os bancos de dados tradicionais historicamente exigiam correspondências exatas para encontrar resultados.
Isso está mudando. Com a extensão pgvector para PostgreSQL, seu banco de dados pode finalmente realizar buscas baseadas em significado, e não apenas na correspondência de padrões de texto. Ao transformar textos e outros dados em vetores de alta dimensão (representações numéricas de significado), o PostgreSQL consegue realizar buscas de similaridade baseadas na intenção e no contexto.
Isso abre caminho para novos e poderosos recursos de busca, recomendações e aplicações impulsionadas por IA — tudo isso dentro do banco de dados SQL que você já conhece e adora.

Por que a busca tradicional por palavras-chave é insuficiente
Consultas SQL padrão são excelentes para correspondências exatas (como encontrar produtos em uma categoria específica ou documentos que contenham certas palavras-chave), mas deixam a desejar quando se trata de compreender o contexto ou a intenção. Por exemplo:
Um cliente pode pesquisar por "notebooks acessíveis para programação", mas seu catálogo classifica os produtos como "computadores de baixo custo para desenvolvimento de software". Uma busca simples por palavras-chave poderia não identificar essas correspondências, embora a intenção seja a mesma.
Você deseja recomendar artigos semelhantes aos que o usuário acabou de ler, mesmo que utilizem termos diferentes. A sobreposição exata de palavras-chave pode ser mínima ou inexistente.
Um chatbot de suporte precisa recuperar respostas que correspondam à intenção da pergunta do cliente, e não apenas às palavras exatas digitadas. Esses cenários exigem compreensão semântica — isto é, captar o significado por trás das palavras. É aí que entram os vector embeddings .
O que são Vector Embeddings?
Um vector embedding é, essencialmente, uma lista de números que codifica a essência semântica de dados — frequentemente, texto. Pense nisso como o mapeamento de um significado para um ponto em um espaço de alta dimensionalidade. Conceitos semelhantes acabam sendo representados por pontos próximos nesse espaço.

Principais características dos embeddings:
- Representação semântica: Frases ou conceitos com significados semelhantes possuem vetores parecidos. Por exemplo, "Preciso de férias" e "À procura de uma viagem de férias" gerariam embeddings próximos entre si, mesmo que as palavras sejam diferentes.
- Similaridade mensurável: É possível medir a proximidade entre dois trechos de texto calculando a distância entre seus vetores.
- Gerados por IA: Os embeddings são provenientes de modelos de IA treinados com grandes volumes de dados. Modelos criados pela OpenAI ou pela Cohere, por exemplo, ou sentence transformers, convertem texto nessas assinaturas numéricas.
Você pode imaginar um embedding de texto como um "índice de ideias" — uma assinatura numérica de uma frase ou documento. Ao comparar essas assinaturas, podemos localizar rapidamente conteúdos semanticamente relacionados em um banco de dados.
Eu já comentei sobre conteúdos semanticamente relacionados na esfera do SEO aqui, é basicamente como as buscas no Google e outros buscadores funcionam hoje em dia. Eles são capazes de identificar a intenção de busca, não somente atrelada a correspondência exata da palavra-chave.
O que é a extensão pgvector no PostgreSQL?
Até agora, a busca semântica frequentemente exigia o uso de um banco de dados vetorial especializado ou de um serviço de busca externo. A extensão pgvector muda esse cenário ao trazer a busca vetorial diretamente para o PostgreSQL. Com o pgvector, você pode:
- Armazenar embeddings em uma coluna nativa do tipo
VECTOR. - Executar consultas de similaridade usando operadores vetoriais, como o
<->(para distância). - Criar índices em colunas vetoriais para realizar buscas de vizinhos mais próximos com altíssima velocidade.
- Combinar a busca vetorial com filtros SQL tradicionais e busca de texto completo (full-text search).
Em resumo, o pgvector permite criar recursos de busca e recomendação baseados em IA diretamente no seu banco de dados PostgreSQL. Não é necessário um mecanismo de busca separado — basta usar SQL e vetores.
Entenda também como otimizar suas queries no PostgreSQL quando se trata de arrays.
Criando tabelas com uma coluna de vetor
Em seguida, crie uma tabela para armazenar seus dados (documentos, produtos, etc.) e inclua uma coluna para embeddings. Por exemplo:
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(1536) -- As dimensões dependem do seu modelo de embedding.
);
Aqui, VECTOR(1536) significa que cada embedding possui 1536 dimensões. Isso corresponde ao modelo text-embedding-3-small da OpenAI. Se você utilizar um modelo diferente (como um modelo da Cohere ou um transformer de código aberto), ajuste a dimensão de acordo.
Dica: Para testes rápidos, você pode usar uma dimensão pequena (como VECTOR(3)), mas, em produção, é recomendável utilizar a dimensão completa do seu modelo de embedding para obter os melhores resultados.
Gerando e Armazenando Embeddings
Agora, vamos gerar embeddings para os seus dados e inseri-los. Como exemplo, veja como você pode utilizar Python e a API da OpenAI:
import openai
import psycopg
client = openai.OpenAI(api_key="YOUR_API_KEY")
# Texto de exemplo
text = "Uma aventura de ficção científica cheia de suspense e com uma reviravolta no final."
# Gere um embedding para o texto
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embedding = response.data[0].embedding # Esta é uma lista de 1536 números de ponto flutuante.
# Inserir no Postgres
conn = psycopg.connect("dbname=vector_demo user=postgres")
with conn.cursor() as cur:
cur.execute(
"INSERT INTO documents (content, embedding) VALUES (%s, %s)",
(text, embedding)
)
conn.commit()
Observe que o driver psycopg converte automaticamente a lista do Python para o tipo VECTOR do PostgreSQL.
Execução de consultas de similaridade
Com os dados já carregados, você pode encontrar entradas semelhantes a uma consulta comparando embeddings. Por exemplo, se você tiver um embedding de consulta (talvez proveniente de outra chamada de API), pode executar:
SELECT id, content
FROM documents
ORDER BY embedding <-> '[0.12, -0.44, ...]' -- seu vetor de consulta aqui
LIMIT 5;
Por exemplo, a consulta pode retornar resultados como:
| id | content |
|---|---|
| 3 | "Um drama de ficção científica emocionante ambientado em uma colônia distante." |
| 7 | "Uma história sobre a sobrevivência em um mundo alienígena." |
| 1 | "Aventura de ficção científica cheia de suspense com uma reviravolta no final." |
| 9 | "Narrativa de exploração com fortes temas científicos." |
Estes são os documentos cujos vetores estão mais próximos do embedding da consulta, tornando-os os mais semanticamente relevantes.
O operador <-> calcula a distância euclidiana entre vetores (uma distância menor indica maior similaridade). Esta consulta retorna os 5 documentos cujos embeddings estão mais próximos da consulta. Esses são os resultados semanticamente mais relevantes.
Otimizando o Desempenho: Indexação de Vetores para Velocidade
Quando se tem apenas algumas linhas, uma varredura completa da tabela (full table scan) pode ser suficiente. No entanto, à medida que os dados crescem, é recomendável criar um índice na coluna de embeddings. O pgvector oferece dois tipos principais de índice:
- HNSW (Hierarchical Navigable Small World): Um índice baseado em grafos que conecta vetores aos seus "vizinhos". É muito rápido e preciso por padrão, sem exigir treinamento adicional. Excelente para conjuntos de dados de até dezenas de milhões de vetores.
- IVFFlat (Inverted File with Flat Quantization): Agrupa vetores em clusters (usando k-means) para que apenas determinados grupos sejam pesquisados. Requer uma etapa de treinamento (
ANALYZE), mas escala para conjuntos de dados muito grandes (centenas de milhões de vetores). Permite trocar um pouco de recall por velocidade, ajustando a quantidade de clusters consultados.
Tanto o HNSW quanto o IVFFlat são formas de indexação ANN (Approximate Nearest Neighbor ou Vizinho Mais Próximo). Em vez de percorrer todos os vetores, as estruturas ANN exploram apenas um subconjunto do espaço de busca, reduzindo drasticamente o tempo de consulta e mantendo um alto recall. Esse equilíbrio torna a indexação ANN ideal para aplicações de busca em tempo real, onde a velocidade é mais importante do que a precisão absoluta.
Para criar um índice na tabela de documents:
Para HNSW (usando distância euclidiana):
CREATE INDEX ON documents USING hnsw (embedding vector_l2_ops);
Use vector_cosine_ops se preferir a similaridade de cosseno. Para IVFFlat (com distância L2, digamos, 100 listas):
CREATE INDEX ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
ANALYZE documents; -- treina o índice IVFFlat
A configuração lists = 100 significa "agrupar em 100 clusters". Mais listas significam mais clusters (potencialmente maior precisão, mas maior consumo de memória e tempo de treinamento). O comando ANALYZE é fundamental para o IVFFlat, pois ele constrói os clusters com base nos seus dados.
Ajuste do índice
Estes índices são aproximados (utilizam algoritmos de vizinho mais próximo), portanto, você pode ajustá-los:
Para HNSW, aumente
hnsw.ef_searchpara obter maior recall (ao custo de uma busca mais lenta). Por exemplo:SET hnsw.ef_search = 100;Para IVFFlat, aumente
ivfflat.probespara pesquisar mais clusters por consulta:SET ivfflat.probes = 10;Experimente essas configurações com seus dados para equilibrar precisão e desempenho.
Combinando Busca Semântica e Tradicional
A busca vetorial funciona muito bem em conjunto com a filtragem SQL convencional e até mesmo com a busca de texto completo. Muitas vezes, você deseja encontrar resultados que correspondam a determinados metadados ou palavras-chave e que sejam semanticamente relevantes. Essa abordagem híbrida proporciona resultados de alta precisão.
Por exemplo, suponha que você esteja procurando artigos recentes de ficção científica sobre o espaço. Você poderia escrever:
SELECT id, content
FROM documents
WHERE category = 'sci-fi'
AND published_date > '2023-01-01'
AND to_tsvector('english', content) @@ plainto_tsquery('space')
ORDER BY embedding <-> '[0.12, -0.44, ...]' -- your query vector
LIMIT 10;
Esta consulta realiza quatro ações:
- Filtra por
category = "sci-fi". - Filtra conteúdo recente.
- Aplica um filtro de palavra-chave (
space) usando a busca de texto completo (full-text search) do PostgreSQL. - Ordena os resultados pela proximidade semântica em relação ao vetor de consulta.
O resultado final é uma lista selecionada de artigos recentes de ficção científica sobre o espaço que são mais relevantes para a intenção da consulta.
Casos de uso práticos de busca semântica com pgvector
Aqui estão algumas formas concretas de como as equipes estão utilizando a busca semântica com pgvector:
Busca inteligente de produtos: Uma loja online pode pegar a consulta de um usuário, como "notebook leve e bom para edição de vídeo", convertê-la em um vetor e executar:
# Código da aplicação
query_embedding = get_embedding("notebook leve e bom para edição de vídeo")
Em seguida, no SQL:
SELECT id, name, price, description
FROM products
WHERE category = 'computers' AND in_stock = true
ORDER BY embedding <-> query_embedding
LIMIT 10;
Isso retorna o item que melhor corresponde semanticamente à consulta, filtrado por categoria e disponibilidade em estoque. O sistema encontra notebooks relevantes mesmo que as descrições utilizem termos diferentes.
Chatbots de Base de Conhecimento
Um sistema de suporte pode converter a pergunta do usuário em um embedding e pesquisar nas FAQs:
question_embedding = get_embedding("Como redefino minha senha?")
Então:
SELECT id, question, answer
FROM knowledge_base
ORDER BY embedding <-> question_embedding
LIMIT 3;
O chatbot recupera as principais perguntas frequentes sobre redefinição de senha (mesmo que formuladas de maneira diferente), fornecendo respostas mais precisas.
Recomendações de conteúdo
Um site de notícias pode recomendar artigos semelhantes. Calcule um embedding para o artigo atual:
article_embedding = get_embedding(current_article.content)
Em seguida, encontre artigos semelhantes:
SELECT id, title, summary
FROM articles
WHERE id != current_article.id
AND published_date > (CURRENT_DATE - INTERVAL '30 days')
ORDER BY embedding <-> article_embedding
LIMIT 5;
Isso encontra outros artigos recentes cujo conteúdo seja mais próximo em significado ao que o leitor está visualizando.
Esses exemplos mostram como a busca semântica pode revelar conteúdo relevante com base no significado, e não apenas na correspondência de palavras-chave.
Melhores Práticas para Produção
À medida que você escala a operação, aqui estão algumas melhores práticas a seguir:
Escolha o Modelo de Embedding Adequado
Diferentes modelos apresentam diferentes equilíbrios entre fatores. Para muitos casos de uso, o modelo text-embedding-3-small da OpenAI (1.536 dimensões) oferece um excelente equilíbrio entre qualidade e custo. Se você precisa de resultados de alto desempenho, o text-embedding-3-large (mais de 3.000 dimensões) é mais preciso, porém tem um custo maior por consulta. Testes comparativos indicam que modelos maiores (mesmo com redução de dimensionalidade) frequentemente superam modelos mais antigos, como o text-embedding-ada-002. Avalie e escolha o modelo que melhor atenda às suas necessidades de precisão e ao seu orçamento.
Agrupe suas solicitações de embedding
Para evitar fazer uma chamada de API por documento, envie os textos em lotes. Por exemplo:
texts = ["Document 1", "Document 2", "Document 3", ...]
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
embeddings = [item.embedding for item in response.data]
Isso obtém embeddings para vários documentos de uma só vez, o que é mais eficiente e econômico.
Carregue dados em lote
Ao inserir muitas linhas, utilize métodos de processamento em lote (bulk methods). Por exemplo, com o psycopg:
data = [(texts[i], embeddings[i]) for i in range(len(texts))]
with conn.cursor() as cur:
cur.executemany(
"INSERT INTO documents (content, embedding) VALUES (%s, %s)",
data
)
conn.commit()
Ou utilize o comando COPY do PostgreSQL caso tenha um arquivo grande. O carregamento em lote acelera drasticamente a ingestão de dados.
Garanta que seu índice seja utilizado
Após criar um índice vetorial, verifique se as consultas o utilizam. Use o EXPLAIN:
EXPLAIN SELECT id, content
FROM documents
ORDER BY embedding <-> '[0.12, -0.44, ...]'
LIMIT 5;
O plano deve exibir algo como Index Scan using ..._embedding_idx. Se ele recorrer a um sequential scan, verifique as definições da sua consulta e do índice.
Particione tabelas grandes
Se você tiver uma coleção muito grande (centenas de milhões de vetores), considere o particionamento. Por exemplo:
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
category TEXT,
content TEXT,
embedding VECTOR(1536)
) PARTITION BY LIST (category);
CREATE TABLE documents_tech PARTITION OF documents FOR VALUES IN ('technology');
CREATE TABLE documents_health PARTITION OF documents FOR VALUES IN ('health');
-- Adicione mais partições conforme necessário.
O particionamento por categoria (ou tempo, região, etc.) pode melhorar a capacidade de gerenciamento e o desempenho de consultas em escala.
O Futuro da Busca Vetorial no PostgreSQL
As capacidades vetoriais do PostgreSQL continuam evoluindo. A cada nova versão do pgvector, vemos melhorias como:
- Algoritmos ANN e tipos de índice mais avançados.
- À medida que os métodos ANN evoluem, o pgvector incorpora novos algoritmos que aprimoram ainda mais a qualidade da busca e reduzem a latência.
- Suporte a métricas de distância adicionais (cosseno*, produto interno, etc.) e tipos de vetores.
- Execução paralela de consultas mais eficiente para buscas vetoriais.
- Planejamento e otimização de consultas mais inteligentes para consultas híbridas (vetor + texto).
Similaridade de Cosseno
Confesso que fiquei um pouco confuso quando ouvi essa explicação pela primeira vez, mas a técnica é amplamente aplicada para quantificar a semelhança na análise de textos — identificando pontos em comum entre documentos — e em sistemas de recomendação (como os da Netflix ou da Amazon).
A similaridade de cosseno varia de -1 a 1: o valor 1 indica vetores perfeitamente alinhados (alta similaridade), 0 indica vetores ortogonais (nenhuma similaridade) e -1 sugere um alinhamento oposto. Se você quiser saber mais sobre a fundamentação matemática dessa técnica, consulte este artigo.
Essas melhorias contínuas tornam o PostgreSQL uma plataforma ainda mais poderosa para aplicações impulsionadas por IA. É possível combinar a busca vetorial com todos os recursos tradicionais do SQL (joins, transações, etc.), permitindo arquiteturas mais simples. Não há necessidade de um mecanismo de busca separado: o seu banco de dados faz tudo isso.
Principais Pontos
A busca vetorial transforma o PostgreSQL de um banco de dados relacional tradicional em um mecanismo de busca semântica. Com o pgvector, você pode:
- Armazenar e pesquisar dados com base no significado, e não apenas em palavras-chave.
- Criar sistemas de busca semântica, sistemas de recomendação e outros recursos impulsionados por IA.
- Manter tudo dentro do banco de dados PostgreSQL que você já utiliza.
- Combinar a similaridade vetorial com seus filtros, joins e recursos de busca de texto completo (full-text search) existentes.
- Escalar para milhões de embeddings com a indexação adequada.
A capacidade de encontrar dados com base no significado está revolucionando a busca e a descoberta de informações. O melhor de tudo é que você pode implementar essas funcionalidades utilizando ferramentas nas quais já confia. Agora, seu banco de dados não apenas armazena os dados, mas também os compreende.
Aplicação Prática: AI Search com PostgreSQL e pgvector
Para ilustrar na prática todos os conceitos abordados neste artigo, desenvolvi uma aplicação completa disponível no GitHub:
🔗 reulison/ai-search-postgresql-pgvector
O que a aplicação faz?
A aplicação é uma API REST construída com FastAPI que permite:
- Armazenar documentos com embeddings gerados por IA
- Realizar buscas semânticas por similaridade vetorial
- Filtrar por categorias (ex: filmes)
Arquitetura simplificada
Usuário → API (FastAPI) → OpenAI Embeddings → PostgreSQL (pgvector) → Resultados semânticos
Exemplo de uso
Inserindo um documento
curl -X 'POST' \
'http://127.0.0.1:8000/documents' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"content": "Um suspense psicológico sobre uma escritora que começa a confundir realidade e ficção.",
"category": "filmes"
}'
Resposta da API:
{
"id": 5
}

O documento é armazenado no banco de dados com seu respectivo embedding vetorial, gerado via OpenAI.
Realizando uma busca semântica
Agora, vamos buscar por filmes relacionados ao tema "filme de astronauta preso em outro planeta":
curl -X 'POST' \
'http://127.0.0.1:8000/search' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' \
-d '{
"query": "filme de astronauta preso em outro planeta",
"limit": 5,
"category": "filmes"
}'
Resposta da API:

{
"query": "filme de astronauta preso em outro planeta",
"results": [
{
"id": 2,
"content": "Um astronauta preso em Marte tenta voltar para casa.",
"category": "filmes",
"distance": 0.7849
},
{
"id": 1,
"content": "Um drama de ficção científica sobre sobrevivência em Marte.",
"category": "filmes",
"distance": 0.9434
},
{
"id": 4,
"content": "Um filme de ação em que um ex-agente secreto precisa impedir um ataque global.",
"category": "filmes",
"distance": 1.0372
},
{
"id": 3,
"content": "Uma animação sobre um jovem inventor que embarca em uma aventura para salvar sua vila.",
"category": "filmes",
"distance": 1.0974
}
]
}
Interpretando os resultados
- O primeiro resultado (
id: 2) tem a menor distância (0.7849), indicando a maior similaridade semântica com a consulta. - Os resultados são ordenados por relevância, exatamente como uma busca inteligente deve funcionar.
- O filtro por categoria (
"filmes") garante que apenas conteúdos relevantes sejam retornados.
Tecnologias utilizadas
| Tecnologia | Função |
|---|---|
| FastAPI | Criação da API REST |
| PostgreSQL + pgvector | Armazenamento e consulta vetorial |
| OpenAI Embeddings | Geração dos vetores semânticos |
| Docker | Ambiente isolado e fácil reprodução |
| Uvicorn | Servidor ASGI para performance |
Essa aplicação demonstra, de forma simples e funcional, como:
- Embeddings transformam texto em vetores numéricos
pgvectorpermite busca por similaridade diretamente no PostgreSQL- APIs modernas (FastAPI) integram tudo de forma performática
O projeto serve como ponto de partida para sistemas de busca semântica, recomendação, classificação automática e muito mais — tudo com a robustez do PostgreSQL.
FAQs
1. O que é busca semântica?
A busca semântica encontra resultados com base no significado (compreendendo a intenção e o contexto), e não apenas em palavras-chave exatas.
2. Como o pgvector viabiliza a busca semântica no PostgreSQL?
O pgvector armazena embeddings vetoriais de alta dimensão de textos (ou outros dados) e permite realizar consultas por similaridade diretamente em SQL.
3. O que é um embedding vetorial?
Uma representação numérica de significado. Conceitos semelhantes geram vetores próximos uns dos outros em um espaço de alta dimensão.
4. Como encontro conteúdo semelhante?
Gere um embedding para a consulta e ordene os resultados por similaridade usando o operador <->:
SELECT id, content
FROM documents
ORDER BY embedding <-> '[query_vector]'
LIMIT 5;
5. Posso combinar busca semântica e tradicional?
Sim: você pode filtrar por metadados, palavras-chave ou busca de texto completo (full-text search) enquanto classifica os resultados pela similaridade semântica.
6. Como posso escalar as buscas para grandes conjuntos de dados?
Utilize índices de vizinho mais próximo (ANN), como HNSW ou IVFFlat, e ajuste seus parâmetros para equilibrar velocidade e precisão.
7. Quais são alguns casos de uso comuns da busca semântica?
Busca de produtos, recuperação de informações em bases de conhecimento, recomendação de conteúdo e localização de documentos ou mídias semelhantes.
Um caso real de uso: Sumedh Datar da 7-Eleven demostra nesse caso.
Fique ligado
Seja um Expert em Growth
Receba insights práticos sobre marketing, dados, performance e tecnologia direto no seu email.