Text embeddings · Compare embeddings with cosine similarity4 / 5
  1. 01
  2. 02
  3. 03
  4. 05

Compare embeddings with cosine similarity

Example on GitHub(packages/sdk/examples/embed-p2p.ts)

We have three vectors from the last lesson. Two are similar (both about a fox). One is unrelated (Python). The comparison is one short function: multiply matching positions, then sum them up.

For two 1024-dimension vectors a and b, the similarity is the dot product Σᵢ aᵢ × bᵢ, the sum of the element-wise products. That's all there is to it. Strictly speaking, "cosine similarity" divides by the magnitudes too, but for normalized embeddings like these the dot product alone ranks similarity correctly.

Same batch embed() call, but destructured into three named variables. Different destructuring pattern than the previous lesson, since we want three separate handles rather than one batch. You would destructure like so:

const { embedding: [emb1, emb2, emb3] } = await embed({ modelId, text: texts });

The cosineSimilarity helper is one-line math: a loop that multiplies matching positions and sums. Output range is -1 to 1, but GTE_LARGE_FP16 typically produces 0 to 1. Note that the ?? 0 is defensive against sparse arrays. The function looks as follows:

function cosineSimilarity(vecA: number[], vecB: number[]) {
  let dotProduct = 0;
  for (let i = 0; i < vecA.length; i++) {
    dotProduct += (vecA[i] ?? 0) * (vecB[i] ?? 0);
  }
  return dotProduct;
}

With cosineSimilarity defined, the side-by-side compare is two calls and two logs. The similar pair (text 1 vs text 2) should land high (around 0.87), the unrelated one (text 1 vs text 3) should land low (around 0.11). .toFixed(4) rounds each to four decimal places so the numbers stay readable on one line:

const similarity1 = cosineSimilarity(emb1, emb2);
const similarity2 = cosineSimilarity(emb1, emb3);
console.log(`Similarity between texts 1 and 2 (similar meaning): ${similarity1.toFixed(4)}`);
console.log(`Similarity between texts 1 and 3 (different topics): ${similarity2.toFixed(4)}`);

Note: vecA[i] ?? 0 defends against undefined if the array is ever sparse, which doesn't happen for our case. Drop the ?? 0 and use vecA[i]! if you want the leaner read.

Put it to the test

  1. Call embed({ modelId, text: texts }) and destructure the batch as emb1, emb2, emb3.
  2. Define cosineSimilarity(vecA: number[], vecB: number[]) that returns the dot product.
  3. Call cosineSimilarity(emb1, emb2) and cosineSimilarity(emb1, emb3), then console.log both with .toFixed(4).
index.ts

$ Run your code to see results

$