diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs index 9b5b5c4a65..70e1398621 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs @@ -42,6 +42,19 @@ public class Embedding : IEquatable [JsonConverter(typeof(StringEnumConverter))] public DistanceFunction DistanceFunction { get; set; } + /// + /// Gets or sets the optional describing the source + /// document paths and embedding service that the Cosmos DB service should use to + /// generate the vector value for this embedding. + /// + [JsonProperty(PropertyName = "embeddingSource", NullValueHandling = NullValueHandling.Ignore)] +#if PREVIEW + public +#else + internal +#endif + EmbeddingSource EmbeddingSource { get; set; } + /// /// This contains additional values for scenarios where the SDK is not aware of new fields. /// This ensures that if resource is read and updated none of the fields will be lost in the process. @@ -68,10 +81,16 @@ public void ValidateEmbeddingPath() /// public bool Equals(Embedding that) { + if (that is null) + { + return false; + } + return this.Path.Equals(that.Path) && this.DataType.Equals(that.DataType) && this.Dimensions == that.Dimensions - && this.Dimensions.Equals(that.Dimensions); + && this.DistanceFunction.Equals(that.DistanceFunction) + && object.Equals(this.EmbeddingSource, that.EmbeddingSource); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingAuthType.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingAuthType.cs new file mode 100644 index 0000000000..fe4067c480 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingAuthType.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System.Runtime.Serialization; + + /// + /// Defines the authentication type used by the Azure Cosmos DB service to call the + /// embedding service referenced from a . + /// +#if PREVIEW + public +#else + internal +#endif + enum EmbeddingAuthType + { + /// + /// Default sentinel — indicates no authentication type has been configured. + /// + [EnumMember(Value = "Unknown")] + Unknown = 0, + + /// + /// Authenticate to the embedding service using Microsoft Entra ID (managed identity / token credential). + /// + [EnumMember(Value = "Entra")] + Entra, + + /// + /// Authenticate to the embedding service using an API key. + /// + [EnumMember(Value = "ApiKey")] + ApiKey, + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingSource.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingSource.cs new file mode 100644 index 0000000000..f7a79e1040 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/EmbeddingSource.cs @@ -0,0 +1,118 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + using Newtonsoft.Json.Linq; + + /// + /// Describes the source document paths and the embedding service that the Azure Cosmos DB + /// service should use to generate the vector value for an . + /// + /// + /// When present on an , this block tells the SDK (and the Cosmos + /// DB embedding provider) where and how to call the embedding service for the vector + /// path in question. + /// +#if PREVIEW + public +#else + internal +#endif + sealed class EmbeddingSource : IEquatable + { + /// + /// Gets or sets the list of document paths whose values are concatenated and sent to + /// the embedding service to generate the vector. + /// + [JsonProperty(PropertyName = "sourcePaths")] + public Collection SourcePaths { get; set; } + + /// + /// Gets or sets the deployment name of the embedding model on the embedding service. + /// + [JsonProperty(PropertyName = "deploymentName")] + public string DeploymentName { get; set; } + + /// + /// Gets or sets the name of the embedding model. + /// + [JsonProperty(PropertyName = "modelName")] + public string ModelName { get; set; } + + /// + /// Gets or sets the endpoint of the embedding service. + /// + [JsonProperty(PropertyName = "endpoint")] + public string Endpoint { get; set; } + + /// + /// Gets or sets the used to authenticate to the + /// embedding service. + /// + [JsonProperty(PropertyName = "authType")] + [JsonConverter(typeof(StringEnumConverter))] + public EmbeddingAuthType AuthType { get; set; } + + /// + /// This contains additional values for scenarios where the SDK is not aware of new fields. + /// This ensures that if resource is read and updated none of the fields will be lost in the process. + /// + [JsonExtensionData] + internal IDictionary AdditionalProperties { get; private set; } + + /// + public bool Equals(EmbeddingSource that) + { + if (that is null) + { + return false; + } + + if (object.ReferenceEquals(this, that)) + { + return true; + } + + return ((this.SourcePaths == null && that.SourcePaths == null) || + (this.SourcePaths != null && that.SourcePaths != null && Enumerable.SequenceEqual(this.SourcePaths, that.SourcePaths))) + && this.AuthType == that.AuthType + && this.DeploymentName == that.DeploymentName + && this.Endpoint == that.Endpoint + && this.ModelName == that.ModelName; + } + + /// + public override bool Equals(object obj) + { + return this.Equals(obj as EmbeddingSource); + } + + /// + public override int GetHashCode() + { + int hashCode = 1265339359; + + if (this.SourcePaths != null) + { + foreach (string sourcePath in this.SourcePaths) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(sourcePath); + } + } + + hashCode = (hashCode * -1521134295) + this.AuthType.GetHashCode(); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.DeploymentName); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Endpoint); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.ModelName); + return hashCode; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs index c7f32f6027..93881cb7fb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs @@ -17,8 +17,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests [TestClass] public class ContainerSettingsTests : BaseCosmosClientHelper { - private static long ToEpoch(DateTime dateTime) => (long)(dateTime - new DateTime(1970, 1, 1)).TotalSeconds; - + private static long ToEpoch(DateTime dateTime) + { + return (long)(dateTime - new DateTime(1970, 1, 1)).TotalSeconds; + } + [TestInitialize] public async Task TestInitialize() { @@ -629,6 +632,105 @@ await databaseForVectorEmbedding.DefineContainer(containerName, partitionKeyPath } } + [TestMethod] + [Ignore("Requires a real Cosmos DB account with vector-embedding preview enabled. Fill in the endpoint and key before running.")] + public async Task TestVectorEmbeddingPolicyWithEmbeddingSource() + { + const string accountEndpoint = ""; + const string accountKey = ""; + + const string databaseId = "embeddingSourceIntegrationDb"; + const string containerId = "embeddingSourceIntegrationContainer"; + const string partitionKeyPath = "/pk"; + const string embeddingPath = "/embedding"; + + CosmosClient client = new CosmosClient(accountEndpoint, accountKey); + Database database = await client.CreateDatabaseIfNotExistsAsync(databaseId); + + try + { + EmbeddingSource embeddingSource = new EmbeddingSource() + { + SourcePaths = new Collection + { + "/journal_title", + "/title", + "/toc_abstract", + "/abstract", + "/full_text", + }, + DeploymentName = "text-embedding-3-small", + ModelName = "text-embedding-3-small", + Endpoint = "https://embedding-south-central.cognitiveservices.azure.com/", + AuthType = EmbeddingAuthType.ApiKey, + }; + + Collection embeddings = new Collection() + { + new Embedding() + { + Path = embeddingPath, + DataType = VectorDataType.Float32, + DistanceFunction = DistanceFunction.Cosine, + Dimensions = 1536, + EmbeddingSource = embeddingSource, + }, + }; + + try + { + await database.GetContainer(containerId).DeleteContainerAsync(); + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + } + + ContainerResponse containerResponse = + await database.DefineContainer(containerId, partitionKeyPath) + .WithVectorEmbeddingPolicy(embeddings) + .Attach() + .CreateAsync(); + + Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); + Assert.AreEqual(containerId, containerResponse.Resource.Id); + Assert.AreEqual(partitionKeyPath, containerResponse.Resource.PartitionKey.Paths.First()); + + this.AssertEmbeddingSourceRoundTrip(containerResponse.Resource.VectorEmbeddingPolicy, embeddingPath, embeddingSource); + + ContainerResponse readResponse = await containerResponse.Container.ReadContainerAsync(); + Assert.AreEqual(HttpStatusCode.OK, readResponse.StatusCode); + Assert.AreEqual(containerId, readResponse.Resource.Id); + Assert.AreEqual(partitionKeyPath, readResponse.Resource.PartitionKey.Paths.First()); + + this.AssertEmbeddingSourceRoundTrip(readResponse.Resource.VectorEmbeddingPolicy, embeddingPath, embeddingSource); + } + finally + { + await database.DeleteAsync(); + client.Dispose(); + } + } + + private void AssertEmbeddingSourceRoundTrip(VectorEmbeddingPolicy policy, string expectedEmbeddingPath, EmbeddingSource expected) + { + Assert.IsNotNull(policy); + Assert.AreEqual(1, policy.Embeddings.Count()); + + Embedding readEmbedding = policy.Embeddings.Single(); + Assert.AreEqual(expectedEmbeddingPath, readEmbedding.Path); + Assert.AreEqual(VectorDataType.Float32, readEmbedding.DataType); + Assert.AreEqual(DistanceFunction.Cosine, readEmbedding.DistanceFunction); + Assert.AreEqual(1536, readEmbedding.Dimensions); + + EmbeddingSource readSource = readEmbedding.EmbeddingSource; + Assert.IsNotNull(readSource, "EmbeddingSource should be returned by the server."); + CollectionAssert.AreEqual(expected.SourcePaths.ToArray(), readSource.SourcePaths.ToArray()); + Assert.AreEqual(expected.DeploymentName, readSource.DeploymentName); + Assert.AreEqual(expected.ModelName, readSource.ModelName); + Assert.AreEqual(expected.Endpoint, readSource.Endpoint); + Assert.AreEqual(expected.AuthType, readSource.AuthType); + } + [TestMethod] public async Task WithIndexingPolicy() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json index b69524604c..9e97718123 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json @@ -1221,6 +1221,197 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.Embedding;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.EmbeddingSource EmbeddingSource[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = NullValueHandling.Ignore = 1, PropertyName = \"embeddingSource\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingSource EmbeddingSource;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.EmbeddingSource get_EmbeddingSource();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_EmbeddingSource(Microsoft.Azure.Cosmos.EmbeddingSource);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.EmbeddingSource get_EmbeddingSource()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingSource get_EmbeddingSource();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_EmbeddingSource(Microsoft.Azure.Cosmos.EmbeddingSource)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_EmbeddingSource(Microsoft.Azure.Cosmos.EmbeddingSource);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Subclasses": {}, + "Members": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType ApiKey[System.Runtime.Serialization.EnumMemberAttribute(Value = \"ApiKey\")]": { + "Type": "Field", + "Attributes": [ + "EnumMemberAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingAuthType ApiKey;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType Entra[System.Runtime.Serialization.EnumMemberAttribute(Value = \"Entra\")]": { + "Type": "Field", + "Attributes": [ + "EnumMemberAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingAuthType Entra;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType Unknown[System.Runtime.Serialization.EnumMemberAttribute(Value = \"Unknown\")]": { + "Type": "Field", + "Attributes": [ + "EnumMemberAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingAuthType Unknown;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.EmbeddingSource;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Boolean Equals(Microsoft.Azure.Cosmos.EmbeddingSource)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.EmbeddingSource);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" + }, + "Boolean Equals(System.Object)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 GetHashCode()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType AuthType[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"authType\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]": { + "Type": "Property", + "Attributes": [ + "JsonConverterAttribute", + "JsonPropertyAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingAuthType AuthType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.EmbeddingAuthType get_AuthType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_AuthType(Microsoft.Azure.Cosmos.EmbeddingAuthType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.EmbeddingAuthType get_AuthType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.EmbeddingAuthType get_AuthType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Collections.ObjectModel.Collection`1[System.String] get_SourcePaths()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.Collection`1[System.String] get_SourcePaths();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Collections.ObjectModel.Collection`1[System.String] SourcePaths[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"sourcePaths\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.Collection`1[System.String] SourcePaths;CanRead:True;CanWrite:True;System.Collections.ObjectModel.Collection`1[System.String] get_SourcePaths();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_SourcePaths(System.Collections.ObjectModel.Collection`1[System.String]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String DeploymentName[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"deploymentName\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.String DeploymentName;CanRead:True;CanWrite:True;System.String get_DeploymentName();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_DeploymentName(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String Endpoint[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"endpoint\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.String Endpoint;CanRead:True;CanWrite:True;System.String get_Endpoint();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Endpoint(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_DeploymentName()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_DeploymentName();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_Endpoint()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_Endpoint();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_ModelName()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_ModelName();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String ModelName[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"modelName\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.String ModelName;CanRead:True;CanWrite:True;System.String get_ModelName();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_ModelName(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "Void .ctor()" + }, + "Void set_AuthType(Microsoft.Azure.Cosmos.EmbeddingAuthType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_AuthType(Microsoft.Azure.Cosmos.EmbeddingAuthType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_DeploymentName(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_DeploymentName(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_Endpoint(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_Endpoint(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_ModelName(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_ModelName(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_SourcePaths(System.Collections.ObjectModel.Collection`1[System.String])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_SourcePaths(System.Collections.ObjectModel.Collection`1[System.String]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs index 0e023740e1..425c4f7a3f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs @@ -1154,6 +1154,80 @@ public void VectorEmbeddingPolicySerialization() Assert.IsTrue(embedding2.Equals(vectorEmbeddings.Value()[1].ToObject())); } + [TestMethod] + public void EmbeddingSourceRoundTripSerialization() + { + const string embeddingPolicyJson = "{\"vectorEmbeddings\":[{\"path\":\"/embedding\",\"dataType\":\"float32\",\"dimensions\":1536,\"distanceFunction\":\"cosine\",\"embeddingSource\":{\"sourcePaths\":[\"/journal_title\",\"/title\",\"/toc_abstract\",\"/abstract\",\"/full_text\"],\"deploymentName\":\"text-embedding-3-small\",\"modelName\":\"text-embedding-3-small\",\"endpoint\":\"https://embedding-south-central.cognitiveservices.azure.com/\",\"authType\":\"ApiKey\"}},{\"path\":\"/embedding2\",\"dataType\":\"float32\",\"dimensions\":1536,\"distanceFunction\":\"cosine\",\"embeddingSource\":{\"sourcePaths\":[\"/title\"],\"deploymentName\":\"text-embedding-3-small\",\"modelName\":\"text-embedding-3-small\",\"endpoint\":\"https://embedding-south-central.cognitiveservices.azure.com/\",\"authType\":\"Entra\"}}]}"; + + Cosmos.VectorEmbeddingPolicy policy = JsonConvert.DeserializeObject(embeddingPolicyJson); + Cosmos.EmbeddingSource source = policy.Embeddings[0].EmbeddingSource; + CollectionAssert.AreEqual( + new[] { "/journal_title", "/title", "/toc_abstract", "/abstract", "/full_text" }, + source.SourcePaths.ToArray()); + Assert.AreEqual("text-embedding-3-small", source.DeploymentName); + Assert.AreEqual("text-embedding-3-small", source.ModelName); + Assert.AreEqual("https://embedding-south-central.cognitiveservices.azure.com/", source.Endpoint); + Assert.AreEqual(Cosmos.EmbeddingAuthType.ApiKey, source.AuthType); + Assert.AreEqual(Cosmos.EmbeddingAuthType.Entra, policy.Embeddings[1].EmbeddingSource.AuthType); + + string roundTripped = JsonConvert.SerializeObject(policy); + Assert.IsTrue( + JToken.DeepEquals(JObject.Parse(embeddingPolicyJson), JObject.Parse(roundTripped)), + $"Round-tripped JSON differs.\nExpected: {embeddingPolicyJson}\nActual: {roundTripped}"); + } + + [TestMethod] + public void EmbeddingSourceValueEquality() + { + static Cosmos.EmbeddingSource Build(string deployment, Cosmos.EmbeddingAuthType auth) + { + return new() + { + SourcePaths = new Collection { "/title", "/abstract" }, + DeploymentName = deployment, + ModelName = "text-embedding-3-small", + Endpoint = "https://embedding.example.com/", + AuthType = auth, + }; + } + + Cosmos.EmbeddingSource a = Build("text-embedding-3-small", Cosmos.EmbeddingAuthType.ApiKey); + Cosmos.EmbeddingSource b = Build("text-embedding-3-small", Cosmos.EmbeddingAuthType.ApiKey); + + Assert.AreNotSame(a, b); + Assert.IsTrue(a.Equals(b)); + Assert.IsTrue(a.Equals((object)b)); + Assert.AreEqual(a.GetHashCode(), b.GetHashCode()); + + Cosmos.EmbeddingSource differentAuth = Build("text-embedding-3-small", Cosmos.EmbeddingAuthType.Entra); + Assert.IsFalse(a.Equals(differentAuth)); + + Cosmos.EmbeddingSource reorderedPaths = Build("text-embedding-3-small", Cosmos.EmbeddingAuthType.ApiKey); + reorderedPaths.SourcePaths = new Collection { "/abstract", "/title" }; + Assert.IsFalse(a.Equals(reorderedPaths)); + + Assert.IsFalse(a.Equals((Cosmos.EmbeddingSource)null)); + Assert.IsFalse(a.Equals((object)null)); + + Cosmos.Embedding e1 = new Cosmos.Embedding() + { + Path = "/embedding", + DataType = Cosmos.VectorDataType.Float32, + DistanceFunction = Cosmos.DistanceFunction.Cosine, + Dimensions = 1536, + EmbeddingSource = a, + }; + Cosmos.Embedding e2 = new Cosmos.Embedding() + { + Path = "/embedding", + DataType = Cosmos.VectorDataType.Float32, + DistanceFunction = Cosmos.DistanceFunction.Cosine, + Dimensions = 1536, + EmbeddingSource = b, + }; + Assert.IsTrue(e1.Equals(e2)); + } + [TestMethod] public void FullTextPolicySerialization() { diff --git a/changelog.md b/changelog.md index ef4502bb30..a64224c403 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added - [5815](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5815) Read Consistency Strategy: Adds hub region header for LastCommittedSingleWriteRegion strategy. +- [5848](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5848) VectorEmbeddingPolicy: Adds EmbeddingSource block to Embedding model ### [3.60.0-preview.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.60.0-preview.0) - 2026-4-24