ealeng
Menu

Episode 07 of 25 · Embeddings & search

Embeddings and semantic search

How search by meaning works: text becomes a vector, vectors are compared by cosine similarity, and results are ranked. What a similarity score does and doesn't tell you, how to set a cutoff for questions nothing answers, and what a cutoff can't fix.

Lab 03 · Embeddings & Semantic Search7 min read
Video transcript5:59

0:00Cold open

0:01

A customer typed into our help center: can I get my money back if I cancel?

0:07

Our new search answered in a blink. With: what happens to your data if you cancel.

0:13

Not a word about money. And search was confident: a score of zero point six six. That’s higher than most of the right matches I found.

0:23

So how does search by meaning decide what a question means? And how do you catch it when it’s wrong?

0:30Intro

0:32

Hands-on AI engineering: embeddings and search, part one.

0:36

Today: what an embedding is, what a similarity score tells you, and when to trust a match.

0:43Meet Chloe

0:45

I’m Chloe, an AI engineer. Our help center has fifteen questions with answers, about billing, data, the app, and support. Customers should find the right one, whatever words they use.

0:57

The help center and the test questions are made up. Every score here is real, from OpenAI’s small embedding model, text embedding three small. No chat model is involved.

1:1001 · Embeddings

1:11

Search by meaning starts with an embedding. A model turns each sentence into a list of numbers.

1:18

For “how do I reset my password?”, that’s one thousand five hundred and thirty-six numbers.

1:25

No single number means anything you could name. Together, they place the sentence somewhere.

1:30

And sentences that mean similar things land near each other.

1:35

Here are all fifteen help questions, flattened onto a page. The real space has fifteen hundred directions, so this flat picture is rough.

1:45

Still, the data questions sit together at the bottom, and the support questions at the top.

1:5202 · The score

1:54

To compare two sentences, compare their embeddings. The usual score is called cosine similarity.

2:01

Picture each embedding as an arrow. The score says how closely two arrows point the same way.

2:07

One means the same direction. Zero means unrelated.

2:12

“I forgot my password, how do I reset it?” scores zero point eight three against “how do I reset my password?”.

2:19

Close, but not one, even though it means the same thing.

2:24

“What is the refund policy?”: zero point two two. A sentence about AI models: about zero.

2:32

Now the tricky part. “Steps to recover access to my account” is the same need. It scores zero point five eight.

2:41

“How do I update my email address?” is a different question. It scores zero point five five. Almost the same.

2:49

So a score only means something next to other scores. Zero point five isn’t good or bad on its own.

2:5703 · Search

2:58

Search is just that score, fifteen times. Compare the customer’s question with every help question, and sort.

3:06

“Does it work without internet?” The key word in the help question is different: offline.

3:12

It still comes first, at zero point seven two. The next one is down at about zero point three.

3:20

Keyword search can’t connect “internet” with “offline”. Search by meaning matches the idea, not the exact words.

3:2804 · A cutoff

3:30

But search always returns something. A customer asked: how do I cook pasta?

3:35

The top match: “how do I reset my password?”, at zero point two two. Low, but it still comes first.

3:43

So you add a cutoff. Below it, search hands the customer to a person.

3:48

Say we copy one from a tutorial: zero point four. Here it is on seven test questions: five the help center can answer, and two it can’t.

3:58

Pasta and “the capital of France” fall below it. Good.

4:03

But so does “I need to get my money back”. Its top match was the refund policy, the right answer, at zero point three eight.

4:12

Move the cutoff to zero point three. Now all five right answers stay, and both off-topic questions still stop.

4:21

So don’t copy a cutoff from somewhere else. Set it from your own questions: some that should be answered, and some that shouldn’t.

4:28

Seven test questions only give you a first guess.

4:3305 · A confident wrong match

4:34

Now the refund question from the start. Its wrong match scored zero point six six.

4:41

That’s higher than three of the five right answers. So it passes any cutoff that keeps them.

4:47

A cutoff stops weak matches. It can’t stop a confident wrong one.

4:53

Look at its top three: they all mention cancelling.

4:57

The refund policy, the answer the customer wanted, is only fourth, at zero point five two.

5:04

The fix here is context. The customer was on the billing page, so search only the billing questions.

5:11

Now the top match is “how do I cancel my subscription?”, then the refund policy. Both answers talk about money.

5:2006 · What I do now

5:21

So now I read scores next to each other, never against a number from a tutorial.

5:26

I set the cutoff from my own questions, the ones that should get an answer and the ones that shouldn’t. And I filter by context when I have it.

5:35

But I found that wrong match by hand. Next time: a test set that finds them for me, and how to split long documents into pieces worth searching.Chloe: Score, compare, then filter. 🔎

5:46Summary

5:47

Embeddings place meaning. Scores compare. Cutoffs stop weak matches. Filters add context.

5:54

And test it all on your own questions.Chloe: Thanks! 😄

Key takeaways

  • ✓An embedding turns text into a fixed-length vector (1,536 numbers for text-embedding-3-small) so that texts with similar meanings land close together. Search embeds the question and ranks stored texts by cosine similarity.
  • ✓A score says how close two texts are, not whether one answers the other. A same-need rewording scored 0.58 and a different question 0.55: read scores next to each other, never against a fixed number.
  • ✓Search always returns something, so add a cutoff that hands weak matches to a person. Set it from your own answerable and unanswerable questions: 0.40 sent a right answer at 0.38 away; 0.30 kept all five and stopped both off-topic questions.
  • ✓A cutoff only stops weak matches. A wrong match scored 0.66, higher than three of five right ones, so no cutoff that keeps the right ones can stop it.
  • ✓Context narrows the candidates: filtering to billing FAQs removed the wrong data answer, though the refund policy was still second. Filtering changes what can rank; a cutoff only decides whether to serve the top result.

Search by meaning finds text that means the same thing as a question, even when it shares none of its words. It works by turning text into vectors and ranking stored texts by how close their vectors are to the question's. The ranking is useful. The score that comes with it is easy to misread: it says how close two texts are, not whether one answers the other. This chapter covers how embedding search works, what a similarity score tells you, how to set a cutoff for questions nothing answers, and what a cutoff can't fix.

The examples are from ai-eng-labs Lab 03, 01_embeddings_basics.py and 04_faq_retrieval.py: a made-up help center of 15 FAQs in four categories (billing, data, technical, support), and made-up customer questions. Every score is a real result from OpenAI's text-embedding-3-small; no chat model is involved.

Embeddings

An embedding is a fixed-length list of numbers, a vector, that represents the meaning of a text. text-embedding-3-small turns any text into 1,536 numbers. No single number means anything you could name. Together they place the text in a space where texts with similar meanings land near each other: "I can't log in" and "my account is inaccessible" end up close, with no word in common.

Two practical rules follow:

  • Embed questions and documents with the same model. Vectors from different models live in different spaces and can't be compared.
  • A flat picture is only a sketch. Squashing 1,536 directions onto a page (with PCA or UMAP) shows rough clusters, but distances on the page don't match the real ones.

Similarity scores

To compare two embeddings, the usual measure is cosine similarity: whether two vectors point the same way. 1 means the same direction, 0 means unrelated.

score = dot(a, b) / (norm(a) * norm(b))

OpenAI's vectors already have length 1, so the dot product gives the same number. Euclidean distance moves the opposite way (closer means a smaller distance) and, for vectors of length 1, ranks results in the same order.

Scored against "How do I reset my password?":

Text Relationship Score
I forgot my password, how do I reset it? Same meaning, other words 0.83
Steps to recover access to my account The same need 0.58
How do I update my email address? A different question 0.55
What is the refund policy? A different topic 0.22
The transformer architecture uses self-attention. Unrelated −0.01

Two things to notice. A near-identical rewording scores 0.83, not 1. And the same need (0.58) and a different question (0.55) are almost tied. A score only means something next to other scores, from the same model and collection. 0.5 isn't good or bad on its own.

Search: score, then sort

Search is that score, computed against every stored text, then sorted:

  1. Store: embed every FAQ question and keep the vector with its question and answer.
  2. Compare: embed the customer's question with the same model and score it against every stored vector.
  3. Rank: sort by score. The first result is the closest candidate in this collection, not necessarily a right one.

The lab normalizes the vectors and uses FAISS IndexFlatIP (inner product), which gives cosine rankings. That's exact search: every vector is compared. It's fine up to around 100,000 vectors; beyond that, approximate indexes (HNSW, IVF) skip most comparisons, run much faster, and occasionally miss a match.

The strength of search by meaning: "Does it work without internet?" finds "Does the app work offline?" first at 0.72, with the next result around 0.30. Keyword search can't connect "internet" with "offline".

A cutoff for "nothing fits"

Search always returns something. "How do I cook pasta?" gets the password-reset FAQ first, at 0.22. So add a cutoff: if the top score is below it, hand the question to a person. A cutoff doesn't change the ranking; it only decides whether to serve the first result.

Set it from your own questions, some the help center can answer and some it can't. Move it across the lab's seven:

Try it · Move the handoff lineFAQ lab · seven recorded top scores

Each bar is the best result search found. The vertical line is the cutoff. Move it and watch which useful matches disappear.

Useful matches kept
4/5
Off-topic questions stopped
2/2
Does it work without internet?0.72 · answer
Can I use the API for free?0.66 · answer
Can't log in to my account0.47 · answer
Where can I download my information?0.43 · answer
I need to get my money back0.38 · handoff · miss
How do I cook pasta?0.22 · handoff
What is the capital of France?0.11 · handoff

What the line cannot fix

A separate refund question ranked the wrong data policy first at 0.66. A right refund FAQ in this test scored 0.38. Any cutoff that lets 0.38 through also lets 0.66 through. Inspect the ranked result and use context, not just a score gate.

Cutoff Answerable questions kept (of 5) Off-topic questions stopped (of 2)
0.40 4: "I need to get my money back" found the refund policy at 0.38, and was handed off 2
0.30 5 2
0.20 5 1: pasta (0.22) gets through

0.40 looks like a reasonable number from a tutorial, and it throws away a right answer. 0.30 works on these seven. That's a first guess: a different model, collection or way of asking needs its own calibration, on more questions (Chapter 8).

What a cutoff can't fix

"Can I get my money back if I cancel?" returns "What happens to my data if I cancel?" first, at 0.66: about cancelling, not money. That's higher than three of the five right answers above (0.47, 0.43, 0.38). Any cutoff low enough to keep them keeps this wrong match too. A cutoff stops weak matches. It can't stop a confident wrong one.

The top three all mention cancelling; the refund policy is only fourth, at 0.52. What helps is changing the candidates, not the threshold:

  • Filter by context. The customer was on the billing page, so search only billing FAQs. Now "How do I cancel my subscription?" is first (0.56) and the refund policy second (0.52). The data answer is gone, though the refund policy still isn't first. (The lab filters after searching, post-filtering; a vector database can also pre-filter, searching only the matching items.)
  • Show more than one result, or ask a clarifying question when the top results are close.
  • Test for confident wrong matches on purpose: label which FAQ should answer each question and check the top result, not just its score (Chapter 8).

The checklist

  1. Use the same embedding model for questions and documents, and record it with every result.
  2. Read scores relative to each other, never against a number from somewhere else.
  3. Set the cutoff from your own data: questions that should be answered and questions that shouldn't. Check what it wrongly sends away.
  4. Check the top result itself, not just its score, for confident wrong matches.
  5. Use trustworthy context (page, product area, category) to narrow the candidates, then test the ranking again.
  6. Switch to approximate search only when exact search gets too slow, and measure what it misses.

Practice: rank, then decide

Implement cosine similarity (with a clear error for mismatched vectors), a stable ranking, the answer-or-hand-off decision, and a report that calibrates a cutoff on the seven recorded top scores. The tests include the right refund match at 0.38 and the wrong data match at 0.66.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
search_practice.py40 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.What does text-embedding-3-small return for one sentence?

  2. 2."Steps to recover access to my account" (the same need as a password reset) scored 0.58; "How do I update my email address?" (a different question) scored 0.55. What follows?

  3. 3."How do I cook pasta?" returns the password-reset FAQ at 0.22. Why?

  4. 4.A cutoff of 0.40, copied from a tutorial, was tried on five answerable and two off-topic questions. What went wrong?

  5. 5."Can I get my money back if I cancel?" returned "What happens to my data if I cancel?" at 0.66. Can a cutoff stop it while keeping the right refund match at 0.38?

  6. 6.The lab searches with FAISS IndexFlatIP on normalized vectors. What does that mean?

Check your understanding

Try each question before opening its answer.

What is an embedding, and why does search by meaning work?

A fixed-length vector (here 1,536 numbers) that places a text by its meaning. Texts with similar meanings get nearby vectors, so a question can find an answer that shares none of its words, like "without internet" and "offline".

What does a similarity score tell you?

How closely two vectors point the same way in this model's space. It ranks candidates; it doesn't say the top one is right. A same-need rewording scored 0.58 and a different question 0.55.

Why does search need a cutoff, and how do you set it?

Search always returns something, even for "how do I cook pasta?". Below the cutoff, hand the question to a person. Set it from your own answerable and unanswerable questions, and check which right answers it sends away: 0.40 lost one at 0.38; 0.30 kept all five here.

Why can't a cutoff fix "Can I get my money back if I cancel?"

Its wrong match scored 0.66, higher than three right answers. Any threshold that keeps 0.38 also keeps 0.66. It needs better candidates: context, more results, or a clarifying question.

What did filtering to billing change, and what didn't it?

It removed the data FAQ from the candidates, so cancel subscription (0.56) and the refund policy (0.52) came first and second. The refund policy still wasn't first, so a serving flow might show both or ask.

When would you use approximate search?

When exact search, which compares every vector, gets too slow: around 100,000 vectors and up. Approximate indexes like HNSW or IVF skip most comparisons and occasionally miss a match, so measure recall before and after switching.

Next up: Chapter 8 builds a search test, and shows how test questions written from the docs make search look better than it is.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.