Objetivo do Volume: Desmistificar a matemática dos vetores e embeddings para quem não tem formação técnica, entender por que 90% das implementações de busca semântica em empresas B2B falham catastroficamente ao cotar produtos com códigos numéricos, dominar a indexação HNSW no pgvector e implementar uma função industrial de Busca Híbrida com RRF (Reciprocal Rank Fusion) diretamente em SQL no PostgreSQL 16.
1. Fundamentos para Não-Técnicos: O Que São Vetores e Embeddings?
Quando cientistas da computação falam em "espaço vetorial de alta dimensão", quem não é da área costuma se assustar. No entanto, o conceito intuitivo por trás disso é simples e elegante:
A Analogia do Mapa de GPS Semântico: Imagine um mapa do Brasil. Cidades que ficam próximas geograficamente (como Campinas e São Paulo) possuem coordenadas de latitude e longitude parecidas. Um Embedding é exatamente um par de coordenadas de GPS — só que em vez de mapear a geografia de cidades em 2 dimensões (latitude e longitude), ele mapeia o significado das palavras e frases em 1.536 dimensões conceituais. - Palavras com significados próximos ficam "vizinhas" no mapa; - "Cimento CP-II", "Concreto", "Argamassa" e "Massa corrida" caem todos no mesmo bairro do mapa semântico; - "Tomada 20A", "Disjuntor Bipolar" e "Cabo flexível 4mm" caem em outro bairro completamente diferente.
Quando um modelo de embedding (como o text-embedding-3-small da OpenAI) lê uma frase como "Quero cotar piso cerâmico resistente pra colocar na garagem do galpão", ele transforma esse texto em uma lista de 1.536 números decimais. Esse array de números é o vetor.
A Falha Trágica da Busca Tradicional por Palavra-Chave (Lexical)
Se uma distribuidora de materiais usar apenas uma busca de banco tradicional (WHERE descricao LIKE '%piso%'), ela enfrenta dois problemas fatais:
- Sinônimos Não Encontrados: Se o cliente digitar "revestimento para chão da oficina", e o catálogo estiver cadastrado como "porcelanato técnico acetinado", a busca tradicional retorna zero resultados, mesmo que a empresa tenha 5.000 metros do produto em estoque.
- Erros de Digitação no WhatsApp: Se o pedreiro digitar "cimeto foty" comendo letras, a busca tradicional falha por completo.
A Falha Inversa (e Pior) da Busca Puramente Vetorial
Muitos desenvolvedores que assistem a tutoriais básicos no YouTube cometem o erro oposto: jogam todo o catálogo de produtos em um banco vetorial puro (como Pinecone ou ChromaDB) e usam apenas a similaridade de cossenos.
Em uma empresa B2B técnica, isso gera desastres comerciais graves:
- Um eletricista envia: "Preciso do disjuntor bipolar de 32A".
- O banco vetorial analisa o significado semântico ("disjuntor bipolar") e retorna como primeiro resultado um "Disjuntor bipolar de 63A" ou "Disjuntor bipolar de 16A", porque conceitualmente todos são disjuntores da mesma família!
- Resultado: A empresa cota a amperagem errada, o equipamento do cliente final queima por sobrecarga na obra e a distribuidora leva um processo milionário.
2. A Solução Industrial: Busca Híbrida (Hybrid Search)
Para resolver esse dilema em ambientes corporativos de missão crítica, a engenharia de dados moderna adota o padrão de Busca Híbrida.
A Busca Híbrida combina simultaneamente duas forças complementares:
- Perna Lexical (Busca Textual Exata - BM25 / Full-Text Search com Trigramas): Excelente para encontrar códigos exatos de peças, medidas numéricas ("100mm", "32A", "3/4 de polegada"), marcas ("Tigre", "Amanco") e SKUs de catálogo.
- Perna Semântica (Busca Vetorial Densa - pgvector Cosine Distance): Excelente para entender intenções amplas, sinônimos, descrições leigas de problemas e gírias regionais.
3. Indexação HNSW: Alta Precisão e Milissegundos em Grande Escala
Quando você tem um catálogo com 100.000 produtos ou uma base com 2 milhões de mensagens arquivadas, calcular a distância matemática entre a pergunta do cliente e cada um dos 100.000 vetores um por um (busca linear exata) é inaceitavelmente lento: consome 100% da CPU do servidor e demora vários segundos.
Para permitir buscas em menos de 5 milissegundos, o pgvector suporta o algoritmo de indexação mais avançado do mundo: o HNSW (Hierarchical Navigable Small World).
A Analogia das Rodovias e Ruas de Bairro: O índice HNSW constrói uma rede em camadas parecida com o sistema viário: - A camada superior contém apenas as grandes "rodovias expressas" que ligam conceitos muito distantes (Elétrica vs Hidráulica vs Ferramentas); - O algoritmo viaja a toda velocidade pela rodovia até a cidade certa; - Ao chegar na região aproximada, ele desce para a camada intermediária de avenidas locais; - Por fim, desce para as ruas de bairro (os produtos individuais específicos).
Parâmetros de Produção do HNSW no PostgreSQL 16:
m = 16: Número máximo de conexões bidirecionais que cada vetor mantém com seus vizinhos mais próximos. (16 é o equilíbrio ideal entre uso de memória RAM e precisão de busca).ef_construction = 64: O tamanho da lista dinâmica avaliada durante a criação do índice. Garante que o grafo construído tenha alta densidade de rotas sem caminhos mortos.
4. Implementação Completa em SQL: Tabela, Índices e Função RRF
Abaixo está o script SQL definitivo pronto para ser executado no PostgreSQL 16 / Supabase. Ele cria a tabela de produtos técnicos, gera o índice HNSW, gera os índices de busca textual completa com trigramas em português e implementa a função armazenada de Busca Híbrida com Reciprocal Rank Fusion (RRF).
-- ============================================================================
-- AI-FIRST OS: ENGENHARIA VETORIAL & BUSCA HÍBRIDA COM RRF
-- PostgreSQL 16 / pgvector / Supabase
-- ============================================================================
-- 1. Habilitação das extensões de vetores e análise textual
CREATE EXTENSION IF NOT EXISTS "vector";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
-- ============================================================================
-- TABELA: PRODUCTS_CATALOG (Catálogo Técnico de Produtos Multitenant)
-- ============================================================================
CREATE TABLE IF NOT EXISTS public.products_catalog (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
sku VARCHAR(64) NOT NULL,
title VARCHAR(255) NOT NULL,
brand VARCHAR(128),
category VARCHAR(128),
description TEXT,
technical_specs JSONB DEFAULT '{}'::jsonb NOT NULL,
unit_of_measure VARCHAR(16) DEFAULT 'UN' NOT NULL,
current_price_brl NUMERIC(12, 2) NOT NULL,
inventory_quantity INT DEFAULT 0 NOT NULL,
-- Vetor de embedding de 1.536 dimensões (Padrão OpenAI text-embedding-3-small)
embedding vector(1536),
-- Coluna gerada automaticamente para Full-Text Search em Português
fts_document tsvector GENERATED ALWAYS AS (
to_tsvector('portuguese', coalesce(title, '') || ' ' || coalesce(brand, '') || ' ' || coalesce(category, '') || ' ' || coalesce(description, ''))
) STORED,
created_at TIMESTAMPTZ DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
updated_at TIMESTAMPTZ DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
CONSTRAINT uq_tenant_sku UNIQUE(tenant_id, sku)
);
-- ============================================================================
-- ÍNDICES DE ALTA PERFORMANCE (HNSW + GIN TEXTUAL + TRIGRAMAS)
-- ============================================================================
-- Índice Vetorial HNSW com distância de Cosseno (vector_cosine_ops)
CREATE INDEX IF NOT EXISTS idx_products_embedding_hnsw
ON public.products_catalog
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Índice GIN para busca rápida de palavras inteiras (Full-Text Search)
CREATE INDEX IF NOT EXISTS idx_products_fts_document
ON public.products_catalog
USING gin (fts_document);
-- Índice de Trigramas para tolerância a erros ortográficos e buscas parciais de SKUs
CREATE INDEX IF NOT EXISTS idx_products_title_trgm
ON public.products_catalog
USING gin (title gin_trgm_ops);
-- ============================================================================
-- FUNÇÃO ARMAZENADA: BUSCA HÍBRIDA COM RECIPROCAL RANK FUSION (RRF)
-- ============================================================================
CREATE OR REPLACE FUNCTION public.match_products_hybrid(
p_tenant_id UUID,
p_query_text TEXT,
p_query_embedding vector(1536),
p_match_count INT DEFAULT 10,
p_rrf_k INT DEFAULT 60
)
RETURNS TABLE (
id UUID,
sku VARCHAR,
title VARCHAR,
brand VARCHAR,
category VARCHAR,
current_price_brl NUMERIC,
inventory_quantity INT,
final_rrf_score DOUBLE PRECISION,
text_rank BIGINT,
vector_rank BIGINT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
WITH
-- Perna 1: Os melhores resultados por similaridade semântica (Vetorial)
vector_matches AS (
SELECT
p.id,
ROW_NUMBER() OVER (ORDER BY p.embedding <=> p_query_embedding) AS v_rank
FROM public.products_catalog p
WHERE p.tenant_id = p_tenant_id
AND p.embedding IS NOT NULL
ORDER BY p.embedding <=> p_query_embedding
LIMIT (p_match_count * 3)
),
-- Perna 2: Os melhores resultados por relevância lexical (Full-Text + Trigramas)
text_matches AS (
SELECT
p.id,
ROW_NUMBER() OVER (
ORDER BY (
ts_rank(p.fts_document, websearch_to_tsquery('portuguese', p_query_text)) * 2.0 +
similarity(p.title, p_query_text)
) DESC
) AS t_rank
FROM public.products_catalog p
WHERE p.tenant_id = p_tenant_id
AND (
p.fts_document @@ websearch_to_tsquery('portuguese', p_query_text)
OR similarity(p.title, p_query_text) > 0.15
)
ORDER BY (
ts_rank(p.fts_document, websearch_to_tsquery('portuguese', p_query_text)) * 2.0 +
similarity(p.title, p_query_text)
) DESC
LIMIT (p_match_count * 3)
),
-- Perna 3: Fusão Recíproca dos Ranques (RRF)
all_candidates AS (
SELECT v.id FROM vector_matches v
UNION
SELECT t.id FROM text_matches t
)
SELECT
p.id,
p.sku,
p.title,
p.brand,
p.category,
p.current_price_brl,
p.inventory_quantity,
(
coalesce(1.0 / (p_rrf_k + vm.v_rank), 0.0) +
coalesce(1.0 / (p_rrf_k + tm.t_rank), 0.0)
)::DOUBLE PRECISION AS final_rrf_score,
tm.t_rank AS text_rank,
vm.v_rank AS vector_rank
FROM all_candidates c
JOIN public.products_catalog p ON p.id = c.id
LEFT JOIN vector_matches vm ON vm.id = c.id
LEFT JOIN text_matches tm ON tm.id = c.id
ORDER BY final_rrf_score DESC
LIMIT p_match_count;
END;
$$;
5. Como a Função Opera na Prática Comercial
Quando o agente de atendimento da distribuidora recebe uma mensagem do cliente no WhatsApp:
- O agente extrai os nomes dos itens cotados;
- Dispara uma chamada única para a API de embeddings gerando o vetor da frase;
- Executa a função
match_products_hybrid:
SELECT sku, title, current_price_brl, inventory_quantity, final_rrf_score
FROM public.match_products_hybrid(
'd84f1a23-4e89-4bc2-9e22-83b6f8490a12'::uuid, -- ID do cliente/distribuidora
'curva esgoto 100 tigre', -- Texto digitado pelo cliente
'[0.0124, -0.0431, ... 1536 números ...]'::vector,
5 -- Retornar os 5 melhores
);
- O motor do banco de dados une a precisão cirúrgica do código da marca Tigre com a compreensão semântica de curvatura e esgoto em menos de 8 milissegundos;
- O agente recebe a lista perfeita de produtos com estoque e preço exatos para montar a cotação no mesmo minuto.
6. Exercício Prático de Fixação do Volume 5
- Análise de Cenário Real: Suponha que um comprador envie para a sua transportadora: "Preciso levar 3 pallets de bobinas de aço de Betim/MG para Joinville/SC com carga fechada".
- Por que uma busca puramente semântica poderia sugerir uma rota de carga fracionada para Curitiba?
- Como a busca híbrida (filtrando exatamente pelas cidades de origem e destino na perna lexical e pela modalidade de peso na perna vetorial) elimina esse erro?
- Entendimento da Fórmula RRF: Se um produto ficou em 1º lugar na busca vetorial ($v\_rank = 1$) e em 2º lugar na busca textual ($t\_rank = 2$), qual é o score final RRF dele considerando $k = 60$? Calcule a fração decimal correspondente.
No próximo volume (Volume 6), entraremos na Orquestração Industrial: vamos entender como operar o n8n em Modo Fila (Queue Mode) com Redis para aguentar milhares de mensagens sem travar e aprender a transição definitiva para código puro com LangGraph e Execução Durável para eliminar a fragilidade de ferramentas no-code em fluxos complexos.