Skip to content

Embeddings

Embeddings convert text into dense vectors that capture meaning. Similar texts get similar vectors — enabling semantic search, clustering, and RAG retrieval. Choosing the right embedding model directly impacts retrieval quality.

Table of Contents

Installation / Setup

pip install openai sentence-transformers    # or: uv add openai sentence-transformers

Core Syntax

Model selection table

Model Provider Dimensions Speed Quality Cost
text-embedding-3-small OpenAI 1536 ★★★★★ ★★★★ $0.02/1M tokens
text-embedding-3-large OpenAI 3072 ★★★★ ★★★★★ $0.13/1M tokens
embed-english-v3.0 Cohere 1024 ★★★★★ ★★★★ $0.10/1M tokens
all-MiniLM-L6-v2 Local 384 ★★★★★ ★★★ Free
all-mpnet-base-v2 Local 768 ★★★★ ★★★★ Free
BAAI/bge-large-en-v1.5 Local 1024 ★★★ ★★★★★ Free
BAAI/bge-m3 Local 1024 ★★★ ★★★★★ Free (multilingual)

OpenAI embeddings

from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY")

response = client.embeddings.create(
    model="text-embedding-3-small",
    input=["What is machine learning?", "How does RAG work?"],
)
vectors = [item.embedding for item in response.data]
# vectors[0] is a 1536-dimensional list of floats

Local embeddings (sentence-transformers)

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = model.encode(
    ["What is machine learning?", "How does RAG work?"],
    batch_size=32,
    show_progress_bar=True,
)
# embeddings is a numpy array of shape (2, 384)

LangChain embeddings

from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings

# OpenAI
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Local
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

# use with vector store
vectorstore = FAISS.from_texts(texts, embeddings)

Common Operations

Cosine similarity

import numpy as np

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-10)

# example
vec1 = embeddings[0]
vec2 = embeddings[1]
print(cosine_similarity(vec1, vec2))   # 0.0 to 1.0 (1.0 = identical)

Batch embedding for cost efficiency

# OpenAI charges per token — batch to reduce API calls
def batch_embed(texts, model="text-embedding-3-small", batch_size=100):
    all_embeddings = []
    for i in range(0, len(texts), batch_size):
        batch = texts[i:i+batch_size]
        response = client.embeddings.create(model=model, input=batch)
        all_embeddings.extend([e.embedding for e in response.data])
    return all_embeddings

Dimension reduction (for OpenAI v3 models)

# OpenAI v3 supports dimension reduction
response = client.embeddings.create(
    model="text-embedding-3-large",
    input="Hello world",
    dimensions=256,    # reduce from 3072 to 256
)

Multilingual embeddings

# BGE-M3 supports 100+ languages
model = SentenceTransformer("BAAI/bge-m3")
embeddings = model.encode([
    "Hello in English",
    "Bonjour en français",
    "Hola en español",
])

Practical Examples

Example 1 — Compare embedding models for your data

from openai import OpenAI
from sentence_transformers import SentenceTransformer
import numpy as np

client = OpenAI(api_key="YOUR_API_KEY")

# sample queries and documents
queries = ["How do I train a model?", "What is overfitting?"]
docs = [
    "Training involves fitting parameters to data using gradient descent.",
    "Overfitting occurs when a model memorizes training data.",
    "Python is a popular programming language.",
]

# OpenAI embeddings
openai_emb = client.embeddings.create(model="text-embedding-3-small", input=queries + docs)
openai_vecs = np.array([e.embedding for e in openai_emb.data])

# local embeddings
local_model = SentenceTransformer("all-MiniLM-L6-v2")
local_vecs = local_model.encode(queries + docs)

# compare retrieval quality
for qi, q in enumerate(queries):
    o_sims = np.dot(openai_vecs[qi+2:], openai_vecs[qi]) / np.linalg.norm(openai_vecs[qi+2:], axis=1)
    l_sims = np.dot(local_vecs[qi+2:], local_vecs[qi]) / np.linalg.norm(local_vecs[qi+2:], axis=1)
    print(f"Query: {q}")
    print(f"  OpenAI top: {np.argmax(o_sims)} ({o_sims.max():.3f})")
    print(f"  Local top:  {np.argmax(l_sims)} ({l_sims.max():.3f})")

Example 2 — Embed a document collection

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# load and chunk
loader = DirectoryLoader("./docs", glob="**/*.md", loader_cls=TextLoader)
docs = loader.load()
chunks = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50).split_documents(docs)

# embed and index
vectorstore = FAISS.from_documents(chunks, OpenAIEmbeddings(model="text-embedding-3-small"))
vectorstore.save_local("docs_index")

Common Patterns

Pattern: embedding caching

import hashlib, json, os

def embed_with_cache(texts, model="text-embedding-3-small", cache_file="embeddings.json"):
    cache = {}
    if os.path.exists(cache_file):
        with open(cache_file) as f:
            cache = json.load(f)

    to_embed = [t for t in texts if t not in cache]
    if to_embed:
        response = client.embeddings.create(model=model, input=to_embed)
        for text, emb in zip(to_embed, [e.embedding for e in response.data]):
            cache[text] = emb
        with open(cache_file, "w") as f:
            json.dump(cache, f)

    return [cache[t] for t in texts]

Pattern: semantic deduplication

import numpy as np

def deduplicate(texts, threshold=0.95):
    model = SentenceTransformer("all-MiniLM-L6-v2")
    embeddings = model.encode(texts)
    keep = [True] * len(texts)
    for i in range(len(texts)):
        if not keep[i]:
            continue
        for j in range(i+1, len(texts)):
            if keep[j]:
                sim = np.dot(embeddings[i], embeddings[j]) / (
                    np.linalg.norm(embeddings[i]) * np.linalg.norm(embeddings[j]))
                if sim > threshold:
                    keep[j] = False
    return [t for t, k in zip(texts, keep) if k]

Best Practices

  • Start with text-embedding-3-small — fast, cheap, good quality.
  • Use text-embedding-3-large when retrieval quality matters most.
  • Use local models (all-MiniLM-L6-v2, BAAI/bge-large-en-v1.5) for privacy or offline use.
  • Match embedding model at query and index time — never mix models.
  • Batch embedding calls to reduce API overhead.
  • Cache embeddings for repeated texts — they don't change.
  • Use dimension reduction for OpenAI v3 models when storage is constrained.
  • Multilingual: use BAAI/bge-m3 or embed-english-v3.0 (Cohere supports 100+ languages).

Common Mistakes

Mistake Fix
Mixing embedding models at query and index Use the same model for both
Not batching API calls Batch 100–500 texts per call
Ignoring embedding dimensions Check model dimensions before indexing
Using the same model for different languages Use multilingual model for multilingual data
Not caching embeddings Cache to avoid re-embedding unchanged text
Embedding very long texts Chunk first, then embed chunks

Troubleshooting

Symptom Cause / Fix
Poor retrieval quality Wrong embedding model — try a different one
dimension mismatch error Query and index use different models
Slow embedding Use batch processing; consider local model
High API costs Switch to local model or use dimension reduction
Multilingual queries fail Use multilingual embedding model

Official Documentation

Last Updated

2026-09-06 — Initial version.