Using Embeddings in Practice: Generating Them and Computing Cosine Similarity
~13 min read
Turning the theory into code: call an embedding API or a local Hugging Face model to get vectors, then use cosine similarity — the standard metric — to measure how close two pieces of text are in meaning.
With the theory from the previous three subtopics in hand, actually USING embeddings in a real project comes down to two steps: get a vector for your text, and compare vectors to each other.
Getting an embedding usually means one of two paths. The hosted-API path calls a provider like OpenAI's embeddings endpoint: you send text, you get back a vector, and the provider handles running the model — no GPU or local setup needed, at the cost of a per-call fee and a network round trip. The local/open-source path uses a library like Hugging Face's sentence-transformers: you download a pretrained model once, then run it on your own machine (or server) with no per-call cost and no data leaving your infrastructure, at the cost of needing to host and run the model yourself. Both paths return the same kind of thing — a fixed-length list of numbers, exactly as described in the first subtopic — so code that USES embeddings (like the similarity comparison below) works the same regardless of which path produced them.
Once you have two embeddings, cosine similarity is the standard way to compare them (the very next unit, vector-search-basics, covers WHY this particular metric is the usual default over plain distance). Cosine similarity measures the ANGLE between two vectors rather than their raw distance — it asks 'do these two vectors point in roughly the same direction,' regardless of how long each vector happens to be. The result ranges from -1 (pointing in exactly opposite directions) through 0 (perpendicular, i.e. unrelated) to 1 (pointing in exactly the same direction, i.e. as similar as possible). In practice, for text embeddings from a well-trained model, similar meanings reliably produce high cosine similarity (often 0.7-0.95+ for genuinely related text), while unrelated text lands much lower.
Putting it together is the whole recipe behind semantic search: embed a collection of documents once (and store the vectors), embed an incoming query the same way, compute cosine similarity between the query's vector and every stored document vector, and return whichever documents scored highest. At small scale, comparing against every stored vector directly (like the code below does) is perfectly fine — it's only at large scale, with millions of vectors, that you need the smarter approximate-search techniques the next unit covers.
💻 Code example
import math
def cosine_similarity(a: list[float], b: list[float]) -> float:
"""Angle-based similarity: 1 = same direction (very similar),
0 = unrelated, -1 = opposite direction."""
dot = sum(ai * bi for ai, bi in zip(a, b))
norm_a = math.sqrt(sum(ai ** 2 for ai in a))
norm_b = math.sqrt(sum(bi ** 2 for bi in b))
return dot / (norm_a * norm_b)
# --- Path 1: hosted API (illustrative -- needs an API key to actually call) ---
# from openai import OpenAI
# client = OpenAI()
# def embed(text):
# resp = client.embeddings.create(model="text-embedding-3-small", input=text)
# return resp.data[0].embedding
# --- Path 2: local open-source model ---
# from sentence_transformers import SentenceTransformer
# model = SentenceTransformer("all-MiniLM-L6-v2")
# def embed(text):
# return model.encode(text).tolist()
# Toy stand-in embeddings so this example runs without any API key
# or downloaded model -- in real code, `embed(text)` replaces these.
toy_vectors = {
"I love pizza": [0.9, 0.2, 0.1],
"Pizza is my favorite food": [0.85, 0.25, 0.05],
"The stock market crashed": [0.1, 0.9, 0.4],
}
query = "I love pizza"
for doc, vec in toy_vectors.items():
if doc == query:
continue
sim = cosine_similarity(toy_vectors[query], vec)
print(f"similarity({query!r}, {doc!r}) = {sim:.3f}")
# The pizza sentences score much higher similarity than the unrelated one
💬 Deep Dive with AI
Key points
- •Getting an embedding means calling a hosted API (OpenAI, no local setup, per-call cost) or running a local model (Hugging Face sentence-transformers, no per-call cost, you host it)
- •Both paths return the same shape of thing — a fixed-length vector — so downstream comparison code works the same regardless of source
- •Cosine similarity measures the ANGLE between two vectors, ignoring their length — ranges from -1 (opposite) to 0 (unrelated) to 1 (identical direction)
- •For well-trained text embeddings, similar meanings reliably score high cosine similarity (often 0.7+), unrelated text scores much lower
- •The full semantic-search recipe: embed documents once, embed the query the same way, rank documents by cosine similarity to the query — brute-force fine at small scale