| File: ProviderServices\CollectionJsonModelBuilder.cs | |
| Project: ..\..\..\src\Libraries\Microsoft.Extensions.VectorData.Abstractions\Microsoft.Extensions.VectorData.Abstractions.csproj (Microsoft.Extensions.VectorData.Abstractions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Extensions.VectorData.ProviderServices; /// <summary> /// Represents a model builder that performs logic specific to providers that use System.Text.Json for serialization. /// This is an internal support type meant for use by providers only and not by applications. /// </summary> [Experimental(DiagnosticIds.Experiments.VectorDataProviderServices, UrlFormat = DiagnosticIds.UrlFormat)] public abstract class CollectionJsonModelBuilder : CollectionModelBuilder { private JsonSerializerOptions? _jsonSerializerOptions; /// <summary> /// Initializes a new instance of the <see cref="CollectionJsonModelBuilder"/> class. /// </summary> protected CollectionJsonModelBuilder(CollectionModelBuildingOptions options) : base(options) { } /// <summary> /// Builds and returns a <see cref="CollectionModel"/> from the given <paramref name="recordType"/> and <paramref name="definition"/>. /// </summary> /// <returns>The built <see cref="CollectionModel"/>.</returns> [RequiresDynamicCode("This model building variant is not compatible with NativeAOT. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] [RequiresUnreferencedCode("This model building variant is not compatible with trimming. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] public virtual CollectionModel Build( Type recordType, Type keyType, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator, JsonSerializerOptions jsonSerializerOptions) { _jsonSerializerOptions = jsonSerializerOptions; return Build(recordType, keyType, definition, defaultEmbeddingGenerator); } /// <summary> /// Builds and returns a <see cref="CollectionModel"/> for dynamic mapping scenarios from the given <paramref name="definition"/>. /// </summary> /// <returns>The built <see cref="CollectionModel"/>.</returns> public virtual CollectionModel BuildDynamic( VectorStoreCollectionDefinition definition, IEmbeddingGenerator? defaultEmbeddingGenerator, JsonSerializerOptions jsonSerializerOptions) { _jsonSerializerOptions = jsonSerializerOptions; return BuildDynamic(definition, defaultEmbeddingGenerator); } /// <inheritdoc/> protected override void Customize() { // This mimics the naming behavior of the System.Text.Json serializer, which we use for serialization/deserialization. // The property storage names in the model must be in sync with the serializer configuration, since the model is used e.g. for filtering // even if serialization/deserialization doesn't use the model. var namingPolicy = _jsonSerializerOptions?.PropertyNamingPolicy; foreach (var property in Properties) { var keyPropertyWithReservedName = Options.ReservedKeyStorageName is not null && property is KeyPropertyModel; string storageName; if (property.PropertyInfo?.GetCustomAttribute<JsonPropertyNameAttribute>() is { } jsonPropertyNameAttribute) { if (keyPropertyWithReservedName && jsonPropertyNameAttribute.Name != Options.ReservedKeyStorageName) { throw new InvalidOperationException($"The key property for your provider must always have the reserved name '{Options.ReservedKeyStorageName}' and cannot be changed."); } storageName = jsonPropertyNameAttribute.Name; } else if (namingPolicy is not null) { storageName = namingPolicy.ConvertName(property.ModelName); } else { storageName = property.ModelName; } if (keyPropertyWithReservedName) { // Some providers (Weaviate, Cosmos NoSQL) have a fixed, reserved storage name for keys (id), and at the same time use an external // JSON serializer to serialize the entire user POCO. Since the serializer is unaware of the reserved storage name, it will produce // a storage name as usual, based on the .NET property's name, possibly with a naming policy applied to it. The provider then needs // to look that up and replace with the reserved name. ((KeyPropertyModel)property).SerializedKeyName = storageName; } else { property.StorageName = storageName; } } } }