次の方法で共有


Vector Store コネクタのカスタム マッパーを構築する方法 (プレビュー)

警告

セマンティック カーネル ベクター ストア機能はプレビュー段階であり、破壊的変更を必要とする機能強化は、リリース前の限られた状況で引き続き発生する可能性があります。

この方法では、ベクター ストア レコード コレクションの既定のマッパーを独自のマッパーに置き換える方法について説明します。

Qdrant を使用してこの機能を示しますが、その概念は他のコネクタでも似ています。

背景

各ベクター ストア コネクタには、指定されたデータ モデルから、基になるストアでサポートされているストレージ スキーマにマップできる既定のマッパーが含まれています。 一部のストアでは、データの格納方法に関して自由に使用できますが、他のストアではより構造化されたアプローチが必要です。たとえば、すべてのベクターをベクターのディクショナリに追加し、すべての非ベクター フィールドをデータ フィールドのディクショナリに追加する必要があります。 そのため、マッピングは、各データ ストア実装の違いを抽象化する上で重要な部分です。

場合によっては、開発者が既定のマッパーを置き換える必要がある場合があります (例:

  1. ストレージ スキーマとは異なるデータ モデルを使用する必要があります。
  2. 彼らは、自分のシナリオに合わせてパフォーマンス最適化マッパーを構築したいと考えています。
  3. 既定のマッパーは、開発者が必要とするストレージ構造をサポートしていません。

Vector Store コネクタのすべての実装では、カスタム マッパーを提供できます。

ベクター ストアの種類による違い

各 Vector Store コネクタの基になるデータ ストアには、データを格納するさまざまな方法があります。 そのため、ストレージ側でマッピングする対象は、コネクタごとに異なる場合があります。

たとえば、Qdrant コネクタを使用する場合、ストレージの種類は Qdrant SDK によって提供される PointStruct クラスです。 Redis JSON コネクタを使用する場合、ストレージの種類は string キーと JsonNodeですが、JSON HashSet コネクタを使用する場合、ストレージの種類は string キーと HashEntry 配列です。

カスタム マッピングを実行し、複数のコネクタの種類を使用する場合は、コネクタの種類ごとにマッパーを実装する必要があります。

データ モデルの作成

最初の手順は、データ モデルを作成することです。 ここでは、データベース スキーマの外観を記述する個別のレコード定義を提供するため、データ モデルに属性の注釈を付けません。

また、このモデルは複雑であり、ベクター用の個別のクラスと追加情報が含まれる点にも注意してください。

public class Product
{
    public ulong Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public ProductVectors Vectors { get; set; }
    public ProductInfo ProductInfo { get; set; }
}

public class ProductInfo
{
    public double Price { get; set; }
    public string SupplierId { get; set; }
}

public class ProductVectors
{
    public ReadOnlyMemory<float> NameEmbedding { get; set; }
    public ReadOnlyMemory<float> DescriptionEmbedding { get; set; }
}

レコード定義の作成

データベース スキーマの外観を定義するには、レコード定義インスタンスを作成する必要があります。 通常、コネクタでは、既定のマッパーを使用するときにこの情報をマッピングする必要があります。 カスタム マッパーを作成するため、これはマッピングには必要ありませんが、コネクタではデータ ストアにコレクションを作成するためにこの情報が引き続き必要になります。

ここでの定義は、上記のデータ モデルとは異なる点に注意してください。 ProductInfoを格納するために、ProductInfoJsonという文字列プロパティがあり、2 つのベクトルはIdName、およびDescriptionフィールドと同じレベルで定義されます。

using Microsoft.Extensions.VectorData;

var productDefinition = new VectorStoreRecordDefinition
{
    Properties = new List<VectorStoreRecordProperty>
    {
        new VectorStoreRecordKeyProperty("Id", typeof(ulong)),
        new VectorStoreRecordDataProperty("Name", typeof(string)) { IsFilterable = true },
        new VectorStoreRecordDataProperty("Description", typeof(string)),
        new VectorStoreRecordDataProperty("ProductInfoJson", typeof(string)),
        new VectorStoreRecordVectorProperty("NameEmbedding", typeof(ReadOnlyMemory<float>)) { Dimensions = 1536 },
        new VectorStoreRecordVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory<float>)) { Dimensions = 1536 }
    }
};

重要

このシナリオでは、ストレージ スキーマがデータ モデルに似ているわけではないため、レコード定義の代わりに属性を使用することはできません。

カスタム マッパーの作成

すべてのマッパーは、ジェネリック インターフェイス Microsoft.SemanticKernel.Data.IVectorStoreRecordMapper<TRecordDataModel, TStorageModel>を実装します。 TRecordDataModel は開発者が使用するデータ モデルによって異なり、 TStorageModel は Vector Store の種類によって決まります。

Qdrant TStorageModel の場合は Qdrant.Client.Grpc.PointStruct

そのため、 Product データ モデルと Qdrant PointStructの間でマップされるマッパーを実装する必要があります。

using Microsoft.Extensions.VectorData;
using Qdrant.Client.Grpc;

public class ProductMapper : IVectorStoreRecordMapper<Product, PointStruct>
{
    public PointStruct MapFromDataToStorageModel(Product dataModel)
    {
        // Create a Qdrant PointStruct to map our data to.
        var pointStruct = new PointStruct
        {
            Id = new PointId { Num = dataModel.Id },
            Vectors = new Vectors(),
            Payload = { },
        };

        // Add the data fields to the payload dictionary and serialize the product info into a json string.
        pointStruct.Payload.Add("Name", dataModel.Name);
        pointStruct.Payload.Add("Description", dataModel.Description);
        pointStruct.Payload.Add("ProductInfoJson", JsonSerializer.Serialize(dataModel.ProductInfo));

        // Add the vector fields to the vector dictionary.
        var namedVectors = new NamedVectors();
        namedVectors.Vectors.Add("NameEmbedding", dataModel.Vectors.NameEmbedding.ToArray());
        namedVectors.Vectors.Add("DescriptionEmbedding", dataModel.Vectors.DescriptionEmbedding.ToArray());
        pointStruct.Vectors.Vectors_ = namedVectors;

        return pointStruct;
    }

    public Product MapFromStorageToDataModel(PointStruct storageModel, StorageToDataModelMapperOptions options)
    {
        var product = new Product
        {
            Id = storageModel.Id.Num,

            // Retrieve the data fields from the payload dictionary and deserialize the product info from the json string that it was stored as.
            Name = storageModel.Payload["Name"].StringValue,
            Description = storageModel.Payload["Description"].StringValue,
            ProductInfo = JsonSerializer.Deserialize<ProductInfo>(storageModel.Payload["ProductInfoJson"].StringValue)!,

            // Retrieve the vector fields from the vector dictionary.
            Vectors = new ProductVectors
            {
                NameEmbedding = new ReadOnlyMemory<float>(storageModel.Vectors.Vectors_.Vectors["NameEmbedding"].Data.ToArray()),
                DescriptionEmbedding = new ReadOnlyMemory<float>(storageModel.Vectors.Vectors_.Vectors["DescriptionEmbedding"].Data.ToArray())
            }
        };

        return product;
    }
}

レコード コレクションでのカスタム マッパーの使用

作成したカスタム マッパーを使用するには、構築時にレコード コレクションに渡す必要があります。 また、適切なスキーマを使用してデータ ストアにコレクションが作成されるように、前に作成したレコード定義を渡す必要があります。 ここで重要なもう 1 つの設定は、複数のベクターがあるため、Qdrant の名前付きベクター モードです。 このモードをオンにしないと、1 つのベクターのみがサポートされます。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client;

var productMapper = new ProductMapper();
var collection = new QdrantVectorStoreRecordCollection<Product>(
    new QdrantClient("localhost"),
    "skproducts",
    new()
    {
        HasNamedVectors = true,
        PointStructCustomMapper = productMapper,
        VectorStoreRecordDefinition = productDefinition
    });

IVectorStore でのカスタム マッパーの使用

IVectorStoreを使用してオブジェクト インスタンスIVectorStoreRecordCollection取得する場合、カスタム マッパーを GetCollection メソッドに直接提供することはできません。 これは、カスタム マッパーはベクター ストアの種類ごとに異なり、 IVectorStore を使用してベクター ストアの実装と通信できなくなるためです。

ただし、Vector Store の実装を構築するときにファクトリを提供することはできます。 これは、インスタンスの作成時に IVectorStoreRecordCollection インスタンスをカスタマイズするために使用できます。

このようなファクトリの例を次に示します。このファクトリは、製品定義とデータ型で CreateCollection が呼び出されたかどうかを確認し、その場合はカスタム マッパーを挿入し、名前付きベクター モードで切り替えます。

public class QdrantCollectionFactory(VectorStoreRecordDefinition productDefinition) : IQdrantVectorStoreRecordCollectionFactory
{
    public IVectorStoreRecordCollection<TKey, TRecord> CreateVectorStoreRecordCollection<TKey, TRecord>(QdrantClient qdrantClient, string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition)
        where TKey : notnull
        where TRecord : class
    {
        // If the record definition is the product definition and the record type is the product data
        // model, inject the custom mapper into the collection options.
        if (vectorStoreRecordDefinition == productDefinition && typeof(TRecord) == typeof(Product))
        {
            var customCollection = new QdrantVectorStoreRecordCollection<Product>(
                qdrantClient,
                name,
                new()
                {
                    HasNamedVectors = true,
                    PointStructCustomMapper = new ProductMapper(),
                    VectorStoreRecordDefinition = vectorStoreRecordDefinition
                }) as IVectorStoreRecordCollection<TKey, TRecord>;
            return customCollection!;
        }

        // Otherwise, just create a standard collection with the default mapper.
        var collection = new QdrantVectorStoreRecordCollection<TRecord>(
            qdrantClient,
            name,
            new()
            {
                VectorStoreRecordDefinition = vectorStoreRecordDefinition
            }) as IVectorStoreRecordCollection<TKey, TRecord>;
        return collection!;
    }
}

コレクション ファクトリを使用するには、構築時、または依存関係挿入コンテナーに登録するときに、それを Vector ストアに渡します。

// When registering with the dependency injection container on the kernel builder.
kernelBuilder.AddQdrantVectorStore(
    "localhost",
    options: new()
    {
        VectorStoreCollectionFactory = new QdrantCollectionFactory(productDefinition)
    });
// When constructing the Vector Store instance directly.
var vectorStore = new QdrantVectorStore(
    new QdrantClient("localhost"),
    new()
    {
        VectorStoreCollectionFactory = new QdrantCollectionFactory(productDefinition)
    });

これで、ベクター ストアを通常どおりに使用してコレクションを取得できるようになりました。

var collection = vectorStore.GetCollection<ulong, Product>("skproducts", productDefinition);

間もなく公開

詳細については、近日公開予定です。

間もなく公開

詳細については、近日公開予定です。