Lesson 14: Embeddings & Vector Databases
Embeddings turn text into coordinates where meaning = proximity. Learn similarity search and choosing a vector database.
What Is an Embedding?
An embedding is a list of numbers (a vector) that represents a piece of text. The trick: texts with similar meaning end up with similar vectors — so you can find "related" content by measuring distance.
"cat" -> [0.21, -0.45, 0.88, ...] 384–3072 numbers
"kitten" -> [0.22, -0.44, 0.87, ...] very close to "cat"
"refund" -> [-0.11, 0.63, 0.02, ...] far from "cat"
How Similarity Is Measured
| Metric | What It Measures | Typical Use |
|---|---|---|
| Cosine similarity | Angle between vectors (−1…1) | Most common for text |
| Dot product | Magnitude + angle | When vectors are normalized |
| Euclidean distance | Straight-line distance | Geometric contexts |
Cosine similarity is the default: 1.0 = identical direction, 0 = unrelated.
Embeddings in Practice
- Pick an embedding model (e.g.
text-embedding-3-small,bge-m3,nomic-embed). - Embed your chunks once (offline) and store the vectors.
- At query time, embed the question with the SAME model.
- Search the database for the nearest vectors.
Rule: never mix embedding models in one database — vectors are only comparable within the same model.
Vector Databases Compared
| Option | Type | Best For |
|---|---|---|
| pgvector | Postgres extension | Already using Postgres |
| Chroma | Lightweight, embedded | Local dev, small datasets |
| Qdrant | Purpose-built, Rust | Production scale, filtering |
| Pinecone | Managed cloud | Zero-ops at scale |
| Weaviate / Milvus | Purpose-built | Large hybrid workloads |
Choosing: if you already run Postgres, start with pgvector. Move to a dedicated vector DB when you need scale, filtering, or hybrid search.
Beyond Similarity: Practical Retrieval Upgrades
- Hybrid search: vector + keyword (BM25) combined — catches exact IDs/names that vectors miss.
- Metadata filtering: "only 2026 documents" — filter before similarity.
- Reranking: a cross-encoder re-scores top-50 candidates → much better precision.
- Multi-vector (ColBERT-style): per-token vectors for finer matching.
Embeddings Are Not Magic
- They capture statistical similarity, not logic ("bat" ≈ baseball bat ≈ vampire bat).
- Small models → cheap + fast but less nuanced; big models → better but slower/costlier.
- Evaluate retrieval on YOUR data — public benchmarks don't predict your domain.
Key Takeaways
- Embeddings map text to vectors; meaning proximity = vector proximity.
- Cosine similarity is the standard distance metric for text.
- Choose the DB by your stack: pgvector first, purpose-built at scale.
- Add hybrid search and reranking before switching models — usually bigger wins.
Next up: Evaluating LLM systems — how to know if your AI actually works.
# Embeddings turn words into vectors; similar words live close together
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
mag_a = math.sqrt(sum(x * x for x in a))
mag_b = math.sqrt(sum(x * x for x in b))
return dot / (mag_a * mag_b)
# Tiny 3-D "embeddings" (real ones have 384-3072 dimensions)
king = [0.9, 0.8, 0.3]
queen = [0.85, 0.75, 0.4]
apple = [0.1, 0.2, 0.9]
print(f"cosine(king, queen) = {cosine(king, queen):.3f} (similar meaning)")
print(f"cosine(king, apple) = {cosine(king, apple):.3f} (unrelated)")
print(f"cosine(queen, apple) = {cosine(queen, apple):.3f}")
# Nearest-neighbor search: which stored vector is closest to a query?
query = king
stored = {"queen": queen, "apple": apple}
best, best_score = None, -1
for name, vec in stored.items():
score = cosine(query, vec)
print(f" query vs {name:6s}: {score:.3f}")
if score > best_score:
best, best_score = name, score
print(f"\nNearest neighbor: {best}")Lesson Code (Python)
# Embeddings turn words into vectors; similar words live close together
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
mag_a = math.sqrt(sum(x * x for x in a))
mag_b = math.sqrt(sum(x * x for x in b))
return dot / (mag_a * mag_b)
# Tiny 3-D "embeddings" (real ones have 384-3072 dimensions)
king = [0.9, 0.8, 0.3]
queen = [0.85, 0.75, 0.4]
apple = [0.1, 0.2, 0.9]
print(f"cosine(king, queen) = {cosine(king, queen):.3f} (similar meaning)")
print(f"cosine(king, apple) = {cosine(king, apple):.3f} (unrelated)")
print(f"cosine(queen, apple) = {cosine(queen, apple):.3f}")
# Nearest-neighbor search: which stored vector is closest to a query?
query = king
stored = {"queen": queen, "apple": apple}
best, best_score = None, -1
for name, vec in stored.items():
score = cosine(query, vec)
print(f" query vs {name:6s}: {score:.3f}")
if score > best_score:
best, best_score = name, score
print(f"\nNearest neighbor: {best}")Console Output
cosine(king, queen) = 0.995 (similar meaning)
cosine(king, apple) = 0.452 (unrelated)
cosine(queen, apple) = 0.534
query vs queen : 0.995
query vs apple : 0.452
Nearest neighbor: queenCode Visualization Tips
- Plot the 3D vectors as points and draw the angles — smaller angle = more similar.
- Draw a 2D 'semantic map' with clusters: animals, tech, finance — nearest neighbors close together.
- Visualize the retrieval step: query vector lands among stored vectors; circle the top-k nearest.
Professional Tips & Tricks
- Normalize vectors before storing — cosine similarity then equals the dot product (faster queries).
- Keep the embedding model version in your metadata — re-embedding everything is a real chore.
- Test embedding models on 20–50 of YOUR real queries before committing.
Python Code Judge & Practice Arena
LeetCode StyleRun real Python 3.12 WebAssembly code directly in your browser against automated test suites.
Reason About Similarity
Up next · Continue learning
Evaluating LLM Systems
Benchmarks, metrics, and LLM-as-judge — how to measure quality, catch regressions, and know when your AI is good enough.