~/beer-and-code
▪ próximo evento AI Engineering Lab 3ª Edição — Da Arquitetura à Produção · 19—20 Set · 19 e 20 de setembro, das 09h às 13h — ao vivo via Google Meet garantir vaga
~ / tutoriais / o-que-e-embedding $
Tutoriais

Embeddings: o que são e por que sua busca semântica erra

LS Lucas Souza · · 12 min de leitura
Embeddings: o que são e por que sua busca semântica erra

Você montou a busca semântica. Subiu o pgvector, gerou os embeddings, rodou o <=> e... o resultado volta sem nexo. O usuário busca "como cancelar assinatura" e o sistema devolve um artigo sobre "novos planos". Frustração na veia.

A primeira reação de todo dev é a mesma: "o banco vetorial tá ruim". Quase nunca é. O problema mora antes — no embedding. É lá que o sentido do seu texto vira número, e é lá que ele se perde.

Neste post você vai entender o que é embedding de verdade (sem fórmula mágica), por que a busca por similaridade erra, e os três pontos onde 90% das buscas semânticas quebram: modelo errado, vetor não normalizado e chunking mal feito.

TL;DR

  • O que é: embedding é a tradução de um texto (ou imagem, ou áudio) para um vetor de números que representa o sentido daquilo, não as palavras.
  • Pra que serve: busca semântica, RAG, recomendação, deduplicação, classificação — tudo que precisa medir "isso é parecido com aquilo".
  • Por que erra: modelo de embedding inadequado, vetores sem normalização, chunk grande demais ou cortado no meio da frase.
  • Stack do exemplo: PostgreSQL + pgvector, modelo text-embedding-3-small da OpenAI.

O que é embedding, sem enrolação

Computador não entende "cachorro". Ele entende número.

Embedding é o processo de pegar um pedaço de texto e transformá-lo num vetor — uma lista de números de tamanho fixo. O text-embedding-3-small da OpenAI, por exemplo, devolve um vetor de 1536 dimensões. Cada texto vira um ponto num espaço de 1536 eixos.

O pulo do gato: esse espaço é organizado por significado. Textos com sentido parecido caem perto um do outro, mesmo sem nenhuma palavra em comum. "Adoro programar em Python" e "curto codar numa linguagem com símbolo de cobra" praticamente não compartilham palavras — mas seus vetores ficam colados (Pinecone).

É por isso que embedding destrava busca semântica, recomendação de conteúdo, memória de usuário e classificação inteligente. Você para de comparar palavras e passa a comparar sentido.

E como o computador mede "perto"? Quase sempre com similaridade de cosseno — o ângulo entre dois vetores. Quanto menor o ângulo, mais parecido o sentido. O valor vai de -1 (oposto) a 1 (idêntico), ignorando o tamanho do vetor e olhando só pra direção (Milvus).

Guarda essa palavra: direção. Ela é a raiz de um dos bugs mais comuns.

Por que sua busca devolve lixo

Quando a busca erra, o instinto é mexer no banco — trocar índice, aumentar o k, adicionar filtro. Na maioria das vezes isso é tratar o sintoma. A doença está no embedding. São três focos.

Foco 1: você está usando o modelo errado

O ranking do MTEB — o benchmark que compara modelos de embedding — é ótimo pra ter um ponto de partida. Mas tem uma armadilha: o modelo que lidera o MTEB pode ir mal no seu domínio.

Dois erros clássicos aqui:

  1. Modelo monolíngue num conteúdo em português. Muito embedding foi treinado majoritariamente em inglês. Joga texto técnico em PT-BR e a noção de similaridade fica grossa — "fatura" e "boleto" deveriam estar colados, e não estão. (E tem domínio onde nenhum embedding sozinho resolve: código, SKU, part-number. "RX-7000" e "RX-5000" são quase idênticos pro vetor. Aí a saída é busca híbrida com BM25 + vetor, não trocar de embedding.)
  2. Modelos diferentes pra query e pra documento. Esse é o erro silencioso. Você indexou os documentos com um modelo, e na hora da busca embedou a query com outro. São dois espaços vetoriais diferentes. A similaridade vira número aleatório. O sistema não dá erro — só devolve resultado ruim, e você fica caçando bug no lugar errado.

Regra de ouro: o mesmo modelo, a mesma versão, dos dois lados. Sempre.

Foco 2: você esqueceu de normalizar

Lembra da palavra direção? Cosseno só olha pro ângulo. Mas várias implementações, por performance, usam inner product (produto interno) em vez de cosseno puro.

Aqui está o detalhe que derruba gente: inner product e cosseno só dão o mesmo ranking se os vetores estiverem normalizados — com comprimento 1 (McClarence). Se não estiverem, o produto interno passa a premiar vetor "grande" em vez de vetor "parecido". Documento comprido sobe no ranking só por ser comprido. Resultado: lixo relevante por acidente.

No pgvector isso aparece na escolha do operador:

-- distância de cosseno: seguro, normaliza implicitamente
SELECT id, conteudo
FROM documentos
ORDER BY embedding <=> '[0.12, -0.04, ...]'
LIMIT 5;

-- inner product (<#>): mais rápido, MAS só correto se os vetores
-- já vierem normalizados na hora de gravar
SELECT id, conteudo
FROM documentos
ORDER BY embedding <#> '[0.12, -0.04, ...]'
LIMIT 5;

Os operadores do pgvector: <=> é distância de cosseno, <#> é o produto interno negativo, <-> é distância euclidiana (L2). Modelos modernos como os da OpenAI já entregam vetores normalizados — mas no instante em que você trunca um vetor, faz média de chunks ou mistura fontes, a normalização vai embora. Se for usar <#>, normalize na escrita. Se não quer pensar nisso, use <=> e durma tranquilo.

Foco 3: seu chunking está sabotando tudo

Embedding não tem memória infinita. Cada modelo tem uma janela de contexto, e texto que passa do limite é truncado antes de virar vetor — a parte cortada simplesmente não existe na representação (OpenSearch). Você acha que indexou o documento inteiro. Indexou metade.

Aí entra o chunking — quebrar o documento em pedaços antes de embedar. E é onde mora o segundo grande erro:

  • Chunk grande demais: uma frase relevante enterrada em nove irrelevantes. O vetor vira uma média borrada de dez assuntos, e a precisão despenca (Pinecone).
  • Chunk pequeno demais: falta contexto. O pedaço "ele deve ser cancelado em até 7 dias" sem saber que "ele" é a assinatura não serve pra nada.
  • Corte burro: chunking de tamanho fixo ignora a estrutura do texto e corta no meio da frase, partindo o sentido em dois vetores capengas.

Não existe tamanho de chunk universal. Trate chunking como problema de avaliação, não de chute (Pinecone): monte um conjunto de 30–50 queries reais com a resposta certa, e meça. É a única forma honesta de saber se o seu chunking ajuda ou atrapalha. Se quiser ver o chunking certo aplicado de ponta a ponta, com overlap e índice HNSW, o post RAG do zero: chunking, embeddings e busca que funciona mostra o pipeline inteiro.

▪ Clã Beer and Code

Tutorial te mostra o caminho — no Clã você constrói junto. Aula ao vivo toda semana, projetos reais de Engenharia de IA, ao lado de quem já está em produção.

Entrar no Clã

Consertando, na ordem certa

Antes de trocar de banco vetorial — que é o passo mais caro e o que menos resolve — siga esta ordem:

  1. Confira o modelo. Mesmo modelo e versão na indexação e na query. Modelo com bom desempenho em português se o conteúdo é PT-BR.
  2. Confira a normalização. Se usa <#>, garanta vetores normalizados na escrita. Na dúvida, use <=>.
  3. Confira o chunking. Pedaços com começo e fim de sentido, com um pouco de sobreposição entre eles. Mede com um dataset de avaliação.
  4. Só então mexa em índice, k e filtros de metadata.

Repara que três dos quatro passos acontecem antes do banco. É exatamente por isso que "trocar o pgvector" raramente conserta a busca.

Qual modelo de embeddings usar em 2026

Diagnóstico resolvido, sobra a decisão que aparece primeiro em qualquer projeto: qual modelo escolher. Não existe "o melhor". Existem quatro variáveis pra cruzar — dimensão, qualidade em português, custo por milhão de tokens e onde o modelo roda (API de terceiro ou sua própria infra).

Modelo Dimensões Português Roda onde Quando compensa
OpenAI text-embedding-3-small 1536 (truncável) Bom API Default sensato: mais barato da linha, previsível, integra com tudo
OpenAI text-embedding-3-large 3072 (truncável) Bom API Quando a avaliação mostra ganho real que paga o dobro de storage
Cohere embed-multilingual-v3 / embed-v4 1024 Muito bom API Base multilíngue e quando você quer vetor comprimido (int8/binário)
Google gemini-embedding-001 3072 (truncável p/ 1536 ou 768) Muito bom API Já está no ecossistema Google Cloud/Vertex
Voyage voyage-3 (família) 1024 (truncável) Bom API Domínio específico com variante dedicada (código, jurídico, financeiro)
intfloat/multilingual-e5-large 1024 Bom Self-hosted Dado que não pode sair da sua infra; custo marginal por token = zero
BAAI/bge-m3 1024 Muito bom Self-hosted Multilíngue forte e denso + esparso no mesmo modelo, útil pra híbrida

Sobre custo: os modelos de API cobram por token de entrada, e a faixa vai de alguns centavos a algumas dezenas de centavos por milhão de tokens — o text-embedding-3-small é o piso do mercado e o -large custa um múltiplo dele. Os números exatos mudam de tabela em tabela e de mês em mês, então confira a página de preço do provedor no dia em que for fechar a conta. O que não muda é a conta que você precisa fazer antes.

Faz essa conta. Uma base de 500 mil chunks de ~400 tokens dá 200 milhões de tokens. Num modelo de US$ 0,02 por milhão, indexar a base inteira custa US$ 4. Num modelo dez vezes mais caro, US$ 40. Nos dois casos indexar é barato, porque é evento único (ou por reprocessamento). O que dói é o outro lado: armazenamento e latência. Esses mesmos 500 mil vetores de 1536 dimensões em float32 ocupam cerca de 3 GB só de vetor, sem contar o índice HNSW por cima. Em 3072 dimensões, ~6 GB. Truncados para 512 dimensões, ~1 GB. Índice que não cabe na RAM é o que transforma busca de 20 ms em busca de 400 ms — e isso custa mais caro, todo dia, do que a diferença de preço na hora de embedar.

Por isso dimensão não é destino. Modelos treinados com Matryoshka (os text-embedding-3-* da OpenAI, o gemini-embedding-001, a família Voyage) aceitam truncar o vetor perdendo pouca qualidade: você corta 1536 para 512 e recupera dois terços da memória. Só que truncar quebra a normalização — o pedaço que sobrou não tem mais comprimento 1. Se você usa <#>, renormalize depois de cortar, ou cai exatamente no Foco 2 deste post.

Sobre português, o MTEB em inglês te engana. Não dá pra herdar a decisão de um leaderboard. Monte um conjunto de 30–50 pares reais da sua base (pergunta do usuário + o trecho que responde) e meça recall@5 com dois ou três candidatos. É uma tarde de trabalho. É comum um modelo multilíngue menor ganhar de um modelo maior treinado majoritariamente em inglês num corpus de suporte em PT-BR, simplesmente porque o vocabulário do domínio — fatura, boleto, chargeback, portabilidade — apareceu mais no treino dele.

Se você quiser uma regra pra sair do zero: comece no text-embedding-3-small, porque é barato e previsível; troque por um multilíngue dedicado quando a sua avaliação em PT-BR mostrar diferença real, não quando o leaderboard mudar; vá pra self-hosted quando o dado não puder sair da sua infra ou o volume tornar a API cara; e só suba pra 3072 dimensões com número na mão mostrando o ganho. Lembra que trocar de modelo depois obriga a re-embedar a base inteira — é justamente por isso que a tarde de avaliação no começo se paga.

Três posts, três escopos, pra não misturar: aqui a gente cuida do vetor — como o texto vira número e o que estraga essa tradução. O pipeline em volta dele (chunking, overlap, indexação, recuperação de ponta a ponta) está em RAG do zero: chunking, embeddings e busca que funciona. E onde guardar esses vetores, com índice e operador, está em o que é um banco de dados vetorial.

Escolher o modelo é decisão de arquitetura, e ela nunca vem sozinha: embedding é uma peça; o Lab monta o sistema inteiro em volta dela, do roteamento ao tracing e ao custo.

FAQ rápido

Embedding e LLM são a mesma coisa? Não. O modelo de embedding só transforma texto em vetor — ele não gera texto. Num RAG, o embedding faz a recuperação (achar os pedaços certos) e o LLM faz a geração (escrever a resposta com base neles). São dois trabalhos distintos.

Quantas dimensões meu embedding precisa ter? Não é "quanto mais, melhor". Mais dimensões custam mais memória e busca mais lenta. O text-embedding-3-small tem 1536 e resolve a grande maioria dos casos. Comece pequeno, meça, só aumente se a avaliação pedir.

Posso embedar query com um modelo e documentos com outro? Não. São espaços vetoriais diferentes e a similaridade perde o sentido. Mesmo modelo dos dois lados — esse é, de longe, o erro silencioso mais comum.

Preciso re-embedar tudo se trocar de modelo? Sim. Cada modelo cria seu próprio espaço. Trocou o modelo, reprocessa a base inteira — query e documentos.

O embedding é o alicerce, não o detalhe

Busca semântica que erra quase nunca é problema de banco vetorial. É embedding mal escolhido, vetor não normalizado ou chunk mal cortado. Acerte esses três e o pgvector vira coadjuvante — que é o lugar dele.

E embedding é só a porta de entrada. Recuperação é uma peça de um sistema maior: reranking, avaliação, contexto, custo, latência. Decidir onde cada peça entra na arquitetura é o que separa um protótipo de RAG de um produto que aguenta produção — e é exatamente o tipo de decisão que a gente coloca na mesa no Workshop Arquitetando Soluções de IA, com código rodando e arquitetura de verdade, não slide.

O próximo passo, depois de domar o embedding, é colocar a mão na massa: o tutorial de como implementar busca semântica no Laravel com pgvector leva isso pra dentro de uma aplicação real, e se você ainda confunde recuperação com memória, vale ler o que é RAG também.

E depois disso, mede. Monta teu dataset de avaliação antes de qualquer otimização. Sem número, você não está consertando busca — está adivinhando.

Lucas Souza
Escrito por
Lucas Souza

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

▪ Clã Beer and Code

Conteúdo é o que não falta. Falta quem desembaralhe: o que importa agora é como implementar do jeito certo. No Clã você tem isso ao vivo, toda semana, com quem já filtrou o ruído.

Entrar no Clã
Conheça o Clã Beer and Code
tocando