Traditional search matches words. Ask a keyword search for “how to cancel my plan” and it’ll miss a document titled “ending your subscription” — same meaning, different words. Semantic search fixes that by matching on meaning using embeddings. This guide builds a minimal semantic search in C#.
How it works
Two phases:
- Index — turn every document into an embedding vector and store it.
- Search — turn the query into a vector, then find the stored vectors closest to it.
“Closest” means most similar in meaning. That’s the entire idea.
Step 1: Index your documents
Generate an embedding per document (or per chunk) and keep it with the original text:
using Microsoft.Extensions.AI;
record Doc(string Text, ReadOnlyMemory<float> Vector);
IEmbeddingGenerator<string, Embedding<float>> embedder = /* your embeddings client */;
async Task<List<Doc>> IndexAsync(IEnumerable<string> texts)
{
var docs = new List<Doc>();
foreach (var text in texts)
{
var e = await embedder.GenerateAsync(text);
docs.Add(new Doc(text, e.Vector));
}
return docs;
}
Step 2: Search by similarity
Embed the query, then rank documents by cosine similarity and return the top matches:
async Task<List<string>> SearchAsync(string query, List<Doc> docs, int topK = 3)
{
var q = (await embedder.GenerateAsync(query)).Vector;
return docs
.Select(d => (d.Text, Score: Cosine(q.Span, d.Vector.Span)))
.OrderByDescending(x => x.Score)
.Take(topK)
.Select(x => x.Text)
.ToList();
}
Now searching “how do I cancel” returns “ending your subscription” — because they mean the same thing, even with zero shared keywords.
Step 3: Scale it with a vector database
The in-memory approach above is perfect for hundreds of documents. For thousands or millions, computing similarity against every vector on each query is too slow. That’s what vector databases are for — they index vectors for fast approximate nearest-neighbour search. See vector search with pgvector for a Postgres-based option that most .NET teams can adopt without new infrastructure.
Making results better
Semantic search quality comes down to a few knobs:
- Chunking — split long documents so a match points to the relevant passage, not a whole file.
- Hybrid search — combine keyword and vector search; keywords catch exact terms (product codes, names) that embeddings can blur.
- Re-ranking — for hard queries, re-score the top candidates with a more precise model.
Most “semantic search isn’t accurate” problems are chunking or retrieval issues, not the embedding model — the same lesson as RAG.
Note:
IEmbeddingGeneratorAPIs are still settling; verify against the current .NET AI docs. The two-phase design — index vectors, search by nearest vector — is fundamental.
Takeaway
Semantic search matches meaning, not words: embed your documents, embed the query, and return the nearest vectors. It’s a couple dozen lines of C# for a small corpus, and a vector database when you scale. Add chunking, hybrid search, and re-ranking to sharpen results — and you have the retrieval engine that powers everything from site search to RAG-grounded agents.
Have a correction or a topic you want covered? Email mani.bc72@gmail.com.