Skip to main content

Vector Embedding & Vector Databases

TL;DR
  • An embedding is a vector that captures meaning; similar texts sit close together.
  • Use embed_query for the question, embed_documents for your chunks β€” same model.
  • Rank by cosine similarity; MiniLM = 384-dim (free, local), OpenAI small = 1536.

Embeddings are how RAG turns text into something a computer can compare. This section covers the intuition, the similarity math, and how to actually generate embeddings with HuggingFace and OpenAI models. One submodule per topic, ending with a cheat sheet.

What an embedding is​

An embedding translates text into numbers β€” a fixed-length vector that captures meaning. Texts about similar things land close together in this number-space; unrelated texts land far apart.

Text becomes a vector; similar meanings sit close in space

A tiny 2-D example makes it concrete. Notice the animal words cluster on one side and the vehicle words on the other:

import numpy as np
import matplotlib.pyplot as plt

# Real embeddings have hundreds of dimensions; this is just 2D to visualize.
word_embeddings = {
"cat": [0.8, 0.6],
"kitten": [0.75, 0.65],
"dog": [0.7, 0.3],
"puppy": [0.65, 0.35],
"car": [-0.5, 0.2],
"truck": [-0.45, 0.15],
}

cat/kitten sit together, car/truck sit together, and the two groups are far apart β€” that spatial closeness is semantic similarity.

Measuring similarity (cosine)​

To compare two vectors we use cosine similarity β€” it measures the angle between them, ignoring length:

  • close to 1 β†’ very similar
  • close to 0 β†’ unrelated
  • close to βˆ’1 β†’ opposite meaning
def cosine_similarity(vec1, vec2):
dot_product = np.dot(vec1, vec2)
norm_a = np.linalg.norm(vec1)
norm_b = np.linalg.norm(vec2)
return dot_product / (norm_a * norm_b)

cat_vector = [0.8, 0.6, 0.3]
kitten_vector = [0.75, 0.65, 0.35]
car_vector = [-0.5, 0.2, 0.1]

cosine_similarity(cat_vector, kitten_vector) # β‰ˆ 0.99 β†’ very similar
cosine_similarity(cat_vector, car_vector) # β‰ˆ 0.30 β†’ unrelated

The big gap between the two scores is exactly what retrieval relies on to rank chunks.

Your first embeddings β€” HuggingFace​

HuggingFaceEmbeddings runs an open model locally β€” no API key needed. The classic starter model is all-MiniLM-L6-v2, which outputs a 384-dimension vector.

from langchain_huggingface import HuggingFaceEmbeddings

# Local, free, no API key
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)

text = "Hello, I am learning about embeddings!"
embedding = embeddings.embed_query(text)

print(len(embedding)) # 384 β†’ the model's fixed vector size

Embedding a query vs a batch of documents​

Two methods, and the difference matters:

  • embed_query(text) β€” one string β†’ one vector. Use for the user's question.
  • embed_documents([...]) β€” a list of strings β†’ a list of vectors. Use to embed all your chunks at once (much faster than looping one at a time).
sentences = [
"The cat sat on the mat",
"The dog played in the yard",
"I love programming in Python",
"Python is my favorite programming language",
]

doc_vectors = embeddings.embed_documents(sentences) # one vector per sentence
print(len(doc_vectors), len(doc_vectors[0])) # 4 vectors, each 384-dim

OpenAI embeddings (the API alternative)​

When you want a hosted, higher-quality model instead of a local one, swap in OpenAIEmbeddings β€” same embed_query / embed_documents interface, but it calls the API (needs a key).

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 1536-dim
vector = embeddings.embed_query("Hello, embeddings!")

The interface is identical, so you can switch providers without rewriting your pipeline β€” just don't mix models between indexing and querying.

Choosing an embedding model​

Different models trade size (quality/storage) against speed. Common open choices:

ModelDimBest for
all-MiniLM-L6-v2384fast, general purpose, real-time
all-MiniLM-L12-v2384slightly better, a bit slower
all-mpnet-base-v2768best quality, slower
multi-qa-MiniLM-L6-cos-v1384Q&A / semantic search
paraphrase-multilingual-MiniLM-L12-v238450+ languages

Start with all-MiniLM-L6-v2; move up to mpnet only if your eval set shows it's worth the extra cost and latency.

Cheat sheet​

TaskCode
Local model (no key)HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
Hosted modelOpenAIEmbeddings(model="text-embedding-3-small")
Embed one queryembeddings.embed_query(text)
Embed many chunksembeddings.embed_documents([...])
Compare two vectorscosine_similarity(a, b) β†’ 1 = same, 0 = unrelated
MiniLM size384 dimensions Β· OpenAI small = 1536
⚠ Common mistakes
  • Embedding queries and documents with different models β†’ incomparable vectors.
  • Re-embedding only new docs after switching models β€” you must re-embed everything.
  • Confusing embed_query (one string) with embed_documents (a list); using the wrong one breaks shapes or wastes calls.
  • Comparing raw distances without knowing the metric (cosine: higher = closer; L2: lower = closer).

Embeddings = meaning as a vector; cosine similarity ranks them. Use embed_query for the question and embed_documents for your chunks, with the same model on both sides. Start with all-MiniLM-L6-v2 (384-dim, free, local).

Quick self-check

What does an embedding represent?

The meaning of a piece of text as a fixed-length vector β€” similar meanings land close together.

embed_query vs embed_documents?

embed_query embeds one string (the question); embed_documents embeds a list (your chunks) in one batch.

How many dimensions does all-MiniLM-L6-v2 output?

384. (OpenAI text-embedding-3-small = 1536.)

What does cosine similarity of ~0.9 vs ~0.1 mean?

~0.9 = very similar meaning; ~0.1 = basically unrelated. That gap is what retrieval uses to rank chunks.

Related: Cosine similarity (Glossary) Β· Vector Stores β†’

Next: Vector Stores & Vector Databases β€” coming soon (studying next).