Semantic Search in .NET 9 with Microsoft.Extensions.VectorData: Build RAG APIs

Step-by-step guide to implementing semantic search and RAG in .NET 9 using Microsoft.Extensions.VectorData. Store embeddings, query vectors, and expose search APIs.

0

Why Semantic Search Matters for Enterprise Applications

Traditional keyword-based search works well for exact matches. When a user searches for “employee benefits policy,” a full-text search finds documents with those exact words. But what if your knowledge base contains “staff compensation guidelines” or “team welfare programs”? These documents are relevant, but keyword matching misses them.

Semantic search understands meaning. It converts documents and queries into vector embeddings, mathematical representations that capture conceptual similarity. Documents with related meanings cluster together in vector space, regardless of exact wording. This is the foundation of Retrieval-Augmented Generation (RAG), where a language model retrieves relevant context before generating answers.

Microsoft.Extensions.VectorData, released as part of the .NET AI stack, brings this capability directly into .NET 9. You can generate embeddings from your documents, store them in a vector collection, and perform semantic searches through clean APIs.

Understanding the .NET AI Stack: Where VectorData Fits

Microsoft’s .NET AI ecosystem has three main building blocks:

  • IChatClient: Unified interface for working with language models like GPT, Claude, or Phi. Handles chat, streaming, and function calling.
  • Microsoft.Extensions.VectorData: Vector storage and retrieval abstraction. Store embeddings, perform semantic search, and manage vector collections.
  • Agent Framework: Orchestrates agents that can think, plan, and use tools. Combines chat and vector search for intelligent workflows.

VectorData sits in the middle. It bridges your data layer with your AI layer. You generate embeddings from your documents, store them, and retrieve the most relevant ones when needed.

Setting Up Microsoft.Extensions.VectorData

Start by installing the NuGet packages in your .NET 9 project:

dotnet add package Microsoft.Extensions.VectorData
dotnet add package Microsoft.Extensions.AI.OpenAI

Configure your services in Program.cs:

using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;

var builder = WebApplicationBuilder.CreateBuilder(args);

builder.Services.AddOpenAIEmbeddingGenerator(
    modelId: "text-embedding-3-small",
    apiKey: builder.Configuration["OpenAI:ApiKey"]);

builder.Services.AddSingleton<IVectorStore>(sp =>
    new InMemoryVectorStore());

var app = builder.Build();

For development and testing, the in-memory provider works well. For production, replace InMemoryVectorStore with a provider for your chosen database. Available providers include [microsoft.com](https://learn.microsoft.com/en-us/dotnet/ai/vector-stores/how-to/use-vector-stores) Qdrant, Azure AI Search, SQL Server, and Cosmos DB.

Defining Your Data Model

Create a class to represent documents in your vector store. Use VectorData attributes to mark which properties are indexed and which contain embeddings:

using Microsoft.Extensions.VectorData;

public class ComplianceDocument
{
    [VectorStoreRecordKey]
    public string Id { get; set; }
    
    [VectorStoreRecordData(IsFilterable = true)]
    public string Title { get; set; }
    
    [VectorStoreRecordData]
    public string Content { get; set; }
    
    [VectorStoreRecordVector]
    public ReadOnlyMemory<float> Embedding { get; set; }
    
    [VectorStoreRecordData(IsFilterable = true)]
    public string Department { get; set; }
    
    [VectorStoreRecordData(IsFilterable = true)]
    public DateTime CreatedDate { get; set; }
}

Populating Your Vector Store

Generate embeddings for your documents and store them. The IEmbeddingGenerator handles embedding creation automatically:

var vectorStore = app.Services.GetRequiredService<IVectorStore>();
var embeddingGenerator = app.Services.GetRequiredService<IEmbeddingGenerator<string, Embedding<float>>>();

var collection = vectorStore.GetCollection<string, ComplianceDocument>("compliance_docs");
await collection.EnsureCollectionExistsAsync();

var documents = new List<ComplianceDocument>
{
    new() 
    { 
        Id = "doc-001",
        Title = "Employee Benefits Policy",
        Content = "This document outlines staff compensation guidelines, health insurance coverage, and team welfare programs.",
        Department = "HR",
        CreatedDate = DateTime.UtcNow
    },
    new() 
    { 
        Id = "doc-002",
        Title = "Data Protection Compliance",
        Content = "Guidelines for handling personal information, data security protocols, and privacy compliance requirements.",
        Department = "Legal",
        CreatedDate = DateTime.UtcNow
    }
};

foreach (var doc in documents)
{
    var embedding = await embeddingGenerator.GenerateEmbeddingAsync(doc.Content);
    doc.Embedding = embedding.Vector;
    await collection.UpsertAsync(doc);
}

Performing Semantic Search

Query your vector store by converting the user’s search query into an embedding and finding semantically similar documents:

var query = "What are the staff compensation guidelines?";
var queryEmbedding = await embeddingGenerator.GenerateEmbeddingAsync(query);

var searchResults = await collection.SearchAsync(
    queryEmbedding.Vector,
    new VectorSearchOptions { Top = 5 }
);

await foreach (var result in searchResults)
{
    Console.WriteLine($"Match: {result.Record.Title}");
    Console.WriteLine($"Similarity Score: {result.SimilarityScore}");
    Console.WriteLine($"Content: {result.Record.Content}
");
}

Exposing Semantic Search as an API Endpoint

Create an ASP.NET Core endpoint to expose semantic search to your front-end applications:

app.MapPost("/api/search", async (
    string query,
    IVectorStore vectorStore,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator) =>
{
    var collection = vectorStore.GetCollection<string, ComplianceDocument>("compliance_docs");
    var queryEmbedding = await embeddingGenerator.GenerateEmbeddingAsync(query);
    
    var results = await collection.SearchAsync(
        queryEmbedding.Vector,
        new VectorSearchOptions { Top = 5 }
    ).ToListAsync();
    
    return Results.Ok(results.Select(r => new
    {
        r.Record.Id,
        r.Record.Title,
        r.Record.Content,
        r.Record.Department,
        SimilarityScore = r.SimilarityScore
    }));
});

app.Run();

Now your Flutter or React front-end can call this endpoint to retrieve semantically relevant documents. The search understands meaning, not just keywords.

Advanced: Filtering and Hybrid Search

VectorData supports filtering results before vector comparison, which reduces latency and processing cost. For example, search only within the HR department:

var searchOptions = new VectorSearchOptions 
{ 
    Top = 5,
    Filter = new VectorSearchFilter().EqualTo(nameof(ComplianceDocument.Department), "HR")
};

var results = await collection.SearchAsync(queryEmbedding.Vector, searchOptions);

Some vector databases, like Azure AI Search and Qdrant, support hybrid search, which combines vector similarity with keyword matching. This can improve relevance by capturing exact term matches that semantic search alone might miss. Check your database provider’s documentation to see if this feature is available.

Production Considerations

For development, the in-memory vector store is convenient. For production applications, consider these options:

  • Azure AI Search: Enterprise-grade search-as-a-service with built-in security, encryption, and compliance features. Ideal for regulated environments.
  • Qdrant: Open-source vector database with high performance. Can run locally in Docker or on Qdrant Cloud.
  • SQL Server or Cosmos DB: If you prefer to keep vectors alongside relational or document data.

Choose based on your scale, compliance requirements, and infrastructure preferences. The beauty of VectorData is that your application code remains the same regardless of which provider you select.

What is semantic search?

Semantic search understands the meaning behind queries and documents, not just matching keywords. It converts text into vector embeddings (numerical representations of meaning) and finds documents with similar embeddings, even if the exact words differ.

How does VectorData differ from traditional databases?

Traditional databases use exact matching and keyword indexing. VectorData is optimized for vector similarity searches, enabling AI-powered applications to find conceptually related content. Both serve different purposes: use traditional databases for transactional data and VectorData for semantic search and RAG.

Can I use VectorData with local embedding models?

Yes. VectorData supports custom IEmbeddingGenerator implementations, allowing you to use local models, on-premises services, or any embedding provider you prefer.

What vector storage options does VectorData support?

VectorData provides an abstraction layer supporting in-memory storage (for development), Azure AI Search, Qdrant, SQL Server, Cosmos DB, and other providers. See [microsoft.com](https://learn.microsoft.com/en-us/dotnet/ai/vector-stores/how-to/use-vector-stores) for the complete list of out-of-the-box providers.

Is VectorData part of Semantic Kernel?

No. VectorData is a separate library in the Microsoft.Extensions.AI ecosystem. While Semantic Kernel includes connectors that use VectorData, the vector store providers themselves are independent and can be used anywhere in .NET applications.

Leave a Reply

Your email address will not be published. Required fields are marked *