O que são embeddings e por que importam
Embeddings são representações numéricas de texto — listas de números (vetores) que capturam significado semântico. Textos com sentido parecido ficam próximos no espaço vetorial; textos diferentes ficam distantes.
Essa ideia é a base de quase tudo em IA generativa aplicada: busca semântica, RAG, clustering de documentos, detecção de duplicatas, recomendação. Sem embeddings, você depende de busca por palavra-chave — que falha quando o usuário pergunta "como compartilhar dados financeiros" e o documento diz "Open Finance com consentimento".
A pergunta prática não é "o que é um vetor", mas como escolher o modelo, como medir similaridade e como indexar milhões de documentos sem consultar cada um linearmente. Este post cobre esses três pontos.
Como funciona na prática
O fluxo básico tem três passos:
- Encoder — modelo de embedding converte texto em vetor (tipicamente 384 a 3072 dimensões)
- Indexação — vetores são armazenados em um vector store (Chroma, Pinecone, FAISS)
- Consulta — a pergunta também vira vetor; o sistema retorna os documentos mais próximos
Digite uma consulta e clique em Buscar para ver a similaridade semântica
A demo acima simula o que acontece na consulta: cada documento recebe um score de similaridade em relação à pergunta. Documentos sobre Open Finance sobem no ranking quando a consulta é sobre compartilhamento de dados financeiros — mesmo sem palavras idênticas.
Medindo similaridade: cosine similarity
A métrica mais usada entre embeddings é a similaridade de cosseno — mede o ângulo entre dois vetores, ignorando magnitude:
import numpy as np
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Vetores normalizados: valores entre -1 e 1 (tipicamente 0 a 1 para embeddings de texto)
query = np.array([0.12, 0.45, 0.88, 0.31])
doc_a = np.array([0.15, 0.42, 0.85, 0.29]) # similar
doc_b = np.array([0.91, 0.05, 0.12, 0.77]) # diferente
print(cosine_similarity(query, doc_a)) # ~0.99
print(cosine_similarity(query, doc_b)) # ~0.35
Na prática, você não calcula isso manualmente — bibliotecas e vector stores fazem busca aproximada de vizinho mais próximo (ANN) em escala.
Gerando embeddings em Python
Com OpenAI:
from openai import OpenAI
client = OpenAI()
def embed_texts(texts: list[str], model: str = "text-embedding-3-small") -> list[list[float]]:
response = client.embeddings.create(input=texts, model=model)
return [item.embedding for item in response.data]
documents = [
"Open Finance permite compartilhar dados financeiros com consentimento.",
"Fine-tuning ajusta pesos do modelo com dados rotulados.",
]
vectors = embed_texts(documents)
query_vector = embed_texts(["como funciona open finance?"])[0]
Com sentence-transformers (local, sem API):
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
documents = [
"Open Finance permite compartilhar dados financeiros com consentimento.",
"Fine-tuning ajusta pesos do modelo com dados rotulados.",
]
doc_vectors = model.encode(documents)
query_vector = model.encode("como funciona open finance?")
# Similaridade
from sentence_transformers.util import cos_sim
scores = cos_sim(query_vector, doc_vectors)
print(scores) # tensor([[0.72, 0.18]])
Três decisões ao escolher modelo:
- Dimensão — vetores maiores capturam mais nuance, mas custam mais storage e latência
- Domínio — modelos generalistas funcionam bem; domínios muito específicos (jurídico, médico) podem pedir fine-tune do encoder
- Consistência — use o mesmo modelo na indexação e na consulta; trocar exige reindexar tudo
Indexação e busca em escala
Para poucos documentos, similaridade linear basta. Em produção, use ANN (Approximate Nearest Neighbors):
import chromadb
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("docs")
collection.add(
documents=documents,
ids=["doc1", "doc2"],
embeddings=vectors, # ou deixe o Chroma gerar com embedding function
)
results = collection.query(
query_embeddings=[query_vector],
n_results=3,
)
print(results["documents"])
FAISS (Facebook AI Similarity Search) é alternativa para volumes massivos em memória ou disco. Pinecone e pgvector servem quando você quer vector store gerenciado ou SQL integrado.
Além do básico: pontos de atenção
Limitações e boas práticas:
- Busca semântica não é mágica — perguntas vagas retornam resultados vagos; chunking e metadados ajudam
- Idioma — modelos multilíngues (ex.:
multilingual-e5) para conteúdo em PT e EN - Normalização — muitos modelos já retornam vetores normalizados; verifique na documentação
- Custo — embedding de milhões de chunks via API tem custo; cache e batch reduzem
- Avaliação — meça precision@k com perguntas reais antes de confiar no retrieval
Armadilhas comuns:
- Reindexar com modelo diferente sem avisar — scores ficam incomparáveis
- Embedar documentos inteiros sem chunking — vetor diluído, busca imprecisa
- Ignorar metadados — filtrar por data, tipo ou tenant antes da busca vetorial melhora muito
Conclusão
Embeddings transformam texto em busca por significado. O fluxo é: encoder gera vetores, vector store indexa, consulta retorna os mais similares via cosine similarity ou ANN.
Domine isso antes de montar um RAG — a qualidade do retrieval depende quase inteiramente da qualidade dos embeddings e do chunking. O próximo passo natural é avaliar se o sistema RAG completo está pronto para produção.