🌍 Engenharia & Boas Práticas

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:

💡 Analogia do Mundo Real

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:

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:


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:

  1. 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.
  2. 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.
MENSAGEM DO CLIENTE NO WHATSAPP "Quero 30 curvas curtas de 90 graus de 100mm da tigre" │ ┌──────────────────────────────────┴──────────────────────────────────┐ ↓ ↓ BUSCA TEXTUAL (BM25 / TRIGRAMAS) BUSCA SEMÂNTICA (EMBEDDINGS) • Filtra por termos exatos: • Vetoriza o significado contextual: "100mm", "tigre", "curva", "90" "conexão hidráulica de esgoto em ângulo" • Ranqueia os 20 melhores resultados lexicais • Ranqueia os 20 melhores resultados semânticos │ │ └──────────────────────────────────┬──────────────────────────────────┘ │ ▼ ALGORITMO DE FUSÃO RECÍPROCA (RRF) RRF_Score = 1 / (60 + Rank_Text) + 1 / (60 + Rank_Vector) │ ▼ RESULTADO PERFEITO NO TOPO DO CATÁLOGO: SKU: TIG-CUR-90-100 (Curva Curta Esgoto 100mm Tigre)

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).

💡 Analogia do Mundo Real

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:


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:

  1. O agente extrai os nomes dos itens cotados;
  2. Dispara uma chamada única para a API de embeddings gerando o vetor da frase;
  3. 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
   );
  1. 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;
  2. 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

  1. 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".
  1. 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.