次の方法で共有


.NET AI アプリでベクター ストアを使用する

📦 Microsoft.Extensions.VectorData.Abstractions (MEVD) パッケージは、.NET のベクター ストアを操作するための統合 API を提供します。 同じコードを使用して、異なるベクター データベース プロバイダー間で埋め込みを格納および検索できます。

この記事では、ライブラリの主な機能を使用する方法について説明します。

前提条件

パッケージのインストール

ベクター データベースのプロバイダー パッケージをインストールします。 Microsoft.Extensions.VectorData.Abstractions パッケージは推移的な依存関係として自動的に取り込まれます。 次の例では、開発とテストにメモリ内プロバイダーを使用します。

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

運用環境のシナリオでは、 Microsoft.SemanticKernel.Connectors.InMemory をデータベースのプロバイダーに置き換えます。 使用可能なプロバイダーについては、「すぐに使用できる ベクター ストア プロバイダー」を参照してください。 (プロバイダー パッケージ名に "SemanticKernel" が含まれているにもかかわらず、これらのプロバイダーはセマンティック カーネルとは関係なく、エージェント フレームワークを含む .NET のどこでも使用できます)。

データ モデルを定義する

ベクター ストアに格納するレコードを表す .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();

コレクションを取得する

GetCollectionVectorStoreを呼び出して、型指定された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 Search のインデックス、Cosmos DB のコンテナーなど) にマップされます。

レコードの挿入・更新

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);
}

Important

実際のアプリでは、レコードを格納する前に MEVD で埋め込みを生成することをお 勧めします。

レコードを取得する

GetAsyncを使用して、キーによって 1 つのレコードを取得します。 複数のレコードを取得するには、 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}");
}

次の表では、使用可能なオプションについて説明します。

オプション 説明
Filter ベクター比較の前にレコードをフィルター処理する LINQ 式。
VectorProperty 検索対象のベクター プロパティ。 データ モデルに複数のベクター プロパティがある場合に必要です。
Skip 結果が返される前にスキップする数。 ページングに便利です。 既定値は 0 です。
IncludeVectors 返されたレコードにベクター データを含めるかどうか。 ベクターを省略すると、データ転送が減ります。 既定値は false です。

詳細については、「 ベクター検索オプション」を参照してください。

組み込みの埋め込み生成を使用する

各アップサートの前に埋め込みを手動で生成する代わりに、ベクター ストアまたはコレクションで IEmbeddingGenerator を構成できます。 その場合は、ベクター プロパティを string 型 (ソース テキスト) として宣言すると、ストアによって埋め込みが自動的に生成されます。

詳細については、「 ベクター ストアで埋め込みを生成できるようにする」を参照してください。

一部のベクター ストアでは、ベクトルの類似性とキーワードマッチングを組み合わせた ハイブリッド検索がサポートされています。 この方法では、ベクターのみの検索と比較して結果の関連性を向上させることができます。

ハイブリッド検索を使用するには、コレクションが IKeywordHybridSearchable<TRecord>を実装しているかどうかを確認します。 この機能をサポートするデータベースのプロバイダーのみが、このインターフェイスを実装します。

詳細については、「 ベクター ストア プロバイダーを使用したハイブリッド検索」を参照してください。

レコードを削除する

キーで 1 つのレコードを削除するには、 DeleteAsyncを使用します。

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

コレクションを削除する

ベクター ストアからコレクション全体を削除するには、 EnsureCollectionDeletedAsyncを使用します。

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

ベクター ストア プロバイダーの切り替え

すべてのプロバイダーは同じ VectorStore 抽象クラスを実装するため、起動時に具象型を変更することで、それらを切り替えることができます。 ほとんどの場合、コレクションと検索コードは変わりません。 ただし、一部の調整は通常必要です。たとえば、データベースによってサポートされるデータ型が異なるためです。