Skip to content
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ClientOfficialVersion>3.55.0</ClientOfficialVersion>
<ClientPreviewVersion>3.56.0</ClientPreviewVersion>
<ClientPreviewSuffixVersion>preview.0</ClientPreviewSuffixVersion>
<DirectVersion>3.41.2</DirectVersion>
<DirectVersion>3.41.3</DirectVersion>
Comment thread
aavasthy marked this conversation as resolved.
<FaultInjectionVersion>1.0.0</FaultInjectionVersion>
<FaultInjectionSuffixVersion>beta.0</FaultInjectionSuffixVersion>
<EncryptionOfficialVersion>2.0.5</EncryptionOfficialVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ public VectorIndexDefinition<T> Path(
return this;
}

/// <summary>
/// Configures the quantizer type for the current <see cref="VectorIndexPath"/> definition.
/// </summary>
/// <param name="quantizerType">
/// The quantizer type to be used for vector quantization. This is an optional parameter and applies to index
/// types DiskANN and quantizedFlat. Allowed values are Product and Spherical.
/// </param>
/// <returns>An instance of the current <see cref="VectorIndexDefinition{T}"/>.</returns>
#if PREVIEW
public
#else
internal
#endif
VectorIndexDefinition<T> WithQuantizerType(
QuantizerType quantizerType)
{
this.vectorIndexPath.QuantizerType = quantizerType;
return this;
}

/// <summary>
/// Configures the quantization byte size for the current <see cref="VectorIndexPath"/> definition.
/// </summary>
Expand Down
108 changes: 108 additions & 0 deletions Microsoft.Azure.Cosmos/src/Resource/Settings/IndexingPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,114 @@ public int GetHashCode(IndexingPolicy indexingPolicy)
return hashCode;
}
}

internal sealed class VectorIndexPathEqualityComparer : IEqualityComparer<VectorIndexPath>
{
public static readonly VectorIndexPathEqualityComparer Singleton = new VectorIndexPathEqualityComparer();

public bool Equals(VectorIndexPath vectorIndexPath1, VectorIndexPath vectorIndexPath2)
{
if (Object.ReferenceEquals(vectorIndexPath1, vectorIndexPath2))
{
return true;
}

if (vectorIndexPath1 == null || vectorIndexPath2 == null)
{
return false;
}

if (vectorIndexPath1.Path != vectorIndexPath2.Path ||
vectorIndexPath1.Type != vectorIndexPath2.Type ||
vectorIndexPath1.QuantizerType != vectorIndexPath2.QuantizerType ||
vectorIndexPath1.QuantizationByteSize != vectorIndexPath2.QuantizationByteSize ||
vectorIndexPath1.IndexingSearchListSize != vectorIndexPath2.IndexingSearchListSize ||
!vectorIndexPath1.AdditionalProperties.EqualsTo(vectorIndexPath2.AdditionalProperties))
{
return false;
}

// Compare VectorIndexShardKey arrays
if (vectorIndexPath1.VectorIndexShardKey == null && vectorIndexPath2.VectorIndexShardKey == null)
{
return true;
}

if (vectorIndexPath1.VectorIndexShardKey == null || vectorIndexPath2.VectorIndexShardKey == null)
{
return false;
}

if (vectorIndexPath1.VectorIndexShardKey.Length != vectorIndexPath2.VectorIndexShardKey.Length)
{
return false;
}

HashSet<string> shardKeys1 = new HashSet<string>(vectorIndexPath1.VectorIndexShardKey);
HashSet<string> shardKeys2 = new HashSet<string>(vectorIndexPath2.VectorIndexShardKey);

return shardKeys1.SetEquals(shardKeys2);
}

public int GetHashCode(VectorIndexPath vectorIndexPath)
{
if (vectorIndexPath == null)
{
return 0;
}

int hashCode = 0;
hashCode ^= vectorIndexPath.Path?.GetHashCode() ?? 0;
hashCode ^= vectorIndexPath.Type.GetHashCode();
hashCode ^= vectorIndexPath.QuantizerType?.GetHashCode() ?? 0;
hashCode ^= vectorIndexPath.QuantizationByteSize.GetHashCode();
hashCode ^= vectorIndexPath.IndexingSearchListSize.GetHashCode();

if (vectorIndexPath.VectorIndexShardKey != null)
{
foreach (string shardKey in vectorIndexPath.VectorIndexShardKey)
{
hashCode ^= shardKey?.GetHashCode() ?? 0;
}
}

return hashCode;
}
}

internal sealed class VectorIndexesEqualityComparer : IEqualityComparer<Collection<VectorIndexPath>>
{
private static readonly VectorIndexPathEqualityComparer vectorIndexPathEqualityComparer = new VectorIndexPathEqualityComparer();

public bool Equals(Collection<VectorIndexPath> vectorIndexes1, Collection<VectorIndexPath> vectorIndexes2)
{
if (Object.ReferenceEquals(vectorIndexes1, vectorIndexes2))
{
return true;
}

if (vectorIndexes1 == null || vectorIndexes2 == null)
{
return false;
}

HashSet<VectorIndexPath> hashedVectorIndexes1 = new HashSet<VectorIndexPath>(vectorIndexes1, vectorIndexPathEqualityComparer);
HashSet<VectorIndexPath> hashedVectorIndexes2 = new HashSet<VectorIndexPath>(vectorIndexes2, vectorIndexPathEqualityComparer);

return hashedVectorIndexes1.SetEquals(hashedVectorIndexes2);
}

public int GetHashCode(Collection<VectorIndexPath> vectorIndexes)
{
int hashCode = 0;
foreach (VectorIndexPath vectorIndexPath in vectorIndexes)
{
hashCode ^= vectorIndexPathEqualityComparer.GetHashCode(vectorIndexPath);
}

return hashCode;
}
}
#endregion
}
}
33 changes: 33 additions & 0 deletions Microsoft.Azure.Cosmos/src/Resource/Settings/QuantizerType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

/// <summary>
/// Defines the quantizer type of a vector index path specification in the Azure Cosmos DB service.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
#if PREVIEW
public
#else
internal
#endif
enum QuantizerType
{
/// <summary>
/// Represents a product quantizer type.
/// </summary>
[EnumMember(Value = "product")]
Comment thread
aavasthy marked this conversation as resolved.
Product,

/// <summary>
/// Represents a spherical quantizer type.
/// </summary>
[EnumMember(Value = "spherical")]
Spherical
}
}
15 changes: 15 additions & 0 deletions Microsoft.Azure.Cosmos/src/Resource/Settings/VectorIndexPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace Microsoft.Azure.Cosmos
/// {
/// "path": "/embeddings/vector",
/// "type": "DiskANN",
/// "quantizerType": "product", // or "spherical"
/// "quantizationByteSize": 2,
/// "indexingSearchListSize": 100,
/// "vectorIndexShardKey": ["/Country"]
Expand Down Expand Up @@ -68,6 +69,20 @@ public sealed class VectorIndexPath
[JsonConverter(typeof(StringEnumConverter))]
public VectorIndexType Type { get; set; }

/// <summary>
/// Gets or sets the quantizer type for the vector index path. This is only applicable for the quantizedFlat and diskann vector index types.
/// Allowed values are "product" and "spherical".
/// </summary>
[JsonProperty(PropertyName = Constants.Properties.QuantizerType, NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
#if PREVIEW
public
#else
internal
#endif
QuantizerType? QuantizerType
{ get; set; }

/// <summary>
/// Gets or sets the quantization byte size for the vector index path. This is only applicable for the quantizedFlat and diskann vector index types.
/// The allowed range for this parameter is between 1 and min(dimensions, 512).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,40 +212,42 @@ public void ValidateVectorEmbeddingsAndIndexes()
};

Collection<Cosmos.Embedding> embeddings = new Collection<Cosmos.Embedding>()
{
embedding1,
embedding2,
embedding3,
};
{
embedding1,
embedding2,
embedding3,
};

ContainerProperties containerSettings = new ContainerProperties(id: "TestContainer", partitionKeyPath: "/partitionKey")
{
VectorEmbeddingPolicy = new(embeddings),
IndexingPolicy = new Cosmos.IndexingPolicy()
{
VectorIndexes = new()
{
new Cosmos.VectorIndexPath()
{
Path = "/vector1",
Type = Cosmos.VectorIndexType.Flat,
},
new Cosmos.VectorIndexPath()
{
Path = "/vector2",
Type = Cosmos.VectorIndexType.QuantizedFlat,
VectorIndexShardKey = new[] { "/Country" },
QuantizationByteSize = 3,
},
new Cosmos.VectorIndexPath()
{
Path = "/vector3",
Type = Cosmos.VectorIndexType.DiskANN,
VectorIndexShardKey = new[] { "/ZipCode" },
QuantizationByteSize = 2,
IndexingSearchListSize = 5,
}
},
{
new Cosmos.VectorIndexPath()
{
Path = "/vector1",
Type = Cosmos.VectorIndexType.Flat,
},
new Cosmos.VectorIndexPath()
{
Path = "/vector2",
Type = Cosmos.VectorIndexType.QuantizedFlat,
QuantizerType = Cosmos.QuantizerType.Product,
VectorIndexShardKey = new[] { "/Country" },
QuantizationByteSize = 3,
},
new Cosmos.VectorIndexPath()
{
Path = "/vector3",
Type = Cosmos.VectorIndexType.DiskANN,
QuantizerType = Cosmos.QuantizerType.Spherical,
VectorIndexShardKey = new[] { "/ZipCode" },
QuantizationByteSize = 2,
IndexingSearchListSize = 5,
}
},
},
};

Expand All @@ -261,18 +263,99 @@ public void ValidateVectorEmbeddingsAndIndexes()
Collection<Cosmos.VectorIndexPath> vectorIndexes = containerSettings.IndexingPolicy.VectorIndexes;
Assert.AreEqual("/vector1", vectorIndexes[0].Path);
Assert.AreEqual(Cosmos.VectorIndexType.Flat, vectorIndexes[0].Type);
Assert.IsNull(vectorIndexes[0].QuantizerType); // Flat type doesn't use quantizer

Assert.AreEqual("/vector2", vectorIndexes[1].Path);
Assert.AreEqual(Cosmos.VectorIndexType.QuantizedFlat, vectorIndexes[1].Type);
Assert.AreEqual(Cosmos.QuantizerType.Product, vectorIndexes[1].QuantizerType);
Assert.AreEqual(3, vectorIndexes[1].QuantizationByteSize);
CollectionAssert.AreEqual(new string[] { "/Country" }, vectorIndexes[1].VectorIndexShardKey);

Assert.AreEqual("/vector3", vectorIndexes[2].Path);
Assert.AreEqual(Cosmos.VectorIndexType.DiskANN, vectorIndexes[2].Type);
Assert.AreEqual(Cosmos.QuantizerType.Spherical, vectorIndexes[2].QuantizerType);
Assert.AreEqual(2, vectorIndexes[2].QuantizationByteSize);
Assert.AreEqual(5, vectorIndexes[2].IndexingSearchListSize);
CollectionAssert.AreEqual(new string[] { "/ZipCode" }, vectorIndexes[2].VectorIndexShardKey);
}

[TestMethod]
public void ValidateVectorIndexQuantizerTypeSerialization()
{
// Test with Product quantizer
Cosmos.VectorIndexPath vectorIndexProduct = new Cosmos.VectorIndexPath()
{
Path = "/vector",
Type = Cosmos.VectorIndexType.DiskANN,
QuantizerType = Cosmos.QuantizerType.Product,
QuantizationByteSize = 2,
IndexingSearchListSize = 100
};

// Serialize
string serializedProduct = JsonConvert.SerializeObject(vectorIndexProduct);

// Verify the JSON contains quantizerType
Assert.IsTrue(serializedProduct.Contains("\"quantizerType\":\"product\""));

// Deserialize
Cosmos.VectorIndexPath deserializedProduct = JsonConvert.DeserializeObject<Cosmos.VectorIndexPath>(serializedProduct);

// Verify round-trip
Assert.AreEqual(vectorIndexProduct.Path, deserializedProduct.Path);
Assert.AreEqual(vectorIndexProduct.Type, deserializedProduct.Type);
Assert.AreEqual(Cosmos.QuantizerType.Product, deserializedProduct.QuantizerType);
Assert.AreEqual(vectorIndexProduct.QuantizationByteSize, deserializedProduct.QuantizationByteSize);
Assert.AreEqual(vectorIndexProduct.IndexingSearchListSize, deserializedProduct.IndexingSearchListSize);

// Test with Spherical quantizer
Cosmos.VectorIndexPath vectorIndexSpherical = new Cosmos.VectorIndexPath()
{
Path = "/embedding",
Type = Cosmos.VectorIndexType.QuantizedFlat,
QuantizerType = Cosmos.QuantizerType.Spherical,
QuantizationByteSize = 3,
VectorIndexShardKey = new[] { "/region" }
};

// Serialize
string serializedSpherical = JsonConvert.SerializeObject(vectorIndexSpherical);

// Verify the JSON contains quantizerType
Assert.IsTrue(serializedSpherical.Contains("\"quantizerType\":\"spherical\""));

// Deserialize
Cosmos.VectorIndexPath deserializedSpherical = JsonConvert.DeserializeObject<Cosmos.VectorIndexPath>(serializedSpherical);

// Verify round-trip
Assert.AreEqual(vectorIndexSpherical.Path, deserializedSpherical.Path);
Assert.AreEqual(vectorIndexSpherical.Type, deserializedSpherical.Type);
Assert.AreEqual(Cosmos.QuantizerType.Spherical, deserializedSpherical.QuantizerType);
Assert.AreEqual(vectorIndexSpherical.QuantizationByteSize, deserializedSpherical.QuantizationByteSize);
CollectionAssert.AreEqual(vectorIndexSpherical.VectorIndexShardKey, deserializedSpherical.VectorIndexShardKey);

// Test with null quantizer type (for Flat index)
Cosmos.VectorIndexPath vectorIndexFlat = new Cosmos.VectorIndexPath()
{
Path = "/flatVector",
Type = Cosmos.VectorIndexType.Flat
};

// Serialize
string serializedFlat = JsonConvert.SerializeObject(vectorIndexFlat);

// Verify the JSON doesn't contain quantizerType (null value handling)
Assert.IsFalse(serializedFlat.Contains("quantizerType"));

// Deserialize
Cosmos.VectorIndexPath deserializedFlat = JsonConvert.DeserializeObject<Cosmos.VectorIndexPath>(serializedFlat);

// Verify round-trip
Assert.AreEqual(vectorIndexFlat.Path, deserializedFlat.Path);
Assert.AreEqual(vectorIndexFlat.Type, deserializedFlat.Type);
Assert.IsNull(deserializedFlat.QuantizerType);
}

[TestMethod]
public void ValidateFullTextPathsAndIndexes()
{
Expand Down
Loading
Loading