I asked one question, "How do I stop one user from sending too many requests?", and scored one short text about against it. Same model, same two texts. One score came out as 0.741. The other came out as 521.380.

Neither number is wrong. They come from two different ways of comparing , used on vectors of two different lengths. Both words, vector and length, are explained on the next slide. This lesson is about which way to compare, and the one check that tells you whether the choice matters.



Vector. An is a list of numbers. It helps to picture it as an arrow from a starting point, pointing somewhere in a space with many directions.
Length. How long that arrow is. You get it by squaring every number, adding them up and taking the square root.
Dot product. Multiply the two lists number by number, then add everything up.
Cosine. The dot product divided by both lengths. It works out to the cosine of the angle between the two arrows: 1 when they point the same way, 0 when they are at a right angle. You met it in lesson 3 of this chapter as .
Distance. How far apart the tips of the two arrows are.
Normalised. A vector that has been stretched or shrunk to length exactly 1, without changing its direction. To normalise, divide every number in the vector by its length. A dot product on vectors that were not normalised first is called a raw dot product.
Endpoint. An address on a server that you send a request to. Ollama has two for embeddings: /api/embed, which the earlier lessons used, and an older one, /api/embeddings.
This is the file that printed those two scores, open in my VS Code. It asks for the same three texts from both endpoints and prints the length of each vector and all three scores.

To run it: install Ollama from ollama.com, open it and leave it running. Run ollama pull bge-m3 in a terminal (about 1.2 GB), save the file below as three_ways.py, and run python3 three_ways.py (on Windows: python three_ways.py). You need only Python 3. The "(venv)" in my screenshot is my own setup. If you see "Connection refused", open the Ollama app first.
# Cosine, dot product and distance on the same vectors, from Ollama's two embedding endpoints.
# Needs Ollama (ollama.com) running, and: ollama pull bge-m3
import json, math, urllib.request
def call(path, body):
req = urllib.request.Request("http://localhost:11434/api/" + path, data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req).read())
def new_endpoint(text): # /api/embed
return call("embed", {"model": "bge-m3", "input": [text]})["embeddings"][0]
def old_endpoint(text): # /api/embeddings, the older one
return call("embeddings", {"model": "bge-m3", "prompt": text})["embedding"]
def length(v):
return math.sqrt(sum(x * x for x in v))
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def cosine(a, b):
return dot(a, b) / (length(a) * length(b))
def distance(a, b):
return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))
question = "How do I stop one user from sending too many requests?"
texts = ["Rate limiting: cap how many requests each user may send.",
"Caching: keep a copy of data close to where it is used."]
for name, embed in [("new /api/embed", new_endpoint), ("old /api/embeddings", old_endpoint)]:
q = embed(question)
print(f"{name}: question vector length {length(q):.2f}")
for t in texts:
d = embed(t)
print(f" {t.split(':')[0]:<13} length {length(d):5.2f} cosine {cosine(q, d):.3f}"
f" dot {dot(q, d):7.3f} distance {distance(q, d):6.3f}")
Picture the question and a document as two arrows starting from the same point.


In an , the meaning of a text lives in the direction of its arrow. That is why cosine is the usual default. It is the only one of the three that ignores length.
Now suppose every vector has been normalised to length 1. Then:

So a higher cosine always means a higher dot product and a smaller distance. On length-1 vectors, all three measures put the results in exactly the same order. So it does not matter which one you pick: you get the same results.
The earlier lessons in this chapter used /api/embed. With Ollama 0.32.14, it returned vectors of length 1 for all 878 lesson texts used below, with both models, nomic-embed-text and bge-m3, to six decimal places. The older endpoint, /api/embeddings, returned the same directions at other lengths.

I measured every one of the 878 lesson vectors used in the test below.

Two things stand out. The lengths are not 1. And they are not all the same, so some texts get longer arrows than others. The lab also checked that, for the first 50 texts of each model, the old and new vectors point in the same direction: their cosine was 1.0000 every time.
Lengths that differ by up to about 16 percent (19.0 to 22.1 for nomic) sound harmless. So I tested it on a real search.

Here is the lab's report, from the terminal.


Each model ran 60 searches, and each search returns 5 lessons, so there are 300 places in all. Compared with cosine:

Ignoring order, the dot product gave the same five lessons as cosine in only 2 of nomic's 60 searches, and in 18 of bge-m3's.

The number of right answers barely moved: 9, 8 and 6 for nomic, and 27, 25 and 27 for bge-m3. The lab did not grade the lessons that were swapped in, so I cannot tell you how much better or worse they were. What it does show is that a raw dot product on these vectors did not break search. It changed what the user sees, in most searches, for a reason that has nothing to do with meaning.
Here is one bge-m3 search where the right lesson dropped out.

The question was "Most of my database traffic is reads. How do I spread that load?" The right lesson is Read Replicas.
The dot product is the cosine times both lengths. Read Replicas scored 428.9 and Distributed Transactions 436.9, so the longer vector wins. Read Replicas fell out of the top 5, and a lesson about something else took its place.

Sometimes the opposite happened. On the same question with nomic, the dot product found Read Replicas in its top 5 and cosine did not. Here, length says nothing about meaning, so it moves results up or down by luck. That is exactly why you do not want it in your ranking.
This box uses three-number vectors, small enough to check by hand. Press Run. Then change STRETCH = 10 to 2 or 100 and run it again.
What you should see: cosine stays at 0.941 on every line. The dot product goes from 24 to 240 when the document is stretched ten times, and distance jumps from 1.73 to 46.31. Stretching changed neither vector's direction, only a length. On the last line both vectors have length 1, and the dot product equals the cosine again.

length function from three_ways.py. A result like 0.9999 counts as 1; the tiny difference is rounding.<-> for distance, <#> for the dot product with a minus sign in front, and <=> for 1 minus the cosine.
Some systems keep the length on purpose, because their model was trained to put meaning in it. There, a dot product is the intended measure. The model's documentation says which measure it expects. The two models here gave no sign of that: the lengths varied by at most about 16 percent, and the old and new endpoints pointed the same way on every text I checked.

Measured: two models served by Ollama 0.32.14, the lengths of their vectors from both endpoints, and how the top 5 changed across 878 short lesson texts, each a title and a one-line description, and 60 searches per model.
Not measured: other tools and libraries, which may or may not normalise for you; long documents; and the settings inside vector databases. The one rule that carries over is the check: print the length before you pick the measure.

three_ways.py and look at the lengths it prints.
5 questions - Score 80% to pass
The same question and text scored cosine 0.741 on both endpoints, but dot product 0.741 on one and 521.380 on the other. What is different between the two endpoints' vectors?
Every vector you have stored has length exactly 1. Which measure puts your search results in a different order from cosine?
In the bge-m3 example, Read Replicas had cosine 0.6190 and length 26.11, while Distributed Transactions had cosine 0.6134 and length 26.85. Why did the dot product rank Distributed Transactions higher?
On the old endpoint's vectors, the right lesson reached the top 5 in 27, 25 and 27 of 60 bge-m3 searches with cosine, dot and distance. What is the fair summary?
You start with a new embedding tool. What is the one check to run before you choose between cosine and a dot product?
Read the output again. Cosine is 0.741 from both endpoints. So for these texts, the two endpoints give vectors that point the same way. The lab later checks this on 50 more texts. What differs is only their length: 1.00 from the new one, 26.60 for the question from the old one.
