Semantic Search with Embeddings
Embeddings power meaning-based search; combine retrieved results with Claude for answers.
Semantic search finds results by meaning, not exact keywords. It relies on embeddings — numeric vectors that represent the meaning of text. Similar meanings produce nearby vectors.
The pattern (RAG)
- Turn your documents into embeddings and store them in a vector database.
- When a user asks a question, embed the question and find the most similar documents.
- Pass those documents to Claude as context and ask it to answer using them.
This is called Retrieval-Augmented Generation: retrieve the relevant text, then let Claude generate an answer grounded in it.
Where Claude fits
Claude does the final step — reading the retrieved documents and writing a clear, grounded answer with far less risk of making things up.
Example
// After retrieving the top matching documents from your vector DB:
async function answerFromDocs(question, docs) {
const context = docs.map((d, i) => `[Doc ${i + 1}]\n${d.text}`).join("\n\n");
const res = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 512,
system: "Answer using ONLY the provided documents. " +
"If the answer is not in them, say you don't know.",
messages: [{
role: "user",
content: `Documents:\n${context}\n\nQuestion: ${question}`,
}],
});
return res.content.find((b) => b.type === "text")?.text ?? "";
}When to use it
- Let users search your help centre by meaning rather than keywords so 'I can not log in' finds 'Account access issues'.
- Build a smart product finder where a natural-language description retrieves the closest matching items from your catalogue.
- Embed support tickets and use semantic similarity to surface related resolved tickets for faster agent responses.
More examples
Generate an embedding for a query
Creates a vector embedding from a text string — the first step in any semantic search pipeline.
// Using OpenAI embeddings as a common example alongside Claude for answers
async function embed(text) {
const res = await openai.embeddings.create({
model: "text-embedding-3-small",
input: text,
});
return res.data[0].embedding;
}Cosine similarity to rank documents
Computes cosine similarity between the query vector and stored document vectors to rank by semantic closeness.
function cosineSim(a, b) {
const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
const magA = Math.sqrt(a.reduce((s, x) => s + x * x, 0));
const magB = Math.sqrt(b.reduce((s, x) => s + x * x, 0));
return dot / (magA * magB);
}
function topK(queryVec, docs, k = 3) {
return docs
.map(doc => ({ ...doc, score: cosineSim(queryVec, doc.embedding) }))
.sort((a, b) => b.score - a.score)
.slice(0, k);
}Pass retrieved docs to Claude for an answer
Retrieves the top-3 semantically similar documents and feeds them to Claude as context for a grounded answer.
async function semanticSearch(query, docs) {
const qVec = await embed(query);
const top = topK(qVec, docs, 3);
const context = top.map((d, i) => `[${i+1}] ${d.text}`).join("\n");
const msg = await client.messages.create({
model: "claude-haiku-4-5",
max_tokens: 256,
system: "Answer using only the provided documents. Cite document numbers.",
messages: [{ role: "user", content: `Documents:\n${context}\n\nQuestion: ${query}` }],
});
return msg.content[0].text;
}
Discussion