Embeddings & Vector Search· Lesson 3 of 7
Semantic Search — Searching by Meaning, Not Words
In one line · Semantic search embeds the user's query into a vector and finds the nearest vectors in your data — matching meaning even when the words differ.
Keyword search fails the moment the user phrases things differently than you did; semantic search fixes that and is half of any serious RAG system.
Instead of matching characters like Ctrl+F, you match ideas: the system understands that 'busted ride' and 'damaged vehicle' point at the same thing.
Like asking a librarian who gets what you mean instead of flipping through a word index: they hand you the right book even when you described the topic in your own words, not its title.
A user types 'can't get into my account' in a help center; semantic search surfaces the 'Troubleshooting sign-in problems' article even though not one word matched literally.
Semantic search has two phases. Indexing (once): split your documents into chunks, embed each one, and store the vectors in a vector database. Query time (every request): embed the user's question with the exact same model, fetch the k nearest vectors by cosine, and return their texts. In a RAG system those texts go into the prompt and the model answers from them. Many strong systems add a hybrid step: merging semantic results with keyword-search results to catch both kinds of matches.
// Minimal semantic-search flow (Workers AI + Vectorize)
// 1) Indexing — once
for (const chunk of chunks) {
const { data } = await env.AI.run("@cf/baai/bge-m3", { text: [chunk.text] });
await env.VECTORIZE.insert([{ id: chunk.id, values: data[0], metadata: { text: chunk.text } }]);
}
// 2) Query time — same model!
const { data } = await env.AI.run("@cf/baai/bge-m3", { text: [userQuery] });
const hits = await env.VECTORIZE.query(data[0], { topK: 5, returnMetadata: "all" });
// hits.matches → the 5 nearest chunks, ready for the prompt🧪 Try it: take 10 short documents (manual paragraphs or help articles), embed and store each one, then run 3 queries phrased completely differently from the documents' wording. Inspect the top-3 for each: when a wrong result ranks high, ask yourself — is the problem the chunking, the model, or a query that really needs keyword matching?
The biggest mistake: querying with a different model (or settings) than you indexed with — results silently degrade to garbage with no error. Second: going semantic-only and dropping keyword search — error codes, product names, and numbers slip through. Third: always returning a fixed top-k with no similarity floor, so when nothing truly matches you still hand back the nearest irrelevant thing.
Use it when users ask in their own free words: help centers, internal-docs search, context retrieval for RAG, or any case where the user's phrasing differs from the documents' wording.
If queries are all exact identifiers (SKUs, error codes, contract numbers), keyword search is more precise and cheaper. And with only 50 documents, you might just put them all in the context directly — no search needed.