Search by meaning
Keyword search misses "dumplings" when you typed "momo". An embedding turns text into a vector so that similar meanings point in similar directions. Compare directions with cosine similarity and you get semantic search — the heart of RAG, recommendations and de-duplication.
Vectors and angles
// A toy embedding: one dimension per concept. Real models learn hundreds of dimensions.
const CONCEPTS = {
food: ["momo", "dal", "bhat", "rice", "tea", "chiya", "eat", "restaurant", "spicy", "dumplings"],
travel: ["bus", "flight", "trek", "trip", "pokhara", "hotel", "travel", "visa"],
code: ["javascript", "bug", "code", "function", "deploy", "server", "error"],
weather: ["rain", "monsoon", "sunny", "cold", "weather", "snow"],
money: ["price", "cost", "rupees", "pay", "cheap", "expensive", "budget"],
};
function embed(text) {
const words = text.toLowerCase().match(/[a-z]+/g) ?? [];
return Object.values(CONCEPTS).map((list) => words.filter((w) => list.includes(w)).length);
}function cosine(a, b) {
let dot = 0;
let na = 0;
let nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return na === 0 || nb === 0 ? 0 : dot / Math.sqrt(na * nb); // the angle, not the length
}
function createIndex() {
const items = [];
return {
add(id, text) {
items.push({ id, text, vector: embed(text) }); // embed once, when indexing
},
search(query, k = 3) {
const q = embed(query);
return items
.map(({ id, text, vector }) => ({ id, text, score: cosine(q, vector) }))
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score) // stable: ties keep insertion order
.slice(0, k);
},
};
}