Embeddings

Dense vector representations of text for semantic search, clustering, and retrieval pipelines.

What Embeddings Represent

An embedding model maps text (words, sentences, passages) to fixed-dimensional vectors in a continuous space. Semantically similar texts land near each other; dissimilar texts are farther apart. Unlike generative LLMs, embedding models are trained for representation quality, not open-ended generation.

Typical dimensions range from 384 to 3072 depending on model. Higher dimensions can capture finer distinctions but increase storage and search cost in vector databases.

How Embedding Models Are Trained

Contrastive learning

Models learn by pulling positive pairs (query and relevant passage) together and pushing negatives apart in vector space. Loss functions include InfoNCE, triplet loss, and multi-negative cross-entropy across in-batch negatives.

Dual encoders

Separate encoders for queries and documents (or shared weights) produce embeddings independently. At search time you precompute document vectors and only encode the query live, enabling fast retrieval at scale.

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-small-en-v1.5")

doc_embeddings = model.encode(
    passages, normalize_embeddings=True, batch_size=64
)
query_embedding = model.encode(
    "query: " + user_query, normalize_embeddings=True
)

# cosine similarity ≈ dot product when vectors are normalized
scores = query_embedding @ doc_embeddings.T

Matryoshka and flexible dimensions

Some models support truncating vectors to fewer dimensions with modest quality loss, letting you tune the cost-quality tradeoff without retraining separate models.

Similarity Metrics

Cosine similarity is standard for normalized embeddings: it measures angle between vectors and is insensitive to magnitude. Dot product is equivalent when vectors are unit-normalized but faster on some hardware when norms vary. Euclidean distance is less common at scale but appears in clustering workflows.

Always use the same similarity metric at index time and query time. Mixing normalized cosine indexed vectors with raw dot product queries silently degrades ranking.

Choosing and Evaluating Models

General-purpose models (OpenAI text-embedding-3, Cohere embed, open models like e5, bge, gte) work across domains. Domain-specific fine-tuned embedders win on specialized corpora (legal, medical, code).

Evaluate on your data with:

Common mistake: Using a generative LLM's hidden states as embeddings without a model trained for retrieval. Results are often worse than dedicated embedding models at the same size.

Hybrid Search

Pure vector search misses exact keyword matches (SKUs, error codes, rare names). Hybrid search combines:

Scores are fused with reciprocal rank fusion (RRF) or learned rerankers. Hybrid is the default recommendation for production RAG over heterogeneous documents.

def reciprocal_rank_fusion(dense_ranks, sparse_ranks, k=60):
    scores = {}
    for ranks in (dense_ranks, sparse_ranks):
        for rank, doc_id in enumerate(ranks):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores, key=scores.get, reverse=True)

Pipeline Integration

Embed documents at ingestion, store vectors with metadata (source, ACL, timestamp), and embed queries at request time. Batch embedding jobs should be idempotent and versioned: when you change embedding models, re-embed the corpus or maintain separate indexes per model version.

Prefix instructions matter for models trained with them (e.g., "query: " vs "passage: " in e5 family). Apply the correct prefix or retrieval quality drops sharply.