แก้ไข

แชร์ผ่าน


Vector search using vector store providers

The Microsoft.Extensions.VectorData library provides vector search capabilities as part of its vector store abstractions. These capabilities include filtering and many other options.

Tip

To see how you can search without generating embeddings yourself, see Let the vector store generate embeddings.

The SearchAsync method allows searching using data that has already been vectorized. This method takes a vector and an optional VectorSearchOptions<TRecord> class as input. SearchAsync is available on the following types:

Note that VectorStoreCollection<TKey,TRecord> implements IVectorSearchable<TRecord>.

Assuming you have a collection that already contains data, you can easily search it. Here is an example using Qdrant.

public async Task SearchAsync()
{
    // Create a Qdrant VectorStore object and choose
    // an existing collection that already contains records.
    VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true);
    VectorStoreCollection<ulong, Hotel> collection =
        vectorStore.GetCollection<ulong, Hotel>("skhotels");

    // Generate a vector for your search text, using
    // your chosen embedding generation implementation.
    ReadOnlyMemory<float> searchVector =
            await GenerateAsync("I'm looking for a hotel where customer happiness is the priority.");

    // Do the search, passing an options object with
    // a Top value to limit results to the single top match.
    IAsyncEnumerable<VectorSearchResult<Hotel>> searchResult =
        collection.SearchAsync(searchVector, top: 1);

    // Inspect the returned hotel.
    await foreach (VectorSearchResult<Hotel> record in searchResult)
    {
        Console.WriteLine("Found hotel description: " + record.Record.Description);
        Console.WriteLine("Found record score: " + record.Score);
    }
}

Tip

For more information on how to generate embeddings see embedding generation.

Search with auto-generated embeddings

If you configured an IEmbeddingGenerator<TInput,TEmbedding> on your vector store or collection, you can pass a string directly to SearchAsync instead of a precomputed vector. The vector store generates the search embedding automatically:

public async Task SearchWithAutoEmbeddingAsync()
{
    // If an IEmbeddingGenerator is configured on the vector store or collection,
    // you can pass a string directly to SearchAsync. The store generates
    // the search embedding for you.
    VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true);
    VectorStoreCollection<ulong, Hotel> collection =
        vectorStore.GetCollection<ulong, Hotel>("skhotels");

    // Pass the search text directly — no manual embedding generation required.
    IAsyncEnumerable<VectorSearchResult<Hotel>> searchResult =
        collection.SearchAsync("I'm looking for a hotel where customer happiness is the priority.", top: 1);

    await foreach (VectorSearchResult<Hotel> record in searchResult)
    {
        Console.WriteLine("Found hotel description: " + record.Record.Description);
        Console.WriteLine("Found record score: " + record.Score);
    }
}

For information on how to configure an embedding generator on your vector store, see Let the vector store generate embeddings.

Supported vector types

SearchAsync takes a generic type as the vector parameter. The types of vectors supported by each data store vary.

It's also important for the search vector type to match the target vector that is being searched, for example, if you have two vectors on the same record with different vector types, make sure that the search vector you supply matches the type of the specific vector you are targeting. For information on how to pick a target vector if you have more than one per record, see VectorProperty.

Vector search options

The following options can be provided using the VectorSearchOptions<TRecord> class.

VectorProperty

Use the VectorProperty option to specify the vector property to target during the search. If none is provided and the data model contains only one vector, that vector will be used. If the data model contains no vector or multiple vectors and VectorProperty is not provided, the search method throws an exception.

public async Task VectorPropertySearch()
{
    var vectorStore = new InMemoryVectorStore();
    InMemoryCollection<int, Product> collection =
        vectorStore.GetCollection<int, Product>("skproducts");

    // Create the vector search options and indicate that you want to search the FeatureListEmbedding property.
    var vectorSearchOptions = new VectorSearchOptions<Product>
    {
        VectorProperty = r => r.FeatureListEmbedding
    };

    // This snippet assumes searchVector is already provided, having been created using the embedding model of your choice.
    IAsyncEnumerable<VectorSearchResult<Product>> searchResult =
        collection.SearchAsync(searchVector, top: 3, vectorSearchOptions);
}

public sealed class Product
{
    [VectorStoreKey]
    public int Key { get; set; }

    [VectorStoreData]
    public required string Description { get; set; }

    [VectorStoreData]
    public required List<string> FeatureList { get; set; }

    [VectorStoreVector(1536)]
    public ReadOnlyMemory<float> DescriptionEmbedding { get; set; }

    [VectorStoreVector(1536)]
    public ReadOnlyMemory<float> FeatureListEmbedding { get; set; }
}

Skip results or select top results

The top parameter on SearchAsync<TInput>(TInput, Int32, VectorSearchOptions<TRecord>, CancellationToken) and the VectorSearchOptions<TRecord>.Skip option let you limit the number of results. The top parameter limits the results to the top n results. The Skip option skips a number of results from the top of the result set. You can use these controls to perform paging if you want to retrieve a large number of results using separate calls.

// Create the vector search options and indicate
// that you want to skip the first 40 results.
VectorSearchOptions<Product> vectorSearchOptions = new()
{
    Skip = 40
};

// This snippet assumes searchVector is already provided,
// having been created using the embedding model of your choice.
// Pass 'top: 20' to indicate that you want to retrieve
// the next 20 results after skipping the first 40.
IAsyncEnumerable<VectorSearchResult<Product>> searchResult =
    collection.SearchAsync(searchVector, top: 20, vectorSearchOptions);

// Iterate over the search results.
await foreach (VectorSearchResult<Product> result in searchResult)
{
    Console.WriteLine(result.Record.FeatureList);
}

The default value for Skip is 0.

IncludeVectors

The VectorSearchOptions<TRecord>.IncludeVectors option lets you specify whether you want to return vectors in the search results. If false, the vector properties on the returned model are left null. Using false can significantly reduce the amount of data retrieved from the vector store during search, making searches more efficient.

The default value for IncludeVectors is false.

// Create the vector search options and indicate that you want to include vectors in the search results.
var vectorSearchOptions = new VectorSearchOptions<Product>
{
    IncludeVectors = true
};

// This snippet assumes searchVector is already provided,
// having been created using the embedding model of your choice.
IAsyncEnumerable<VectorSearchResult<Product>> searchResult =
    collection.SearchAsync(searchVector, top: 3, vectorSearchOptions);

// Iterate over the search results.
await foreach (VectorSearchResult<Product> result in searchResult)
{
    Console.WriteLine(result.Record.FeatureList);
}

Filter

Use the VectorSearchOptions<TRecord>.Filter option to filter the records in the chosen collection before applying the vector search. This has multiple benefits:

  • Reduces latency and processing cost, since only records remaining after filtering need to be compared with the search vector and therefore fewer vector comparisons have to be done.
  • Limits the result set. For example, you can implement access control by excluding data that the user shouldn't have access to.

For fields to be used for filtering, many vector stores require those fields to be indexed first. Some vector stores will allow filtering using any field, but might optionally allow indexing to improve filtering performance.

If you're creating a collection via the vector store abstractions and you want to enable filtering on a field, set the IsIndexed property to true when defining your data model or when creating your record definition.

Tip

For more information on how to enable IsIndexed, see VectorStoreDataAttribute or VectorStoreDataProperty.

Filters are expressed using LINQ expressions based on the type of the data model. The set of LINQ expressions supported will vary depending on the functionality supported by each database, but all databases support a broad base of common expressions, for example, equals, not equals, and, and or.

public static async Task FilteredSearchAsync()
{
    // Create the vector search options and set the filter on the options.
    VectorSearchOptions<Glossary> vectorSearchOptions = new()
    {
        Filter = r => r.Category == "External Definitions" && r.Tags.Contains("memory")
    };

    // This snippet assumes searchVector is already provided,
    // having been created using the embedding model of your choice.
    IAsyncEnumerable<VectorSearchResult<Glossary>> searchResult =
        collection.SearchAsync(searchVector, top: 3, vectorSearchOptions);

    // Iterate over the search results.
    await foreach (VectorSearchResult<Glossary> result in searchResult)
    {
        Console.WriteLine(result.Record.Definition);

    }
}

sealed class Glossary
{
    [VectorStoreKey]
    public ulong Key { get; set; }

    // Category is marked as indexed, since you want to filter using this property.
    [VectorStoreData(IsIndexed = true)]
    public required string Category { get; set; }

    // Tags is marked as indexed, since you want to filter using this property.
    [VectorStoreData(IsIndexed = true)]
    public required List<string> Tags { get; set; }
    [VectorStoreData]
    public required string Term { get; set; }

    [VectorStoreData]
    public required string Definition { get; set; }

    [VectorStoreVector(1536)]
    public ReadOnlyMemory<float> DefinitionEmbedding { get; set; }
}