Como Implementar Busca Semântica no Laravel com Embeddings e PostgreSQL (PGVector)

Neste post vamos explicar passo a passo como você pode transformar a busca da sua aplicação Laravel em algo que entenda o significado por trás das consultas, utilizando embeddings e a extensão pgvector do PostgreSQL para realizar buscas por similaridade semântica diretamente no banco de dados

Publicado em: 09, janeiro 2026

Uma lousa escrito Laravel mais embeddings e busca semantica

🧠 O Que É Busca Semântica

Antes de mais nada, vale entender o conceito: ao invés de comparar apenas palavras, busca semântica converte textos e consultas em vetores numéricos (embeddings) que capturam o significado do conteúdo. Com isso, sua aplicação consegue encontrar resultados relevantes mesmo que os termos pesquisados não apareçam literalmente no texto — porque eles ocupam posições próximas no espaço vetorial. 

🔧 Tecnologias Usadas

✔️ Laravel – framework PHP
✔️ PostgreSQL com pgvector – extensão para armazenar e buscar vetores
✔️ OpenAI (ou outro serviço de embeddings) – gerar representações vetoriais dos textos
✔️ SQL direto – consultas de similaridade sem camadas de abstração como Laravel Scout 

🛠️ Passo a Passo

1) Preparar o Banco de Dados com pgvector

No PostgreSQL você precisa habilitar a extensão que permite armazenar vetores:

CREATE EXTENSION IF NOT EXISTS vector;

Essa extensão adiciona um tipo de dado vector ao PostgreSQL, possibilitando operações de similaridade diretamente no banco. 

Na sua migration do Laravel, inclua uma coluna para guardar o vetor resultante do embedding:

Schema::table('articles', function (Blueprint $table) {
    $table->vector('embedding', 1536)->nullable();
});

O número 1536 representa a dimensão escolhida para os embeddings — ajuste conforme o modelo de linguagem que você usar. 

2) Gerar Embeddings para os Documentos

Para cada item que você quer buscar (por exemplo, um artigo, produto ou FAQ), gere um embedding usando uma API de linguagem, como a de OpenAI:

$client = new \OpenAI\Client(env('OPENAI_API_KEY'));

$response = $client->embeddings()->create([
    'model' => 'text-embedding-3-small',
    'input' => $article->content,
]);

$embedding = $response->data[0]->embedding;

$article->embedding = $embedding;
$article->save();

Esse vetor agora representa semanticamente o conteúdo do artigo. Você pode fazer isso em Jobs em background para não travar a experiência do usuário. 

3) Implementar a Busca por Similaridade

Quando o usuário faz uma consulta, você também converte essa pesquisa em um embedding:

$queryEmbedding = $client->embeddings()->create([
    'model' => 'text-embedding-3-small',
    'input' => $request->query('search')
])->data[0]->embedding;

Em seguida você faz uma consulta SQL para encontrar os registros mais próximos no espaço vetorial — ou seja, semanticamente mais similares:

$results = DB::select(
   "SELECT *, embedding <=> :query AS distance
    FROM articles
    ORDER BY embedding <=> :query
    LIMIT 10",
   ['query' => $queryEmbedding]
);
Aqui, o operador <=> calcula a distância cosseno entre vetores no PostgreSQL habilitado com a extensão pgvector — ou seja, ele mede o quão semanticamente próximos dois vetores estão. Quanto menor a distância cosseno, mais semanticamente similares os textos são, e mais relevante será o resultado para a consulta do usuário. 

Quando você usa similaridade vetorial (como com embeddings e o operador <=> no PostgreSQL com pgvector), o valor retornado representa a distância cosseno entre dois vetores que codificam significado (por exemplo: a consulta do usuário e o texto de um documento).

👉 Distância cosseno é uma maneira de medir o ângulo entre dois vetores no espaço multidimensional — quanto menor a distância, mais parecidos semanticamente eles são. 

  • Valor 0 → os vetores apontam na mesma direção (significados muito parecidos). 
  • Valores próximos a 0,1–0,3 → texto e consulta são bastante relacionados semanticamente. 
  • Valores em torno de ~0,45 → indica um nível intermediário de similaridade: existe alguma relação de significado, mas não é uma correspondência forte.
  • Valores maiores (por exemplo ~0,7–1,0) → implicam pouca ou nenhuma similaridade semântica.

Vamos pegar um exemplo, distance = 0,4523423

🔍 Interpretando o valor “0,4523423”

💡 Um valor de 0,4523423 significa que os vetores — ou seja, o texto do documento e a consulta — têm um ângulo considerável entre eles:
✔️ Não são semanticamente idênticos
✔️ Têm alguma relação de significado, mas não tão próxima quanto um valor menor (por exemplo ~0,1–0,2)

Isso pode acontecer quando:

  • O documento menciona conceitos relacionados à consulta, mas não responde exatamente ao que foi perguntado;
  • O texto trata de tópicos semelhantes mas com foco diferente;
  • Há sinônimos ou temas transversais, mas não uma correspondência direta.

📌 Em buscas semânticas, geralmente ordenamos os resultados do menor para o maior valor de distância — ou seja, quanto menor a distância cosseno, mais relevante o resultado tende a ser.

🧠 Uma analogia simples 🧠

Pense nos vetores como setas apontando em direções no espaço:

✅ Distância = 0 → as setas apontam quase exatamente para a mesma direção.
 📍 Distância ≈ 0,45 → as setas ainda estão relativamente alinhadas, mas com um ângulo perceptível entre elas — ou seja, existe alguma conexão, mas não uma correspondência perfeita. 

💡 Dicas e Boas Práticas

Pré-indexe os embeddings ao criar/atualizar documentos
Realize a geração de embeddings em background com Jobs
Use caches de resultados populares para reduzir chamadas à API
✅ Combine com busca full-text tradicional para um sistema híbrido ainda mais eficaz (semântica + palavras-chave) 

🎯 O Que Você Ganha com Essa Abordagem

✔️ Resultados relevantes mesmo sem termos exatos
✔️ Busca por intenção e significado em vez de apenas correspondência textual
✔️ Funciona com qualquer texto estruturado — artigos, produtos, FAQs, etc.
✔️ Escalável, porque a maior parte da lógica fica no banco de dados 

🧠 Conclusão

Implementar busca semântica no Laravel sem usar frameworks como Scout é totalmente possível e valioso. Com PostgreSQL + pgvector + embeddings, você cria um sistema de busca que realmente entende o conteúdo, entregando resultados mais inteligentes e relevantes aos seus usuários — e tudo sem depender de ferramentas externas para indexação. 


/ Veja o passo a passo no youtube

/ Autor

Foto do autor do post Lucas Souza (Virgu)

Lucas Souza (Virgu)

{Full-Stack Specialist Engineer}

Mais de 10 anos de experiência com Laravel e sólidos conhecimentos em frameworks front-end, como ReactJS, React Native e Vue JS.
Experiência em Design de Serviço.
No primeiro projeto profissional como júnior, desenvolveu em e-commerce para a maior indústria de equipamentos odontológicos da América Latina. Atualmente, atua como Full Stack Engineer Specialist em uma grande multinacional.
Lidera decisões técnicas e é um suporte fundamental para a equipe de desenvolvimento.

/ Conteúdos Relacionados