Embeddings and similarity search

Embeddings turn text into points so that similar meanings land close together. Search is finding the nearest points.

Time O(n × d)Space O(n × d)Code C#
Watch it animatedStep through it, with the running line highlighted. Free with an account.

The idea

An embedding model turns a piece of text into a list of numbers (a vector) that captures its meaning. Texts with similar meaning get similar vectors, so they sit close together. To search, embed the query with the same model, compare it with every stored vector using cosine similarity (the angle between them), and keep the top few. It finds matches by meaning, not by shared words. Here the vectors have only 2 numbers so we can draw them; real ones have hundreds or thousands.

Code

public static float Cosine(float[] a, float[] b)
{
    float dot = 0, na = 0, nb = 0;
    for (int i = 0; i < a.Length; i++)
    {
        dot += a[i] * b[i];
        na += a[i] * a[i];
        nb += b[i] * b[i];
    }
    return dot / (MathF.Sqrt(na) * MathF.Sqrt(nb));
}

public static async Task<List<string>> SearchAsync(
    IEmbedder embedder,
    List<(string Text, float[] Vector)> index,
    string query)
{
    float[] q = await embedder.EmbedAsync(query);
    var scored = index
        .Select(d => (d.Text, Score: Cosine(q, d.Vector)))
        .OrderByDescending(x => x.Score);
    return scored.Take(3).Select(x => x.Text).ToList();
}

Tips

In the .NET library

Store vectors as float[] (or ReadOnlyMemory<float>) and keep the embedding call behind an interface like IEmbedder so you can swap models, cache results, and unit test the search with a fake embedder.