Embeddings
An embedding turns text into a list of numbers that places it in a space where “similar meaning” means “nearby”. Everything about vector search follows from that one idea.
Meaning as a position
An embedding model reads a piece of text and returns a few hundred numbers. Each number is a coordinate, and the whole list is a position in a space the model learned during training. Texts that were used in similar ways end up near each other.
The useful consequence is that “price” and “cost” can be close even though they share no letters. Keyword search cannot do that without a synonym list; an embedding already has one, learned from how people write.
The model learns those positions from co-occurrence: texts used in similar contexts get pushed together. No single coordinate has a name — one dimension is not “price” and another is not “time” — but distance across all of them still tracks meaning. That is why the space works for search even though no human can read a vector.
Cosine similarity, in one line
The standard measure is the cosine of the angle between two vectors: the dot product divided by the product of their lengths. It ignores magnitude, so a long document and a short one about the same thing still score high.
In plain terms, it asks how much two vectors point in the same direction. A score near one means aligned, near zero means unrelated, and near minus one means opposite.
Take two vectors that point mostly along the same axis. Their dot product is large, both lengths are similar, and the cosine lands near 0.95. A vector that points across the axes has a small dot product and scores near 0.1. The lengths cancel, so a long passage and a short query can still score high when they point the same way.
Embedding a query and a document
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector = embeddings.embed_query("How do refunds work?")
print(len(vector)) # 1536 numbers
print(vector[:3]) # [-0.0123, 0.0451, ...]
# Documents are embedded with the same model, never a different one.
doc_vectors = embeddings.embed_documents([chunk.page_content for chunk in chunks])Cosine similarity, forty lines
Compute the similarity of each sentence to the query, then add a sentence about payments and see where it lands.
Two models, one space
A query and its documents must be embedded by the same model. Two models each produce a consistent space, but the spaces are unrelated: a vector from one means nothing when compared with a vector from the other, and the similarity scores will look random.
This is the most common cause of “the index returns nonsense” after a model upgrade. Changing the embedding model means re-embedding the entire corpus; there is no partial migration.
The symptom is specific: every query returns results that look plausible but are in the wrong order, and the scores cluster in a narrow band. Nothing errors. Re-embedding the corpus is the only fix, which is why the model name belongs in the index metadata and in the deployment checklist.
Store the model name and version beside every vector. Without it, a re-index looks like a mysterious quality drop months later.
One vector per chunk, for better or worse
An embedding compresses a whole chunk into one vector, so a chunk with two subjects gets an average of both. A paragraph that spends three sentences on pricing and one on refunds will be closer to a pricing question than to a refund question, even though it contains the refund sentence. If a passage is important enough to retrieve, give it a chunk where it is the subject.
Prepending the section heading helps for the same reason: it shifts the average toward the topic the author named. The vector is not a summary of the text; it is a position for the text as a whole, and every extra topic pulls that position away from each one.
What embeddings cannot do
They do not understand numbers or negation particularly well. “Under 10” and “over 10” can sit close together, because they are about the same topic. Exact identifiers, codes and dates are also weak: an invoice number is near other invoice numbers.
Domain vocabulary is the quiet version of this. An internal product name that appears in ten documents has no useful neighbours, because the model never saw it during training. Hybrid search covers it, and so does a short glossary appended to the chunk metadata.
That is why production retrieval is usually hybrid — embeddings for meaning, keyword search for exact terms. The next chapters build both.
Why synonyms need vectors
Run both searches for the query “cost”. Keyword search finds nothing; the concept vectors find the pricing passage. Then add a passage that shares no words but means the same thing.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is an embedding?
