RAG Step 2: Embeddings and Vectors

Before we can search documents, we need to understand how text becomes numbers.

What Is an Embedding?

An embedding is a list of numbers (a "vector") that captures the meaning of a piece of text.

"Cat"     → [0.21, -0.45, 0.83, ...]   (384 numbers)
"Kitten"  → [0.19, -0.42, 0.80, ...]   (very similar to cat!)
"Car"     → [0.55, 0.12, -0.33, ...]   (not similar to cat)

Similar words have similar vectors. The model learns this by reading billions of sentences.

Visualizing Embeddings

Imagine a 3D space where each word is a point:

                    "dog"
                      ●
                     /
                    /
        "cat" ●────● "kitten"
                   /
                  /
    "car" ●───────● "truck"

In reality, embeddings are 384-dimensional (or 768, 1024, etc.) — impossible to visualize, but the math works the same way.

Cosine Similarity

To measure how similar two vectors are, we use cosine similarity:

similarity = cos(θ) = (A · B) / (||A|| × ||B||)
  • 1.0 = identical direction (same meaning)
  • 0.0 = perpendicular (unrelated)
  • -1.0 = opposite direction (opposite meaning)
"Kitten" vs "Cat":     cosine = 0.92  (very similar)
"Kitten" vs "Car":     cosine = 0.15  (not similar)
"Kitten" vs "Phone":   cosine = 0.08  (not similar)

Word Embeddings vs Sentence Embeddings

Type Example Model Input Output Vector
Word Word2Vec, GloVe Single word 1 vector per word
Sentence all-MiniLM-L6-v2 Sentence or paragraph 1 vector for the whole thing

For RAG, we use sentence embeddings — a whole paragraph becomes one vector.

The Model We'll Use: all-MiniLM-L6-v2

  • Size: 80MB (small, runs on any laptop)
  • Dimensions: 384 (good balance of speed vs accuracy)
  • Speed: ~10,000 sentences/second on a laptop CPU
  • Free: No API key needed, runs locally
  • License: MIT (free for any use)

It's made by the sentence-transformers library from Hugging Face.

Try It

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

sentences = [
    "Clark College offers free tutoring",
    "Students can get help with math",
    "I love pizza",
]

vectors = model.encode(sentences)
print(f"Shape: {vectors.shape}")   # (3, 384)
print(f"Vector 0: {vectors[0][:5]}...")  # First 5 numbers

Output:

Shape: (3, 384)
Vector 0: [0.042, -0.133, 0.287, -0.091, 0.155]...

Why 384 Dimensions?

More dimensions = more nuanced meaning, but slower and more storage.

Dimensions Model Storage (1M docs) Speed
384 all-MiniLM-L6-v2 ~1.5 GB Fast
768 all-mpnet-base-v2 ~3 GB Medium
1536 OpenAI ada-002 ~6 GB Slow (API call)

For this project, 384 dimensions is plenty.

Key Takeaway

Embeddings turn text into coordinates in a "meaning space." Similar meanings have nearby coordinates. Cosine similarity measures the distance. This is how your RAG system will find relevant documents.