Module 02 · Lesson 2 of 12Free
Embeddings: turning words into numbers with meaning
- Level
- Beginner
- Topic
- How LLMs work: what builders need to know
- Reading time
- 13 min
When you finish this page you can
- Explain what an embedding is and why texts with similar meaning get similar lists of numbers.
- Work out the cosine similarity of two short vectors by hand and say what the score means.
- Explain where embeddings come from, an embedding model with its own API, and what builders use them for.
- Choose between keyword search, a language model and embeddings for finding similar tickets, and say what would change the choice.
Builds on 1 lesson
Start with Module 01, lesson 315 minVideo lesson · coming soon
Two tickets with no words in common
Deskora is a help desk app. A shop’s support team uses it to answer customer emails from one shared inbox. Each customer problem becomes a ticket: one customer’s whole conversation with the support team.
Priya is a support agent at a kitchenware shop that uses Deskora. A new ticket arrives from Daniela: “My parcel never came.” The shop has 40,000 past tickets, and dozens of them solved this exact problem. Priya would like to see how.
Tomás, an engineer at Deskora, is building a “Similar past tickets” panel for her. His first version uses keyword search. It ranks past tickets by the words they share with the new one, ignoring small words such as “my” and “I”. It looks words up in an , so it is fast.
Diagram not drawn yet: embeddings-keyword-search-miss.svg
On the left, the new ticket: “My parcel never came”, with its words parcel, never and came highlighted. On the right, three past tickets ranked by shared words. First: “My parcel came damaged”, 2 shared words (parcel, came), marked wrong problem. Second: “I never got my refund”, 1 shared word (never), marked wrong problem. Last: “Package not delivered”, 0 shared words, marked the ticket Priya needs.
The ticket Priya needs says “Package not delivered.” It means the same thing and shares no words.
Tomás adds a synonym list that treats parcel and package as the same word. Does “Package not delivered” now rank first?Show answer
This lesson answers one question: how can software tell that two pieces of text mean the same thing when they share no words?
The 40,000 past tickets, and the other numbers in this lesson except Deskora’s 1,500 shops and 120,000 tickets a day, are estimates for this lesson’s scenario.
An embedding is a list of numbers
An is a list of numbers that stands for a piece of text. A model produces it. It places the text so that texts with similar meaning get similar numbers. A list of numbers like this is called a vector.
Here is the start of a real one, from Anthropic’s embeddings guide. Voyage AI’s voyage-4 model turned the text “Sample text 1” into 1,024 numbers 1:
{
"object": "embedding",
"embedding": [-0.013131560757756233, 0.019828535616397858 /* ... 1,022 more */],
"index": 0
}No single number has a meaning you can name. The meaning is in how the whole list compares with other lists.
To see how this works, take a hand-made version with two numbers per ticket. Pretend the first number means “about a delivery that didn’t arrive” and the second means “about a damaged item”:
| Ticket | Hand-made vector |
|---|---|
| New: “My parcel never came” | [0.9, 0.1] |
| Past: “Package not delivered” | [0.8, 0.2] |
| Past: “My parcel came damaged” | [0.4, 0.9] |
Diagram not drawn yet: embeddings-ticket-vectors-2d.svg
A graph with two axes: the horizontal axis is “delivery didn’t arrive”, the vertical axis is “item damaged”, both from 0 to 1. Three arrows start at the origin. “My parcel never came” points to (0.9, 0.1). “Package not delivered” points to (0.8, 0.2), 7.7 degrees away from it. “My parcel came damaged” points to (0.4, 0.9), 59.7 degrees away from the new ticket.
The two missing-parcel tickets point almost the same way, with no shared words. The damaged-parcel ticket, keyword search’s top match, points far away.
To rank 40,000 tickets, Tomás needs “almost the same way” as a single number.
Closeness is one number: cosine similarity
measures how closely two vectors point the same way. It ignores how long they are:
The score runs from 1 (same direction) through 0 (unrelated) to −1 (opposite).
Dot product, new and “Package not delivered”
0.9 × 0.8 + 0.1 × 0.2 = 0.72 + 0.02 = 0.74
Multiply the first numbers, multiply the second numbers, add.
Lengths
√(0.9² + 0.1²) = √0.82 = 0.906 √(0.8² + 0.2²) = √0.68 = 0.825
The length of each arrow.
Cosine
0.74 ÷ (0.906 × 0.825) = 0.74 ÷ 0.747 = 0.99
Almost the same direction.
New and “My parcel came damaged”
(0.9 × 0.4 + 0.1 × 0.9) ÷ (0.906 × 0.985) = 0.45 ÷ 0.892 = 0.50
About 60 degrees apart: related, but a different problem.
The ticket Priya needs now ranks first, with 0.99 against 0.50.
Voyage AI returns vectors already scaled to length 1 1. So does OpenAI 2. For such vectors the plain dot product gives the same score, faster. Similarity measures compares cosine, dot product and distance in full.
Here is the same arithmetic in plain Python, with a third ticket added. Run it with any Python 3.9 or newer; it needs no libraries and no API key.
import math
def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
length_a = math.sqrt(sum(x * x for x in a))
length_b = math.sqrt(sum(y * y for y in b))
return dot / (length_a * length_b)
# Hand-made vectors: (delivery didn't arrive, item damaged, account problem)
new_ticket = [0.9, 0.1, 0.0] # "My parcel never came"
past_tickets = {
"Package not delivered": [0.8, 0.2, 0.0],
"My parcel came damaged": [0.4, 0.9, 0.0],
"How do I reset my password?": [0.0, 0.1, 0.9],
}
ranked = sorted(
past_tickets.items(),
key=lambda item: cosine(new_ticket, item[1]),
reverse=True,
)
for text, vector in ranked:
print(f"{cosine(new_ticket, vector):.2f} {text}")It prints 0.99, 0.50 and 0.01, in that order.
Two numbers can’t hold every meaning
Look at the password ticket in the code. With only the two numbers, “delivery” and “damage”, it has nowhere to go: it is about neither. Tomás had to add a third number, “account problem”, to place it. Its score of 0.01 against Daniela’s ticket says the two are unrelated.
Real tickets differ in hundreds of ways: refunds, sizes, discount codes, tone. Each needs room in the list, so real embeddings are long. As of October 2026, voyage-4 returns 1,024 numbers by default 1. OpenAI’s text-embedding-3-small returns 1,536 and text-embedding-3-large returns 3,072 2. The length of the list is the embedding’s dimension.
In a real embedding nobody chooses what each number means; the model learns where to place texts during training. Cosine similarity works the same way on 1,024 numbers as on two. But where do the numbers come from?
An embedding model makes the numbers
An embedding model is a model that reads text and returns a vector. It does not write text. You call it through its own API, separate from a text-generating API such as the Claude Messages API covered in Calling a model API. The request carries a list of texts and a model name; the response carries one vector per text and a count of the tokens it read 1.
Anthropic doesn’t offer its own embedding model. Its documentation points to Voyage AI as one provider 1. OpenAI also offers embedding models 2. Embedding models covers how to choose one.
Diagram not drawn yet: embeddings-similar-tickets-flow.svg
Left to right. Top row, dashed and marked “once per ticket, in the background”: 1, a closed past ticket goes from the Deskora app server to the embedding model; 2, a vector of 1,024 numbers comes back; 3, the app server saves it next to the ticket in the ticket database. Bottom row, when Daniela’s ticket arrives: 4, the app server sends “My parcel never came” to the embedding model; 5, one vector comes back; 6, the app server compares it with the shop’s 40,000 saved vectors using cosine similarity; 7, the top three past tickets appear in Priya’s “Similar past tickets” panel.
For the panel, the work splits in two:
- Once per past ticket, in the background: send its text to the embedding model and save the vector next to the ticket.
- When a new ticket arrives: embed it with the same model, compare its vector with the shop’s saved vectors, and show the top three.
The same vectors serve other features. Builders use embeddings for search, grouping similar items, recommendations, spotting unusual items and sorting items into categories 2. For Deskora, that could mean grouping this week’s tickets to find that 300 are about one courier, or suggesting help-centre articles to Priya. Finding the right documents to hand a model before it answers is RAG, built on the same search.
Three ways to find similar tickets
A language model also understands that “never came” means “not delivered”. Why not ask Claude?
The unit of cost is one completed task: one “Similar past tickets” panel. Asking Claude means one call per past ticket, 40,000 calls a panel. As of October 2026, Claude Haiku 5.5 costs $0.10 per million input tokens and $0.50 per million output tokens 4. It thinks by default, and thinking is billed as output.
The formula
cost = input tokens × input price + (thinking + text tokens) × output price, per call, × calls per panel
Prices are per million tokens, so divide by 1,000,000.
One comparison call
300 × $0.10 ÷ 1,000,000 + (100 + 5) × $0.50 ÷ 1,000,000 = $0.00003 + $0.0000525 = $0.0000825
Assumed: an instruction plus the first message of both tickets, about 300 input tokens; 100 thinking tokens; a 5-token answer. The response’s usage.output_tokens_details.thinking_tokens reports the real thinking count.
One panel
40,000 calls × $0.0000825 = $3.30
First messages only. Whole threads would cost several times more.
One day, Priya’s shop
80 new tickets × $3.30 = $132
80 is Deskora’s average: 120,000 tickets a day across 1,500 shops. Retries are left out.
Twenty thousand calls are also far too slow for a panel Priya sees on opening the ticket. Research on an older model found the same pattern. Finding the most similar pair among 10,000 sentences by comparing every pair took about 65 hours; with embeddings it took about 5 seconds 3.
How should Deskora find past tickets that are similar to a new one?
Keyword search over an inverted index.
Fast and nearly free, and exact on order numbers and error codes. But it ranked “My parcel came damaged” first and missed “Package not delivered” entirely.
Ask Claude to compare the new ticket with every past ticket.
Understands meaning well, but needs 40,000 calls per panel: about $3.30 each, and far too slow to show when Priya opens the ticket.
Embed every ticket and rank by cosine similarity.
One embedding call per ticket, each past ticket embedded once. The comparison is arithmetic on Deskora’s server, and “Package not delivered” ranks first.
Why this choice
Embeddings move the expensive part, reading the text, to once per ticket. After that, each search is cheap arithmetic. A language model reads every pair again for every search.
When the choice changes. If agents mostly search by order number or error code, keyword search is the better tool. Many systems run both and merge the results, covered in Hybrid search. A language model also becomes affordable for re-checking a short list: the top 10 from embeddings, not all 40,000.
Where embeddings fall short
- Exact identifiers. “Order 48213” and “order 48231” differ by two digits. Their vectors can be nearly the same, and keyword search tells them apart better.
- Small words that flip meaning. “Parcel delivered” and “parcel not delivered” share almost all their meaning, and can score close together. Priya might see a ticket about the opposite problem.
- Scale. At the default 32-bit size, 1,024 numbers take 4,096 bytes 1. Deskora’s 120,000 tickets a day add about 490 MB of vectors a day. Comparing one new ticket with all of them, one by one, gets slow. Vector databases store vectors and search them without checking every one.
How a model learns where to place texts
An embedding model is a neural network trained on very large amounts of text. Training adjusts it until texts used in similar ways, or pairs known to match, such as a question and its answer, get vectors that point the same way. Early word embeddings learned one vector per word from the words around it 5. Today’s models embed whole sentences and passages at once.
Where this shows up
- Design: a support assistant over company docs embeds help articles so it can find the ones that answer a customer’s question.
- Case: Intercom’s custom reranker for support RAG re-orders the passages a first search returns, a step that only helps once a search like this one exists.
- Design: an AI customer support platform finds past tickets and help articles by meaning, next to summaries and reply drafts.
Inside the language models later lessons open up, each token also starts as a list of numbers. Its numbers must then change with the words around it, so that “charge” in “charge my card” and “charge my phone” mean different things. How the model does that is the subject of Attention.
Common mistakes
Each embedding model places texts in its own way, so a vector from one model means nothing next to a vector from another. Embed the new ticket with the same model as the saved ones. When Deskora switches models, it must embed every past ticket again.
A score of 0.8 can mean “very similar” for one model and “loosely related” for another. Choose any cut-off, such as “only show tickets above this score”, by testing it on your own tickets with the model you use.
Check your understanding
Tomás doubles every number in the vector for “Package not delivered”, from [0.8, 0.2] to [1.6, 0.4]. What happens to its cosine similarity with Daniela’s ticket?Show answer
Deskora upgrades to a newer embedding model and embeds new tickets with it. The saved vectors stay from the old model. What does Priya see?Show answer
A customer writes “Where is order 58214?” and Priya wants the earlier tickets about that exact order. Which search finds them best?Show answer
Why does an embedding of 1,024 numbers find similar tickets better than Tomás’s hand-made three numbers?Show answer
Summary
- Software tells that two texts mean the same thing by turning each into an embedding, a list of numbers placed so that similar meanings point the same way, and comparing the lists.
- Cosine similarity scores how closely two vectors point the same way: 1 for the same direction, 0 for unrelated. It ignores length.
- Real embeddings have about 1,000 to 3,000 numbers, as of October 2026, and no single number has a name.
- An embedding model, called through its own API, makes the vectors. Anthropic doesn’t offer one; Voyage AI and OpenAI do.
- Embed each item once, embed each query with the same model, and compare: cheap and fast, but weak on exact identifiers and on small words like “not”.