An embedding is a mathematical representation of text as a vector (list of numbers).

Key: Similar text gets similar vectors.

The Concept

Text: "The dog is cute"
Embedding: [0.2, -0.5, 0.8, ..., 0.1]  (e.g., 1536 dims)

Text: "A cute dog"
Embedding: [0.21, -0.48, 0.79, ..., 0.12]  (similar!)

Text: "The car is red"
Embedding: [-0.8, 0.2, 0.1, ..., -0.5]  (not similar)

Vectors are points in space. Similar meanings are spatially close.

Why Embeddings?

Reason 1: Similarity Search

You have 1000 documents. User: "Which is similar to my question?"

Without: Keyword search misses semantic meaning.

With:

  1. Convert question to vector
  2. Documents already vectorized
  3. Find closest vector
  4. Return document

Foundation for RAG (Retrieval-Augmented Generation).

Reason 2: Semantic Similarity

Query: "How do I cook spaghetti?"

Document A: "Cooking pasta: 1. Boil water..."
Distance: 0.05 (close) ✓

Document B: "History of Rome"
Distance: 0.9 (far) ✗

Keyword match would find B too. Embeddings are smarter.

How Embeddings Work

An embedding model is a neural net trained to place semantically similar texts close together.

Training:

Input: "The cat is black"
       "A black cat"
       "The car is blue"

Goal:
- First two get similar vectors
- Third gets different vector

Called Contrastive Learning.

text-embedding-3-small (OpenAI)

from openai import OpenAI

client = OpenAI(api_key="sk-...")

response = client.embeddings.create(
    input="Hello, I'm text",
    model="text-embedding-3-small"
)

embedding = response.data[0].embedding
  • Dimensions: 1536
  • Price: $0.02 / 1M tokens
  • Quality: Excellent

nomic-embed-text (Open Source)

Small, fast, free:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('nomic-ai/nomic-embed-text-v1')
embedding = model.encode("Hello")
  • Dimensions: 768
  • Price: Free
  • Quality: Good
  • Speed: Fast (runs locally)

bge-large-en-v1.5 (BAAI)

Specialized for retrieval:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-large-en-v1.5")
embedding = model.encode("What is AI?")
  • Quality: Excellent for semantic search
  • Speed: Medium
  • Overhead: 370MB

Similarity Search Practical

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

doc_embeddings = [
    [0.2, 0.5, 0.1],
    [0.21, 0.48, 0.12],
    [-0.8, 0.2, -0.5],
]

query = "Cute dog"
query_embedding = [0.21, 0.49, 0.11]

similarities = cosine_similarity([query_embedding], doc_embeddings)[0]
# [0.99, 0.95, 0.1]

top_indices = np.argsort(similarities)[::-1][:2]
# [0, 1] → Most similar docs

Storing Embeddings

For many documents, use a Vector Database:

  • Pinecone: Managed, Cloud
  • Weaviate: Open source
  • Milvus: High performance
  • Qdrant: Modern
  • ChromaDB: Simple, local

Example ChromaDB:

import chromadb

client = chromadb.Client()
collection = client.create_collection("documents")

collection.add(
    ids=["doc_1", "doc_2"],
    embeddings=[[0.2, 0.5], [0.21, 0.48]],
    documents=["Text 1", "Text 2"]
)

results = collection.query(
    query_embeddings=[[0.21, 0.49]],
    n_results=2
)

Dimensions: Trade-offs

More dimensions = more info but:

  • Higher cost
  • Slower compute
  • More storage
text-embedding-3-small: 1536 dims
nomic-embed: 768 dims
DistilBERT: 384 dims

For most tasks: 768 enough. Very precise search: 1536+.

Dimensionality Reduction

Save costs by compressing:

from sklearn.decomposition import PCA

pca = PCA(n_components=256)
reduced = pca.fit_transform(embeddings)

# Quality: ~90%, Memory: -83%

Works for most applications with reduced embeddings.

Multilingual Embeddings

Some models support multiple languages:

from sentence_transformers import SentenceTransformer

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

embeddings = model.encode([
    "How do I cook pasta?",
    "Wie koche ich Pasta?",
    "¿Cómo cocino pasta?"
])

Great for RAG across languages.

Advanced: Embedding Fine-Tuning

For specialized domains (medical, legal, technical), fine-tune embeddings on your data:

# Fine-tune text-embedding-3-small on your domain
from sentence_transformers import models, losses, SentenceTransformer
from sentence_transformers.util import sentences2labels

# Your training pairs (similar sentences)
train_examples = [
    ("How do I cook pasta?", "Cooking spaghetti: boil water, add salt..."),
    ("What's machine learning?", "ML is a type of AI that learns from data..."),
]

model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# Train with contrastive loss
train_loss = losses.ContrastiveLoss(model)
model.fit(
    [(e[0], e[1]) for e in train_examples],
    epochs=1,
    warmup_steps=100,
    show_progress_bar=True
)

model.save('./my-domain-embeddings')

Result: Model learns that your domain-specific pairs are similar, improves search relevance.

Similarity Metrics Deep Dive

Cosine Similarity (Most Common)

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

v1 = np.array([0.2, 0.5, 0.8])
v2 = np.array([0.19, 0.51, 0.79])
v3 = np.array([-0.5, 0.1, 0.2])

print(cosine_similarity([v1], [v2])[0][0])  # 0.9999 (nearly identical)
print(cosine_similarity([v1], [v3])[0][0])  # 0.34 (different)

Range: -1 (opposite) to 1 (identical) For embeddings: Usually 0.7-0.99 for relevant, <0.5 for irrelevant

Euclidean Distance (Alternative)

from scipy.spatial.distance import euclidean

v1 = np.array([0.2, 0.5, 0.8])
v2 = np.array([0.19, 0.51, 0.79])

dist = euclidean(v1, v2)  # 0.014 (small = similar)

When to use: Less common than cosine, useful when magnitude matters

Dot Product (Fastest)

# Fastest for large-scale search
dot_product = np.dot(v1, v2)  # 0.85 (needs normalization first)

Vector Database Comparison

Pinecone (Managed)

import pinecone

pinecone.init(api_key="...", environment="us-west1-gcp")

index = pinecone.Index("documents")

# Upsert vectors
index.upsert(vectors=[
    ("doc_1", [0.2, 0.5, 0.1, ...], {"title": "Doc 1"}),
    ("doc_2", [0.21, 0.48, 0.12, ...], {"title": "Doc 2"}),
])

# Query
results = index.query([0.21, 0.49, 0.11], top_k=2)
# Returns: [("doc_1", 0.99), ("doc_2", 0.95)]

Pros: Fully managed, scalable, API-based Cons: Proprietary, cost per query (EUR 0.001-0.01 per)

Weaviate (Open Source)

import weaviate

client = weaviate.Client("http://localhost:8080")

# Define schema
client.schema.create_class({
    "class": "Document",
    "vectorizer": "text2vec-transformers",
    "properties": [{"name": "content", "dataType": ["text"]}]
})

# Add documents
client.data_object.create(
    data_object={"content": "The cat is on the mat"},
    class_name="Document"
)

# Query
result = client.query.get("Document", ["content"]).with_near_text({
    "concepts": ["cat mat"]
}).do()

Pros: Open source, self-hosted, GraphQL API Cons: More complex setup, requires infra

Qdrant (Modern & Fast)

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct

client = QdrantClient("localhost", port=6333)

# Create collection
client.recreate_collection(
    collection_name="documents",
    vectors_config={"size": 1536, "distance": "Cosine"}
)

# Add vectors
client.upsert(
    collection_name="documents",
    points=[
        PointStruct(id=1, vector=[0.2, 0.5, ...], payload={"text": "Doc 1"}),
    ]
)

# Search
results = client.search(
    collection_name="documents",
    query_vector=[0.21, 0.49, ...],
    limit=5
)

Pros: Very fast, REST/gRPC API, modern Python lib Cons: Newer, smaller community than Pinecone

RAG (Retrieval-Augmented Generation) Pipeline

Combining embeddings with LLMs:

from openai import OpenAI
import chromadb

# 1. Index documents
client = chromadb.Client()
collection = client.create_collection("documents")

docs = [
    "Paris is the capital of France",
    "Tokyo is the capital of Japan",
    "The Earth orbits the Sun"
]

collection.add(ids=[str(i) for i in range(len(docs))], documents=docs)

# 2. User question
question = "What's the capital of France?"

# 3. Retrieve relevant docs (embedding search)
results = collection.query(query_texts=[question], n_results=1)
retrieved_doc = results["documents"][0][0]

# 4. Generate answer with LLM
llm = OpenAI()
response = llm.chat.completions.create(
    model="gpt-4",
    messages=[
        {
            "role": "user",
            "content": f"Based on: {retrieved_doc}\n\nAnswer: {question}"
        }
    ]
)

print(response.choices[0].message.content)
# Output: "According to the context, Paris is the capital of France."

Why RAG: Avoids hallucination, uses fresh data, grounded in sources.

Hybrid Search (Embeddings + Keywords)

Combining semantic + keyword search:

# Hybrid search with Weaviate
result = client.query.get("Document", ["content"]).with_where({
    "operator": "And",
    "operands": [
        {"path": ["content"], "operator": "ContainsAny", "valueText": ["pasta"]},  # Keyword
        {"path": ["embedding"], "operator": "WithinDistance", "valueDistance": 0.1}  # Semantic
    ]
}).do()

When to use:

  • Keyword search misses semantic matches
  • Semantic search returns irrelevant results
  • Hybrid: Best of both worlds

Embedding Dimension Trade-offs

Deep analysis:

Dimensions Model Quality Storage Speed Cost
64 tiny 70% 1x 10x $0.001
384 small 85% 1.5x 5x $0.005
768 medium 92% 3x 2x $0.01
1536 large 98% 6x 1x $0.02

Practical: Use 768 for most tasks. 1536 only if precision critical.

Dimensionality Reduction (Compromise)

from sklearn.decomposition import PCA

# 1536 → 384 dims
pca = PCA(n_components=384)
compressed = pca.fit_transform(embeddings_1536)

# Quality loss: ~2-5%
# Storage gain: 75% reduction
from sentence_transformers import SentenceTransformer

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

# All languages encoded to same space
embeddings = model.encode([
    "What is artificial intelligence?",        # English
    "What es inteligencia artificial?",         # Spanish
    "Qu'est-ce que l'IA?",                     # French
    "What ist künstliche Intelligenz?",         # German (sic)
])

# All close together in vector space
# Cross-language similarity search works!

Use case: Support multi-language document retrieval without separate models.

Cost Calculation Examples

Scenario 1: Small FAQ Bot (10k documents)

Embedding cost:
- 10k docs × 200 tokens/doc = 2M tokens
- text-embedding-3-small: $0.02/1M = $0.04

Inference cost (monthly, 1000 queries):
- 1000 queries × 200 tokens = 200k tokens = $0.004

Total monthly: $0.004 (embedding cost amortized, negligible)

Scenario 2: Enterprise RAG (500k documents, 10k queries/day)

One-time indexing:
- 500k docs × 200 tokens = 100M tokens = $2.00

Monthly inference:
- 10k queries/day × 30 days × 200 tokens = 60M tokens = $1.20

Infrastructure (vector DB):
- Pinecone: ~$1000/month (managed)
- Self-hosted: $200/month (VPS)

Monthly total: $1200+ (with Pinecone)

References

Last Updated: 21.03.2026 | Total Lines: 450+