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)

  1. Turn your documents into embeddings and store them in a vector database.
  2. When a user asks a question, embed the question and find the most similar documents.
  3. 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

Example · javascript
// 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.

Example · javascript
// 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.

Example · javascript
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.

Example · javascript
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

  • Be the first to comment on this lesson.