LLM & NLP

Embeddings and semantic search: from word2vec to Voyage

TL;DR

Embeddings turn meaning into distance between vectors. Anthropic has no embedding model of its own and points to Voyage AI; choose a model with evals, set query/document correctly and combine with keyword search.

A user types "how to change my password", while the internal docs say "recover login credentials". Keyword search returns nothing because the two phrases share no words. Embeddings solve exactly this: they turn text into numeric vectors so that sentences with the same meaning sit close together in space, whatever words they use. This is the foundation of semantic search, RAG, content recommendations and duplicate detection.

From word2vec to sentence embeddings

  • word2vec (Mikolov et al., 2013): learns a vector for each word from its surrounding context; famous for arithmetic like "king − man + woman ≈ queen". Drawback: each word has only one vector, regardless of context.
  • Sentence-BERT (Reimers & Gurevych, 2019): fine-tunes BERT to produce vectors for whole sentences, making similarity comparisons across thousands of sentences far faster than passing every pair through the model.
  • Modern embedding models: dedicated, multilingual, long-context (tens of thousands of tokens), with specialised versions for code, law and finance.

Does Claude have embeddings?

No. Anthropic's docs state plainly that it doesn't offer its own embedding model and point to Voyage AI (part of MongoDB) as one option, while recommending you evaluate several providers. According to the current docs:

NeedSuggested model
Best quality, multilingualvoyage-4-large
Balancedvoyage-4
Cheap and fastvoyage-4-lite
Code, agentic codingvoyage-code-4
Run locally (open-weight)voyage-4-nano

The semantic search flow

import voyageai, numpy as np
vo = voyageai.Client()  # reads VOYAGE_API_KEY

docs = ["Recover your login credentials via email…", "Refund policy…"]
doc_vecs = vo.embed(docs, model="voyage-4", input_type="document").embeddings
q_vec = vo.embed(["how to change my password"], model="voyage-4", input_type="query").embeddings[0]

scores = np.dot(doc_vecs, q_vec)   # normalised vectors: dot = cosine
print(docs[int(np.argmax(scores))])

Two details that often get skipped:

  • input_type: mark "query" or "document" correctly; the docs advise against leaving it empty for retrieval tasks.
  • Metric: Voyage vectors are normalised to length 1, so cosine and dot product give the same result (dot is faster).

Optimising storage

  • Fewer dimensions (Matryoshka): some models let you take shorter vectors (256/512 dimensions) while keeping most of the quality.
  • Quantisation: storing int8 or binary instead of float32 cuts storage 4 to 32 times, at a slight cost in accuracy.

Risks and caveats

  • Don't mix models: vectors from two different models aren't comparable. Switching models = re-embedding the whole store.
  • Meaning isn't everything: for product codes, contract numbers and rare proper names, keyword search (BM25) is more precise. Combine both (hybrid search).
  • "Close" ≠ "answers the question": a similar passage doesn't necessarily contain the answer; add a reranking step for important results.
  • Sensitive data: sending text out for embedding means sending data outside; consider a locally run model for internal data.

How to apply it

  • Dev: Start with 50 real user questions and expected answers; measure how often the right one is in the top 5 before choosing a model.
  • Tech Lead: Store the model name and version next to each vector; design a re-embedding process for upgrades up front.
  • PM: "Smart search" over FAQs, tickets and internal docs is the cheapest embedding use case with the fastest visible value.

Sources & references

  1. Embeddings — Claude Docs
  2. Efficient Estimation of Word Representations in Vector Space — Mikolov et al. (arXiv)
  3. Sentence-BERT — Reimers & Gurevych (arXiv)