Pular para o conteúdo

Trilha de RAG · parte 3 de 12

Embeddings: texto que vira vetor com significado

Como transformar um texto em números que carregam significado, de modo que textos parecidos fiquem perto uns dos outros no espaço.

Publicada em · 42 min de leitura

Tamanho da fonte:
Largura da coluna:

Um embedding é uma lista de números que representa o significado de um trecho de texto, de modo que trechos com significado parecido recebem listas parecidas. Com isso, “achar o trecho certo” vira “achar a lista de números mais próxima”, e um computador faz isso muito bem. Ao terminar, você deve saber o que é um embedding e por que texto vira vetor, o que “perto” quer dizer, o que o modelo de embedding aprendeu e como, o que são as dimensões e quanto elas custam, por que a pergunta e o documento precisam viver no mesmo espaço, como gerar embeddings por API e com um modelo local, como escolher um modelo, onde os embeddings falham e por que nunca se misturam vetores de modelos diferentes no mesmo índice.

Esta aula parte do zero. Não pressupõe nenhuma matemática além de saber o que é uma lista de números. Quando precisa, relembra só o necessário: o texto vira tokens e o modelo tem uma janela de contexto limitada. Os trechos de código estão em Python. Os que usam um modelo local foram executados numa instalação descartável, e os que chamam APIs de provedores não foram executados, porque exigem chave de acesso (a seção de fontes detalha). As figuras são animadas e foram geradas com o ArchFlow. Tudo o que muda com o tempo (nomes de modelos, dimensões, limites de entrada, parâmetros) vem da documentação oficial consultada em 15 de outubro de 2026, e os números de tamanho e de similaridade que aparecem são medidos por mim ou marcados como ilustrativos.

Mapa da aula

ParteSeçõesPergunta que responde
1. Fundamentos1 a 3O que é um embedding, que problema ele resolve e o que é, de fato, um vetor?
2. Como funciona por dentro4 a 6O que o modelo aprendeu e como, o que são as dimensões e como pergunta e documento se encontram?
3. Na prática7 e 8Como gerar embeddings por API e com um modelo local, e como escolher um modelo?
4. Limites e regra de ouro9 e 10Onde os embeddings falham e por que nunca se misturam modelos?
5. Outros usos e prática11 a 14Para que mais servem, que erros evitar, que dúvidas ficam em aberto, como treinar e que palavras guardar?

Pré-requisitos: LLMs e Prompt e contexto. Ajuda ter lido Agents e MCP, que voltam na seção 11. Os diagramas seguem um mapa de cores fixo: laranja para o que a pessoa escreve ou pergunta, bronze escuro (o hexágono) para modelos, verde para documentos, dados e resultados bons, azul para vetores, números e instruções, cinza para o que é neutro, e rosa para o que dá errado.

Parte 1: Fundamentos

1. A ideia: um mapa em que o que é parecido fica perto

Imagine uma biblioteca em que os livros não estão em ordem alfabética, e sim organizados por assunto: receitas numa estante, programação em outra, finanças em uma terceira. Quem procura um livro sobre bolo caminha até a estante de receitas e já está perto de todos os livros úteis, mesmo sem saber o título. A vizinhança física faz o trabalho de busca. Agora imagine que, em vez de poucas estantes, a biblioteca tivesse centenas ou milhares de eixos de organização ao mesmo tempo: assunto, tom, formalidade, tipo de pergunta, área técnica e muitos outros, a maioria sem nome. Cada livro (ou, no nosso caso, cada trecho de texto) teria uma posição nesse espaço, e trechos de significado parecido ficariam perto uns dos outros. Isso é um espaço de embeddings.

A definição curta é esta: um embedding é uma lista de números (um vetor) que um modelo calcula a partir de um texto e que funciona como as coordenadas desse texto no mapa de significados. Duas frases que dizem coisas parecidas recebem coordenadas próximas. Duas que falam de assuntos distantes recebem coordenadas distantes. Não existe nenhum rótulo escrito na posição; existe só a regra de que a vizinhança carrega significado.

mapa de significado
figura 1 · mapa de significado

A figura é um esquema, e as posições dos grupos são ilustrativas: um mapa de verdade tem centenas de eixos e não cabe numa tela. Mesmo assim, o que ela mostra vale. Doze frases de quatro assuntos formam quatro regiões: cozinha em laranja, programação em azul, dinheiro em verde e atendimento em roxo. Frases dentro da mesma região falam de coisas parecidas (receitas com receitas, juros com ações). Depois chega uma consulta nova, em rosa, “Quero encerrar meu plano”. Ela nunca apareceu antes e não usa nenhuma das palavras das frases do atendimento, mas cai ao lado delas, e a seta grossa e lenta marca esse vizinho mais próximo. A seta pontilhada mostra que o resto do mapa fica longe. Para conferir que a ideia não é só desenho, projetei em duas dimensões as 12 frases reais do exercício 1 usando um modelo pequeno: as receitas e as frases de finanças ficaram bem agrupadas, e as de programação ficaram menos coesas, porque o modelo é pequeno e as frases são poucas. O mapa real é imperfeito, e a seção 8 volta a isso.

O mesmo padrão aparece em qualquer tipo de texto. Frases sobre receitas (“Misture a farinha com os ovos”, “Refogue a cebola no azeite”) ficam juntas porque falam de cozinhar, mesmo sem repetir palavras. Frases sobre código (“O laço for percorre o array”, “Erro de sintaxe na linha 12”) ficam juntas porque falam de programar. Frases sobre finanças (“A taxa de juros subiu”, “O fundo rendeu mais que a poupança”) formam outra região. E perguntas de atendimento ao cliente formam mais uma. O modelo que produz essas coordenadas nunca recebeu uma lista de assuntos; a seção 4 explica como ele chegou a organizar o espaço assim.

2. O problema que isso resolve

Um computador compara números e sequências de letras com facilidade. O que ele não faz de graça é perceber que “como cancelar a assinatura” e “quero encerrar meu plano” pedem a mesma coisa. A busca tradicional por palavra-chave, a que está por trás de um Ctrl+F e do índice de muitos sistemas, procura as mesmas palavras. Se o documento diz “para encerrar seu plano, acesse Conta” e a pessoa escreveu “cancelar a assinatura”, não há uma palavra em comum, e o documento certo fica de fora.

Em escala, o problema aparece assim: o cliente escreve “posso devolver?” e o documento diz “política de reembolso”. Pessoas parafraseiam, usam sinônimos, escrevem de forma coloquial, e os documentos usam o vocabulário da empresa. Uma busca que só casa letras erra justamente nos casos em que uma pessoa acharia a resposta óbvia. Antes dos embeddings, a saída era manter listas de sinônimos escritas à mão, o que não escala nem acompanha a língua.

palavra vs significado
figura 2 · palavra vs significado

A figura compara as duas buscas com a mesma pergunta e os mesmos dois documentos. No grupo de cima, a busca por palavra compara letras. O Doc A (o que ensina a encerrar o plano) não tem nenhuma palavra em comum com a pergunta, e a seta é barrada com o ✕. O Doc B, sobre desconto na assinatura anual, divide a palavra “assinatura”, e a seta rosa e grossa mostra que ele passa: a busca leva o documento errado e perde o certo. No grupo de baixo, a pergunta passa pelo modelo de embedding, e a seta grossa verde leva ao vizinho mais próximo, o Doc A, enquanto o Doc B fica “mais longe” na seta pontilhada. A contagem de palavras em comum foi medida de verdade (zero para o Doc A, duas para o Doc B contando “a” e “assinatura”); o lado do significado é ilustrativo, e a seção 8 mostra o que um modelo pequeno real faz com esses mesmos textos.

Um aviso para não criar expectativa errada: a busca por significado não substitui a busca por palavra. Ela resolve sinônimos e paráfrases e perde onde a palavra exata importa, como em códigos de erro e siglas (seção 9). A combinação das duas é tratada em Busca híbrida.

3. O que é, exatamente, um embedding

Vetor, na prática, é uma lista ordenada de números. A lista [0,21, 0,42, -0,24] é um vetor de três números e pode ser lida como um ponto num espaço de três dimensões, do mesmo jeito que latitude e longitude são um vetor de dois números que apontam um ponto num mapa. Um embedding de texto é um vetor com muito mais números. O modelo pequeno que uso nos exemplos devolve 384 números para cada texto, e modelos maiores devolvem 1.024, 3.072 ou mais. Não importa se o texto é uma palavra, uma frase ou um parágrafo: o tamanho do vetor é sempre o mesmo para um dado modelo, e isso é o que permite comparar qualquer texto com qualquer outro.

texto vira vetor
figura 3 · texto vira vetor

Siga a figura de cima para baixo. O texto laranja entra pelo mesmo caminho que o LLMs usa: o tokenizador o corta em tokens e troca cada um por um id. Do outro lado do grupo, o modelo de embedding lê a sequência de ids, com as camadas olhando cada token no contexto dos outros, e produz uma saída para cada token. A caixa “resumo do trecho” junta essas saídas em uma só (o nome técnico é pooling, que aqui é só uma média ou operação parecida, e a variação exata depende do modelo). O resultado é o vetor azul, com os primeiros números reais que obtive para “Como cancelar a assinatura?” (0,21, 0,42, -0,24, -0,22) e a etiqueta que lembra que são 384 números, seja o texto uma frase ou um parágrafo. A diferença para um LLM é o fim do caminho: o LLM usa a saída para prever o próximo token e continuar escrevendo, e o modelo de embedding para por aqui, entregando um ponto em vez de uma continuação.

Há uma tentação natural, que vale desfazer logo: olhar a lista e achar que cada posição guarda um assunto, como “posição 7 é esporte”. Não é assim que funciona.

numero sem nome
figura 4 · numero sem nome

O grupo da esquerda mostra a crença, em rosa e tracejado: a posição 7 seria “esporte” e a 12 “comida”, e bastaria ler um número para dizer o assunto. A seta para o grupo da direita é barrada com o ✕, porque não há rótulo legível em nenhuma posição. O vetor real, em azul, é uma sequência de números como 0,21, 0,42, -0,24, -0,22, -0,16, -0,14, e o significado está espalhado pelo conjunto inteiro: muitas posições contribuem um pouco para muitos aspectos ao mesmo tempo. A única coisa que tem sentido é comparar dois vetores, e é isso que o losango “perto de outro vetor?” representa. Se dois vetores apontam para direções parecidas, os textos se parecem. A pergunta de como medir isso com precisão é o tema da aula Similaridade entre vetores, e esta aula só precisa da ideia: perto quer dizer parecido em significado, longe quer dizer diferente. As fórmulas ficam para Similaridade entre vetores.

Parte 2: Como funciona por dentro

4. O que o modelo aprendeu e como

Um modelo de embedding é um modelo de linguagem treinado para um fim diferente do LLM. O LLM aprende a prever o próximo token. O modelo de embedding aprende a colocar textos de significado parecido em pontos próximos. Em geral são modelos menores e mais baratos de rodar do que um LLM de conversa, e são treinados separadamente: um chatbot que usa RAG tem, na prática, dois modelos, um para gerar a resposta e outro para buscar. A documentação da Claude diz que a Anthropic não oferece um modelo próprio de embeddings, e a seção 7 volta a isso.

O método mais comum de treino, em alto nível, é o treino por contraste. Pelo que sei do conhecimento geral da área (essa explicação não vem de uma fonte consultada hoje), o ingrediente principal são pares de textos. Há pares que deveriam ficar perto: uma pergunta e o trecho que a responde, duas paráfrases da mesma frase, um título e o parágrafo a que ele pertence. E há pares que deveriam ficar longe: a mesma pergunta com um trecho que não tem relação. O modelo começa com pesos aleatórios, calcula um vetor para cada texto do par, mede a distância entre os vetores e ajusta os pesos para aproximar os pares parecidos e afastar os diferentes. Repetido milhões de vezes, com pares de muitos assuntos, o resultado é um espaço que organiza significado.

treino por contraste
figura 5 · treino por contraste

A figura percorre um passo de treino de cima para baixo. No topo, o grupo “Dados de treino” tem um par parecido em verde (“Como cancelar a assinatura?” e “Quero encerrar meu plano”) e um par diferente em rosa (a mesma pergunta e “Receita de bolo de fubá”). Os dois pares entram no modelo, que ainda é fraco e gira (a animação marca o aprendizado em andamento), e saem como vetores azuis. O losango mede a distância entre os textos de cada par. Quando um par parecido está longe, o ramo verde “aproximar” puxa os dois vetores um para o outro. Quando um par diferente está perto, o ramo rosa “afastar” empurra-os para longe. Os dois ramos convergem em “ajusta os pesos”, um passo pequeno que se repete milhões de vezes (a seta lenta e grossa que sai dele), até que o espaço fique organizado, com assuntos parecidos vizinhos.

Isso explica três coisas que importam na prática. Primeiro, por que a vizinhança é de significado e não de letras: o treino premiou pares que dizem a mesma coisa com palavras diferentes, de modo que “encerrar o plano” e “cancelar a assinatura” acabaram perto. Segundo, por que o modelo só sabe o que o treino mostrou: se os pares de treino quase não tinham português, ou quase não tinham medicina, o espaço fica pior nessas áreas, e é por isso que idioma e domínio pesam na escolha de um modelo (seção 8). Terceiro, por que um modelo de embedding não “entende” como um LLM: ele aprendeu uma geometria de semelhança, não a raciocinar sobre o texto. Ele sabe que duas frases são vizinhas, mas não sabe dizer por quê.

5. Dimensões: quantos números por trecho e quanto custa

As dimensões são os números do vetor. Um modelo de 384 dimensões devolve 384 números por texto, um de 1.024 devolve 1.024, e assim por diante. Mais dimensões dão mais espaço para separar nuances, o que costuma ajudar a qualidade, mas há retornos decrescentes: passar de 1.024 para 3.072 dimensões custa três vezes mais armazenamento e processamento, e o ganho de qualidade depende do modelo e dos seus dados, não é garantido. Por isso “mais dimensões” não quer dizer “melhor”; quer dizer “mais caro, talvez melhor”.

O custo de armazenamento é uma conta simples. Cada número do vetor costuma ser um float de 32 bits, ou seja, 4 bytes. Um índice de mil trechos com um modelo de 384 dimensões ocupa 1.000 × 384 × 4 = 1.536.000 bytes, cerca de 1,5 MB (medi isso no exercício 4: o array de numpy tinha exatamente nbytes igual a 1.536.000). Um acervo de um milhão de trechos com 1.024 dimensões ocupa 1.000.000 × 1.024 × 4 = 4.096.000.000 bytes, cerca de 4,1 GB. Com 3.072 dimensões, 12,3 GB. Esses tamanhos são ilustrativos de ordem de grandeza (não incluem o texto original, os metadados nem o índice de busca, que a aula Busca vetorial apresenta), mas mostram por que a dimensão entra na decisão.

dimensoes e custo
figura 6 · dimensoes e custo

A figura parte do índice de mil trechos, em verde e com um disco, e segue pela seta lenta “mil vezes mais trechos” até o índice de um milhão de trechos com 1.024 dimensões (4,1 GB). Dali saem quatro caminhos. O de cima, em rosa e tracejado, é o custo de triplicar as dimensões: 12,3 GB, com a seta grossa e pacotes lentos. Os três de baixo, em azul, são as formas de reduzir. Cortar para 256 dimensões deixa 1 GB. Guardar cada número como inteiro de 1 byte (int8) também chega a 1 GB, e guardar um único bit por número (binário) chega a 128 MB, e os pacotes rápidos da última seta indicam que a busca fica mais barata. Todos os valores são cálculos meus a partir da fórmula acima, com um milhão de trechos como exemplo.

Duas técnicas reduzem o custo sem trocar de modelo, e ambas aparecem na documentação dos provedores. A primeira são as dimensões reduzidas. A documentação da Claude, ao falar da Voyage AI, descreve o aprendizado Matryoshka como a criação de embeddings com representações do grosso ao fino dentro de um mesmo vetor, e diz que os modelos que aceitam várias dimensões de saída geram vetores desse tipo. Na prática, o provedor aceita um parâmetro de dimensão (output_dimension na Voyage; dimensions na OpenAI; output_dimensionality no Gemini), e, para vetores já guardados, a documentação da Voyage manda manter o começo da lista e normalizar de novo. A ideia é a das bonecas russas: as primeiras posições guardam o essencial e as seguintes acrescentam detalhe, então dá para guardar só o começo. Isso só vale para modelos treinados desse jeito; cortar um vetor de um modelo qualquer destrói a informação.

A segunda é a quantização, que troca a precisão de cada número por menos bytes. Segundo a documentação da Claude sobre a Voyage, passar de 32 bits para inteiros de 8 bits reduz armazenamento e custo em 4 vezes, e para 1 bit em 32 vezes, ao custo de alguma perda de precisão de busca. Como isso interage com os índices e com o banco vetorial é assunto das aulas Busca vetorial e Banco de dados vetorial; aqui basta saber que existe.

6. Pergunta e documento no mesmo espaço

Para que a busca funcione, a pergunta e os documentos precisam ser colocados no mesmo mapa, pelo mesmo modelo. A pergunta é convertida em vetor e comparada com os vetores dos trechos, e os mais próximos viram o contexto do prompt. Se a pergunta fosse mapeada por um modelo e os documentos por outro, as coordenadas não significariam a mesma coisa, e a comparação seria vazia (seção 10).

mesmo espaco
figura 7 · mesmo espaco

A figura tem duas colunas que se encontram embaixo. À esquerda, o fluxo da indexação, que acontece uma vez: os documentos (cilindro verde) passam pelo modelo de embedding e viram o índice de vetores (azul), com um vetor por trecho. À direita, o fluxo da consulta, que se repete a cada pergunta: a pergunta laranja passa pelo mesmo modelo (o selo “o mesmo X v1” nos dois hexágonos é o ponto central do desenho) e vira um único vetor. As duas setas encontram o losango “quais vetores estão mais perto?”, e a diferença de volume aparece nos pacotes: milhares de vetores chegam do índice, um só chega da pergunta. A seta grossa verde leva aos trechos vizinhos, que viram contexto no prompt. A etapa de comparar a pergunta com milhares ou milhões de vetores de forma eficiente é o tema da aula Busca vetorial.

Há uma sutileza: a pergunta e o documento são textos de natureza diferente. A pergunta é curta e escrita por quem não sabe a resposta (“Como cancelar a assinatura?”). O documento é longo e escrito por quem sabe (“Para encerrar seu plano, acesse Conta, depois Plano, e escolha Encerrar”). Muitos modelos de embedding são treinados para essa assimetria: o treino casa perguntas com trechos que as respondem, e o modelo precisa saber qual dos dois papéis cada texto está exercendo. Os provedores resolvem isso de formas ligeiramente diferentes. A documentação da Voyage, reproduzida na página da Claude, pede que em toda tarefa de busca se informe input_type como query ou document e avisa para não omitir o parâmetro; por baixo, a biblioteca acrescenta um prefixo ao texto (para consulta, “Represent the query for retrieving supporting documents: ”; para documento, “Represent the document for retrieval: ”). O Gemini tem um parâmetro task_type com valores como RETRIEVAL_QUERY e RETRIEVAL_DOCUMENT para o gemini-embedding-001, e a página diz que o gemini-embedding-2 não aceita o campo. Na parte da página da OpenAI que consultei não encontrei um parâmetro desse tipo para os modelos text-embedding-3, e não afirmo que ele não exista. E alguns modelos abertos pedem prefixos escritos à mão no próprio texto, como “query: ” e “passage: ”, costume que conheço de modelos da família E5 (não confirmei isso na documentação, e o exemplo da seção 7 mostra uma pegadinha).

consulta e documento
figura 8 · consulta e documento

A figura mostra o caminho de cada papel. A consulta curta (laranja) e o documento longo (verde) recebem, cada um, a marca do seu papel (as caixas azuis, com input_type ou o prefixo de texto) antes de entrar no mesmo modelo. Saem dois vetores, no mesmo espaço, e o losango pergunta se estão perto. A caixa rosa tracejada de cima, “papel esquecido”, é a falha silenciosa: se você omite o parâmetro ou o prefixo, o código roda sem erro, o vetor sai com o tamanho certo, e a qualidade da busca cai sem aviso. A seta pontilhada lenta mostra que o erro não trava nada, só piora os resultados, o que o torna difícil de perceber.

Parte 3: Na prática

7. Como gerar embeddings

Há dois caminhos: chamar a API de um provedor ou rodar um modelo na sua máquina. O trecho de código muda pouco, mas as consequências mudam bastante.

api ou local
figura 9 · api ou local

No grupo de cima, o código Python envia o texto em lote pela internet, com uma chave de API, a um modelo que roda no servidor do provedor, e os vetores voltam na resposta. O texto sai da sua máquina, o custo é por token e você não precisa de hardware. No grupo de baixo, a biblioteca sentence-transformers carrega os pesos do modelo (baixados do Hugging Face uma vez e guardados no disco, a seta verde lenta) e calcula os vetores na sua CPU ou GPU. O texto não sai da máquina, não há custo por token, e você cuida do hardware e das atualizações.

Para a API, a documentação da Claude diz que a Anthropic não oferece um modelo próprio de embeddings e aponta a Voyage AI (da MongoDB) como um provedor com vários modelos, ressalvando que vale avaliar vários fornecedores. O código abaixo é o exemplo da documentação (consultada em 15 de outubro de 2026). Ele exige a variável de ambiente VOYAGE_API_KEY e o pacote voyageai (pip install -U voyageai).

import voyageai

vo = voyageai.Client()  # usa a variável de ambiente VOYAGE_API_KEY

textos = ["Sample text 1", "Sample text 2"]

result = vo.embed(textos, model="voyage-4", input_type="document")
print(len(result.embeddings), len(result.embeddings[0]))

A documentação diz que result.embeddings é uma lista de dois vetores, cada um com 1.024 números de ponto flutuante (a saída esperada da última linha é 2 1024). O nome voyage-4 é um exemplo da página de hoje e vai envelhecer. Para a pergunta, a documentação usa o mesmo método com input_type="query" e vo.embed([pergunta], model="voyage-4", input_type="query").embeddings[0]. A página também documenta truncation (por padrão verdadeiro, o que significa que um texto maior que o limite do modelo é cortado em silêncio), output_dimension e output_dtype. Não executei esse código: não tenho uma chave.

A OpenAI segue o mesmo desenho, com outra forma de chamar. O exemplo da página oficial é este (também não executado):

from openai import OpenAI

client = OpenAI()

response = client.embeddings.create(
    input="Your text string goes here", model="text-embedding-3-small"
)

print(response.data[0].embedding)

A resposta traz o vetor numa lista de floats dentro de data, junto com metadados de uso (usage). A documentação informa que o tamanho padrão do vetor é 1.536 para o text-embedding-3-small e 3.072 para o text-embedding-3-large, que o parâmetro dimensions encurta o vetor, e que o limite de entrada é de 8.192 tokens.

Para o modelo local, o código abaixo foi executado por mim com sentence-transformers 6.1.0 (a seção de fontes lista o ambiente). O nome do modelo é um exemplo multilíngue pequeno, escolhido porque roda em CPU.

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")

frases = [
    "Como cancelar a assinatura?",
    "Quero encerrar meu plano.",
    "Qual o horário de funcionamento da loja?",
]

emb = model.encode(frases)
print(emb.shape)          # (3, 384)
print(emb[0][:5])         # cinco primeiros números do primeiro vetor
print(model.similarity(emb, emb))

A saída que obtive foi (3, 384), os cinco números 0.2057 0.4158 -0.2388 -0.2161 -0.1617 (arredondados) e uma matriz 3 por 3 de similaridades: 0,47 entre a primeira e a segunda frase (o par que diz a mesma coisa), 0,08 entre a primeira e a terceira, e 0,19 entre a segunda e a terceira. O model.similarity calcula a similaridade entre todos os pares, e como ela é calculada é o tema de Similaridade entre vetores. O que importa aqui: as duas frases sobre cancelar ficaram mais próximas uma da outra do que de qualquer outra, mesmo sem palavras em comum, e os valores absolutos são pequenos, o que é normal para esse modelo.

Dois detalhes do uso local, ambos observados na instalação que testei. O primeiro é que este modelo devolve vetores que não têm comprimento 1: a norma do vetor de “Como cancelar a assinatura?” foi cerca de 4,5. A documentação da OpenAI e da Voyage diz que os embeddings deles são normalizados para comprimento 1, o que torna o produto escalar equivalente à similaridade de cosseno (Similaridade entre vetores explica o termo). Em sentence-transformers, model.encode(frases, normalize_embeddings=True) normaliza na saída, e vale conferir o que o seu modelo faz em vez de supor. O segundo é a pegadinha dos prefixos. Com um modelo que pede “query: ” e “passage: ” no texto (usei intfloat/multilingual-e5-small), a biblioteca tem métodos encode_query e encode_document para os dois papéis, mas na versão e no modelo que testei os prompts configurados estavam vazios, ou seja, esses métodos não acrescentaram prefixo nenhum. Escrever o prefixo à mão funcionou:

import numpy as np
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("intfloat/multilingual-e5-small")

docs = [
    "Para encerrar seu plano, acesse Conta > Plano e escolha 'Encerrar'.",
    "Nossa loja abre das 9h às 18h, de segunda a sábado.",
]
pergunta = "Como cancelar a assinatura?"

# este modelo pede prefixos escritos à mão: "query: " e "passage: "
q = model.encode("query: " + pergunta, normalize_embeddings=True)
d = model.encode(["passage: " + t for t in docs], normalize_embeddings=True)

print(d @ q)   # produto escalar = cosseno, porque os vetores têm tamanho 1

A saída foi [0.870 0.815]: o documento sobre encerrar o plano ficou à frente do da loja, mas a diferença é pequena, e a escala dos números é bem diferente da do modelo anterior (0,47 contra 0,08 ali, 0,87 contra 0,81 aqui). Por isso, um valor de similaridade só tem sentido dentro de um mesmo modelo, e comparar “0,8” de um modelo com “0,5” de outro não quer dizer nada. Sem os prefixos, os números neste exemplo foram praticamente os mesmos (0,874 e 0,809), então não dá para concluir daqui o quanto o prefixo importa na prática. O que vale como regra é seguir o que a documentação do modelo manda, e testar.

Um terceiro ponto vale registrar: os modelos têm um limite de entrada. A documentação do sentence-transformers diz que textos mais longos que model.max_seq_length tokens são cortados, e que modelos treinados com textos curtos podem representar mal textos longos. No modelo pequeno que usei, o limite era de 128 tokens, e medi o efeito com um texto de 404 tokens (400 repetições da palavra “gato” seguidas de “FIM”): o vetor desse texto ficou com similaridade 0,94 com o vetor das primeiras 60 repetições, ou seja, quase tudo depois do limite foi ignorado, inclusive o “FIM”. Os limites dos serviços de API são bem maiores (a documentação da OpenAI consultada diz 8.192 tokens; a da Voyage lista 32.000 para os modelos recentes), mas o princípio é o mesmo: o que passa do limite não entra no vetor, e na Voyage isso acontece em silêncio por padrão. Como cortar documentos longos em trechos que cabem é o tema da aula Chunking.

8. Escolher um modelo de embedding

Não existe um modelo melhor para tudo. A escolha equilibra alguns fatores, e quase todos têm a ver com os seus dados. O idioma é o primeiro: um modelo treinado quase só em inglês tende a funcionar pior em português (pela mesma razão do tokenizador, o corpus de treino pesa), e por isso, para acervos em português, procure modelos que se declaram multilíngues e teste com frases do seu domínio. O tamanho de entrada decide se o trecho cabe de uma vez. A dimensão afeta armazenamento e velocidade (seção 5). O custo e a latência pesam em volume: uma API cobra por token e adiciona a ida e volta pela rede, e um modelo local troca isso por hardware e manutenção. E a escolha entre modelo aberto e API é também uma escolha sobre privacidade (o texto sai da sua máquina ou não), disponibilidade e controle de versão (um provedor pode aposentar um modelo, e a seção 10 mostra o que isso custa).

Para comparar modelos, o benchmark mais citado é o MTEB (Massive Text Embedding Benchmark). Segundo o artigo do Hugging Face que o apresentou, ele reúne 56 datasets em 8 tarefas e até 112 idiomas, com um painel público de resultados. O mesmo artigo avisa que o desempenho varia muito conforme a tarefa e o dataset, e recomenda olhar as abas do painel em vez de um número único. Esses números de cobertura são da apresentação original do benchmark, e o painel atual pode ter mudado; confira na fonte. O cuidado que o artigo sugere é o que importa: um ranking geral é um ponto de partida, não um veredito. O seu acervo tem vocabulário, idioma e tipo de pergunta próprios, e um modelo bem colocado no ranking pode ir mal nele.

O jeito confiável de escolher é medir. Monte um conjunto pequeno de perguntas reais, com o trecho que deveria ser achado para cada uma, e veja em que posição cada modelo candidato coloca esse trecho. Avaliar com perguntas reais é o caminho, e o exercício 5 propõe uma versão minúscula. Para dar a dimensão do que um teste pode revelar, repeti com o modelo pequeno um caso simples. A pergunta “Como cancelar a assinatura?” e quatro documentos (encerrar o plano, desconto da assinatura anual, horário da loja, cancelamento de pedidos) tiveram similaridades de 0,40, 0,25, 0,07 e 0,46. O modelo colocou “cancelamento de pedidos” à frente do documento que realmente responde, porque ambos falam de cancelar. Um modelo maior pode acertar, ou não; o ponto é que só o teste mostra, e que a promessa do mapa da seção 1 vale em média, não em cada caso.

As tabelas de modelos mudam de mês para mês. Na página da Claude consultada em 15 de outubro de 2026, a Voyage lista, como exemplo, uma família geral (voyage-4-large, voyage-4 e voyage-4-lite, de melhor qualidade a menor custo e latência), modelos para código e para finanças, um modelo aberto (voyage-4-nano, para baixar e rodar por conta própria), modelos multimodais e modelos que embutem trechos já levando em conta o documento inteiro. Cito como ilustração do tipo de variedade que existe, e não como recomendação. Não incluo preços porque envelhecem depressa: consulte a tabela de preços do fornecedor no dia em que for decidir.

Parte 4: Limites e regra de ouro

9. Onde os embeddings falham

O embedding mede semelhança de assunto e de sentido geral, e isso tem consequências nos casos em que a diferença que importa é pequena em palavras e grande em significado. Medi alguns desses casos com o modelo pequeno, e a figura reúne os valores.

onde falha
figura 10 · onde falha

À esquerda, em rosa, estão os cinco casos, e as etiquetas mostram a similaridade medida (1,00 seria idêntico). À direita estão os remédios, e as setas mostram qual conserta qual.

A negação é o exemplo clássico. “Este pão contém glúten” e “Este pão não contém glúten” tiveram similaridade 0,93, mais alta do que “Este pão contém glúten” e “Este pão é feito com trigo” (0,85), que dizem coisas compatíveis. As duas frases falam do mesmo assunto, e o modelo captura o assunto mais do que o “não”. Num sistema de RAG sobre alimentos ou contratos, isso quer dizer que a busca pode devolver o trecho que afirma o contrário do que a pergunta quer. O reranking (Reranking), que relê pergunta e trecho juntos em vez de comparar dois vetores prontos, lida melhor com isso.

Datas e números também escapam. “O contrato vence em 12 de março de 2025” e “O contrato vence em 12 de março de 2026” tiveram similaridade 0,96, e “Preço: R$ 10,00” e “Preço: R$ 1.000,00” tiveram 0,88. Para o modelo, são frases do mesmo tipo; a diferença de um dígito mal move o ponto. Códigos e siglas seguem o mesmo padrão: “Erro E-4012 ao emitir nota” e “Erro E-4021 ao emitir nota” tiveram 0,99, praticamente idênticos, apesar de serem erros diferentes. Quando a pergunta contém um código exato, a busca por palavra acha o trecho certo de forma trivial, e é por isso que a busca híbrida (Busca híbrida) soma as duas abordagens.

O trecho longo demais é o caso do limite de entrada da seção 7: o que passa do limite não entra no vetor, então uma informação no fim de um trecho grande fica invisível para a busca. A saída é cortar o documento em trechos menores, que é a aula Chunking. O domínio muito específico aparece porque o modelo só viu o que o treino mostrou: em jurídico, médico ou num jargão de empresa, siglas e termos parecidos ficam próximos demais (“O CNPJ é obrigatório na NF-e” e “O CPF é obrigatório na NF-e” tiveram 0,86). Um modelo treinado ou ajustado para o domínio, ou o teste com os seus dados, é o que mostra se isso pesa para você.

Um caso a mais, sem figura: o idioma misturado. Num acervo que mistura português e inglês, a pergunta em um idioma precisa achar o trecho no outro. O modelo multilíngue que usei deu 0,66 entre “Como cancelar a assinatura?” e “How do I cancel my subscription?”, bem acima de uma pergunta sem relação (0,07), mas abaixo de 1,00, e o resultado muda de modelo para modelo. Se o seu acervo é bilíngue, inclua perguntas dos dois idiomas no teste.

Nenhum desses casos torna o embedding inútil. Eles definem o papel dele: achar candidatos por assunto, com tolerância a sinônimos e paráfrases. Os pontos cegos têm remédios conhecidos: a busca híbrida para palavras exatas, o reranking para nuances como a negação, o chunking para o limite de tamanho, e a avaliação para saber se o conjunto funciona nos seus dados.

10. A regra de ouro: nunca misture modelos

Os vetores de um modelo só significam algo para o próprio modelo. As coordenadas de “Como cancelar a assinatura?” no modelo A e no modelo B são dois conjuntos de números sem relação, porque cada modelo aprendeu o seu mapa do zero. Misturar vetores de modelos diferentes (ou de versões diferentes do mesmo modelo) no mesmo índice, ou comparar uma pergunta de um modelo com documentos de outro, dá resultados sem sentido. E o pior é que o código não reclama, quando os tamanhos coincidem.

Testei isso. Indexei quatro documentos com o modelo A (384 dimensões) e fiz a pergunta com o modelo B (também 384 dimensões). A comparação rodou sem erro, e as similaridades foram -0,01, -0,03, -0,08 e -0,01, todas perto de zero. Com o modelo A dos dois lados, os mesmos documentos tiveram 0,40, 0,25, 0,07 e 0,46. E a mesma frase, “Como cancelar a assinatura?”, embutida pelos dois modelos e comparada diretamente, teve similaridade -0,03: o mesmo texto, em dois mapas diferentes, virou dois pontos sem relação.

misturar modelos
figura 11 · misturar modelos

A figura mostra o desastre de cima para baixo. O índice já pronto, em azul, tem vetores do modelo A com 384 dimensões. A pergunta nova (laranja) passa pelo modelo B, que também devolve 384 dimensões, e o vetor da pergunta (B) chega ao losango rosa junto com os vetores A. A seta pontilhada que sai do losango para “nenhum erro” é a armadilha: como o tamanho bate, o programa roda. A seta rosa e grossa leva às similaridades perto de zero (o elemento treme, em erro), e o resultado final, “trechos escolhidos ao acaso”, faz o prompt receber material irrelevante, e a resposta ignora o conteúdo certo. Do lado do usuário, o sintoma é um RAG que “parou de funcionar” depois de uma atualização, sem nenhuma mensagem de erro.

Isso vale também para versões. Um provedor que lança uma versão nova de um modelo, mesmo com o mesmo nome base, pode ter um espaço diferente, e a página da Claude remete a uma página de ciclo de vida dos modelos da Voyage, com substituições recomendadas (não a li). Por isso a regra tem duas partes. Primeiro, trocar de modelo (ou de versão, ou de dimensão em modelos que não são Matryoshka) significa reindexar tudo: calcular de novo os vetores de todos os trechos com o modelo novo. Segundo, guarde o nome e a versão do modelo junto de cada vetor, para detectar uma mistura antes que ela vire bug.

reindexar
figura 12 · reindexar

A figura mostra o procedimento seguro. A tabela trechos guarda, além do vetor, o texto original, o nome do modelo e a versão. O texto original é o que torna a reindexação possível: sem ele, você teria de voltar às fontes. Ele passa por todos os trechos (seta grossa, lenta e com muitos pacotes, porque é o passo caro) pelo modelo novo, e sai um índice novo, em azul. O índice antigo (cinza e tracejado) continua atendendo enquanto o novo é preparado. O losango compara os dois com perguntas de teste: se os resultados melhoram, as consultas passam ao índice novo e a consulta também passa a usar o modelo novo (os dois lados trocam juntos, ou seja, o mesmo princípio da seção 6); se não, fica no antigo e você investiga. Só depois de uma troca bem-sucedida o índice antigo pode ser descartado.

Esse custo de reindexação é um dos motivos para escolher o modelo com cuidado no começo (seção 8) e para guardar os textos originais. A aula Banco de dados vetorial mostra como bancos organizam isso, e a Ingestão de documentos trata de manter o índice atualizado.

Parte 5: Outros usos e prática

11. Outros usos dos embeddings

A busca por significado é o uso mais conhecido, mas o mesmo mapa serve para outras tarefas, e todas dependem da mesma ideia de que perto quer dizer parecido.

alem do rag
figura 13 · alem do rag

A figura mostra que o caminho de entrada é o mesmo para todos. Texto e imagem, em laranja, entram no modelo de embedding, que devolve um vetor por item, guardado num conjunto azul. Dali sai um leque de usos em verde, com os pacotes defasados.

Na busca semântica, comparar a pergunta com todos os trechos devolve os mais próximos, e é a base do RAG. No agrupamento, textos com vetores próximos formam grupos sem rótulos prévios, útil para descobrir os temas de milhares de tickets de suporte. Na recomendação, itens parecidos com um que a pessoa gostou ficam por perto. Na deduplicação, textos quase iguais (como o mesmo anúncio com palavras trocadas) aparecem como vizinhos muito próximos. Na classificação, um texto novo recebe a classe dos vizinhos já rotulados, e funciona bem com poucos exemplos, porque não precisa treinar um classificador do zero.

Existem também modelos de embedding de imagem e multimodais, que colocam texto e imagem no mesmo espaço, de modo que o texto “gato dormindo no sofá” fica perto de fotos de gatos dormindo. A página da Claude lista, como exemplo, um modelo multimodal da Voyage que vetoriza texto, imagens e vídeos intercalados. O princípio é o mesmo: pares parecidos ficam perto no treino, só que um dos lados do par é uma imagem. Esta aula fica no texto.

Duas ligações com o vault. Em Agents, quando um agente precisa lembrar de algo de sessões passadas, uma saída comum é guardar as anotações e buscá-las por significado em vez de recarregar tudo na janela de contexto, o que é o mesmo mecanismo desta aula aplicado a memória (um padrão de projeto, não uma funcionalidade específica de um produto). Em MCP, um servidor pode expor uma ferramenta de busca semântica sobre um acervo, e o agente a chama como qualquer outra ferramenta; o texto que ela devolve entra na janela como material de referência, com os mesmos cuidados de Prompt e contexto (marcar a origem, tratar como dado).

12. Erros comuns de quem começa

O primeiro erro é achar que o embedding entende como um LLM. Ele aprendeu uma geometria de semelhança, não a raciocinar: não responde perguntas, não percebe negação de forma confiável (seção 9) e não sabe dizer por que dois textos são parecidos. A correção é tratar a busca como geradora de candidatos e deixar o julgamento fino para etapas posteriores, como o reranking, ou para o próprio LLM com um prompt bem montado.

O segundo é comparar vetores de modelos diferentes, ou de versões diferentes, como mostrou a seção 10. A correção é um modelo por índice, o nome e a versão guardados junto de cada vetor, e reindexação completa a cada troca.

O terceiro é não normalizar quando o modelo pede. Alguns modelos devolvem vetores de comprimento 1 e outros não (o modelo local da seção 7 devolveu norma cerca de 4,5). Se a sua comparação supõe vetores normalizados, como o produto escalar, e os vetores não estão, o resultado fica distorcido. A correção é conferir a documentação do modelo e a norma real de um vetor (np.linalg.norm), e normalizar na saída quando for o caso. Também vale lembrar do ajuste manual: vetores truncados de modelos Matryoshka precisam ser normalizados de novo depois do corte, como a documentação da Voyage mostra.

O quarto é embutir documentos inteiros num vetor só. Um vetor tem espaço fixo para resumir o texto, e um documento de cinquenta páginas vira um borrão que não se parece com nenhum assunto específico. Além disso, o que passa do limite de entrada é cortado (seção 7). A correção é dividir em trechos, de modo que cada vetor represente uma ideia, e a aula Chunking ensina como.

O quinto é esquecer de guardar o texto original. O vetor é uma representação perdida: dele não se recupera o texto. Se você guarda só os vetores, o sistema acha o vizinho mais próximo e não tem o que colocar no prompt, e uma troca de modelo (seção 10) fica impossível sem voltar às fontes. A correção é guardar sempre o texto, a origem e os metadados ao lado do vetor.

O sexto é omitir o tipo de entrada (consulta ou documento) quando o provedor ou o modelo pede (seção 6). Nada quebra, e a qualidade cai. A correção é seguir a documentação do modelo e testar com e sem, como no exercício 3.

13. Exercícios

Todos rodam em CPU, sem chave de API. O ambiente mínimo é Python com sentence-transformers e numpy (num ambiente virtual novo, pip install sentence-transformers numpy), e o primeiro uso de um modelo baixa os pesos.

  1. Agrupe frases. Escreva umas 12 frases de quatro assuntos (por exemplo receitas, código, finanças e atendimento, como na seção 1), gere os embeddings com o modelo multilíngue da seção 7 e imprima a matriz de model.similarity. Para cada frase, ache a vizinha mais próxima (a de maior valor fora da diagonal) e confira se ela é do mesmo assunto. Anote onde o modelo errou e tente explicar por quê.

  2. Compare português e inglês. Escreva dez frases em português e a tradução de cada uma em inglês. Calcule a similaridade de cada frase com a própria tradução e com as outras nove traduções. Veja se cada frase acha a tradução certa como a mais próxima, e anote o valor típico da similaridade entre traduções (ele dá uma noção de quanto o modelo trata os dois idiomas como o mesmo espaço).

  3. Teste negação, números e códigos. Repita os pares da seção 9 (glúten, datas, preços, códigos de erro) e invente três pares seus. Anote a similaridade de cada par e compare com um par que não tem relação nenhuma. Depois, com intfloat/multilingual-e5-small, teste uma pergunta e dois documentos com e sem os prefixos “query: ” e “passage: ”, e veja se o ranking muda.

  4. Conte os bytes. Gere os vetores de mil textos (podem ser frases repetidas) e imprima emb.nbytes, emb.dtype e emb.shape. Compare com a conta 1000 × dimensões × 4. Depois estime, para o seu acervo real, quantos trechos você esperaria ter e quanto espaço os vetores ocupariam em float32, em int8 (um quarto) e com a metade das dimensões.

  5. Escolha um modelo para o seu caso. Escreva dez perguntas reais do seu domínio e, para cada uma, o texto de um trecho que deveria ser achado. Embuta tudo com dois modelos candidatos e conte, para cada modelo, em quantas perguntas o trecho certo aparece entre os três mais próximos. Anote qual modelo ganhou e se o resultado combina com o ranking geral que você esperava.

  6. Quebre de propósito. Embuta quatro documentos com um modelo e a pergunta com outro de mesma dimensão (os dois modelos da seção 7 têm 384). Imprima as similaridades e confira que o programa roda sem erro e que os valores ficam perto de zero. Depois escreva em duas linhas como você garantiria, no seu sistema, que isso nunca acontece sem ser detectado.

14. Glossário

TermoSignificado
EmbeddingVetor que representa o significado de um texto (ou de uma imagem)
VetorLista ordenada de números, lida como um ponto num espaço com uma dimensão por número
DimensãoCada posição do vetor; um modelo de 384 dimensões devolve 384 números por texto
Espaço de embeddingsO mapa imaginário onde cada texto é um ponto e a vizinhança carrega significado
Modelo de embeddingModelo treinado para transformar texto em vetores, em geral menor que um LLM de conversa
Treino por contrasteTreino que aproxima pares parecidos e afasta pares diferentes
PoolingPasso que junta as saídas de cada token em um único vetor do trecho
NormalizarAjustar um vetor para ter comprimento 1
MatryoshkaTipo de treino em que o começo do vetor já serve como versão reduzida
QuantizaçãoGuardar cada número com menos bits (int8, binário) para economizar espaço
Consulta e documentoOs dois papéis do texto na busca; alguns modelos pedem que se informe qual
TruncamentoCorte do texto que passa do limite de entrada do modelo
MTEBBenchmark público que compara modelos de embedding em várias tarefas
ReindexarRecalcular os vetores de todos os trechos, necessário ao trocar de modelo
Busca semânticaBusca por significado, comparando vetores em vez de palavras

Fontes

Consultadas em 15 de outubro de 2026.

  • Claude: embeddings: a Anthropic não oferece modelo próprio de embeddings e aponta a Voyage AI (da MongoDB), com a ressalva de avaliar vários fornecedores; exemplo de código com voyageai.Client().embed(...); input_type com query e document e os prefixos que a biblioteca acrescenta; vetores normalizados para comprimento 1; quantização (float de 4 bytes, int8 e binário, reduções de 4 e 32 vezes); aprendizado Matryoshka e como cortar e normalizar de novo; lista de modelos de exemplo (família voyage-4, código, finanças, aberto, multimodal).
  • Voyage AI: embeddings: parâmetros de embed() (input_type, truncation com padrão verdadeiro, output_dimension, output_dtype), limites de contexto de 32.000 tokens para os modelos recentes. Esta página, ao contrário da da Claude, não afirma nada sobre normalização; usei a da Claude para isso.
  • OpenAI: embeddings: exemplo de client.embeddings.create, modelos text-embedding-3-small (1.536 dimensões por padrão) e text-embedding-3-large (3.072), parâmetro dimensions, limite de entrada de 8.192 tokens, vetores normalizados, resposta com usage.
  • Gemini API: embeddings: task_type com valores como RETRIEVAL_QUERY e RETRIEVAL_DOCUMENT no gemini-embedding-001 (o gemini-embedding-2 não aceita o campo), output_dimensionality, treino Matryoshka, necessidade de normalizar manualmente em dimensões diferentes de 3.072 no gemini-embedding-001.
  • Sentence Transformers: quickstart e computing embeddings: SentenceTransformer, encode, formato do resultado, similarity, truncamento ao max_seq_length e o aviso de que modelos treinados com textos curtos podem representar mal textos longos, prompts e prompt_name.
  • Hugging Face: MTEB: benchmark com 56 datasets em 8 tarefas e até 112 idiomas na apresentação original, e o aviso de que o desempenho varia por tarefa e dataset.
  • LLMs e Prompt e contexto: tokens, janela de contexto e montagem de prompt.

Medido por mim (ambiente descartável no scratchpad da sessão, Python 3.12, sentence-transformers 6.1.0, torch 2.14.1, transformers 5.20.0 e numpy 2.5.4, nenhum instalado fora dele): todas as similaridades, normas, formatos de saída, o truncamento de 404 tokens, a mistura de modelos e os nbytes citados, com os modelos sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (384 dimensões, limite de 128 tokens) e intfloat/multilingual-e5-small (384 dimensões), usando as frases que aparecem na aula. Os trechos locais das seções 7 e 14 foram executados e a saída descrita é a que obtive. Valores de similaridade são específicos de modelo e de versão e não devem ser reaproveitados como números de referência.

Sem execução ou sem fonte: os trechos de código da Voyage e da OpenAI são os exemplos oficiais e não foram executados, por falta de chave. A descrição do treino por contraste e do pooling é do conhecimento geral do autor, sem fonte consultada hoje, e os detalhes variam por modelo. O costume de pedir os prefixos “query: ” e “passage: ” em modelos da família E5 também é do conhecimento geral, e não li o cartão do modelo; o que observei foi que, na versão testada, os prompts configurados estavam vazios e que, em um único exemplo, os números sem prefixo foram quase iguais aos com prefixo. Não verifiquei a documentação de ciclo de vida da MongoDB para a Voyage (a página da Claude só aponta para ela), nem se o encode_query se comporta igual em outros modelos. As coordenadas da figura 1 e os vetores da figura 14 são ilustrativos, e a figura 1 é um esquema, não uma projeção medida. Os tamanhos de armazenamento da seção 5 são cálculos meus sobre float32, sem contar texto, metadados ou índices. Não citei preços nem o ranking atual do painel do MTEB.

Relacionados

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?