在 .NET AI 應用程式中使用向量儲存

📦 Microsoft。Extensions.VectorData.Abstractions(MEVD)套件提供統一的 API,用於在 .NET 中操作向量儲存。 你可以用同一套程式碼來儲存和搜尋不同向量資料庫提供者間的嵌入。

本文將教你如何使用該函式庫的主要功能。

先決條件

安裝套件

為你的向量資料庫安裝一個提供者套件。 Microsoft.Extensions.VectorData.Abstractions 套件會自動作為傳遞性依賴被引入。 以下範例使用記憶體內提供者進行開發與測試:

dotnet package add Microsoft.SemanticKernel.Connectors.InMemory --prerelease

在生產環境中,將 Microsoft.SemanticKernel.Connectors.InMemory 替換成資料庫的提供者。 有關可用供應商,請參閱「 開箱即用的向量商店供應商」。 (儘管提供者套件名稱中包含「SemanticKernel」,但這些提供者與 語意核心 無關,且可在 .NET 中任何地方使用,包括 Agent Framework。)

定義資料模型

定義一個 .NET 類別來表示你想存放在向量儲存庫中的紀錄。 使用屬性來註解類別中的屬性,依據它們代表主鍵、一般資料或向量資料。 以下是簡單的範例:

public class Hotel
{
    [VectorStoreKey]
    public ulong HotelId { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public required string HotelName { get; set; }

    [VectorStoreData(IsFullTextIndexed = true)]
    public required string Description { get; set; }

    [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw)]
    public ReadOnlyMemory<float>? DescriptionEmbedding { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public required string[] Tags { get; set; }
}

作為使用屬性的替代方案,你可以用 VectorStoreCollectionDefinition程式定義你的結構。 這種方法適用於你想使用相同資料模型但配置不同的資料模型,或無法為資料模型類別新增屬性時。

欲了解更多資訊,請參閱 定義您的資料模型。

建立向量存放區

為你選擇的資料庫建立 VectorStore 實作的實例。 以下範例建立一個記憶體內向量儲存:

// Create an in-memory vector store (no external service required).
// For production, replace this with a connector for your preferred database.
var vectorStore = new InMemoryVectorStore();

取得集合

在GetCollection上調用VectorStore以獲得一個VectorStoreCollection<TKey,TRecord>類型的引用。 如果該集合尚不存在,請呼叫 EnsureCollectionExistsAsync 來建立它:

// Get a reference to a collection named "hotels".
VectorStoreCollection<int, Hotel> collection =
    vectorStore.GetCollection<int, Hotel>("hotels");

// Ensure the collection exists in the database.
await collection.EnsureCollectionExistsAsync();

集合名稱對應到你資料庫底層的儲存概念(例如 SQL Server 中的表格、Azure AI 搜尋服務 中的索引,或 Cosmos DB 中的容器)。

Upsert 唱片

用於 UpsertAsync 插入或更新收藏中的紀錄。 如果已有相同鍵的紀錄存在,則會更新:

// Upsert records into the collection.
// In a real app, generate embeddings using an IEmbeddingGenerator.
// The CreateFakeEmbedding helper at the bottom of this file generates
// placeholder vectors for demonstration purposes only.
var hotels = new List<Hotel>
{
    new()
    {
        HotelId = 1,
        HotelName = "Seaside Retreat",
        Description = "A peaceful hotel on the coast with stunning ocean views.",
        DescriptionEmbedding = CreateFakeEmbedding(1),
        Tags = ["beach", "ocean", "relaxation"]
    },
    new()
    {
        HotelId = 2,
        HotelName = "Mountain Lodge",
        Description = "A cozy lodge in the mountains with hiking trails nearby.",
        DescriptionEmbedding = CreateFakeEmbedding(2),
        Tags = ["mountain", "hiking", "nature"]
    },
    new()
    {
        HotelId = 3,
        HotelName = "City Centre Hotel",
        Description = "A modern hotel in the heart of the city, close to attractions.",
        DescriptionEmbedding = CreateFakeEmbedding(3),
        Tags = ["city", "business", "urban"]
    }
};

foreach (Hotel h in hotels)
{
    await collection.UpsertAsync(h);
}

這很重要

在真正的應用程式中,建議 讓 MEVD 先產生嵌入 ,再儲存紀錄。

取得記錄

用 GetAsync 來透過金鑰擷取單一記錄。 要取得多條記錄,請將一個IEnumerable<TKey>傳遞給GetAsync。

// Get a specific record by its key.
Hotel? hotel = await collection.GetAsync(1);
if (hotel is not null)
{
    Console.WriteLine($"Hotel: {hotel.HotelName}");
    Console.WriteLine($"Description: {hotel.Description}");
}

要一次取得多筆紀錄:

// Get multiple records by their keys.
IAsyncEnumerable<Hotel> hotelBatch = collection.GetAsync([1, 2, 3]);
await foreach (Hotel h in hotelBatch)
{
    Console.WriteLine($"Batch hotel: {h.HotelName}");
}

用來 SearchAsync 尋找與查詢語義相似的紀錄。 傳遞你的查詢嵌入向量及回傳的結果數量:

// Search for the top 2 hotels most similar to the query embedding.
IAsyncEnumerable<VectorSearchResult<Hotel>> searchResults =
    collection.SearchAsync(queryEmbedding, top: 2);

await foreach (VectorSearchResult<Hotel> result in searchResults)
{
    Console.WriteLine($"Found: {result.Record.HotelName} (score: {result.Score:F4})");
}

每個 VectorSearchResult<TRecord> 都包含相應的記錄和相似度分數。 分數越高表示語意吻合越近。

篩選搜尋結果

在向量比較前,請使用 VectorSearchOptions<TRecord> 來篩選搜尋結果。 您可以篩選任何已標記的屬性:IsIndexed = true

// Filter results before the vector comparison.
// Only properties marked with IsIndexed = true can be used in filters.
var searchOptions = new VectorSearchOptions<Hotel>
{
    Filter = h => h.HotelName == "Seaside Retreat"
};

IAsyncEnumerable<VectorSearchResult<Hotel>> filteredResults =
    collection.SearchAsync(queryEmbedding, top: 2, searchOptions);

await foreach (VectorSearchResult<Hotel> result in filteredResults)
{
    Console.WriteLine($"Filtered: {result.Record.HotelName} (score: {result.Score:F4})");
}

濾波器以 LINQ 表達式表示。 支援的操作因供應商而異,但所有供應商都支援常見比較,如等號、不等號、邏輯 && 與 ||。

使用 VectorSearchOptions 控制搜尋行為

用於 VectorSearchOptions<TRecord> 控制向量搜尋行為的各個面向:

// Use VectorSearchOptions to control paging and vector inclusion.
var pagedOptions = new VectorSearchOptions<Hotel>
{
    Skip = 1,           // Skip the first result (useful for paging).
    IncludeVectors = false  // Don't include vector data in results (default).
};

IAsyncEnumerable<VectorSearchResult<Hotel>> pagedResults =
    collection.SearchAsync(queryEmbedding, top: 2, pagedOptions);

await foreach (VectorSearchResult<Hotel> result in pagedResults)
{
    Console.WriteLine($"Paged: {result.Record.HotelName}");
}

下表說明了可用的選項:

Option 說明
Filter 在向量比較前,使用LINQ表達式來過濾記錄。
VectorProperty 要搜尋的向量屬性。 當資料模型具有多個向量屬性時,這是必要的。
Skip 回來前可以跳過的結果數量。 對分頁很有用。 預設值為 0。
IncludeVectors 是否要在回傳的紀錄中包含向量資料。 省略向量會減少資料傳輸。 預設值為 false。

欲了解更多資訊,請參閱 向量搜尋。

使用內建的嵌入向量生成

與其在每次進行更新或插入 (upsert) 操作前手動產生嵌入,不如在向量儲存庫或集合中設置嵌入配置 IEmbeddingGenerator。 當你完成時,將向量屬性宣告為 string 一個型別(原始文本),store 會自動產生嵌入。

欲了解更多資訊,請參閱 自動嵌入產生。

部分向量儲存支援 混合搜尋,結合向量相似度與關鍵字匹配。 此方法相較於僅向量搜尋能提升結果相關性。

要使用混合搜尋,請檢查你的集合是否實作 IKeywordHybridSearchable<TRecord>。 只有支援此功能的資料庫提供者實作此介面。

如需詳細資訊,請參閱 混合式搜尋。

刪除記錄

要依鍵刪除單一記錄,請使用 DeleteAsync:

// Delete a record by its key.
await collection.DeleteAsync(3);

刪除集合

要從向量儲存中移除整個集合,請使用 EnsureCollectionDeletedAsync:

// Delete the entire collection from the vector store.
await collection.EnsureCollectionDeletedAsync();

切換向量儲存服務提供商

因為所有提供者實作的是相同的 VectorStore 抽象類別,你可以在啟動時改變具體型別來切換。 大多數情況下,你的收藏和搜尋代碼保持不變。 然而,通常需要一些調整,例如因為不同資料庫支援不同的資料型態。