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
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.