共用方式為


如何建置向量存放區連接器的自訂對應程式 (預覽)

警告

語意核心向量存放區功能處於預覽狀態,且需要重大變更的改善可能仍會在發行前有限的情況下發生。

在此作法中,我們將示範如何將向量存放區記錄集合的默認對應程式取代為您自己的對應程式。

我們將使用 Qdrant 來示範這項功能,但其他連接器的概念會類似。

背景

每個向量存放區連接器都包含預設對應程式,可從提供的數據模型對應至基礎存放區支援的記憶體架構。 有些存放區允許在儲存數據的方式方面有很多自由,而其他存放區需要更結構化的方法,例如,所有向量都必須新增至向量字典,以及所有非向量字段至數據欄位字典。 因此,對應是抽象化每個數據存放區實作差異的重要部分。

在某些情況下,如果例如,開發人員可能會想要取代默認對應程式。

  1. 他們想要使用與記憶體架構不同的數據模型。
  2. 他們想要為其案例建置效能優化的對應程式。
  3. 默認對應程式不支援開發人員所需的記憶體結構。

所有向量存放區連接器實作都可讓您提供自定義對應程式。

依向量存放區類型的差異

每個向量存放區連接器的基礎數據存放區有不同的儲存方式。 因此,您在儲存端對應到的內容可能會因每個連接器而有所不同。

例如,如果使用 Qdrant 連接器,記憶體類型就是 PointStruct Qdrant SDK 所提供的類別。 如果使用 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的字串屬性,而兩個向量會定義在 與和 Name Description 字段相同的層級Id

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 將由向量存放區的類型決定。

針對 Qdrant TStorageModelQdrant.Client.Grpc.PointStruct

因此,我們必須實作對應程式,以在數據模型與 Qdrant PointStruct之間Product對應。

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

使用自定義對應程式搭配記錄集合

若要使用我們建立的自定義對應程式,我們需要在建構時間將它傳遞至記錄集合。 我們也需要傳遞我們稍早建立的記錄定義,以便使用正確的架構在數據存放區中建立集合。 這裏還有一個很重要的設定是 Qdrant 的具名向量模式,因為我們有多個向量。 若未開啟此模式,僅支援一個向量。

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 與任何向量存放區實作通訊。

不過,建構向量存放區實作時,可以提供處理站。 這可用來在建立實例時自定義 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 Store。

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

即將推出

更多信息即將推出。

即將推出

更多信息即將推出。