Создание кастомного маппера для коннектора Vector Store (предварительная версия)
Предупреждение
Функциональность хранилища векторов семантического ядра находится в стадии предварительной версии. Улучшения, требующие значительных изменений, могут по-прежнему происходить в ограниченных случаях до официального выпуска.
Предупреждение
Поддержка пользовательских карт может быть нерекомендуемой в будущем, так как фильтрация и выбор целевого свойства не могут нацеливать на сопоставленные типы верхнего уровня.
В этом руководстве мы покажем, как заменить средство сопоставления по умолчанию для коллекции записей векторного хранилища на собственное средство сопоставления.
Мы будем использовать Qdrant для демонстрации этой функции, но основные понятия будут аналогичны другим соединителям.
Общие сведения
Каждый соединитель Vector Store включает отображатель по умолчанию, который может отображать предоставленную модель данных со схемой хранения, поддерживаемой основным хранилищем. Некоторые хранилища позволяют много свободы в отношении хранения данных, а другие хранилища требуют более структурированного подхода, например, где все векторы должны быть добавлены в словарь векторов и все неекторные поля в словарь полей данных. Поэтому сопоставление является важной частью абстрагирования различий в каждой реализации хранилища данных.
В некоторых случаях разработчику может потребоваться заменить мэппер по умолчанию, например.
- они хотят использовать модель данных, которая отличается от схемы хранилища.
- они хотят создать оптимизированную для производительности схему для своего сценария.
- Маппер по умолчанию не поддерживает структуру хранилища, в которой нуждается разработчик.
Все реализации соединителя Vector Store позволяют предоставить настраиваемый маппер.
Различия по типу хранилища векторов
Базовые хранилища данных каждого соединителя Vector Store имеют разные способы хранения данных. Поэтому сопоставление с данными на стороне хранилища может отличаться для каждого соединителя.
Например, если используется соединитель Qdrant, тип хранилища — это PointStruct
класс, предоставляемый пакетом SDK Qdrant. При использовании соединителя JSON Redis тип хранилища представляет собой ключ 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
, и два вектора определяются на том же уровне, что Id
Name
и 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
определяется типом векторного хранилища.
Для 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;
}
}
Использование вашего пользовательского средства сопоставления с коллекцией данных
Чтобы использовать созданный нами пользовательский маппер, необходимо передать его в коллекцию записей на этапе создания. Кроме того, необходимо передать определение записи, созданное ранее, чтобы коллекции создавались в хранилище данных с помощью правильной схемы. Еще один параметр, важный здесь, — это режим именованных векторов 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
для взаимодействия с любой реализацией хранилища векторов.
Однако можно переопределить реализацию GetCollection
по умолчанию и предоставить собственную пользовательскую реализацию хранилища векторов.
Вот пример, в котором мы наследуем от QdrantVectorStore
и переопределяем метод GetCollection
для выполнения собственной конструкции.
private sealed class QdrantCustomVectorStore(QdrantClient qdrantClient, VectorStoreRecordDefinition productDefinition)
: QdrantVectorStore(qdrantClient)
{
public override IVectorStoreRecordCollection<TKey, TRecord> GetCollection<TKey, TRecord>(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
{
// 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.
return base.GetCollection<TKey, TRecord>(name, vectorStoreRecordDefinition);
}
}
Чтобы использовать хранилище векторов замены, зарегистрируйте его в контейнере внедрения зависимостей или просто используйте напрямую, как и обычный QdrantVectorStore
.
// When registering with the dependency injection container on the kernel builder.
kernelBuilder.Services.AddTransient<IVectorStore>(
(sp) =>
{
return new QdrantCustomVectorStore(
new QdrantClient("localhost"),
productDefinition);
});
// When constructing the Vector Store instance directly.
var vectorStore = new QdrantCustomVectorStore(
new QdrantClient("localhost"),
productDefinition);
Теперь вы можете использовать векторное хранилище как обычное для получения коллекции.
var collection = vectorStore.GetCollection<ulong, Product>("skproducts", productDefinition);
Скоро
Дополнительные сведения в ближайшее время.
Скоро
Дополнительные сведения в ближайшее время.