From 79b40d54c256612b990f2d1ad1e24ba293db47ad Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 25 Jan 2026 20:45:38 +0100 Subject: [PATCH] Remove old MEVD integration tests And port a few missing cases to the new compliance tests. Closes #12506 --- .../AzureAISearchConfigConditionAttribute.cs | 25 - .../AzureAISearch/AzureAISearchHotel.cs | 48 -- .../AzureAISearchTextSearchTests.cs | 147 ----- ...ureAISearchVectorStoreCollectionFixture.cs | 10 - .../AzureAISearchVectorStoreFixture.cs | 262 -------- ...ISearchVectorStoreRecordCollectionTests.cs | 426 ------------- .../AzureAISearchVectorStoreTests.cs | 20 - .../BaseVectorStoreRecordCollectionTests.cs | 138 ----- .../Connectors/Memory/BaseVectorStoreTests.cs | 51 -- .../Memory/Chroma/ChromaMemoryStoreTests.cs | 472 -------------- .../CosmosMongoCollectionFixture.cs | 9 - .../CosmosMongoCollectionTests.cs | 520 ---------------- .../Memory/CosmosMongoDB/CosmosMongoHotel.cs | 48 -- .../CosmosMongoVectorStoreFixture.cs | 101 --- .../CosmosMongoVectorStoreTests.cs | 17 - .../Memory/CosmosMongoDB/DataHelper.cs | 46 -- .../CosmosNoSqlCollectionFixture.cs | 9 - .../CosmosNoSql/CosmosNoSqlCollectionTests.cs | 455 -------------- ...qlConnectionStringSetConditionAttribute.cs | 24 - .../Memory/CosmosNoSql/CosmosNoSqlHotel.cs | 50 -- .../CosmosNoSqlVectorStoreFixture.cs | 84 --- .../CosmosNoSqlVectorStoreTests.cs | 18 - .../Memory/CosmosNoSql/DataHelper.cs | 36 -- ...nMemoryVectorStoreRecordCollectionTests.cs | 31 - .../InMemoryVectorStoreTextSearchTests.cs | 81 --- .../Connectors/Memory/Milvus/MilvusFixture.cs | 31 - .../Memory/Milvus/MilvusMemoryStoreTests.cs | 352 ----------- .../Connectors/Memory/MongoDB/DataHelper.cs | 44 -- .../Connectors/Memory/MongoDB/MongoDBHotel.cs | 48 -- .../MongoDBVectorStoreCollectionFixture.cs | 9 - .../MongoDB/MongoDBVectorStoreFixture.cs | 92 --- ...MongoDBVectorStoreRecordCollectionTests.cs | 519 ---------------- .../Memory/MongoDB/MongoDBVectorStoreTests.cs | 17 - ...ostgresVectorStoreRecordCollectionTests.cs | 34 -- .../Memory/PgVector/PostgresHotel.cs | 60 -- .../PostgresVectorStoreCollectionFixture.cs | 10 - .../PgVector/PostgresVectorStoreFixture.cs | 247 -------- ...ostgresVectorStoreRecordCollectionTests.cs | 538 ---------------- .../PgVector/PostgresVectorStoreTests.cs | 28 - ...nQdrantVectorStoreRecordCollectionTests.cs | 35 -- .../Memory/Qdrant/QdrantTextSearchTests.cs | 94 --- .../QdrantVectorStoreCollectionFixture.cs | 10 - .../Memory/Qdrant/QdrantVectorStoreFixture.cs | 373 ------------ .../QdrantVectorStoreRecordCollectionTests.cs | 467 -------------- .../Memory/Qdrant/QdrantVectorStoreTests.cs | 42 -- ...HashsetVectorStoreRecordCollectionTests.cs | 38 -- ...disJsonVectorStoreRecordCollectionTests.cs | 38 -- ...HashSetVectorStoreRecordCollectionTests.cs | 471 -------------- .../Connectors/Memory/Redis/RedisHotel.cs | 102 ---- ...disJsonVectorStoreRecordCollectionTests.cs | 506 --------------- .../RedisVectorStoreCollectionFixture.cs | 10 - .../Memory/Redis/RedisVectorStoreFixture.cs | 237 -------- .../Memory/Redis/RedisVectorStoreTests.cs | 20 - .../Memory/SqliteVec/SqliteHotel.cs | 37 -- .../SqliteVectorStoreCollectionFixture.cs | 9 - .../SqliteVec/SqliteVectorStoreFixture.cs | 46 -- .../SqliteVectorStoreRecordCollectionTests.cs | 575 ------------------ .../SqliteVec/SqliteVectorStoreTests.cs | 47 -- ...eaviateVectorStoreRecordCollectionTests.cs | 43 -- .../Memory/Weaviate/WeaviateHotel.cs | 49 -- .../WeaviateVectorStoreCollectionFixture.cs | 9 - .../Weaviate/WeaviateVectorStoreFixture.cs | 126 ---- ...eaviateVectorStoreRecordCollectionTests.cs | 472 -------------- .../Weaviate/WeaviateVectorStoreTests.cs | 20 - .../Memory/Weaviate/docker-compose.yml | 38 -- .../Xunit/DisableVectorStoreTestsAttribute.cs | 23 - .../Connectors/Memory/Xunit/ITestCondition.cs | 12 - .../Memory/Xunit/VectorStoreFactAttribute.cs | 11 - .../Memory/Xunit/VectorStoreFactDiscoverer.cs | 19 - .../Memory/Xunit/VectorStoreFactTestCase.cs | 42 -- .../Xunit/VectorStoreTheoryAttribute.cs | 11 - .../Xunit/VectorStoreTheoryDiscoverer.cs | 36 -- .../Memory/Xunit/VectorStoreTheoryTestCase.cs | 41 -- .../Memory/Xunit/XunitTestCaseExtensions.cs | 55 -- .../CosmosMongoBsonMappingTests.cs | 145 +++++ .../CosmosNoSqlCollectionOptionsTests.cs | 126 ++++ .../MongoBsonMappingTests.cs | 144 +++++ .../RedisJsonOptionsTests.cs | 121 ++++ .../ModelTests/BasicModelTests.cs | 20 + 79 files changed, 556 insertions(+), 9321 deletions(-) delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchConfigConditionAttribute.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Chroma/ChromaMemoryStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/DataHelper.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlConnectionStringSetConditionAttribute.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/DataHelper.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/InMemory/CommonInMemoryVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/InMemory/InMemoryVectorStoreTextSearchTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusMemoryStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/DataHelper.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/CommonPostgresVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/CommonQdrantVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantTextSearchTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisHashsetVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisJsonVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/CommonWeaviateVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateHotel.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreCollectionFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreFixture.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreRecordCollectionTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/docker-compose.yml delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/DisableVectorStoreTestsAttribute.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/ITestCondition.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactAttribute.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactDiscoverer.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactTestCase.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryAttribute.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryDiscoverer.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryTestCase.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Xunit/XunitTestCaseExtensions.cs create mode 100644 dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoBsonMappingTests.cs create mode 100644 dotnet/test/VectorData/CosmosNoSql.ConformanceTests/CosmosNoSqlCollectionOptionsTests.cs create mode 100644 dotnet/test/VectorData/MongoDB.ConformanceTests/MongoBsonMappingTests.cs create mode 100644 dotnet/test/VectorData/Redis.ConformanceTests/RedisJsonOptionsTests.cs diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchConfigConditionAttribute.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchConfigConditionAttribute.cs deleted file mode 100644 index d53e7174f5e3..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchConfigConditionAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -/// -/// Attribute to use to skip tests if the settings for Azure AI Search is not set. -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public sealed class AzureAISearchConfigConditionAttribute : Attribute, ITestCondition -{ - public ValueTask IsMetAsync() - { - var config = AzureAISearchVectorStoreFixture.GetAzureAISearchConfiguration(); - var isMet = config is not null && !string.IsNullOrWhiteSpace(config?.ServiceUrl) && !string.IsNullOrWhiteSpace(config?.ApiKey); - - return ValueTask.FromResult(isMet); - } - - public string SkipReason - => "Azure AI Search ServiceUrl or ApiKey was not specified in user secrets. Use the following command to set them: dotnet user-secrets set \"AzureAISearch:ServiceUrl\" \"your_service_url\" and dotnet user-secrets set \"AzureAISearch:ApiKey\" \"your_api_key\""; -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs deleted file mode 100644 index c77edf7fc438..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Text.Json.Serialization; -using Azure.Search.Documents.Indexes; -using Azure.Search.Documents.Indexes.Models; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -#pragma warning disable CS8618 - -public class AzureAISearchHotel -{ - [SimpleField(IsKey = true, IsFilterable = true)] - [VectorStoreKey] - public string HotelId { get; set; } - - [SearchableField(IsFilterable = true, IsSortable = true)] - [VectorStoreData(IsIndexed = true, IsFullTextIndexed = true)] - public string HotelName { get; set; } - - [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] - [VectorStoreData] - public string Description { get; set; } - - [VectorStoreVector(1536)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - - [SearchableField(IsFilterable = true, IsFacetable = true)] - [VectorStoreData(IsIndexed = true)] -#pragma warning disable CA1819 // Properties should not return arrays - public string[] Tags { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays - - [JsonPropertyName("parking_is_included")] - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreData(IsIndexed = true)] - public bool? ParkingIncluded { get; set; } - - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreData(IsIndexed = true)] - public DateTimeOffset? LastRenovationDate { get; set; } - - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreData] - public double? Rating { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs deleted file mode 100644 index aaf65fa5cb4a..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using Azure.AI.OpenAI; -using Azure.Identity; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel.Connectors.AzureAISearch; -using Microsoft.SemanticKernel.Data; -using SemanticKernel.IntegrationTests.Data; -using SemanticKernel.IntegrationTests.TestSettings; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -/// -/// Integration tests for using with . -/// -[Collection("AzureAISearchVectorStoreCollection")] -public class AzureAISearchTextSearchTests(AzureAISearchVectorStoreFixture fixture) : BaseVectorStoreTextSearchTests -{ - // If null, all tests will be enabled - private const string SkipReason = "Requires Azure AI Search Service instance up and running"; - - [Fact(Skip = SkipReason)] - public override async Task CanSearchAsync() - { - await base.CanSearchAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task CanGetTextSearchResultsAsync() - { - await base.CanGetTextSearchResultsAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task CanGetSearchResultsAsync() - { - await base.CanGetSearchResultsAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task UsingTextSearchWithAFilterAsync() - { - await base.UsingTextSearchWithAFilterAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task FunctionCallingUsingCreateWithSearchAsync() - { - await base.FunctionCallingUsingCreateWithSearchAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task FunctionCallingUsingCreateWithGetSearchResultsAsync() - { - await base.FunctionCallingUsingCreateWithGetSearchResultsAsync(); - } - - [Fact(Skip = SkipReason)] - public override async Task FunctionCallingUsingGetTextSearchResultsAsync() - { - await base.FunctionCallingUsingGetTextSearchResultsAsync(); - } - - /// - public override Task CreateTextSearchAsync() - { - if (this.VectorStore is null) - { - AzureOpenAIConfiguration? azureOpenAIConfiguration = this.Configuration.GetSection("AzureOpenAIEmbeddings").Get(); - Assert.NotNull(azureOpenAIConfiguration); - Assert.NotEmpty(azureOpenAIConfiguration.DeploymentName); - Assert.NotEmpty(azureOpenAIConfiguration.Endpoint); - - this.EmbeddingGenerator = new AzureOpenAIClient(new Uri(azureOpenAIConfiguration.Endpoint), new AzureCliCredential()) - .GetEmbeddingClient(azureOpenAIConfiguration.DeploymentName) - .AsIEmbeddingGenerator(); - - this.VectorStore = new AzureAISearchVectorStore(fixture.SearchIndexClient, new() { EmbeddingGenerator = this.EmbeddingGenerator }); - } - - var vectorSearch = this.VectorStore.GetCollection(fixture.TestIndexName); - var stringMapper = new HotelTextSearchStringMapper(); - var resultMapper = new HotelTextSearchResultMapper(); - - // TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the AzureAISearchVectorStore above instead of passing it here. - var result = new VectorStoreTextSearch(vectorSearch, this.EmbeddingGenerator!, stringMapper, resultMapper); - - return Task.FromResult(result); - } - - /// - public override string GetQuery() => "Find a great hotel"; - - /// - public override TextSearchFilter GetTextSearchFilter() => new TextSearchFilter().Equality("Rating", 3.6); - - /// - public override bool VerifySearchResults(object[] results, string query, TextSearchFilter? filter = null) - { - Assert.NotNull(results); - Assert.NotEmpty(results); - Assert.Equal(filter is null ? 4 : 2, results.Length); - foreach (var result in results) - { - Assert.NotNull(result); - Assert.IsType(result); - } - - return true; - } - - /// - /// String mapper which converts a Hotel to a string. - /// - protected sealed class HotelTextSearchStringMapper : ITextSearchStringMapper - { - /// - public string MapFromResultToString(object result) - { - if (result is AzureAISearchHotel hotel) - { - return $"{hotel.HotelName} {hotel.Description}"; - } - throw new ArgumentException("Invalid result type."); - } - } - - /// - /// Result mapper which converts a Hotel to a TextSearchResult. - /// - protected sealed class HotelTextSearchResultMapper : ITextSearchResultMapper - { - /// - public TextSearchResult MapFromResultToTextSearchResult(object result) - { - if (result is AzureAISearchHotel hotel) - { - return new TextSearchResult(value: hotel.Description) { Name = hotel.HotelName, Link = $"id://{hotel.HotelId}" }; - } - throw new ArgumentException("Invalid result type."); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreCollectionFixture.cs deleted file mode 100644 index 6c9870cf0327..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -[CollectionDefinition("AzureAISearchVectorStoreCollection")] -public class AzureAISearchVectorStoreCollectionFixture : ICollectionFixture -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs deleted file mode 100644 index d70c4e444efa..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Azure; -using Azure.AI.OpenAI; -using Azure.Identity; -using Azure.Search.Documents; -using Azure.Search.Documents.Indexes; -using Azure.Search.Documents.Indexes.Models; -using Azure.Search.Documents.Models; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.VectorData; -using SemanticKernel.IntegrationTests.TestSettings; -using SemanticKernel.IntegrationTests.TestSettings.Memory; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -/// -/// Helper class for setting up and tearing down Azure AI Search indexes for testing purposes. -/// -public class AzureAISearchVectorStoreFixture : IAsyncLifetime -{ - /// - /// Test index name which consists out of "hotels-" and the machine name with any non-alphanumeric characters removed. - /// - private readonly string _testIndexName = "hotels-" + TestIndexPostfix; - - /// - /// Gets the test index name postfix that is derived from the local machine name used to avoid clashes between test runs from different callers. - /// -#pragma warning disable CA1308 // Normalize strings to uppercase - public static string TestIndexPostfix { get; private set; } = new Regex("[^a-zA-Z0-9]").Replace(Environment.MachineName.ToLowerInvariant(), ""); -#pragma warning restore CA1308 // Normalize strings to uppercase - - /// - /// Test Configuration setup. - /// - private static readonly IConfigurationRoot s_configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - - /// - /// Get the test configuration for Azure AI Search. - /// - public static AzureAISearchConfiguration? GetAzureAISearchConfiguration() - { - return s_configuration.GetSection("AzureAISearch").Get(); - } - - /// - /// Initializes a new instance of the class. - /// - public AzureAISearchVectorStoreFixture() - { - var config = GetAzureAISearchConfiguration(); - Assert.NotNull(config); - this.Config = config; - this.SearchIndexClient = new SearchIndexClient(new Uri(config.ServiceUrl), new AzureKeyCredential(config.ApiKey)); - this.VectorStoreRecordDefinition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true, IsFullTextIndexed = true }, - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 1536), - new VectorStoreDataProperty("Tags", typeof(string[])) { IsIndexed = true }, - new VectorStoreDataProperty("ParkingIncluded", typeof(bool?)) { IsIndexed = true, StorageName = "parking_is_included" }, - new VectorStoreDataProperty("LastRenovationDate", typeof(DateTimeOffset?)) { IsIndexed = true }, - new VectorStoreDataProperty("Rating", typeof(double?)) - ] - }; - AzureOpenAIConfiguration? embeddingsConfig = s_configuration.GetSection("AzureOpenAIEmbeddings").Get(); - Assert.NotNull(embeddingsConfig); - Assert.NotEmpty(embeddingsConfig.DeploymentName); - Assert.NotEmpty(embeddingsConfig.Endpoint); - - this.EmbeddingGenerator = new AzureOpenAIClient(new Uri(embeddingsConfig.Endpoint), new AzureCliCredential()) - .GetEmbeddingClient(embeddingsConfig.DeploymentName) - .AsIEmbeddingGenerator(); - } - - /// - /// Gets the Search Index Client to use for connecting to the Azure AI Search service. - /// - public SearchIndexClient SearchIndexClient { get; private set; } - - /// - /// Gets the name of the index that this fixture sets up and tears down. - /// - public string TestIndexName { get => this._testIndexName; } - - /// - /// Gets the manually created vector store record definition for our test model. - /// - public VectorStoreCollectionDefinition VectorStoreRecordDefinition { get; private set; } - - /// - /// Gets the configuration for the Azure AI Search service. - /// - public AzureAISearchConfiguration Config { get; private set; } - - /// - /// Gets the embedding generator to use for generating embeddings for text. - /// - public IEmbeddingGenerator> EmbeddingGenerator { get; private set; } - - /// - /// Gets the embedding used for all test documents that the collection is seeded with. - /// - public ReadOnlyMemory Embedding { get; private set; } - - /// - /// Create / Recreate index and upload documents before test run. - /// - /// An async task. - public async Task InitializeAsync() - { - await AzureAISearchVectorStoreFixture.DeleteIndexIfExistsAsync(this._testIndexName, this.SearchIndexClient); - await AzureAISearchVectorStoreFixture.CreateIndexAsync(this._testIndexName, this.SearchIndexClient); - await this.UploadDocumentsAsync(this.SearchIndexClient.GetSearchClient(this._testIndexName), this.EmbeddingGenerator); - } - - /// - /// Delete the index after the test run. - /// - /// An async task. - public async Task DisposeAsync() - { - await AzureAISearchVectorStoreFixture.DeleteIndexIfExistsAsync(this._testIndexName, this.SearchIndexClient); - } - - /// - /// Delete the index if it exists. - /// - /// The name of the index to delete. - /// The search index client to use for deleting the index. - /// An async task. - public static async Task DeleteIndexIfExistsAsync(string indexName, SearchIndexClient adminClient) - { - adminClient.GetIndexNames(); - { - await adminClient.DeleteIndexAsync(indexName); - } - } - - /// - /// Create an index with the given name. - /// - /// The name of the index to create. - /// The search index client to use for creating the index. - /// An async task. - public static async Task CreateIndexAsync(string indexName, SearchIndexClient adminClient) - { - AzureOpenAIConfiguration openAIConfiguration = s_configuration.GetRequiredSection("AzureOpenAIEmbeddings").Get()!; - - // Build the list of fields from the model, and then replace the DescriptionEmbedding field with a vector field, to work around - // issue where the field is not recognized as an array on parsing on the server side when apply the VectorSearchFieldAttribute. - FieldBuilder fieldBuilder = new(); - var searchFields = fieldBuilder.Build(typeof(AzureAISearchHotel)); - var embeddingfield = searchFields.First(x => x.Name == "DescriptionEmbedding"); - searchFields.Remove(embeddingfield); - searchFields.Add(new VectorSearchField("DescriptionEmbedding", 1536, "my-vector-profile")); - - // Create an index definition with a vectorizer to use when doing vector searches using text. - var definition = new SearchIndex(indexName, searchFields) - { - VectorSearch = new VectorSearch() - }; - definition.VectorSearch.Vectorizers.Add(new AzureOpenAIVectorizer("text-embedding-vectorizer") - { - Parameters = new AzureOpenAIVectorizerParameters - { - ResourceUri = new Uri(openAIConfiguration.Endpoint), - DeploymentName = openAIConfiguration.DeploymentName, - ApiKey = openAIConfiguration.ApiKey, - ModelName = openAIConfiguration.EmbeddingModelId - } - }); - definition.VectorSearch.Algorithms.Add(new HnswAlgorithmConfiguration("my-hnsw-vector-config-1") { Parameters = new HnswParameters { Metric = VectorSearchAlgorithmMetric.Cosine } }); - definition.VectorSearch.Profiles.Add(new VectorSearchProfile("my-vector-profile", "my-hnsw-vector-config-1") { VectorizerName = "text-embedding-vectorizer" }); - - var suggester = new SearchSuggester("sg", new[] { "HotelName" }); - definition.Suggesters.Add(suggester); - - await adminClient.CreateOrUpdateIndexAsync(definition); - } - - /// - /// Upload test documents to the index. - /// - /// The client to use for uploading the documents. - /// An instance of to generate embeddings. - public async Task UploadDocumentsAsync(SearchClient searchClient, IEmbeddingGenerator> embeddingGenerator) - { - this.Embedding = (await embeddingGenerator.GenerateAsync("This is a great hotel")).Vector; - - IndexDocumentsBatch batch = IndexDocumentsBatch.Create( - IndexDocumentsAction.Upload( - new AzureAISearchHotel() - { - HotelId = "BaseSet-1", - HotelName = "Hotel 1", - Description = "This is a great hotel", - DescriptionEmbedding = this.Embedding, - Tags = new[] { "pool", "air conditioning", "concierge" }, - ParkingIncluded = false, - LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - Rating = 3.6 - }), - IndexDocumentsAction.Upload( - new AzureAISearchHotel() - { - HotelId = "BaseSet-2", - HotelName = "Hotel 2", - Description = "This is a great hotel", - DescriptionEmbedding = this.Embedding, - Tags = new[] { "pool", "free wifi", "concierge" }, - ParkingIncluded = false, - LastRenovationDate = new DateTimeOffset(1979, 2, 18, 0, 0, 0, TimeSpan.Zero), - Rating = 3.60 - }), - IndexDocumentsAction.Upload( - new AzureAISearchHotel() - { - HotelId = "BaseSet-3", - HotelName = "Hotel 3", - Description = "This is a great hotel", - DescriptionEmbedding = this.Embedding, - Tags = new[] { "air conditioning", "bar", "continental breakfast" }, - ParkingIncluded = true, - LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero), - Rating = 4.80 - }), - IndexDocumentsAction.Upload( - new AzureAISearchHotel() - { - HotelId = "BaseSet-4", - HotelName = "Hotel 4", - Description = "This is a great hotel", - DescriptionEmbedding = this.Embedding, - Tags = new[] { "concierge", "view", "24-hour front desk service" }, - ParkingIncluded = true, - LastRenovationDate = new DateTimeOffset(1960, 2, 06, 0, 0, 0, TimeSpan.Zero), - Rating = 4.60 - }) - ); - - await searchClient.IndexDocumentsAsync(batch); - - // Add some delay to allow time for the documents to get indexed and show up in search. - await Task.Delay(5000); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 4d7a7c5a725b..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Azure; -using Azure.Search.Documents.Indexes; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.AzureAISearch; -using Xunit; -using Xunit.Abstractions; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Integration tests for class. -/// Tests work with an Azure AI Search Instance. -/// -[Collection("AzureAISearchVectorStoreCollection")] -public sealed class AzureAISearchVectorStoreRecordCollectionTests(ITestOutputHelper output, AzureAISearchVectorStoreFixture fixture) -{ - // If null, all tests will be enabled - private const string SkipReason = "Requires Azure AI Search Service instance up and running"; - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task CollectionExistsReturnsCollectionStateAsync(bool expectedExists) - { - // Arrange. - var collectionName = expectedExists ? fixture.TestIndexName : "nonexistentcollection"; - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, collectionName); - - // Act. - var actual = await sut.CollectionExistsAsync(); - - // Assert. - Assert.Equal(expectedExists, actual); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDefinition) - { - // Arrange - var hotel = this.CreateTestHotel("Upsert-1"); - var testCollectionName = $"{fixture.TestIndexName}-createtest"; - var options = new AzureAISearchCollectionOptions - { - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, testCollectionName, options); - - await sut.EnsureCollectionDeletedAsync(); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(hotel); - var getResult = await sut.GetAsync("Upsert-1", new() { IncludeVectors = true }); - var embedding = fixture.Embedding; - var searchResults = await sut.SearchAsync( - embedding, - top: 3, - new() - { - IncludeVectors = true, - OldFilter = new VectorSearchFilter().EqualTo("HotelName", "MyHotel Upsert-1") - }).ToListAsync(); - - // Assert - var collectionExistResult = await sut.CollectionExistsAsync(); - Assert.True(collectionExistResult); - await sut.EnsureCollectionDeletedAsync(); - - Assert.NotNull(getResult); - Assert.Equal(hotel.HotelName, getResult.HotelName); - Assert.Equal(hotel.Description, getResult.Description); - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(hotel.DescriptionEmbedding?.ToArray(), getResult.DescriptionEmbedding?.ToArray()); - Assert.Equal(hotel.Tags, getResult.Tags); - Assert.Equal(hotel.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(hotel.LastRenovationDate, getResult.LastRenovationDate); - Assert.Equal(hotel.Rating, getResult.Rating); - - Assert.Single(searchResults); - var searchResultRecord = searchResults.First().Record; - Assert.Equal(hotel.HotelName, searchResultRecord.HotelName); - Assert.Equal(hotel.Description, searchResultRecord.Description); - Assert.NotNull(searchResultRecord.DescriptionEmbedding); - Assert.Equal(hotel.DescriptionEmbedding?.ToArray(), searchResultRecord.DescriptionEmbedding?.ToArray()); - Assert.Equal(hotel.Tags, searchResultRecord.Tags); - Assert.Equal(hotel.ParkingIncluded, searchResultRecord.ParkingIncluded); - Assert.Equal(hotel.LastRenovationDate, searchResultRecord.LastRenovationDate); - Assert.Equal(hotel.Rating, searchResultRecord.Rating); - - // Output - output.WriteLine(collectionExistResult.ToString()); - output.WriteLine(getResult.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - var tempCollectionName = fixture.TestIndexName + "-delete"; - await AzureAISearchVectorStoreFixture.CreateIndexAsync(tempCollectionName, fixture.SearchIndexClient); - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, tempCollectionName); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) - { - // Arrange - var options = new AzureAISearchCollectionOptions - { - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); - - // Act - var hotel = this.CreateTestHotel("Upsert-1"); - await sut.UpsertAsync(hotel); - var getResult = await sut.GetAsync("Upsert-1", new() { IncludeVectors = true }); - - // Assert - Assert.NotNull(getResult); - Assert.Equal(hotel.HotelName, getResult.HotelName); - Assert.Equal(hotel.Description, getResult.Description); - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(hotel.DescriptionEmbedding?.ToArray(), getResult.DescriptionEmbedding?.ToArray()); - Assert.Equal(hotel.Tags, getResult.Tags); - Assert.Equal(hotel.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(hotel.LastRenovationDate, getResult.LastRenovationDate); - Assert.Equal(hotel.Rating, getResult.Rating); - - // Output - output.WriteLine(getResult.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertManyDocumentsToVectorStoreAsync() - { - // Arrange - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - - // Act - await sut.UpsertAsync( - [ - this.CreateTestHotel("UpsertMany-1"), - this.CreateTestHotel("UpsertMany-2"), - this.CreateTestHotel("UpsertMany-3"), - ]); - } - - [Theory(Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange - var options = new AzureAISearchCollectionOptions - { - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); - - // Act - var getResult = await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = includeVectors }); - - // Assert - Assert.NotNull(getResult); - - Assert.Equal("Hotel 1", getResult.HotelName); - Assert.Equal("This is a great hotel", getResult.Description); - Assert.Equal(includeVectors, getResult.DescriptionEmbedding != null); - if (includeVectors) - { - var embedding = fixture.Embedding; - Assert.Equal(embedding, getResult.DescriptionEmbedding!.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, getResult.Tags); - Assert.False(getResult.ParkingIncluded); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), getResult.LastRenovationDate); - Assert.Equal(3.6, getResult.Rating); - - // Output - output.WriteLine(getResult.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetManyDocumentsFromVectorStoreAsync() - { - // Arrange - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - - // Act - // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. - var hotels = sut.GetAsync(["BaseSet-1", "BaseSet-2", "BaseSet-3", "BaseSet-5", "BaseSet-4"], new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(hotels); - var hotelsList = await hotels.ToListAsync(); - Assert.Equal(4, hotelsList.Count); - - // Output - foreach (var hotel in hotelsList) - { - output.WriteLine(hotel.ToString()); - } - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) - { - // Arrange - var options = new AzureAISearchCollectionOptions - { - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - await sut.UpsertAsync(this.CreateTestHotel("Remove-1")); - - // Act - await sut.DeleteAsync("Remove-1"); - // Also delete a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync("Remove-2"); - - // Assert - Assert.Null(await sut.GetAsync("Remove-1", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() - { - // Arrange - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - await sut.UpsertAsync(this.CreateTestHotel("RemoveMany-1")); - await sut.UpsertAsync(this.CreateTestHotel("RemoveMany-2")); - await sut.UpsertAsync(this.CreateTestHotel("RemoveMany-3")); - - // Act - // Also include a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync(["RemoveMany-1", "RemoveMany-2", "RemoveMany-3", "RemoveMany-4"]); - - // Assert - Assert.Null(await sut.GetAsync("RemoveMany-1", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("RemoveMany-2", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("RemoveMany-3", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() - { - // Arrange - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - - // Act & Assert - Assert.Null(await sut.GetAsync("BaseSet-5", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItThrowsOperationExceptionForFailedConnectionAsync() - { - // Arrange - var searchIndexClient = new SearchIndexClient(new Uri("https://localhost:12345"), new AzureKeyCredential("12345")); - using var sut = new AzureAISearchCollection(searchIndexClient, fixture.TestIndexName); - - // Act & Assert - await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItThrowsOperationExceptionForFailedAuthenticationAsync() - { - // Arrange - var searchIndexClient = new SearchIndexClient(new Uri(fixture.Config.ServiceUrl), new AzureKeyCredential("12345")); - using var sut = new AzureAISearchCollection(searchIndexClient, fixture.TestIndexName); - - // Act & Assert - await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Theory(Skip = SkipReason)] - [InlineData("equality", true)] - [InlineData("tagContains", false)] - public async Task ItCanSearchWithVectorAndFiltersAsync(string option, bool includeVectors) - { - // Arrange. - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - - // Act. - var filter = option == "equality" ? new VectorSearchFilter().EqualTo("HotelName", "Hotel 3") : new VectorSearchFilter().AnyTagEqualTo("Tags", "bar"); - var searchResults = await sut.SearchAsync( - fixture.Embedding, - top: 3, - new() - { - IncludeVectors = includeVectors, - VectorProperty = r => r.DescriptionEmbedding, - OldFilter = filter, - }).ToListAsync(); - - // Assert. - Assert.Single(searchResults); - var searchResult = searchResults.First(); - Assert.Equal("BaseSet-3", searchResult.Record.HotelId); - Assert.Equal("Hotel 3", searchResult.Record.HotelName); - Assert.Equal("This is a great hotel", searchResult.Record.Description); - Assert.Equal(new[] { "air conditioning", "bar", "continental breakfast" }, searchResult.Record.Tags); - Assert.True(searchResult.Record.ParkingIncluded); - Assert.Equal(new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero), searchResult.Record.LastRenovationDate); - Assert.Equal(4.8, searchResult.Record.Rating); - if (includeVectors) - { - Assert.NotNull(searchResult.Record.DescriptionEmbedding); - var embedding = fixture.Embedding; - Assert.Equal(embedding, searchResult.Record.DescriptionEmbedding!.Value.ToArray()); - } - else - { - Assert.Null(searchResult.Record.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanSearchWithTextAndFiltersAsync() - { - // Arrange. - using var sut = new AzureAISearchCollection(fixture.SearchIndexClient, fixture.TestIndexName); - - // Act. - var filter = new VectorSearchFilter().EqualTo("HotelName", "Hotel 3"); - var searchResults = await sut.SearchAsync( - "A hotel with great views.", - top: 3, - new() - { - VectorProperty = r => r.DescriptionEmbedding, - OldFilter = filter, - }).ToListAsync(); - - // Assert. - Assert.Single(searchResults); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new AzureAISearchCollectionOptions - { - Definition = fixture.VectorStoreRecordDefinition - }; - using var sut = new AzureAISearchDynamicCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); - - // Act - var baseSetGetResult = await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = true }); - var baseSetEmbedding = fixture.Embedding; - var dynamicMapperEmbedding = fixture.Embedding; - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = "DynamicMapper-1", - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["Tags"] = new string[] { "dynamic" }, - ["ParkingIncluded"] = false, - ["LastRenovationDate"] = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - ["Rating"] = 3.6d, - - ["DescriptionEmbedding"] = dynamicMapperEmbedding - }); - var localGetResult = await sut.GetAsync("DynamicMapper-1", new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(baseSetGetResult); - Assert.Equal("Hotel 1", baseSetGetResult["HotelName"]); - Assert.Equal("This is a great hotel", baseSetGetResult["Description"]); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, baseSetGetResult["Tags"]); - Assert.False((bool?)baseSetGetResult["ParkingIncluded"]); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), baseSetGetResult["LastRenovationDate"]); - Assert.Equal(3.6d, baseSetGetResult["Rating"]); - Assert.Equal(baseSetEmbedding, (ReadOnlyMemory)baseSetGetResult["DescriptionEmbedding"]!); - - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new[] { "dynamic" }, localGetResult["Tags"]); - Assert.False((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), localGetResult["LastRenovationDate"]); - Assert.Equal(3.6d, localGetResult["Rating"]); - Assert.Equal(dynamicMapperEmbedding, (ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!); - } - - private AzureAISearchHotel CreateTestHotel(string hotelId) => new() - { - HotelId = hotelId, - HotelName = $"MyHotel {hotelId}", - Description = "My Hotel is great.", - DescriptionEmbedding = fixture.Embedding, - Tags = ["pool", "air conditioning", "concierge"], - ParkingIncluded = true, - LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - Rating = 3.6 - }; -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs deleted file mode 100644 index a7b5781b74c0..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Connectors.AzureAISearch; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; - -/// -/// Contains integration tests for the class. -/// Tests work with an Azure AI Search Instance. -/// -[Collection("AzureAISearchVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Requires Azure AI Search Service instance up and running")] -public class AzureAISearchVectorStoreTests(AzureAISearchVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new AzureAISearchVectorStore(fixture.SearchIndexClient)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreRecordCollectionTests.cs deleted file mode 100644 index b65cd105be10..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory; - -/// -/// Base class for common integration tests that should pass for any . -/// -/// The type of key to use with the record collection. -public abstract class BaseVectorStoreRecordCollectionTests - where TKey : notnull -{ - protected abstract TKey Key1 { get; } - protected abstract TKey Key2 { get; } - protected abstract TKey Key3 { get; } - protected abstract TKey Key4 { get; } - - protected abstract HashSet GetSupportedDistanceFunctions(); - - protected abstract VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) where TRecord : class; - - protected virtual int DelayAfterIndexCreateInMilliseconds { get; } = 0; - - protected virtual int DelayAfterUploadInMilliseconds { get; } = 0; - - [VectorStoreTheory] - [InlineData(DistanceFunction.CosineDistance, 0, 2, 1, new int[] { 0, 2, 1 })] - [InlineData(DistanceFunction.CosineSimilarity, 1, -1, 0, new int[] { 0, 2, 1 })] - [InlineData(DistanceFunction.DotProductSimilarity, 1, -1, 0, new int[] { 0, 2, 1 })] - [InlineData(DistanceFunction.EuclideanDistance, 0, 2, 1.73, new int[] { 0, 2, 1 })] - [InlineData(DistanceFunction.EuclideanSquaredDistance, 0, 4, 3, new int[] { 0, 2, 1 })] - [InlineData(DistanceFunction.HammingDistance, 0, 1, 3, new int[] { 0, 1, 2 })] - [InlineData(DistanceFunction.ManhattanDistance, 0, 2, 3, new int[] { 0, 1, 2 })] - public async Task VectorSearchShouldReturnExpectedScoresAsync(string distanceFunction, double expectedExactMatchScore, double expectedOppositeScore, double expectedOrthogonalScore, int[] resultOrder) - { - var keyDictionary = new Dictionary - { - { 0, this.Key1 }, - { 1, this.Key2 }, - { 2, this.Key3 }, - }; - var scoreDictionary = new Dictionary - { - { 0, expectedExactMatchScore }, - { 1, expectedOppositeScore }, - { 2, expectedOrthogonalScore }, - }; - - // Don't test unsupported distance functions. - var supportedDistanceFunctions = this.GetSupportedDistanceFunctions(); - if (!supportedDistanceFunctions.Contains(distanceFunction)) - { - return; - } - - // Arrange - var definition = CreateKeyWithVectorRecordDefinition(4, distanceFunction); - var sut = this.GetTargetRecordCollection>( - $"scorebydf{distanceFunction}", - definition); - - await sut.EnsureCollectionExistsAsync(); - await Task.Delay(this.DelayAfterIndexCreateInMilliseconds); - - // Create two vectors that are opposite to each other and records that use these - // plus a further vector that is orthogonal to the base vector. - var baseVector = new ReadOnlyMemory([1, 0, 0, 0]); - var oppositeVector = new ReadOnlyMemory([-1, 0, 0, 0]); - var orthogonalVector = new ReadOnlyMemory([0f, -1f, -1f, 0f]); - - var baseRecord = new KeyWithVectorRecord - { - Key = this.Key1, - Vector = baseVector, - }; - - var oppositeRecord = new KeyWithVectorRecord - { - Key = this.Key2, - Vector = oppositeVector, - }; - - var orthogonalRecord = new KeyWithVectorRecord - { - Key = this.Key3, - Vector = orthogonalVector, - }; - - await sut.UpsertAsync([baseRecord, oppositeRecord, orthogonalRecord]); - await Task.Delay(this.DelayAfterUploadInMilliseconds); - - // Act - var results = await sut.SearchAsync(baseVector, top: 3).ToListAsync(); - - // Assert - Assert.Equal(3, results.Count); - - Assert.Equal(keyDictionary[resultOrder[0]], results[0].Record.Key); - Assert.Equal(Math.Round(scoreDictionary[resultOrder[0]], 2), Math.Round(results[0].Score!.Value, 2)); - - Assert.Equal(keyDictionary[resultOrder[1]], results[1].Record.Key); - Assert.Equal(Math.Round(scoreDictionary[resultOrder[1]], 2), Math.Round(results[1].Score!.Value, 2)); - - Assert.Equal(keyDictionary[resultOrder[2]], results[2].Record.Key); - Assert.Equal(Math.Round(scoreDictionary[resultOrder[2]], 2), Math.Round(results[2].Score!.Value, 2)); - - // Cleanup - await sut.EnsureCollectionDeletedAsync(); - } - - private static VectorStoreCollectionDefinition CreateKeyWithVectorRecordDefinition(int vectorDimensions, string distanceFunction) - { - var definition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("Key", typeof(TKey)), - new VectorStoreVectorProperty("Vector", typeof(ReadOnlyMemory), vectorDimensions) { DistanceFunction = distanceFunction }, - ], - }; - - return definition; - } - - private sealed class KeyWithVectorRecord - { - public required TRecordKey Key { get; set; } - - public ReadOnlyMemory Vector { get; set; } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs deleted file mode 100644 index e81987050487..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory; - -/// -/// Base class for integration tests. -/// -public abstract class BaseVectorStoreTests(VectorStore vectorStore) - where TKey : notnull - where TRecord : class -{ - protected virtual IEnumerable CollectionNames => ["listcollectionnames1", "listcollectionnames2", "listcollectionnames3"]; - - [VectorStoreFact] - public virtual async Task ItCanGetAListOfExistingCollectionNamesAsync() - { - // Arrange - var expectedCollectionNames = this.CollectionNames; - - foreach (var collectionName in expectedCollectionNames) - { - var collection = vectorStore.GetCollection(collectionName); - - await collection.EnsureCollectionExistsAsync(); - } - - // Act - var actualCollectionNames = await vectorStore.ListCollectionNamesAsync().ToListAsync(); - - // Assert - var expected = expectedCollectionNames.Select(l => l.ToUpperInvariant()).ToList(); - var actual = actualCollectionNames.Select(l => l.ToUpperInvariant()).ToList(); - - expected.ForEach(item => Assert.Contains(item, actual)); - - // Cleanup - foreach (var collectionName in expectedCollectionNames) - { - var collection = vectorStore.GetCollection(collectionName); - - await collection.EnsureCollectionDeletedAsync(); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Chroma/ChromaMemoryStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Chroma/ChromaMemoryStoreTests.cs deleted file mode 100644 index 770400778817..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Chroma/ChromaMemoryStoreTests.cs +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Connectors.Chroma; -using Microsoft.SemanticKernel.Memory; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Chroma; - -/// -/// Integration tests for class. -/// Tests work with local Chroma server. To setup the server, see dotnet/src/Connectors/Connectors.Memory.Chroma/README.md. -/// -[Experimental("SKEXP0020")] -public sealed class ChromaMemoryStoreTests : IDisposable -{ - // If null, all tests will be enabled - private const string SkipReason = "Requires Chroma server up and running"; - - private const string BaseAddress = "http://localhost:8000"; - - public ChromaMemoryStoreTests() - { - this._httpClient = new() - { - BaseAddress = new Uri(BaseAddress) - }; - - this._chromaMemoryStore = new(this._httpClient); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanCreateCollectionsAsync() - { - // Arrange - var collectionName1 = this.GetRandomCollectionName(); - var collectionName2 = this.GetRandomCollectionName(); - var collectionName3 = this.GetRandomCollectionName(); - - // Act - await this._chromaMemoryStore.CreateCollectionAsync(collectionName1); - await this._chromaMemoryStore.CreateCollectionAsync(collectionName2); - await this._chromaMemoryStore.CreateCollectionAsync(collectionName3); - - // Assert - var collections = await this._chromaMemoryStore.GetCollectionsAsync().ToListAsync(); - - Assert.Contains(collectionName1, collections); - Assert.Contains(collectionName2, collections); - Assert.Contains(collectionName3, collections); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanHandleDuplicateNameDuringCollectionCreationAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - // Act - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Assert - var collections = await this._chromaMemoryStore.GetCollectionsAsync().ToListAsync(); - var filteredCollections = collections.Where(collection => collection.Equals(collectionName, StringComparison.Ordinal)).ToList(); - - Assert.Single(filteredCollections); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanCheckIfCollectionExistsAsync(bool createCollection) - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - if (createCollection) - { - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - } - - // Act - bool doesCollectionExist = await this._chromaMemoryStore.DoesCollectionExistAsync(collectionName); - - // Assert - Assert.Equal(createCollection, doesCollectionExist); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteExistingCollectionAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - var collectionsBeforeDeletion = await this._chromaMemoryStore.GetCollectionsAsync().ToListAsync(); - Assert.Contains(collectionName, collectionsBeforeDeletion); - - // Act - await this._chromaMemoryStore.DeleteCollectionAsync(collectionName); - - // Assert - var collectionsAfterDeletion = await this._chromaMemoryStore.GetCollectionsAsync().ToListAsync(); - Assert.DoesNotContain(collectionName, collectionsAfterDeletion); - } - - [Fact(Skip = SkipReason)] - public async Task ItThrowsExceptionOnNonExistentCollectionDeletionAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - // Act - var exception = await Record.ExceptionAsync(() => this._chromaMemoryStore.DeleteCollectionAsync(collectionName)); - - // Assert - Assert.IsType(exception); - Assert.Contains( - $"Cannot delete non-existent collection {collectionName}", - exception.Message, - StringComparison.InvariantCulture); - } - - [Fact(Skip = SkipReason)] - public async Task ItReturnsNullOnNonExistentRecordRetrievalAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var key = Guid.NewGuid().ToString(); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - var record = await this._chromaMemoryStore.GetAsync(collectionName, key, true); - - // Assert - Assert.Null(record); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertMemoryRecordAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var expectedRecord = this.GetRandomMemoryRecord(); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - var createdRecordKey = await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - - // Assert - Assert.Equal(expectedRecord.Key, createdRecordKey); - - var actualRecord = await this._chromaMemoryStore.GetAsync(collectionName, expectedRecord.Key, true); - - Assert.NotNull(actualRecord); - - this.AssertMemoryRecordEqual(expectedRecord, actualRecord); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertMemoryRecordBatchAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - var expectedRecord1 = this.GetRandomMemoryRecord(); - var expectedRecord2 = this.GetRandomMemoryRecord(); - var expectedRecord3 = this.GetRandomMemoryRecord(); - - var batch = new List { expectedRecord1, expectedRecord2, expectedRecord3 }; - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - var createdRecordKeys = await this._chromaMemoryStore.UpsertBatchAsync(collectionName, batch).ToListAsync(); - - // Assert - Assert.Equal(expectedRecord1.Key, createdRecordKeys[0]); - Assert.Equal(expectedRecord2.Key, createdRecordKeys[1]); - Assert.Equal(expectedRecord3.Key, createdRecordKeys[2]); - - var actualRecords = await this._chromaMemoryStore.GetBatchAsync(collectionName, batch.Select(l => l.Key), true).ToListAsync(); - - actualRecords.ForEach(Assert.NotNull); - - this.AssertMemoryRecordEqual(expectedRecord1, actualRecords[0]); - this.AssertMemoryRecordEqual(expectedRecord2, actualRecords[1]); - this.AssertMemoryRecordEqual(expectedRecord3, actualRecords[2]); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanRemoveMemoryRecordAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var expectedRecord = this.GetRandomMemoryRecord(); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - - var recordBeforeDeletion = await this._chromaMemoryStore.GetAsync(collectionName, expectedRecord.Key); - Assert.NotNull(recordBeforeDeletion); - - // Act - await this._chromaMemoryStore.RemoveAsync(collectionName, expectedRecord.Key); - - // Assert - var recordAfterDeletion = await this._chromaMemoryStore.GetAsync(collectionName, expectedRecord.Key); - Assert.Null(recordAfterDeletion); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanRemoveMemoryRecordBatchAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - var expectedRecord1 = this.GetRandomMemoryRecord(); - var expectedRecord2 = this.GetRandomMemoryRecord(); - var expectedRecord3 = this.GetRandomMemoryRecord(); - - var batch = new List { expectedRecord1, expectedRecord2, expectedRecord3 }; - var keys = batch.Select(l => l.Key); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.UpsertBatchAsync(collectionName, batch).ToListAsync(); - - var recordsBeforeDeletion = await this._chromaMemoryStore.GetBatchAsync(collectionName, keys).ToListAsync(); - - Assert.Equal(batch.Count, recordsBeforeDeletion.Count); - recordsBeforeDeletion.ForEach(Assert.NotNull); - - // Act - await this._chromaMemoryStore.RemoveBatchAsync(collectionName, keys); - - // Assert - var recordsAfterDeletion = await this._chromaMemoryStore.GetBatchAsync(collectionName, keys).ToListAsync(); - Assert.Empty(recordsAfterDeletion); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetNearestMatchAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - var expectedRecord1 = this.GetRandomMemoryRecord(embedding: new[] { 10f, 10f, 10f }); - var expectedRecord2 = this.GetRandomMemoryRecord(embedding: new[] { 5f, 5f, 5f }); - var expectedRecord3 = this.GetRandomMemoryRecord(embedding: new[] { 1f, 1f, 1f }); - - float[] searchEmbedding = [2f, 2f, 2f]; - - var batch = new List { expectedRecord1, expectedRecord2, expectedRecord3 }; - var keys = batch.Select(l => l.Key); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.UpsertBatchAsync(collectionName, batch).ToListAsync(); - - // Act - var nearestMatch = await this._chromaMemoryStore.GetNearestMatchAsync(collectionName, searchEmbedding, withEmbedding: true); - - // Assert - Assert.True(nearestMatch.HasValue); - - var actualRecord = nearestMatch.Value.Item1; - - Assert.NotNull(actualRecord); - - this.AssertMemoryRecordEqual(expectedRecord3, actualRecord); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetNearestMatchesAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - - var expectedRecord1 = this.GetRandomMemoryRecord(embedding: new[] { 10f, 10f, 10f }); - var expectedRecord2 = this.GetRandomMemoryRecord(embedding: new[] { 5f, 5f, 5f }); - var expectedRecord3 = this.GetRandomMemoryRecord(embedding: new[] { 1f, 1f, 1f }); - - float[] searchEmbedding = [2f, 2f, 2f]; - - var batch = new List { expectedRecord1, expectedRecord2, expectedRecord3 }; - var keys = batch.Select(l => l.Key); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.UpsertBatchAsync(collectionName, batch).ToListAsync(); - - // Act - var nearestMatches = await this._chromaMemoryStore - .GetNearestMatchesAsync(collectionName, searchEmbedding, batch.Count, withEmbeddings: true) - .ToListAsync(); - - // Assert - Assert.NotNull(nearestMatches); - Assert.Equal(batch.Count, nearestMatches.Count); - - nearestMatches.ForEach(match => Assert.NotNull(match.Item1)); - - var actualRecord1 = nearestMatches[0].Item1; - var actualRecord2 = nearestMatches[1].Item1; - var actualRecord3 = nearestMatches[2].Item1; - - this.AssertMemoryRecordEqual(expectedRecord3, actualRecord1); - this.AssertMemoryRecordEqual(expectedRecord2, actualRecord2); - this.AssertMemoryRecordEqual(expectedRecord1, actualRecord3); - } - - [Fact(Skip = SkipReason)] - public async Task ItReturnsNoMatchesFromEmptyCollectionAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - float[] searchEmbedding = [2f, 2f, 2f]; - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - var nearestMatch = await this._chromaMemoryStore.GetNearestMatchAsync(collectionName, searchEmbedding, withEmbedding: true); - - // Assert - Assert.Null(nearestMatch?.Item1); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertSameMemoryRecordMultipleTimesAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var expectedRecord = this.GetRandomMemoryRecord(); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - - // Assert - var actualRecord = await this._chromaMemoryStore.GetAsync(collectionName, expectedRecord.Key, true); - - Assert.NotNull(actualRecord); - - this.AssertMemoryRecordEqual(expectedRecord, actualRecord); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertDifferentMemoryRecordsWithSameKeyMultipleTimesAsync() - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var expectedRecord1 = this.GetRandomMemoryRecord(); - var key = expectedRecord1.Key; - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord1); - - var actualRecord1 = await this._chromaMemoryStore.GetAsync(collectionName, key, withEmbedding: true); - - Assert.NotNull(actualRecord1); - this.AssertMemoryRecordEqual(expectedRecord1, actualRecord1); - - // Act - var expectedRecord2 = this.GetRandomMemoryRecord(key: key); - await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord2); - - // Assert - var actualRecord2 = await this._chromaMemoryStore.GetAsync(collectionName, key, withEmbedding: true); - - Assert.NotNull(actualRecord2); - this.AssertMemoryRecordEqual(expectedRecord2, actualRecord2); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItProcessesBooleanValuesCorrectlyAsync(bool isReference) - { - // Arrange - var collectionName = this.GetRandomCollectionName(); - var metadata = this.GetRandomMemoryRecordMetadata(isReference: isReference); - var expectedRecord = this.GetRandomMemoryRecord(metadata: metadata); - - await this._chromaMemoryStore.CreateCollectionAsync(collectionName); - - // Act - var createdRecordKey = await this._chromaMemoryStore.UpsertAsync(collectionName, expectedRecord); - var actualRecord = await this._chromaMemoryStore.GetAsync(collectionName, createdRecordKey, true); - - // Assert - Assert.NotNull(actualRecord); - - Assert.Equal(expectedRecord.Metadata.IsReference, actualRecord.Metadata.IsReference); - } - - public void Dispose() - { - this._httpClient.Dispose(); - } - - #region private ================================================================================ - - private readonly HttpClient _httpClient; - private readonly ChromaMemoryStore _chromaMemoryStore; - - private void AssertMemoryRecordEqual(MemoryRecord expectedRecord, MemoryRecord actualRecord) - { - Assert.Equal(expectedRecord.Key, actualRecord.Key); - Assert.True(expectedRecord.Embedding.Span.SequenceEqual(actualRecord.Embedding.Span)); - Assert.Equal(expectedRecord.Metadata.Id, actualRecord.Metadata.Id); - Assert.Equal(expectedRecord.Metadata.Text, actualRecord.Metadata.Text); - Assert.Equal(expectedRecord.Metadata.Description, actualRecord.Metadata.Description); - Assert.Equal(expectedRecord.Metadata.AdditionalMetadata, actualRecord.Metadata.AdditionalMetadata); - Assert.Equal(expectedRecord.Metadata.IsReference, actualRecord.Metadata.IsReference); - Assert.Equal(expectedRecord.Metadata.ExternalSourceName, actualRecord.Metadata.ExternalSourceName); - } - - private string GetRandomCollectionName() - { - return "sk-test-" + Guid.NewGuid(); - } - - private MemoryRecord GetRandomMemoryRecord(string? key = null, ReadOnlyMemory? embedding = null) - { - var recordKey = key ?? Guid.NewGuid().ToString(); - var recordEmbedding = embedding ?? new[] { 1f, 3f, 5f }; - - return MemoryRecord.LocalRecord( - id: recordKey, - text: "text-" + Guid.NewGuid().ToString(), - description: "description-" + Guid.NewGuid().ToString(), - embedding: recordEmbedding, - additionalMetadata: "metadata-" + Guid.NewGuid().ToString(), - key: recordKey); - } - - private MemoryRecord GetRandomMemoryRecord(MemoryRecordMetadata metadata, ReadOnlyMemory? embedding = null) - { - var recordEmbedding = embedding ?? new[] { 1f, 3f, 5f }; - - return MemoryRecord.FromMetadata( - metadata: metadata, - embedding: recordEmbedding, - key: metadata.Id); - } - - private MemoryRecordMetadata GetRandomMemoryRecordMetadata(bool isReference = false, string? key = null) - { - var recordKey = key ?? Guid.NewGuid().ToString(); - - return new MemoryRecordMetadata( - isReference: isReference, - id: recordKey, - text: "text-" + Guid.NewGuid().ToString(), - description: "description-" + Guid.NewGuid().ToString(), - externalSourceName: "source-name-" + Guid.NewGuid().ToString(), - additionalMetadata: "metadata-" + Guid.NewGuid().ToString()); - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionFixture.cs deleted file mode 100644 index 46fba5703c2f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongoDB; - -[CollectionDefinition("CosmosMongoCollection")] -public class CosmosMongoCollectionFixture : ICollectionFixture -{ } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionTests.cs deleted file mode 100644 index fff6a76d6c4f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoCollectionTests.cs +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.CosmosMongoDB; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongoDB; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -[Collection("CosmosMongoCollection")] -public class CosmosMongoCollectionTests(CosmosMongoVectorStoreFixture fixture) -{ - private const string? SkipReason = "Azure CosmosDB MongoDB cluster is required"; - - [Theory(Skip = SkipReason)] - [InlineData("sk-test-hotels", true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, collectionName); - - // Act - var actual = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(expectedExists, actual); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanEnsureCollectionExistsAsync() - { - // Arrange - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, "sk-test-create-collection"); - - try - { - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - } - finally - { - // Clean up - await sut.EnsureCollectionDeletedAsync(); - } - } - - [Theory(Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanCreateCollectionUpsertAndGetAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - - var collectionNamePostfix = useRecordDefinition ? "with-definition" : "with-type"; - var collectionName = $"collection-{collectionNamePostfix}"; - - var options = new CosmosMongoCollectionOptions - { - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, collectionName); - - var record = this.CreateTestHotel(HotelId); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = includeVectors }); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - await sut.EnsureCollectionDeletedAsync(); - - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Tags.ToArray(), getResult.Tags.ToArray()); - Assert.Equal(record.Description, getResult.Description); - Assert.Equal(record.Timestamp.ToUniversalTime(), getResult.Timestamp.ToUniversalTime()); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - const string TempCollectionName = "temp-test"; - await fixture.MongoDatabase.CreateCollectionAsync(TempCollectionName); - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, TempCollectionName); - - Assert.True(await sut.CollectionExistsAsync()); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetAndDeleteRecordAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(HotelId); - - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.Null(getResult); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetAndDeleteBatchAsync() - { - // Arrange - const string HotelId1 = "11111111-1111-1111-1111-111111111111"; - const string HotelId2 = "22222222-2222-2222-2222-222222222222"; - const string HotelId3 = "33333333-3333-3333-3333-333333333333"; - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record1 = this.CreateTestHotel(HotelId1); - var record2 = this.CreateTestHotel(HotelId2); - var record3 = this.CreateTestHotel(HotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertRecordAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - - await sut.UpsertAsync(record); - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertWithModelWorksCorrectlyAsync() - { - // Arrange - var definition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("Id", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) - ] - }; - - var model = new TestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new CosmosMongoCollection( - fixture.MongoDatabase, - fixture.TestCollection, - new() { Definition = definition }); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertWithVectorStoreModelWorksCorrectlyAsync() - { - // Arrange - var model = new VectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.HotelId); - Assert.Equal("Test Name", getResult.HotelName); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertWithBsonModelWorksCorrectlyAsync() - { - // Arrange - var definition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("Id", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) - ] - }; - - var model = new BsonTestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new CosmosMongoCollection( - fixture.MongoDatabase, - fixture.TestCollection, - new() { Definition = definition }); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertWithBsonVectorStoreModelWorksCorrectlyAsync() - { - // Arrange - var model = new BsonVectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.HotelId); - Assert.Equal("Test Name", getResult.HotelName); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertWithBsonVectorStoreWithNameModelWorksCorrectlyAsync() - { - // Arrange - var model = new BsonVectorStoreWithNameTestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsByDefaultAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, "TestVectorizedSearch"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key1", ids[0]); - Assert.Equal("key2", ids[1]); - Assert.Equal("key3", ids[2]); - - Assert.DoesNotContain("key4", ids); - - Assert.Equal(1, searchResults.First(l => l.Record.HotelId == "key1").Score); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithOffsetAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, "TestVectorizedSearchWithOffset"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 2, new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key3", ids[0]); - Assert.Equal("key4", ids[1]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key2", ids); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithFilterAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosMongoCollection(fixture.MongoDatabase, "TestVectorizedSearchWithFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3, new() - { - OldFilter = new VectorSearchFilter().EqualTo(nameof(CosmosMongoHotel.HotelName), "My Hotel key2") - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key2", ids[0]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key3", ids); - Assert.DoesNotContain("key4", ids); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new CosmosMongoCollectionOptions - { - Definition = fixture.HotelVectorStoreRecordDefinition - }; - - using var sut = new CosmosMongoDynamicCollection(fixture.MongoDatabase, fixture.TestCollection, options); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = "DynamicMapper-1", - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["Tags"] = new string[] { "dynamic" }, - ["ParkingIncluded"] = false, - ["Timestamp"] = new DateTime(1970, 1, 18, 0, 0, 0).ToUniversalTime(), - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync("DynamicMapper-1", new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new[] { "dynamic" }, localGetResult["Tags"]); - Assert.False((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(new DateTime(1970, 1, 18, 0, 0, 0).ToUniversalTime(), localGetResult["Timestamp"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - #region private - - private CosmosMongoHotel CreateTestHotel(string hotelId, ReadOnlyMemory? embedding = null) - { - return new CosmosMongoHotel - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelId}", - HotelCode = 42, - HotelRating = 4.5f, - ParkingIncluded = true, - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - Timestamp = new DateTime(2024, 09, 23, 15, 32, 33), - DescriptionEmbedding = embedding ?? new[] { 30f, 31f, 32f, 33f }, - }; - } - - private sealed class TestModel - { - public string? Id { get; set; } - - public string? HotelName { get; set; } - } - - private sealed class VectorStoreTestModel - { - [VectorStoreKey] - public string? HotelId { get; set; } - - [VectorStoreData(StorageName = "hotel_name")] - public string? HotelName { get; set; } - } - - private sealed class BsonTestModel - { - [BsonId] - public string? Id { get; set; } - - [BsonElement("hotel_name")] - public string? HotelName { get; set; } - } - - private sealed class BsonVectorStoreTestModel - { - [BsonId] - [VectorStoreKey] - public string? HotelId { get; set; } - - [BsonElement("hotel_name")] - [VectorStoreData] - public string? HotelName { get; set; } - } - - private sealed class BsonVectorStoreWithNameTestModel - { - [BsonId] - [VectorStoreKey] - public string? Id { get; set; } - - [BsonElement("bson_hotel_name")] - [VectorStoreData(StorageName = "storage_hotel_name")] - public string? HotelName { get; set; } - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoHotel.cs deleted file mode 100644 index 90b34c7afd91..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoHotel.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongoDB; - -#pragma warning disable CS8618 - -public class CosmosMongoHotel -{ - /// The key of the record. - [VectorStoreKey] - public string HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreData] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A datetime metadata field. - [VectorStoreData] - public DateTime Timestamp { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineDistance, IndexKind = IndexKind.IvfFlat)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreFixture.cs deleted file mode 100644 index f4427b6d5bef..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreFixture.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.VectorData; -using MongoDB.Driver; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongoDB; - -public class CosmosMongoVectorStoreFixture : IAsyncLifetime -{ - private readonly List _testCollections = ["sk-test-hotels", "sk-test-contacts", "sk-test-addresses"]; - - /// Main test collection for tests. - public string TestCollection => this._testCollections[0]; - - /// that can be used to manage the collections in Azure CosmosDB MongoDB. - public IMongoDatabase MongoDatabase { get; } - - /// Gets the manually created vector store record definition for Azure CosmosDB MongoDB test model. - public VectorStoreCollectionDefinition HotelVectorStoreRecordDefinition { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - public CosmosMongoVectorStoreFixture() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile( - path: "testsettings.development.json", - optional: true, - reloadOnChange: true - ) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - - var connectionString = GetConnectionString(configuration); -#pragma warning disable CA2000 // Dispose objects before losing scope - var client = new MongoClient(connectionString); -#pragma warning restore CA2000 - - this.MongoDatabase = client.GetDatabase("test"); - - this.HotelVectorStoreRecordDefinition = new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("HotelRating", typeof(float)), - new VectorStoreDataProperty("Tags", typeof(List)), - new VectorStoreDataProperty("Timestamp", typeof(DateTime)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.IvfFlat, DistanceFunction = DistanceFunction.CosineDistance } - ] - }; - } - - public async Task InitializeAsync() - { - foreach (var collection in this._testCollections) - { - await this.MongoDatabase.CreateCollectionAsync(collection); - } - } - - public async Task DisposeAsync() - { - var cursor = await this.MongoDatabase.ListCollectionNamesAsync(); - - while (await cursor.MoveNextAsync().ConfigureAwait(false)) - { - foreach (var collection in cursor.Current) - { - await this.MongoDatabase.DropCollectionAsync(collection); - } - } - } - - #region private - - private static string GetConnectionString(IConfigurationRoot configuration) - { - var settingValue = configuration["CosmosMongo:ConnectionString"]; - if (string.IsNullOrWhiteSpace(settingValue)) - { - throw new ArgumentNullException($"{settingValue} string is not configured"); - } - - return settingValue; - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreTests.cs deleted file mode 100644 index b1720dee0234..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/CosmosMongoVectorStoreTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Connectors.CosmosMongoDB; -using SemanticKernel.IntegrationTests.Connectors.Memory; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongoDB; - -[Collection("CosmosMongoCollection")] -[DisableVectorStoreTests(Skip = "Azure CosmosDB MongoDB cluster is required")] -public class CosmosMongoVectorStoreTests(CosmosMongoVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new CosmosMongoVectorStore(fixture.MongoDatabase)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/DataHelper.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/DataHelper.cs deleted file mode 100644 index 2b5e07969aab..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosMongoDB/DataHelper.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Linq; -using System.Numerics.Tensors; -using Microsoft.SemanticKernel.Memory; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosMongo; - -internal static class DataHelper -{ - public static MemoryRecord[] VectorSearchExpectedResults { get; } - public static MemoryRecord[] VectorSearchTestRecords { get; } - public static float[] VectorSearchTestEmbedding { get; } - - static DataHelper() - { - VectorSearchTestRecords = CreateBatchRecords(8); - VectorSearchTestEmbedding = [1, 0.699f, 0.701f]; - VectorSearchExpectedResults = VectorSearchTestRecords - .OrderByDescending(r => TensorPrimitives.CosineSimilarity(r.Embedding.Span, VectorSearchTestEmbedding)) - .ToArray(); - } - - public static MemoryRecord CreateRecord(string id) => - MemoryRecord.LocalRecord( - id: id, - text: $"text_{id}", - description: $"description_{id}", - embedding: new[] { 1.1f, 2.2f, 3.3f }, - timestamp: GetDateTime()); - - public static MemoryRecord[] CreateBatchRecords(int count) => - Enumerable - .Range(0, count) - .Select(i => MemoryRecord.LocalRecord( - id: $"test_{i}", - text: $"text_{i}", - description: $"description_{i}", - embedding: new[] { 1, (float)Math.Cos(Math.PI * i / count), (float)Math.Sin(Math.PI * i / count) }, - timestamp: GetDateTime())) - .ToArray(); - - private static DateTime GetDateTime() => - new(TimeSpan.TicksPerMillisecond * (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond), DateTimeKind.Local); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionFixture.cs deleted file mode 100644 index d2357df505b1..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -[CollectionDefinition("CosmosNoSqlCollection")] -public class CosmosNoSqlCollectionFixture : ICollectionFixture -{ } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionTests.cs deleted file mode 100644 index 47cb5d26bb97..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlCollectionTests.cs +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.Cosmos; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.CosmosNoSql; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; -using DistanceFunction = Microsoft.Extensions.VectorData.DistanceFunction; -using IndexKind = Microsoft.Extensions.VectorData.IndexKind; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Integration tests for class. -/// -[Collection("CosmosNoSqlCollection")] -[CosmosNoSqlConnectionStringSetCondition] -public sealed class CosmosNoSqlCollectionTests(CosmosNoSqlVectorStoreFixture fixture) -{ - [VectorStoreFact] - public async Task ItCanEnsureCollectionExistsAsync() - { - // Arrange - using var sut = new CosmosNoSqlCollection(fixture.Database!, "test-create-collection"); - - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - } - - [VectorStoreTheory] - [InlineData("sk-test-hotels", true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange - using var sut = new CosmosNoSqlCollection(fixture.Database!, collectionName); - - if (expectedExists) - { - await fixture.Database!.CreateContainerIfNotExistsAsync(new ContainerProperties(collectionName, "/id")); - } - - // Act - var actual = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(expectedExists, actual); - } - - [VectorStoreTheory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanCreateCollectionUpsertAndGetAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - - var collectionNamePostfix = useRecordDefinition ? "with-definition" : "with-type"; - collectionNamePostfix = includeVectors ? $"{collectionNamePostfix}-with-vectors" : $"{collectionNamePostfix}-without-vectors"; - var collectionName = $"collection-{collectionNamePostfix}"; - - var options = new CosmosNoSqlCollectionOptions - { - Definition = useRecordDefinition ? this.GetTestHotelRecordDefinition() : null - }; - - using var sut = new CosmosNoSqlCollection(fixture.Database!, collectionName); - - var record = this.CreateTestHotel(HotelId); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = includeVectors }); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - await sut.EnsureCollectionDeletedAsync(); - - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Tags.ToArray(), getResult.Tags.ToArray()); - Assert.Equal(record.Description, getResult.Description); - Assert.Equal(record.Timestamp, getResult.Timestamp); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [VectorStoreFact] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - const string TempCollectionName = "test-delete-collection"; - await fixture.Database!.CreateContainerAsync(new ContainerProperties(TempCollectionName, "/id")); - - using var sut = new CosmosNoSqlCollection(fixture.Database!, TempCollectionName); - - Assert.True(await sut.CollectionExistsAsync()); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [VectorStoreTheory] - [InlineData("consistent-mode-collection", IndexingMode.Consistent)] - [InlineData("lazy-mode-collection", IndexingMode.Lazy)] - [InlineData("none-mode-collection", IndexingMode.None)] - public async Task ItCanGetAndDeleteRecordAsync(string collectionName, IndexingMode indexingMode) - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new CosmosNoSqlCollection( - fixture.Database!, - collectionName, - new() { IndexingMode = indexingMode, Automatic = indexingMode != IndexingMode.None }); - - await sut.EnsureCollectionExistsAsync(); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(HotelId); - - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.Null(getResult); - } - - [VectorStoreFact] - public async Task ItCanGetAndDeleteRecordWithPartitionKeyAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - const string HotelName = "Test Hotel Name"; - - using VectorStoreCollection sut = - new CosmosNoSqlCollection( - fixture.Database!, - "delete-with-partition-key", - new() { PartitionKeyPropertyName = "HotelName" }); - - await sut.EnsureCollectionExistsAsync(); - - var record = this.CreateTestHotel(HotelId, HotelName); - - await sut.UpsertAsync(record); - - var key = new CosmosNoSqlCompositeKey(record.HotelId, record.HotelName!); - var getResult = await sut.GetAsync(key); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(key); - - getResult = await sut.GetAsync(key); - - // Assert - Assert.Null(getResult); - } - - [VectorStoreFact] - public async Task ItCanGetAndDeleteBatchAsync() - { - // Arrange - const string HotelId1 = "11111111-1111-1111-1111-111111111111"; - const string HotelId2 = "22222222-2222-2222-2222-222222222222"; - const string HotelId3 = "33333333-3333-3333-3333-333333333333"; - - using var sut = new CosmosNoSqlCollection(fixture.Database!, "get-and-delete-batch"); - - await sut.EnsureCollectionExistsAsync(); - - var record1 = this.CreateTestHotel(HotelId1); - var record2 = this.CreateTestHotel(HotelId2); - var record3 = this.CreateTestHotel(HotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [VectorStoreFact] - public async Task ItCanUpsertRecordAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new CosmosNoSqlCollection(fixture.Database!, "upsert-record"); - - await sut.EnsureCollectionExistsAsync(); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - await sut.UpsertAsync(record); - - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - } - - [VectorStoreFact] - public async Task SearchReturnsValidResultsByDefaultAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosNoSqlCollection(fixture.Database!, "vector-search-default"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key1", ids[0]); - Assert.Equal("key2", ids[1]); - Assert.Equal("key3", ids[2]); - - Assert.DoesNotContain("key4", ids); - - Assert.Equal(1, searchResults.First(l => l.Record.HotelId == "key1").Score); - } - - [VectorStoreFact] - public async Task SearchReturnsValidResultsWithOffsetAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosNoSqlCollection(fixture.Database!, "vector-search-with-offset"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 2, new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key3", ids[0]); - Assert.Equal("key4", ids[1]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key2", ids); - } - - [VectorStoreTheory] - [MemberData(nameof(SearchWithFilterData))] - public async Task SearchReturnsValidResultsWithFilterAsync(VectorSearchFilter filter, List expectedIds) - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new CosmosNoSqlCollection(fixture.Database!, "vector-search-with-filter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 4, new() - { - OldFilter = filter, - }).ToListAsync(); - - // Assert - var actualIds = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal(expectedIds, actualIds); - } - - [VectorStoreFact] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - var options = new CosmosNoSqlCollectionOptions - { - Definition = this.GetTestHotelRecordDefinition() - }; - - using var sut = new CosmosNoSqlDynamicCollection(fixture.Database!, "dynamic-mapper", options); - - await sut.EnsureCollectionExistsAsync(); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = HotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["Tags"] = new List { "dynamic" }, - ["parking_is_included"] = false, - ["Timestamp"] = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync(HotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new List { "dynamic" }, localGetResult["Tags"]); - Assert.False((bool?)localGetResult["parking_is_included"]); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), localGetResult["Timestamp"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - public static TheoryData> SearchWithFilterData => new() - { - { - new VectorSearchFilter() - .EqualTo(nameof(CosmosNoSqlHotel.HotelName), "My Hotel key2"), - ["key2"] - }, - { - new VectorSearchFilter() - .AnyTagEqualTo(nameof(CosmosNoSqlHotel.Tags), "t2"), - ["key1", "key2", "key3", "key4"] - }, - { - new VectorSearchFilter() - .EqualTo(nameof(CosmosNoSqlHotel.HotelName), "My Hotel key2") - .AnyTagEqualTo(nameof(CosmosNoSqlHotel.Tags), "t2"), - ["key2"] - }, - { - new VectorSearchFilter() - .EqualTo(nameof(CosmosNoSqlHotel.HotelName), "non-existent-hotel") - .AnyTagEqualTo(nameof(CosmosNoSqlHotel.Tags), "non-existent-tag"), - [] - }, - }; - - #region private - - private CosmosNoSqlHotel CreateTestHotel( - string hotelId, - string? hotelName = null, - ReadOnlyMemory? embedding = null) - { - return new CosmosNoSqlHotel - { - HotelId = hotelId, - HotelName = hotelName ?? $"My Hotel {hotelId}", - HotelCode = 42, - HotelRating = 4.5f, - ParkingIncluded = true, - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - DescriptionEmbedding = embedding ?? new[] { 30f, 31f, 32f, 33f }, - Timestamp = new DateTimeOffset(2024, 9, 24, 17, 28, 32, TimeSpan.Zero), - }; - } - - private VectorStoreCollectionDefinition GetTestHotelRecordDefinition() - { - return new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("parking_is_included", typeof(bool)), - new VectorStoreDataProperty("HotelRating", typeof(float)), - new VectorStoreDataProperty("Tags", typeof(List)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreDataProperty("Timestamp", typeof(DateTimeOffset)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.Flat, DistanceFunction = DistanceFunction.CosineSimilarity } - ] - }; - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlConnectionStringSetConditionAttribute.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlConnectionStringSetConditionAttribute.cs deleted file mode 100644 index ceeb9fae5c1d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlConnectionStringSetConditionAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -/// -/// Attribute to use to skip tests if the connection string for CosmosDB NoSQL is not set. -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public sealed class CosmosNoSqlConnectionStringSetConditionAttribute : Attribute, ITestCondition -{ - public ValueTask IsMetAsync() - { - var isMet = !string.IsNullOrEmpty(CosmosNoSqlVectorStoreFixture.GetConnectionString()); - - return ValueTask.FromResult(isMet); - } - - public string SkipReason - => $"CosmosDB NoSQL connection string was not specified in user secrets. Use the following command to set it: dotnet user-secrets set \"{CosmosNoSqlVectorStoreFixture.ConnectionStringKey}\" \"your_connection_string\""; -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlHotel.cs deleted file mode 100644 index 74bdfe40e79e..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlHotel.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -#pragma warning disable CS8618 - -public record CosmosNoSqlHotel() -{ - /// The key of the record. - [VectorStoreKey] - public string HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData(IsFullTextIndexed = true)] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [JsonPropertyName("parking_is_included")] - [VectorStoreData] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreData] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A datetime field. - [VectorStoreData] - public DateTimeOffset Timestamp { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Flat)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreFixture.cs deleted file mode 100644 index fa16e85167dd..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreFixture.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Azure.Cosmos; -using Microsoft.Extensions.Configuration; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -public class CosmosNoSqlVectorStoreFixture : IAsyncLifetime, IDisposable -{ - public const string ConnectionStringKey = "CosmosNoSql:ConnectionString"; - private const string DatabaseName = "testdb"; - - private readonly CosmosClient _cosmosClient; - - /// that can be used to manage the collections in Azure CosmosDB NoSQL. - public Database? Database { get; private set; } - - public CosmosNoSqlVectorStoreFixture() - { - var connectionString = GetConnectionString(); - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ArgumentNullException($"{connectionString} string is not configured"); - } - - var options = new CosmosClientOptions - { - UseSystemTextJsonSerializerWithOptions = JsonSerializerOptions.Default, - ConnectionMode = ConnectionMode.Gateway, -#pragma warning disable CA5400 // HttpClient may be created without enabling CheckCertificateRevocationList - HttpClientFactory = () => new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }) -#pragma warning restore CA5400 // HttpClient may be created without enabling CheckCertificateRevocationList - }; - - this._cosmosClient = new CosmosClient(connectionString, options); - } - - public static string? GetConnectionString() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile( - path: "testsettings.development.json", - optional: true, - reloadOnChange: true - ) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - - return configuration[ConnectionStringKey]; - } - - public async Task InitializeAsync() - { - await this._cosmosClient.CreateDatabaseIfNotExistsAsync(DatabaseName); - - this.Database = this._cosmosClient.GetDatabase(DatabaseName); - } - - public async Task DisposeAsync() - { - await this.Database!.DeleteAsync(); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this._cosmosClient.Dispose(); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreTests.cs deleted file mode 100644 index 472f9a9551cf..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/CosmosNoSqlVectorStoreTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Connectors.CosmosNoSql; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.CosmosNoSql; - -/// -/// Integration tests for . -/// -[Collection("CosmosNoSqlCollection")] -[CosmosNoSqlConnectionStringSetCondition] -public sealed class CosmosNoSqlVectorStoreTests(CosmosNoSqlVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new CosmosNoSqlVectorStore(fixture.Database!)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/DataHelper.cs b/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/DataHelper.cs deleted file mode 100644 index 651e67de4523..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/CosmosNoSql/DataHelper.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Linq; -using System.Numerics.Tensors; -using Microsoft.SemanticKernel.Memory; - -namespace SemanticKernel.IntegrationTests.Connectors.CosmosNoSql; - -internal static class DataHelper -{ - public static MemoryRecord[] VectorSearchExpectedResults { get; } - public static MemoryRecord[] VectorSearchTestRecords { get; } - public static float[] VectorSearchTestEmbedding { get; } - - static DataHelper() - { - VectorSearchTestRecords = CreateBatchRecords(8); - VectorSearchTestEmbedding = new[] { 1, 0.699f, 0.701f }; - VectorSearchExpectedResults = VectorSearchTestRecords - .OrderByDescending(r => TensorPrimitives.CosineSimilarity(r.Embedding.Span, VectorSearchTestEmbedding)) - .ToArray(); - } - - public static MemoryRecord[] CreateBatchRecords(int count) => - Enumerable - .Range(0, count) - .Select(i => MemoryRecord.LocalRecord( - id: $"test_{i}", - text: $"text_{i}", - description: $"description_{i}", - embedding: new[] { 1, (float)Math.Cos(Math.PI * i / count), (float)Math.Sin(Math.PI * i / count) }, - key: $"test_{i}", - timestamp: DateTimeOffset.Now)) - .ToArray(); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/CommonInMemoryVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/CommonInMemoryVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 61df5cb49429..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/CommonInMemoryVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.InMemory; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.InMemory; - -/// -/// Inherits common integration tests that should pass for any . -/// -public class CommonInMemoryVectorStoreRecordCollectionTests() : BaseVectorStoreRecordCollectionTests -{ - protected override string Key1 => "1"; - protected override string Key2 => "2"; - protected override string Key3 => "3"; - protected override string Key4 => "4"; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - return new InMemoryCollection(recordCollectionName, new() - { - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - return [DistanceFunction.CosineDistance, DistanceFunction.CosineSimilarity, DistanceFunction.DotProductSimilarity, DistanceFunction.EuclideanDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/InMemoryVectorStoreTextSearchTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/InMemoryVectorStoreTextSearchTests.cs deleted file mode 100644 index bd9658cfb6da..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/InMemory/InMemoryVectorStoreTextSearchTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel.Connectors.InMemory; -using Microsoft.SemanticKernel.Data; -using OpenAI; -using SemanticKernel.IntegrationTests.Data; -using SemanticKernel.IntegrationTests.TestSettings; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.InMemory; - -/// -/// Integration tests for using with . -/// -public class InMemoryVectorStoreTextSearchTests : BaseVectorStoreTextSearchTests -{ - /// - public override async Task CreateTextSearchAsync() - { - if (this.VectorStore is null) - { - OpenAIConfiguration? openAIConfiguration = this.Configuration.GetSection("OpenAIEmbeddings").Get(); - Assert.NotNull(openAIConfiguration); - Assert.NotNull(openAIConfiguration.ModelId); - Assert.NotNull(openAIConfiguration.ApiKey); - - this.EmbeddingGenerator = new OpenAIClient(openAIConfiguration.ApiKey) - .GetEmbeddingClient(openAIConfiguration.ModelId) - .AsIEmbeddingGenerator(); - - // Delegate which will create a record. - static DataModel CreateRecord(int index, string text, ReadOnlyMemory embedding) - { - var guid = Guid.NewGuid(); - return new() - { - Key = guid, - Text = text, - Link = $"noop://{guid}", - Tag = index % 2 == 0 ? "Even" : "Odd", - Embedding = embedding - }; - } - - this.VectorStore = new InMemoryVectorStore(); - await AddRecordsAsync(this.VectorStore, "records", this.EmbeddingGenerator, CreateRecord); - } - - var vectorSearch = this.VectorStore.GetCollection("records"); - var stringMapper = new DataModelTextSearchStringMapper(); - var resultMapper = new DataModelTextSearchResultMapper(); - - // TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the InMemoryVectorStore above instead of passing it here. - return new VectorStoreTextSearch(vectorSearch, this.EmbeddingGenerator!, stringMapper, resultMapper); - } - - /// - public override string GetQuery() => "What is the Semantic Kernel?"; - - /// - public override TextSearchFilter GetTextSearchFilter() => new TextSearchFilter().Equality("Tag", "Even"); - - /// - public override bool VerifySearchResults(object[] results, string query, TextSearchFilter? filter = null) - { - Assert.NotNull(results); - Assert.NotEmpty(results); - Assert.Equal(4, results.Length); - foreach (var result in results) - { - Assert.NotNull(result); - Assert.IsType(result); - } - - return true; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusFixture.cs deleted file mode 100644 index c944cc86fd36..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusFixture.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Threading.Tasks; -using Milvus.Client; -using Testcontainers.Milvus; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Milvus; - -public sealed class MilvusFixture : IAsyncLifetime -{ - private readonly MilvusContainer _container = new MilvusBuilder().Build(); - - public string Host => this._container.Hostname; - public int Port => this._container.GetMappedPublicPort(MilvusBuilder.MilvusGrpcPort); - - public MilvusClient CreateClient() - => new(this.Host, "root", "milvus", this.Port); - - public Task InitializeAsync() - { - return Task.CompletedTask; - //=> this._container.StartAsync(); - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - //=> this._container.DisposeAsync().AsTask(); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusMemoryStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusMemoryStoreTests.cs deleted file mode 100644 index 5ca5e8a26c17..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Milvus/MilvusMemoryStoreTests.cs +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Connectors.Milvus; -using Microsoft.SemanticKernel.Memory; -using Milvus.Client; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Milvus; - -[Experimental("SKEXP0020")] -public class MilvusMemoryStoreTests(MilvusFixture milvusFixture) : IClassFixture, IAsyncLifetime -{ - private const string CollectionName = "test"; - private const string? SkipReason = "Fail starting Milvus container on GitHub intermittently."; - - private readonly MilvusFixture _milvusFixture = milvusFixture; - private MilvusMemoryStore Store { get; set; } = null!; - - [Fact(Skip = SkipReason)] - public async Task CreateCollectionAsync() - { - Assert.False(await this.Store.DoesCollectionExistAsync(CollectionName)); - - await this.Store.CreateCollectionAsync(CollectionName); - Assert.True(await this.Store.DoesCollectionExistAsync(CollectionName)); - } - - [Fact(Skip = SkipReason)] - public async Task DropCollectionAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.Store.DeleteCollectionAsync(CollectionName); - Assert.False(await this.Store.DoesCollectionExistAsync(CollectionName)); - } - - [Fact(Skip = SkipReason)] - public async Task GetCollectionsAsync() - { - await this.Store.CreateCollectionAsync("collection1"); - await this.Store.CreateCollectionAsync("collection2"); - - List collections = await this.Store.GetCollectionsAsync().ToListAsync(); - Assert.Contains("collection1", collections); - Assert.Contains("collection2", collections); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - - string id = await this.Store.UpsertAsync(CollectionName, new MemoryRecord( - new MemoryRecordMetadata( - isReference: true, - id: "Some id", - description: "Some description", - text: "Some text", - externalSourceName: "Some external resource name", - additionalMetadata: "Some additional metadata"), - new[] { 10f, 11f, 12f, 13f, 14f }, - key: "Some key", - timestamp: new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero))); - - Assert.Equal("Some id", id); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task GetAsync(bool withEmbeddings) - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - MemoryRecord? record = await this.Store.GetAsync(CollectionName, "Some id", withEmbedding: withEmbeddings); - Assert.NotNull(record); - - Assert.True(record.Metadata.IsReference); - Assert.Equal("Some id", record.Metadata.Id); - Assert.Equal("Some description", record.Metadata.Description); - Assert.Equal("Some text", record.Metadata.Text); - Assert.Equal("Some external resource name", record.Metadata.ExternalSourceName); - Assert.Equal("Some additional metadata", record.Metadata.AdditionalMetadata); - Assert.Equal("Some key", record.Key); - Assert.Equal(new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero), record.Timestamp); - - Assert.Equal( - withEmbeddings ? [10f, 11f, 12f, 13f, 14f] : [], - record.Embedding.ToArray()); - } - - [Fact(Skip = SkipReason)] - public async Task UpsertBatchAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - List ids = await this.InsertSampleDataAsync(); - - Assert.Collection(ids, - id => Assert.Equal("Some id", id), - id => Assert.Equal("Some other id", id)); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task GetBatchAsync(bool withEmbeddings) - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - List records = await this.Store.GetBatchAsync(CollectionName, ["Some id", "Some other id"], withEmbeddings: withEmbeddings).ToListAsync(); - - Assert.Collection(records.OrderBy(r => r.Metadata.Id), - r => - { - Assert.True(r.Metadata.IsReference); - Assert.Equal("Some id", r.Metadata.Id); - Assert.Equal("Some description", r.Metadata.Description); - Assert.Equal("Some text", r.Metadata.Text); - Assert.Equal("Some external resource name", r.Metadata.ExternalSourceName); - Assert.Equal("Some additional metadata", r.Metadata.AdditionalMetadata); - Assert.Equal("Some key", r.Key); - Assert.Equal(new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero), r.Timestamp); - - Assert.Equal( - withEmbeddings ? [10f, 11f, 12f, 13f, 14f] : [], - r.Embedding.ToArray()); - }, - r => - { - Assert.False(r.Metadata.IsReference); - Assert.Equal("Some other id", r.Metadata.Id); - Assert.Empty(r.Metadata.Description); - Assert.Empty(r.Metadata.Text); - Assert.Empty(r.Metadata.ExternalSourceName); - Assert.Empty(r.Metadata.AdditionalMetadata); - Assert.Empty(r.Key); - Assert.Null(r.Timestamp); - - Assert.Equal( - withEmbeddings ? [20f, 21f, 22f, 23f, 24f] : [], - r.Embedding.ToArray()); - }); - } - - [Fact(Skip = SkipReason)] - public async Task RemoveAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - using var milvusClient = this._milvusFixture.CreateClient(); - - Assert.NotNull(await this.Store.GetAsync(CollectionName, "Some id")); - await this.Store.RemoveAsync(CollectionName, "Some id"); - Assert.Null(await this.Store.GetAsync(CollectionName, "Some id")); - } - - [Fact(Skip = SkipReason)] - public async Task RemoveBatchAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - Assert.NotNull(await this.Store.GetAsync(CollectionName, "Some id")); - Assert.NotNull(await this.Store.GetAsync(CollectionName, "Some other id")); - await this.Store.RemoveBatchAsync(CollectionName, ["Some id", "Some other id"]); - Assert.Null(await this.Store.GetAsync(CollectionName, "Some id")); - Assert.Null(await this.Store.GetAsync(CollectionName, "Some other id")); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task GetNearestMatchesAsync(bool withEmbeddings) - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - // There seems to be some race condition where the upserted data above isn't taken into account in the search below and zero results are returned... - await Task.Delay(1000); - - List<(MemoryRecord Record, double SimilarityScore)> results = - await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2, withEmbeddings: withEmbeddings).ToListAsync(); - - Assert.All(results, t => Assert.True(t.SimilarityScore > 0)); - - Assert.Collection(results.OrderBy(r => r.SimilarityScore).Select(r => r.Record), - r => - { - Assert.True(r.Metadata.IsReference); - Assert.Equal("Some id", r.Metadata.Id); - Assert.Equal("Some description", r.Metadata.Description); - Assert.Equal("Some text", r.Metadata.Text); - Assert.Equal("Some external resource name", r.Metadata.ExternalSourceName); - Assert.Equal("Some additional metadata", r.Metadata.AdditionalMetadata); - Assert.Equal("Some key", r.Key); - Assert.Equal(new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero), r.Timestamp); - - Assert.Equal( - withEmbeddings ? [10f, 11f, 12f, 13f, 14f] : [], - r.Embedding.ToArray()); - }, - r => - { - Assert.False(r.Metadata.IsReference); - Assert.Equal("Some other id", r.Metadata.Id); - Assert.Empty(r.Metadata.Description); - Assert.Empty(r.Metadata.Text); - Assert.Empty(r.Metadata.ExternalSourceName); - Assert.Empty(r.Metadata.AdditionalMetadata); - Assert.Empty(r.Key); - Assert.Null(r.Timestamp); - - Assert.Equal( - withEmbeddings ? [20f, 21f, 22f, 23f, 24f] : [], - r.Embedding.ToArray()); - }); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task GetNearestMatchesWithMetricTypeAsync(bool withEmbeddings) - { - //Create collection with default, Ip metric - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - await this.Store.Client.FlushAsync([CollectionName]); - - //Search with Ip metric, run correctly - List<(MemoryRecord Record, double SimilarityScore)> ipResults = - await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2, withEmbeddings: withEmbeddings).ToListAsync(); - - Assert.All(ipResults, t => Assert.True(t.SimilarityScore > 0)); - - //Set the store to Cosine metric, without recreate collection - this.Store = new(this._milvusFixture.Host, vectorSize: 5, port: this._milvusFixture.Port, metricType: SimilarityMetricType.Cosine, consistencyLevel: ConsistencyLevel.Strong); - - //An exception will be thrown here, the exception message includes "metric type not match" - MilvusException milvusException = await Assert.ThrowsAsync(async () => await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2, withEmbeddings: withEmbeddings).ToListAsync()); - - Assert.NotNull(milvusException); - - Assert.Contains("metric type not match", milvusException.Message); - - //Recreate collection with Cosine metric - await this.Store.DeleteCollectionAsync(CollectionName); - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - await this.Store.Client.FlushAsync([CollectionName]); - - //Search with Ip metric, run correctly - List<(MemoryRecord Record, double SimilarityScore)> cosineResults = - await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2, withEmbeddings: withEmbeddings).ToListAsync(); - - Assert.All(cosineResults, t => Assert.True(t.SimilarityScore > 0)); - } - - [Fact(Skip = SkipReason)] - public async Task GetNearestMatchesWithMinRelevanceScoreAsync() - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - List<(MemoryRecord Record, double SimilarityScore)> results = - await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2).ToListAsync(); - - string firstId = results[0].Record.Metadata.Id; - double firstSimilarityScore = results[0].SimilarityScore; - - results = await this.Store.GetNearestMatchesAsync(CollectionName, new[] { 5f, 6f, 7f, 8f, 9f }, limit: 2, minRelevanceScore: firstSimilarityScore + 0.0001).ToListAsync(); - - Assert.DoesNotContain(firstId, results.Select(r => r.Record.Metadata.Id)); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task GetNearestMatchAsync(bool withEmbeddings) - { - await this.Store.CreateCollectionAsync(CollectionName); - await this.InsertSampleDataAsync(); - - (MemoryRecord Record, double SimilarityScore)? result = - await this.Store.GetNearestMatchAsync(CollectionName, new[] { 20f, 21f, 22f, 23f, 24f }, withEmbedding: withEmbeddings); - - Assert.NotNull(result); - Assert.True(result.Value.SimilarityScore > 0); - MemoryRecord record = result.Value.Record; - - Assert.Equal("Some other id", record.Metadata.Id); - Assert.Equal( - withEmbeddings ? [20f, 21f, 22f, 23f, 24f] : [], - record.Embedding.ToArray()); - } - - private async Task> InsertSampleDataAsync() - { - IAsyncEnumerable ids = this.Store.UpsertBatchAsync(CollectionName, - [ - new MemoryRecord( - new MemoryRecordMetadata( - isReference: true, - id: "Some id", - description: "Some description", - text: "Some text", - externalSourceName: "Some external resource name", - additionalMetadata: "Some additional metadata"), - new[] { 10f, 11f, 12f, 13f, 14f }, - key: "Some key", - timestamp: new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero)), - new MemoryRecord( - new MemoryRecordMetadata( - isReference: false, - id: "Some other id", - description: "", - text: "", - externalSourceName: "", - additionalMetadata: ""), - new[] { 20f, 21f, 22f, 23f, 24f }, - key: null, - timestamp: null), - ]); - - List idList = []; - - await foreach (string id in ids) - { - idList.Add(id); - } - - return idList; - } - - public async Task InitializeAsync() - { - this.Store = new(this._milvusFixture.Host, vectorSize: 5, port: this._milvusFixture.Port, consistencyLevel: ConsistencyLevel.Strong); - await this.Store.DeleteCollectionAsync(CollectionName); - } - - public Task DisposeAsync() - { - this.Store.Dispose(); - return Task.CompletedTask; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/DataHelper.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/DataHelper.cs deleted file mode 100644 index fd0a634b47be..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/DataHelper.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Linq; -using System.Numerics.Tensors; -using Microsoft.SemanticKernel.Memory; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -internal static class DataHelper -{ - public static MemoryRecord[] VectorSearchExpectedResults { get; } - public static MemoryRecord[] VectorSearchTestRecords { get; } - public static float[] VectorSearchTestEmbedding { get; } - - static DataHelper() - { - VectorSearchTestRecords = CreateBatchRecords(8); - VectorSearchTestEmbedding = [1, 0.699f, 0.701f]; - VectorSearchExpectedResults = [.. VectorSearchTestRecords.OrderByDescending(r => TensorPrimitives.CosineSimilarity(r.Embedding.Span, VectorSearchTestEmbedding))]; - } - - public static MemoryRecord CreateRecord(string id) => - MemoryRecord.LocalRecord( - id: id, - text: $"text_{id}", - description: $"description_{id}", - embedding: new[] { 1.1f, 2.2f, 3.3f }, - timestamp: GetDateTime()); - - public static MemoryRecord[] CreateBatchRecords(int count) => - Enumerable - .Range(0, count) - .Select(i => MemoryRecord.LocalRecord( - id: $"test_{i}", - text: $"text_{i}", - description: $"description_{i}", - embedding: new[] { 1, (float)Math.Cos(Math.PI * i / count), (float)Math.Sin(Math.PI * i / count) }, - timestamp: GetDateTime())) - .ToArray(); - - private static DateTime GetDateTime() => - new(TimeSpan.TicksPerMillisecond * (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond), DateTimeKind.Local); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs deleted file mode 100644 index acf48766c4c9..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -#pragma warning disable CS8618 - -public class MongoDBHotel -{ - /// The key of the record. - [VectorStoreKey] - public string HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreData] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A datetime metadata field. - [VectorStoreData] - public DateTime Timestamp { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.IvfFlat)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreCollectionFixture.cs deleted file mode 100644 index 7defbeec1f5c..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -[CollectionDefinition("MongoDBVectorStoreCollection")] -public class MongoDBVectorStoreCollectionFixture : ICollectionFixture -{ } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs deleted file mode 100644 index 5a17920cff6f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using DotNet.Testcontainers.Containers; -using Microsoft.Extensions.VectorData; -using MongoDB.Driver; -using Testcontainers.MongoDb; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - -public class MongoDBVectorStoreFixture : IAsyncLifetime -{ - private readonly MongoDbContainer _container = new MongoDbBuilder() - .WithImage("mongodb/mongodb-atlas-local:7.0.6") - .Build(); - - private readonly List _testCollections = ["sk-test-hotels", "sk-test-contacts", "sk-test-addresses"]; - - /// Main test collection for tests. - public string TestCollection => this._testCollections[0]; - - /// that can be used to manage the collections in MongoDB. - public IMongoDatabase MongoDatabase { get; private set; } - - /// Gets the manually created vector store record definition for MongoDB test model. - public VectorStoreCollectionDefinition HotelVectorStoreRecordDefinition { get; private set; } - - public async Task InitializeAsync() - { - using CancellationTokenSource cts = new(); - cts.CancelAfter(TimeSpan.FromSeconds(60)); - await this._container.StartAsync(cts.Token); - -#pragma warning disable CA2000 // Dispose objects before losing scope - var mongoClient = new MongoClient(new MongoClientSettings - { - Server = new MongoServerAddress(this._container.Hostname, this._container.GetMappedPublicPort(MongoDbBuilder.MongoDbPort)), - DirectConnection = true, - }); -#pragma warning restore CA2000 - - this.MongoDatabase = mongoClient.GetDatabase("test"); - - this.HotelVectorStoreRecordDefinition = new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("HotelRating", typeof(float)), - new VectorStoreDataProperty("Tags", typeof(List)), - new VectorStoreDataProperty("Timestamp", typeof(DateTime)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.IvfFlat, DistanceFunction = DistanceFunction.CosineSimilarity } - ] - }; - - foreach (var collection in this._testCollections) - { - await this.MongoDatabase.CreateCollectionAsync(collection); - } - } - - public async Task DisposeAsync() - { - if (this.MongoDatabase is not null) - { - var cursor = await this.MongoDatabase.ListCollectionNamesAsync(); - - while (await cursor.MoveNextAsync().ConfigureAwait(false)) - { - foreach (var collection in cursor.Current) - { - await this.MongoDatabase.DropCollectionAsync(collection); - } - } - } - - if (this._container is not null && this._container.State == TestcontainersStates.Running) - { - await this._container.StopAsync(); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 019776c3b9e5..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.MongoDB; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Driver; -using xRetry; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -[Collection("MongoDBVectorStoreCollection")] -public class MongoDBVectorStoreRecordCollectionTests(MongoDBVectorStoreFixture fixture) -{ - // If null, all tests will be enabled - private const string? SkipReason = "The MongoDB container is intermittently timing out at startup time blocking prs, so these test should be run manually."; - - [RetryTheory(typeof(MongoCommandException), Skip = SkipReason)] - [InlineData("sk-test-hotels", true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange - using var sut = new MongoCollection(fixture.MongoDatabase, collectionName); - - // Act - var actual = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(expectedExists, actual); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanEnsureCollectionExistsAsync() - { - // Arrange - var newCollection = Guid.NewGuid().ToString(); - using var sut = new MongoCollection(fixture.MongoDatabase, newCollection); - - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - await sut.EnsureCollectionDeletedAsync(); - } - - [RetryTheory(typeof(MongoCommandException), Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanCreateCollectionUpsertAndGetAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - - var collectionNamePostfix = useRecordDefinition ? "with-definition" : "with-type"; - collectionNamePostfix += includeVectors ? "-with-vectors" : "-without-vectors"; - var collectionName = $"collection-{collectionNamePostfix}"; - - var options = new MongoCollectionOptions - { - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - - using var sut = new MongoCollection(fixture.MongoDatabase, collectionName, options); - - var record = this.CreateTestHotel(HotelId); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = includeVectors }); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - await sut.EnsureCollectionDeletedAsync(); - - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Tags.ToArray(), getResult.Tags.ToArray()); - Assert.Equal(record.Description, getResult.Description); - Assert.Equal(record.Timestamp.ToUniversalTime(), getResult.Timestamp.ToUniversalTime()); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - const string TempCollectionName = "temp-test"; - await fixture.MongoDatabase.CreateCollectionAsync(TempCollectionName); - - using var sut = new MongoCollection(fixture.MongoDatabase, TempCollectionName); - - Assert.True(await sut.CollectionExistsAsync()); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanGetAndDeleteRecordAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(HotelId); - - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.Null(getResult); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanGetAndDeleteBatchAsync() - { - // Arrange - const string HotelId1 = "11111111-1111-1111-1111-111111111111"; - const string HotelId2 = "22222222-2222-2222-2222-222222222222"; - const string HotelId3 = "33333333-3333-3333-3333-333333333333"; - - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record1 = this.CreateTestHotel(HotelId1); - var record2 = this.CreateTestHotel(HotelId2); - var record3 = this.CreateTestHotel(HotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanUpsertRecordAsync() - { - // Arrange - const string HotelId = "55555555-5555-5555-5555-555555555555"; - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - var record = this.CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - - await sut.UpsertAsync(record); - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task UpsertWithModelWorksCorrectlyAsync() - { - // Arrange - var definition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("Id", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) - ] - }; - - var model = new TestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new MongoCollection( - fixture.MongoDatabase, - fixture.TestCollection, - new() { Definition = definition }); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task UpsertWithVectorStoreModelWorksCorrectlyAsync() - { - // Arrange - var model = new VectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; - - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.HotelId); - Assert.Equal("Test Name", getResult.HotelName); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task UpsertWithBsonModelWorksCorrectlyAsync() - { - // Arrange - var definition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("Id", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) - ] - }; - - var model = new BsonTestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new MongoCollection( - fixture.MongoDatabase, - fixture.TestCollection, - new() { Definition = definition }); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task UpsertWithBsonVectorStoreModelWorksCorrectlyAsync() - { - // Arrange - var model = new BsonVectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; - - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.HotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.HotelId); - Assert.Equal("Test Name", getResult.HotelName); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task UpsertWithBsonVectorStoreWithNameModelWorksCorrectlyAsync() - { - // Arrange - var model = new BsonVectorStoreWithNameTestModel { Id = "key", HotelName = "Test Name" }; - - using var sut = new MongoCollection(fixture.MongoDatabase, fixture.TestCollection); - - // Act - await sut.UpsertAsync(model); - var getResult = await sut.GetAsync(model.Id); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("key", getResult.Id); - Assert.Equal("Test Name", getResult.HotelName); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task SearchReturnsValidResultsByDefaultAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new MongoCollection(fixture.MongoDatabase, "TestVectorizedSearch"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key1", ids[0]); - Assert.Equal("key2", ids[1]); - Assert.Equal("key3", ids[2]); - - Assert.DoesNotContain("key4", ids); - - Assert.Equal(1, searchResults.First(l => l.Record.HotelId == "key1").Score); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithOffsetAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new MongoCollection(fixture.MongoDatabase, "TestVectorizedSearchWithOffset"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 2, new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key3", ids[0]); - Assert.Equal("key4", ids[1]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key2", ids); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithFilterAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new MongoCollection(fixture.MongoDatabase, "TestVectorizedSearchWithFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3, new() - { - OldFilter = new VectorSearchFilter().EqualTo(nameof(MongoDBHotel.HotelName), "My Hotel key2") - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key2", ids[0]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key3", ids); - Assert.DoesNotContain("key4", ids); - } - - [RetryFact(typeof(MongoCommandException), Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new MongoCollectionOptions - { - Definition = fixture.HotelVectorStoreRecordDefinition - }; - - using var sut = new MongoDynamicCollection(fixture.MongoDatabase, fixture.TestCollection, options); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = "DynamicMapper-1", - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["Tags"] = new string[] { "dynamic" }, - ["ParkingIncluded"] = false, - ["Timestamp"] = new DateTime(1970, 1, 18, 0, 0, 0).ToUniversalTime(), - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync("DynamicMapper-1", new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new[] { "dynamic" }, localGetResult["Tags"]); - Assert.False((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(new DateTime(1970, 1, 18, 0, 0, 0).ToUniversalTime(), localGetResult["Timestamp"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - #region private - - private MongoDBHotel CreateTestHotel(string hotelId, ReadOnlyMemory? embedding = null) - { - return new MongoDBHotel - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelId}", - HotelCode = 42, - HotelRating = 4.5f, - ParkingIncluded = true, - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - Timestamp = new DateTime(2024, 09, 23, 15, 32, 33), - DescriptionEmbedding = embedding ?? new[] { 30f, 31f, 32f, 33f }, - }; - } - - private sealed class TestModel - { - public string? Id { get; set; } - - public string? HotelName { get; set; } - } - - private sealed class VectorStoreTestModel - { - [VectorStoreKey] - public string? HotelId { get; set; } - - [VectorStoreData(StorageName = "hotel_name")] - public string? HotelName { get; set; } - } - - private sealed class BsonTestModel - { - [BsonId] - public string? Id { get; set; } - - [BsonElement("hotel_name")] - public string? HotelName { get; set; } - } - - private sealed class BsonVectorStoreTestModel - { - [BsonId] - [VectorStoreKey] - public string? HotelId { get; set; } - - [BsonElement("hotel_name")] - [VectorStoreData] - public string? HotelName { get; set; } - } - - private sealed class BsonVectorStoreWithNameTestModel - { - [BsonId] - [VectorStoreKey] - public string? Id { get; set; } - - [BsonElement("bson_hotel_name")] - [VectorStoreData(StorageName = "storage_hotel_name")] - public string? HotelName { get; set; } - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs deleted file mode 100644 index 03af0b5c4c3f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Connectors.MongoDB; -using SemanticKernel.IntegrationTests.Connectors.Memory; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; - -[Collection("MongoDBVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "The MongoDB container is intermittently timing out at startup time blocking prs, so these test should be run manually.")] -public class MongoDBVectorStoreTests(MongoDBVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new MongoVectorStore(fixture.MongoDatabase)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/CommonPostgresVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/CommonPostgresVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 225f81926a9c..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/CommonPostgresVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.PgVector; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -/// -/// Inherits common integration tests that should pass for any . -/// -/// Postres setup and teardown. -[Collection("PostgresVectorStoreCollection")] -public class CommonPostgresVectorStoreRecordCollectionTests(PostgresVectorStoreFixture fixture) : BaseVectorStoreRecordCollectionTests -{ - protected override string Key1 => "1"; - protected override string Key2 => "2"; - protected override string Key3 => "3"; - protected override string Key4 => "4"; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - return new PostgresCollection(fixture.DataSource!, recordCollectionName, ownsDataSource: false, new() - { - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - return [DistanceFunction.CosineDistance, DistanceFunction.CosineSimilarity, DistanceFunction.DotProductSimilarity, DistanceFunction.EuclideanDistance, DistanceFunction.ManhattanDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresHotel.cs deleted file mode 100644 index d25a38935925..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresHotel.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -/// -/// A test model for the postgres vector store. -/// -public record PostgresHotel() -{ - /// The key of the record. - [VectorStoreKey] - public T HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData()] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData()] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData()] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - [VectorStoreData] - public List Tags { get; set; } = []; - - [VectorStoreData] - public List? ListInts { get; set; } = null; - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.EuclideanDistance, IndexKind = IndexKind.Hnsw)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - - public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; - - public PostgresHotel(T key) : this() - { - this.HotelId = key; - } -} - -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreCollectionFixture.cs deleted file mode 100644 index 066752a822cc..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -[CollectionDefinition("PostgresVectorStoreCollection")] -public class PostgresVectorStoreCollectionFixture : ICollectionFixture -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreFixture.cs deleted file mode 100644 index b9f71202810d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreFixture.cs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Docker.DotNet; -using Docker.DotNet.Models; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.PgVector; -using Npgsql; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -public class PostgresVectorStoreFixture : IAsyncLifetime -{ - /// The docker client we are using to create a postgres container with. - private readonly DockerClient _client; - - /// The id of the postgres container that we are testing with. - private string? _containerId = null; - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - - /// - /// Initializes a new instance of the class. - /// - public PostgresVectorStoreFixture() - { - using var dockerClientConfiguration = new DockerClientConfiguration(); - this._client = dockerClientConfiguration.CreateClient(); - } - - /// - /// Holds the Npgsql data source to use for tests. - /// - public NpgsqlDataSource? DataSource { get; private set; } - - private string _connectionString = null!; - private string _databaseName = null!; - - /// - /// Gets a vector store to use for tests. - /// - public VectorStore VectorStore => new PostgresVectorStore(this.DataSource!, ownsDataSource: false); - - /// - /// Get a database connection - /// - public NpgsqlConnection GetConnection() - { - return this.DataSource!.OpenConnection(); - } - - public VectorStoreCollection GetCollection( - string collectionName, - VectorStoreCollectionDefinition? definition = default) - where TKey : notnull - where TRecord : class - { - var vectorStore = this.VectorStore; - return vectorStore.GetCollection(collectionName, definition); - } - - public VectorStoreCollection> GetDynamicCollection( - string collectionName, - VectorStoreCollectionDefinition definition) - { - var vectorStore = this.VectorStore; - return vectorStore.GetDynamicCollection(collectionName, definition); - } - - /// - /// Create / Recreate postgres docker container and run it. - /// - /// An async task. - public async Task InitializeAsync() - { - this._containerId = await SetupPostgresContainerAsync(this._client); - this._connectionString = "Host=localhost;Port=5432;Username=postgres;Password=example;Database=postgres;"; - this._databaseName = $"sk_it_{Guid.NewGuid():N}"; - - // Connect to postgres. - NpgsqlConnectionStringBuilder connectionStringBuilder = new(this._connectionString) - { - Database = this._databaseName - }; - - NpgsqlDataSourceBuilder dataSourceBuilder = new(connectionStringBuilder.ToString()); - dataSourceBuilder.UseVector(); - - this.DataSource = dataSourceBuilder.Build(); - - // Wait for the postgres container to be ready and create the test database using the initial data source. - var initialDataSource = NpgsqlDataSource.Create(this._connectionString); - using (initialDataSource) - { - var retryCount = 0; - var exceptionCount = 0; - while (retryCount++ < 5) - { - try - { - NpgsqlConnection connection = await initialDataSource.OpenConnectionAsync().ConfigureAwait(false); - - await using (connection) - { - using NpgsqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';"; - await cmd.ExecuteScalarAsync().ConfigureAwait(false); - } - } - catch (NpgsqlException) - { - exceptionCount++; - await Task.Delay(1000); - } - } - - if (exceptionCount >= 5) - { - // Throw an exception for test setup - throw new InvalidOperationException("Postgres container did not start in time."); - } - - await this.CreateDatabaseAsync(initialDataSource); - } - - // Create the table. - await this.CreateTableAsync(); - } - - private async Task CreateTableAsync() - { - NpgsqlConnection connection = await this.DataSource!.OpenConnectionAsync().ConfigureAwait(false); - - await using (connection) - { - using NpgsqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = @" - CREATE TABLE hotel_info ( - HotelId INTEGER NOT NULL, - HotelName TEXT, - HotelCode INTEGER NOT NULL, - HotelRating REAL, - parking_is_included BOOLEAN, - Tags TEXT[] NOT NULL, - Description TEXT NOT NULL, - DescriptionEmbedding VECTOR(4) NOT NULL, - PRIMARY KEY (HotelId));"; - await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); - } - } - - /// - /// Delete the docker container after the test run. - /// - /// An async task. - public async Task DisposeAsync() - { - if (this.DataSource != null) - { - this.DataSource.Dispose(); - } - - await this.DropDatabaseAsync(); - - if (this._containerId != null) - { - await this._client.Containers.StopContainerAsync(this._containerId, new ContainerStopParameters()); - await this._client.Containers.RemoveContainerAsync(this._containerId, new ContainerRemoveParameters()); - } - } - - /// - /// Setup the postgres container by pulling the image and running it. - /// - /// The docker client to create the container with. - /// The id of the container. - private static async Task SetupPostgresContainerAsync(DockerClient client) - { - await client.Images.CreateImageAsync( - new ImagesCreateParameters - { - FromImage = "pgvector/pgvector", - Tag = "pg16", - }, - null, - new Progress()); - - var container = await client.Containers.CreateContainerAsync(new CreateContainerParameters() - { - Image = "pgvector/pgvector:pg16", - HostConfig = new HostConfig() - { - PortBindings = new Dictionary> - { - {"5432", new List {new() {HostPort = "5432" } }}, - }, - PublishAllPorts = true - }, - ExposedPorts = new Dictionary - { - { "5432", default }, - }, - Env = - [ - "POSTGRES_USER=postgres", - "POSTGRES_PASSWORD=example", - ], - }); - - await client.Containers.StartContainerAsync( - container.ID, - new ContainerStartParameters()); - - return container.ID; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "The database name is generated randomly, it does not support parameterized passing.")] - private async Task CreateDatabaseAsync(NpgsqlDataSource initialDataSource) - { - await using (NpgsqlConnection conn = await initialDataSource.OpenConnectionAsync()) - { - await using NpgsqlCommand command = new($"CREATE DATABASE \"{this._databaseName}\"", conn); - await command.ExecuteNonQueryAsync(); - } - - await using (NpgsqlConnection conn = await this.DataSource!.OpenConnectionAsync()) - { - await using (NpgsqlCommand command = new("CREATE EXTENSION vector", conn)) - { - await command.ExecuteNonQueryAsync(); - } - await conn.ReloadTypesAsync(); - } - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "The database name is generated randomly, it does not support parameterized passing.")] - private async Task DropDatabaseAsync() - { - using NpgsqlDataSource dataSource = NpgsqlDataSource.Create(this._connectionString); - await using NpgsqlConnection conn = await dataSource.OpenConnectionAsync(); - await using NpgsqlCommand command = new($"DROP DATABASE IF EXISTS \"{this._databaseName}\"", conn); - await command.ExecuteNonQueryAsync(); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 05158a141e50..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Npgsql; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -[Collection("PostgresVectorStoreCollection")] -public sealed class PostgresVectorStoreRecordCollectionTests(PostgresVectorStoreFixture fixture) -{ - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task CollectionExistsReturnsCollectionStateAsync(bool createCollection) - { - // Arrange - var sut = fixture.GetCollection>("CollectionExists"); - - if (createCollection) - { - await sut.EnsureCollectionExistsAsync(); - } - - try - { - // Act - var collectionExists = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(createCollection, collectionExists); - } - finally - { - // Cleanup - if (createCollection) - { - await sut.EnsureCollectionDeletedAsync(); - } - } - } - - [Fact] - public async Task CanCreateCollectionWithSpecialCharactersInNameAsync() - { - // Arrange - var sut = fixture.GetCollection>("Special-Char"); - - try - { - // Act - await sut.EnsureCollectionExistsAsync(); - } - finally - { - // Cleanup - await sut.EnsureCollectionDeletedAsync(); - } - } - - [Fact] - public async Task CollectionCanUpsertAndGetAsync() - { - // Arrange - var sut = fixture.GetCollection>("CollectionCanUpsertAndGet"); - if (await sut.CollectionExistsAsync()) - { - await sut.EnsureCollectionDeletedAsync(); - } - - await sut.EnsureCollectionExistsAsync(); - - var writtenHotel1 = new PostgresHotel { HotelId = 1, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 4.5f, Tags = ["tag1", "tag2"] }; - var writtenHotel2 = new PostgresHotel { HotelId = 2, HotelName = "Hotel 2", HotelCode = 2, ParkingIncluded = false, HotelRating = 2.5f, ListInts = [1, 2] }; - - try - { - // Act - - await sut.UpsertAsync(writtenHotel1); - - await sut.UpsertAsync(writtenHotel2); - - var fetchedHotel1 = await sut.GetAsync(1); - var fetchedHotel2 = await sut.GetAsync(2); - - // Assert - Assert.NotNull(fetchedHotel1); - Assert.Equal(1, fetchedHotel1!.HotelId); - Assert.Equal("Hotel 1", fetchedHotel1!.HotelName); - Assert.Equal(1, fetchedHotel1!.HotelCode); - Assert.True(fetchedHotel1!.ParkingIncluded); - Assert.Equal(4.5f, fetchedHotel1!.HotelRating); - Assert.NotNull(fetchedHotel1!.Tags); - Assert.Equal(2, fetchedHotel1!.Tags!.Count); - Assert.Equal("tag1", fetchedHotel1!.Tags![0]); - Assert.Equal("tag2", fetchedHotel1!.Tags![1]); - Assert.Null(fetchedHotel1!.ListInts); - - // Since these values are updated in the database, they will not match existly, but should be very close to each other. - Assert.True(TruncateMilliseconds(fetchedHotel1.CreatedAt) >= TruncateMilliseconds(writtenHotel1.CreatedAt) && TruncateMilliseconds(fetchedHotel1.CreatedAt) <= TruncateMilliseconds(writtenHotel1.CreatedAt).AddSeconds(1)); - Assert.True(TruncateMilliseconds(fetchedHotel1.UpdatedAt) >= TruncateMilliseconds(writtenHotel1.UpdatedAt) && TruncateMilliseconds(fetchedHotel1.UpdatedAt) <= TruncateMilliseconds(writtenHotel1.UpdatedAt).AddSeconds(1)); - - Assert.NotNull(fetchedHotel2); - Assert.Equal(2, fetchedHotel2!.HotelId); - Assert.Equal("Hotel 2", fetchedHotel2!.HotelName); - Assert.Equal(2, fetchedHotel2!.HotelCode); - Assert.False(fetchedHotel2!.ParkingIncluded); - Assert.Equal(2.5f, fetchedHotel2!.HotelRating); - Assert.NotNull(fetchedHotel2!.Tags); - Assert.Empty(fetchedHotel2!.Tags); - Assert.NotNull(fetchedHotel2!.ListInts); - Assert.Equal(2, fetchedHotel2!.ListInts!.Count); - Assert.Equal(1, fetchedHotel2!.ListInts![0]); - Assert.Equal(2, fetchedHotel2!.ListInts![1]); - - // Since these values are updated in the database, they will not match existly, but should be very close to each other. - Assert.True(TruncateMilliseconds(fetchedHotel2.CreatedAt) >= TruncateMilliseconds(writtenHotel2.CreatedAt) && TruncateMilliseconds(fetchedHotel2.CreatedAt) <= TruncateMilliseconds(writtenHotel2.CreatedAt).AddSeconds(1)); - Assert.True(TruncateMilliseconds(fetchedHotel2.UpdatedAt) >= TruncateMilliseconds(writtenHotel2.UpdatedAt) && TruncateMilliseconds(fetchedHotel2.UpdatedAt) <= TruncateMilliseconds(writtenHotel2.UpdatedAt).AddSeconds(1)); - } - finally - { - // Cleanup - await sut.EnsureCollectionDeletedAsync(); - } - } - - public static IEnumerable ItCanGetAndDeleteRecordParameters => - new List - { - new object[] { typeof(short), (short)3 }, - new object[] { typeof(int), 5 }, - new object[] { typeof(long), 7L }, - new object[] { typeof(string), "key1" }, - new object[] { typeof(Guid), Guid.NewGuid() } - }; - - [Theory] - [MemberData(nameof(ItCanGetAndDeleteRecordParameters))] - public async Task ItCanGetAndDeleteRecordAsync(Type idType, TKey? key) - { - // Arrange - var collectionName = "DeleteRecord"; - var sut = this.GetCollection(idType, collectionName); - - await sut.EnsureCollectionExistsAsync(); - - try - { - var record = this.CreateRecord(idType, key!); - var recordKey = record.HotelId; - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(recordKey); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(recordKey); - - getResult = await sut.GetAsync(recordKey); - - // Assert - Assert.Null(getResult); - } - finally - { - // Cleanup - await sut.EnsureCollectionDeletedAsync(); - } - } - - [Fact] - public async Task ItCanGetUpsertDeleteBatchAsync() - { - // Arrange - const int HotelId1 = 1; - const int HotelId2 = 2; - const int HotelId3 = 3; - - var sut = fixture.GetCollection>("GetUpsertDeleteBatch"); - - await sut.EnsureCollectionExistsAsync(); - - var record1 = new PostgresHotel { HotelId = HotelId1, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 4.5f, Tags = ["tag1", "tag2"] }; - var record2 = new PostgresHotel { HotelId = HotelId2, HotelName = "Hotel 2", HotelCode = 1, ParkingIncluded = false, HotelRating = 3.5f, Tags = ["tag1", "tag3"] }; - var record3 = new PostgresHotel { HotelId = HotelId3, HotelName = "Hotel 3", HotelCode = 1, ParkingIncluded = true, HotelRating = 2.5f, Tags = ["tag1", "tag4"] }; - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [Fact] - public async Task ItCanUpsertExistingRecordAsync() - { - // Arrange - const int HotelId = 5; - var sut = fixture.GetCollection>("UpsertRecord"); - - await sut.EnsureCollectionExistsAsync(); - - var record = new PostgresHotel { HotelId = HotelId, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 4.5f, Tags = ["tag1", "tag2"] }; - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = true }); - - Assert.NotNull(getResult); - Assert.Null(getResult!.DescriptionEmbedding); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - record.DescriptionEmbedding = new[] { 1f, 2f, 3f, 4f }; - - await sut.UpsertAsync(record); - getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = true }); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - - [Fact] - public async Task ItCanReadManuallyInsertedRecordAsync() - { - const string CollectionName = "ItCanReadManuallyInsertedRecordAsync"; - // Arrange - var sut = fixture.GetCollection>(CollectionName); - await sut.EnsureCollectionExistsAsync().ConfigureAwait(true); - Assert.True(await sut.CollectionExistsAsync().ConfigureAwait(true)); - await using (var connection = fixture.GetConnection()) - { - using NpgsqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = @$" - INSERT INTO public.""{CollectionName}"" ( - ""HotelId"", ""HotelName"", ""HotelCode"", ""HotelRating"", ""parking_is_included"", ""Tags"", ""Description"", ""DescriptionEmbedding"" - ) VALUES ( - 215, 'Divine Lorraine', 215, 5, false, ARRAY['historic', 'philly'], 'An iconic building on broad street', '[10,20,30,40]' - );"; - await cmd.ExecuteNonQueryAsync().ConfigureAwait(true); - } - - // Act - var getResult = await sut.GetAsync(215, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(getResult); - Assert.Equal(215, getResult!.HotelId); - Assert.Equal("Divine Lorraine", getResult.HotelName); - Assert.Equal(215, getResult.HotelCode); - Assert.Equal(5, getResult.HotelRating); - Assert.False(getResult.ParkingIncluded); - Assert.Equal(["historic", "philly"], getResult.Tags); - Assert.Equal("An iconic building on broad street", getResult.Description); - Assert.Equal([10f, 20f, 30f, 40f], getResult.DescriptionEmbedding!.Value.ToArray()); - } - - [Fact] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - const int HotelId = 5; - - var sut = fixture.GetDynamicCollection("DynamicMapperWithNumericKey", GetVectorStoreRecordDefinition()); - - await sut.EnsureCollectionExistsAsync(); - - var record = new PostgresHotel { HotelId = (int)HotelId, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 4.5f, Tags = ["tag1", "tag2"] }; - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = HotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["HotelCode"] = 1, - ["ParkingIncluded"] = true, - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync(HotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.True((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal([30f, 31f, 32f, 33f], ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - - // Act - update with null embeddings - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = HotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["HotelCode"] = 1, - ["ParkingIncluded"] = true, - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = null - }); - - var localGetResult2 = await sut.GetAsync(HotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult2); - Assert.Null(localGetResult2["DescriptionEmbedding"]); - } - - [Theory] - [InlineData(true, DistanceFunction.CosineDistance)] - [InlineData(false, DistanceFunction.CosineDistance)] - [InlineData(false, DistanceFunction.CosineSimilarity)] - [InlineData(false, DistanceFunction.EuclideanDistance)] - [InlineData(false, DistanceFunction.ManhattanDistance)] - [InlineData(false, DistanceFunction.DotProductSimilarity)] - public async Task SearchReturnsValidResultsByDefaultAsync(bool includeVectors, string distanceFunction) - { - // Arrange - var hotel1 = new PostgresHotel { HotelId = 1, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 4.5f, Tags = ["tag1", "tag2"], DescriptionEmbedding = new[] { 1f, 0f, 0f, 0f } }; - var hotel2 = new PostgresHotel { HotelId = 2, HotelName = "Hotel 2", HotelCode = 2, ParkingIncluded = false, HotelRating = 2.5f, Tags = ["tag1", "tag3"], DescriptionEmbedding = new[] { 0f, 1f, 0f, 0f } }; - var hotel3 = new PostgresHotel { HotelId = 3, HotelName = "Hotel 3", HotelCode = 3, ParkingIncluded = true, HotelRating = 3.5f, Tags = ["tag1", "tag4"], DescriptionEmbedding = new[] { 0f, 0f, 1f, 0f } }; - var hotel4 = new PostgresHotel { HotelId = 4, HotelName = "Hotel 4", HotelCode = 4, ParkingIncluded = false, HotelRating = 1.5f, Tags = ["tag1", "tag5"], DescriptionEmbedding = new[] { 0f, 0f, 0f, 1f } }; - - var sut = fixture.GetCollection>($"VectorizedSearch_{includeVectors}_{distanceFunction}", GetVectorStoreRecordDefinition(distanceFunction)); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([0.9f, 0.1f, 0.5f, 0.8f]), top: 3, new() - { - IncludeVectors = includeVectors - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal(1, ids[0]); - Assert.Equal(4, ids[1]); - Assert.Equal(3, ids[2]); - - // Default limit is 3 - Assert.DoesNotContain(2, ids); - - Assert.True(0 < results.First(l => l.Record.HotelId == 1).Score); - - Assert.Equal(includeVectors, results.All(result => result.Record.DescriptionEmbedding is not null)); - } - - [Fact] - public async Task SearchWithEqualToFilterReturnsValidResultsAsync() - { - // Arrange - var hotel1 = new PostgresHotel { HotelId = 1, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 2.5f, Tags = ["tag1", "tag2"], DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } }; - var hotel2 = new PostgresHotel { HotelId = 2, HotelName = "Hotel 2", HotelCode = 2, ParkingIncluded = false, HotelRating = 2.5f, Tags = ["tag1", "tag3"], DescriptionEmbedding = new[] { 10f, 10f, 10f, 10f } }; - var hotel3 = new PostgresHotel { HotelId = 3, HotelName = "Hotel 3", HotelCode = 3, ParkingIncluded = true, HotelRating = 2.5f, Tags = ["tag1", "tag4"], DescriptionEmbedding = new[] { 20f, 20f, 20f, 20f } }; - var hotel4 = new PostgresHotel { HotelId = 4, HotelName = "Hotel 4", HotelCode = 4, ParkingIncluded = false, HotelRating = 3.5f, Tags = ["tag1", "tag5"], DescriptionEmbedding = new[] { 40f, 40f, 40f, 40f } }; - - var sut = fixture.GetCollection>("VectorizedSearchWithEqualToFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([30f, 29f, 28f, 27f]), top: 5, new() - { - IncludeVectors = false, - OldFilter = new([ - new EqualToFilterClause("HotelRating", 2.5f) - ]) - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal([1, 3, 2], ids); - } - - [Fact] - public async Task SearchWithAnyTagFilterReturnsValidResultsAsync() - { - // Arrange - var hotel1 = new PostgresHotel { HotelId = 1, HotelName = "Hotel 1", HotelCode = 1, ParkingIncluded = true, HotelRating = 2.5f, Tags = ["tag1", "tag2"], DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } }; - var hotel2 = new PostgresHotel { HotelId = 2, HotelName = "Hotel 2", HotelCode = 2, ParkingIncluded = false, HotelRating = 2.5f, Tags = ["tag1", "tag3"], DescriptionEmbedding = new[] { 10f, 10f, 10f, 10f } }; - var hotel3 = new PostgresHotel { HotelId = 3, HotelName = "Hotel 3", HotelCode = 3, ParkingIncluded = true, HotelRating = 2.5f, Tags = ["tag2", "tag4"], DescriptionEmbedding = new[] { 20f, 20f, 20f, 20f } }; - var hotel4 = new PostgresHotel { HotelId = 4, HotelName = "Hotel 4", HotelCode = 4, ParkingIncluded = false, HotelRating = 3.5f, Tags = ["tag1", "tag5"], DescriptionEmbedding = new[] { 40f, 40f, 40f, 40f } }; - - var sut = fixture.GetCollection>("VectorizedSearchWithAnyTagEqualToFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([30f, 29f, 28f, 27f]), top: 5, new() - { - IncludeVectors = false, - OldFilter = new([ - new AnyTagEqualToFilterClause("Tags", "tag2") - ]) - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal([1, 3], ids); - } - - [Fact] - public async Task ItCanUpsertAndGetEnumerableTypesAsync() - { - // Arrange - var sut = fixture.GetCollection("UpsertAndGetEnumerableTypes"); - - await sut.EnsureCollectionExistsAsync(); - - var record = new RecordWithEnumerables - { - Id = 1, - ListInts = [1, 2, 3], - ArrayInts = new[] { 4, 5, 6 }, - }; - - // Act - await sut.UpsertAsync(record); - - var getResult = await sut.GetAsync(1); - - // Assert - Assert.NotNull(getResult); - Assert.Equal(1, getResult!.Id); - Assert.NotNull(getResult.ListInts); - Assert.Equal(3, getResult.ListInts!.Count); - Assert.Equal(1, getResult.ListInts![0]); - Assert.Equal(2, getResult.ListInts![1]); - Assert.Equal(3, getResult.ListInts![2]); - Assert.NotNull(getResult.ArrayInts); - Assert.Equal(3, getResult.ArrayInts!.Length); - Assert.Equal(4, getResult.ArrayInts![0]); - Assert.Equal(5, getResult.ArrayInts![1]); - Assert.Equal(6, getResult.ArrayInts![2]); - } - - #region private ================================================================================== - - private static VectorStoreCollectionDefinition GetVectorStoreRecordDefinition(string distanceFunction = DistanceFunction.CosineDistance) => new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(TKey)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("HotelRating", typeof(float?)), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("Tags", typeof(List)), - new VectorStoreDataProperty("ListInts", typeof(List)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.Hnsw, DistanceFunction = distanceFunction } - ] - }; - - private dynamic GetCollection(Type idType, string collectionName) - { - var method = typeof(PostgresVectorStoreFixture).GetMethod("GetCollection"); - var genericMethod = method!.MakeGenericMethod(idType, typeof(PostgresHotel<>).MakeGenericType(idType)); - return genericMethod.Invoke(fixture, [collectionName, null])!; - } - - private PostgresHotel CreateRecord(Type idType, TKey key) - { - var recordType = typeof(PostgresHotel<>).MakeGenericType(idType); - var record = (PostgresHotel)Activator.CreateInstance(recordType, key)!; - record.HotelName = "Hotel 1"; - record.HotelCode = 1; - record.ParkingIncluded = true; - record.HotelRating = 4.5f; - record.Tags = ["tag1", "tag2"]; - return record; - } - private static DateTime TruncateMilliseconds(DateTime dateTime) - { - return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); - } - - private static DateTimeOffset TruncateMilliseconds(DateTimeOffset dateTimeOffset) - { - return new DateTimeOffset(dateTimeOffset.Ticks - (dateTimeOffset.Ticks % TimeSpan.TicksPerSecond), dateTimeOffset.Offset); - } - -#pragma warning disable CA1812, CA1859 - private sealed class RecordWithEnumerables - { - [VectorStoreKey] - public int Id { get; set; } - - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineDistance)] - public ReadOnlyMemory? Embedding { get; set; } - - [VectorStoreData] - public List? ListInts { get; set; } - - [VectorStoreData] - public int[]? ArrayInts { get; set; } - } -#pragma warning restore CA1812, CA1859 - - #endregion - -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreTests.cs deleted file mode 100644 index a23c32288cd4..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/PgVector/PostgresVectorStoreTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.PgVector; - -[Collection("PostgresVectorStoreCollection")] -public class PostgresVectorStoreTests(PostgresVectorStoreFixture fixture) -{ - [Fact] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() - { - // Arrange - var sut = fixture.VectorStore; - - // Setup - var collection = sut.GetCollection>("VS_TEST_HOTELS"); - await collection.EnsureCollectionExistsAsync(); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("VS_TEST_HOTELS", collectionNames); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/CommonQdrantVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/CommonQdrantVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 3cad654932ce..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/CommonQdrantVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Qdrant; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -/// -/// Inherits common integration tests that should pass for any . -/// -/// Qdrant setup and teardown. -[Collection("QdrantVectorStoreCollection")] -public class CommonQdrantVectorStoreRecordCollectionTests(QdrantVectorStoreFixture fixture) : BaseVectorStoreRecordCollectionTests -{ - protected override ulong Key1 => 1; - protected override ulong Key2 => 2; - protected override ulong Key3 => 3; - protected override ulong Key4 => 4; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - return new QdrantCollection(fixture.QdrantClient, recordCollectionName, ownsClient: false, new() - { - HasNamedVectors = true, - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - return [DistanceFunction.CosineSimilarity, DistanceFunction.EuclideanDistance, DistanceFunction.ManhattanDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantTextSearchTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantTextSearchTests.cs deleted file mode 100644 index 5a1619138472..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantTextSearchTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Connectors.Qdrant; -using Microsoft.SemanticKernel.Data; -using SemanticKernel.IntegrationTests.Data; -using Xunit; -using static SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant.QdrantVectorStoreFixture; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -/// -/// Integration tests for using with . -/// -[Collection("QdrantVectorStoreCollection")] -public class QdrantTextSearchTests(QdrantVectorStoreFixture fixture) : BaseVectorStoreTextSearchTests -{ - /// - public override Task CreateTextSearchAsync() - { - if (this.VectorStore is null) - { - this.EmbeddingGenerator = fixture.EmbeddingGenerator; - this.VectorStore = new QdrantVectorStore(fixture.QdrantClient, ownsClient: false, new QdrantVectorStoreOptions { EmbeddingGenerator = fixture.EmbeddingGenerator }); - } - - var options = new QdrantCollectionOptions - { - HasNamedVectors = true, - Definition = fixture.HotelVectorStoreRecordDefinition, - }; - using var collection = new QdrantCollection(fixture.QdrantClient, "namedVectorsHotels", ownsClient: false, options); - var stringMapper = new HotelInfoTextSearchStringMapper(); - var resultMapper = new HotelInfoTextSearchResultMapper(); - - var result = new VectorStoreTextSearch(collection, this.EmbeddingGenerator!, stringMapper, resultMapper); - - return Task.FromResult(result); - } - - /// - public override string GetQuery() => "Find a great hotel"; - - /// - public override TextSearchFilter GetTextSearchFilter() => new TextSearchFilter().Equality("HotelName", "My Hotel 11"); - - /// - public override bool VerifySearchResults(object[] results, string query, TextSearchFilter? filter = null) - { - Assert.NotNull(results); - Assert.NotEmpty(results); - Assert.Equal(filter is null ? 4 : 1, results.Length); - foreach (var result in results) - { - Assert.NotNull(result); - Assert.IsType(result); - } - - return true; - } - - /// - /// String mapper which converts a Hotel to a string. - /// - protected sealed class HotelInfoTextSearchStringMapper : ITextSearchStringMapper - { - /// - public string MapFromResultToString(object result) - { - if (result is HotelInfo hotel) - { - return $"{hotel.HotelName} {hotel.Description}"; - } - throw new ArgumentException("Invalid result type."); - } - } - - /// - /// Result mapper which converts a Hotel to a TextSearchResult. - /// - protected sealed class HotelInfoTextSearchResultMapper : ITextSearchResultMapper - { - /// - public TextSearchResult MapFromResultToTextSearchResult(object result) - { - if (result is HotelInfo hotel) - { - return new TextSearchResult(value: hotel.Description) { Name = hotel.HotelName, Link = $"id://{hotel.HotelId}" }; - } - throw new ArgumentException("Invalid result type."); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreCollectionFixture.cs deleted file mode 100644 index a7b565d71c2d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -[CollectionDefinition("QdrantVectorStoreCollection")] -public class QdrantVectorStoreCollectionFixture : ICollectionFixture -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreFixture.cs deleted file mode 100644 index bcc9341c2aa4..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreFixture.cs +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Azure.AI.OpenAI; -using Azure.Identity; -using Docker.DotNet; -using Docker.DotNet.Models; -using Grpc.Core; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.VectorData; -using Qdrant.Client; -using Qdrant.Client.Grpc; -using SemanticKernel.IntegrationTests.TestSettings; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -public class QdrantVectorStoreFixture : IAsyncLifetime -{ - /// The docker client we are using to create a qdrant container with. - private readonly DockerClient _client; - - /// The id of the qdrant container that we are testing with. - private string? _containerId = null; - - /// The vector dimension. - private const int VectorDimensions = 1536; - - /// - /// Test Configuration setup. - /// - private static readonly IConfigurationRoot s_configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - - /// - /// Initializes a new instance of the class. - /// - public QdrantVectorStoreFixture() - { - using var dockerClientConfiguration = new DockerClientConfiguration(); - this._client = dockerClientConfiguration.CreateClient(); - this.HotelVectorStoreRecordDefinition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(ulong)), - new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true, IsFullTextIndexed = true }, - new VectorStoreDataProperty("HotelCode", typeof(int)) { IsIndexed = true }, - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { IsIndexed = true, StorageName = "parking_is_included" }, - new VectorStoreDataProperty("HotelRating", typeof(float)) { IsIndexed = true }, - new VectorStoreDataProperty("LastRenovationDate", typeof(DateTimeOffset)) { IsIndexed = true }, - new VectorStoreDataProperty("Tags", typeof(List)) { IsIndexed = true }, - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), VectorDimensions) { DistanceFunction = DistanceFunction.ManhattanDistance } - ] - }; - this.HotelWithGuidIdVectorStoreRecordDefinition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(Guid)), - new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true, IsFullTextIndexed = true }, - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), VectorDimensions) { DistanceFunction = DistanceFunction.ManhattanDistance } - ] - }; - AzureOpenAIConfiguration? embeddingsConfig = s_configuration.GetSection("AzureOpenAIEmbeddings").Get(); - Assert.NotNull(embeddingsConfig); - Assert.NotEmpty(embeddingsConfig.DeploymentName); - Assert.NotEmpty(embeddingsConfig.Endpoint); - - this.EmbeddingGenerator = new AzureOpenAIClient(new Uri(embeddingsConfig.Endpoint), new AzureCliCredential()) - .GetEmbeddingClient(embeddingsConfig.DeploymentName) - .AsIEmbeddingGenerator(); - } - -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - - /// Gets the qdrant client connection to use for tests. - public QdrantClient QdrantClient { get; private set; } - - public IEmbeddingGenerator> EmbeddingGenerator { get; private set; } - - /// Gets the manually created vector store record definition for our test model. - public VectorStoreCollectionDefinition HotelVectorStoreRecordDefinition { get; private set; } - - /// Gets the manually created vector store record definition for our test model. - public VectorStoreCollectionDefinition HotelWithGuidIdVectorStoreRecordDefinition { get; private set; } - - /// - /// Create / Recreate qdrant docker container and run it. - /// - /// An async task. - public async Task InitializeAsync() - { - this._containerId = await SetupQdrantContainerAsync(this._client); - - // Connect to qdrant. - this.QdrantClient = new QdrantClient("localhost"); - - // Create schemas for the vector store. - var vectorParamsMap = new VectorParamsMap(); - vectorParamsMap.Map.Add("DescriptionEmbedding", new VectorParams { Size = VectorDimensions, Distance = Distance.Cosine }); - - // Wait for the qdrant container to be ready. - var retryCount = 0; - while (retryCount++ < 5) - { - try - { - await this.QdrantClient.ListCollectionsAsync(); - } - catch (RpcException e) - { - if (e.StatusCode != Grpc.Core.StatusCode.Unavailable) - { - throw; - } - - await Task.Delay(1000); - } - } - - await this.QdrantClient.CreateCollectionAsync( - "namedVectorsHotels", - vectorParamsMap); - - await this.QdrantClient.CreateCollectionAsync( - "singleVectorHotels", - new VectorParams { Size = VectorDimensions, Distance = Distance.Cosine }); - - await this.QdrantClient.CreateCollectionAsync( - "singleVectorGuidIdHotels", - new VectorParams { Size = VectorDimensions, Distance = Distance.Cosine }); - - // Create test data common to both named and unnamed vectors. - var tags = new ListValue(); - tags.Values.Add("t11.1"); - tags.Values.Add("t11.2"); - var tagsValue = new Value(); - tagsValue.ListValue = tags; - - var tags2 = new ListValue(); - tags2.Values.Add("t13.1"); - tags2.Values.Add("t13.2"); - var tagsValue2 = new Value(); - tagsValue2.ListValue = tags2; - - // Create some test data using named vectors. - var embedding = (await this.EmbeddingGenerator.GenerateAsync("This is a great hotel.")).Vector; - var embeddingArray = embedding.ToArray(); - - var namedVectors1 = new NamedVectors(); - var namedVectors2 = new NamedVectors(); - var namedVectors3 = new NamedVectors(); - var namedVectors4 = new NamedVectors(); - - namedVectors1.Vectors.Add("DescriptionEmbedding", embeddingArray); - namedVectors2.Vectors.Add("DescriptionEmbedding", embeddingArray); - namedVectors3.Vectors.Add("DescriptionEmbedding", embeddingArray); - namedVectors4.Vectors.Add("DescriptionEmbedding", embeddingArray); - - List namedVectorPoints = - [ - new PointStruct - { - Id = 11, - Vectors = new Vectors { Vectors_ = namedVectors1 }, - Payload = { ["HotelName"] = "My Hotel 11", ["HotelCode"] = 11, ["parking_is_included"] = true, ["Tags"] = tagsValue, ["HotelRating"] = 4.5f, ["Description"] = "This is a great hotel.", ["LastRenovationDate"] = "2025-02-10T05:10:15.0000000Z" } - }, - new PointStruct - { - Id = 12, - Vectors = new Vectors { Vectors_ = namedVectors2 }, - Payload = { ["HotelName"] = "My Hotel 12", ["HotelCode"] = 12, ["parking_is_included"] = false, ["Description"] = "This is a great hotel." } - }, - new PointStruct - { - Id = 13, - Vectors = new Vectors { Vectors_ = namedVectors3 }, - Payload = { ["HotelName"] = "My Hotel 13", ["HotelCode"] = 13, ["parking_is_included"] = false, ["Tags"] = tagsValue2, ["Description"] = "This is a great hotel.", ["LastRenovationDate"] = "2020-02-01T00:00:00.0000000Z" } - }, - new PointStruct - { - Id = 14, - Vectors = new Vectors { Vectors_ = namedVectors4 }, - Payload = { ["HotelName"] = "My Hotel 14", ["HotelCode"] = 14, ["parking_is_included"] = false, ["HotelRating"] = 4.5f, ["Description"] = "This is a great hotel." } - }, - ]; - - await this.QdrantClient.UpsertAsync("namedVectorsHotels", namedVectorPoints); - - // Create some test data using a single unnamed vector. - List unnamedVectorPoints = - [ - new PointStruct - { - Id = 11, - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 11", ["HotelCode"] = 11, ["parking_is_included"] = true, ["Tags"] = tagsValue, ["HotelRating"] = 4.5f, ["Description"] = "This is a great hotel.", ["LastRenovationDate"] = "2025-02-10T05:10:15.0000000Z" } - }, - new PointStruct - { - Id = 12, - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 12", ["HotelCode"] = 12, ["parking_is_included"] = false, ["Description"] = "This is a great hotel." } - }, - new PointStruct - { - Id = 13, - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 13", ["HotelCode"] = 13, ["parking_is_included"] = false, ["Tags"] = tagsValue2, ["Description"] = "This is a great hotel.", ["LastRenovationDate"] = "2020-02-01T00:00:00.0000000Z" } - }, - ]; - - await this.QdrantClient.UpsertAsync("singleVectorHotels", unnamedVectorPoints); - - // Create some test data using a single unnamed vector and a guid id. - List unnamedVectorGuidIdPoints = - [ - new PointStruct - { - Id = Guid.Parse("11111111-1111-1111-1111-111111111111"), - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 11", ["Description"] = "This is a great hotel." } - }, - new PointStruct - { - Id = Guid.Parse("22222222-2222-2222-2222-222222222222"), - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 12", ["Description"] = "This is a great hotel." } - }, - new PointStruct - { - Id = Guid.Parse("33333333-3333-3333-3333-333333333333"), - Vectors = embeddingArray, - Payload = { ["HotelName"] = "My Hotel 13", ["Description"] = "This is a great hotel." } - }, - ]; - - await this.QdrantClient.UpsertAsync("singleVectorGuidIdHotels", unnamedVectorGuidIdPoints); - } - - /// - /// Delete the docker container after the test run. - /// - /// An async task. - public async Task DisposeAsync() - { - if (this._containerId != null) - { - await this._client.Containers.StopContainerAsync(this._containerId, new ContainerStopParameters()); - await this._client.Containers.RemoveContainerAsync(this._containerId, new ContainerRemoveParameters()); - } - } - - /// - /// Setup the qdrant container by pulling the image and running it. - /// - /// The docker client to create the container with. - /// The id of the container. - private static async Task SetupQdrantContainerAsync(DockerClient client) - { - await client.Images.CreateImageAsync( - new ImagesCreateParameters - { - FromImage = "qdrant/qdrant", - Tag = "latest", - }, - null, - new Progress()); - - var container = await client.Containers.CreateContainerAsync(new CreateContainerParameters() - { - Image = "qdrant/qdrant", - HostConfig = new HostConfig() - { - PortBindings = new Dictionary> - { - {"6333", new List {new() {HostPort = "6333" } }}, - {"6334", new List {new() {HostPort = "6334" } }} - }, - PublishAllPorts = true - }, - ExposedPorts = new Dictionary - { - { "6333", default }, - { "6334", default } - }, - }); - - await client.Containers.StartContainerAsync( - container.ID, - new ContainerStartParameters()); - - return container.ID; - } - - /// - /// A test model for the qdrant vector store. - /// -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public record HotelInfo() - { - /// The key of the record. - [VectorStoreKey] - public ulong HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true, IsFullTextIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData(IsIndexed = true)] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData(IsIndexed = true)] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreData(IsIndexed = true, StorageName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - [VectorStoreData(IsIndexed = true)] - public List Tags { get; set; } = []; - - /// A datetime metadata field. - [VectorStoreData(IsIndexed = true)] - public DateTimeOffset? LastRenovationDate { get; set; } - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A vector field. - [VectorStoreVector(VectorDimensions, DistanceFunction = DistanceFunction.ManhattanDistance, IndexKind = IndexKind.Hnsw)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - } - - /// - /// A test model for the qdrant vector store. - /// -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public record HotelInfoWithGuidId() - { - /// The key of the record. - [VectorStoreKey] - public Guid HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true, IsFullTextIndexed = true)] - public string? HotelName { get; set; } - - /// A data field. - [VectorStoreData] - public string Description { get; set; } - - /// A vector field. - [VectorStoreVector(VectorDimensions, DistanceFunction = DistanceFunction.ManhattanDistance, IndexKind = IndexKind.Hnsw)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - } -} -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreRecordCollectionTests.cs deleted file mode 100644 index a67776a2e754..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Qdrant; -using Qdrant.Client.Grpc; -using Xunit; -using Xunit.Abstractions; -using static SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant.QdrantVectorStoreFixture; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Contains tests for the class. -/// -/// Used for logging. -/// Qdrant setup and teardown. -[Collection("QdrantVectorStoreCollection")] -public sealed class QdrantVectorStoreRecordCollectionTests(ITestOutputHelper output, QdrantVectorStoreFixture fixture) -{ - [Theory] - [InlineData("singleVectorHotels", true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange. - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false); - - // Act. - var actual = await sut.CollectionExistsAsync(); - - // Assert. - Assert.Equal(expectedExists, actual); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool hasNamedVectors, bool useRecordDefinition) - { - // Arrange - var collectionNamePostfix1 = useRecordDefinition ? "WithDefinition" : "WithType"; - var collectionNamePostfix2 = hasNamedVectors ? "HasNamedVectors" : "SingleUnnamedVector"; - var testCollectionName = $"createtest{collectionNamePostfix1}{collectionNamePostfix2}"; - - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, testCollectionName, ownsClient: false, options); - - var record = await this.CreateTestHotelAsync(30, fixture.EmbeddingGenerator); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(30, new() { IncludeVectors = true }); - var vector = (await fixture.EmbeddingGenerator.GenerateAsync("A great hotel")).Vector; - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() { OldFilter = new VectorSearchFilter().EqualTo("HotelCode", 30).AnyTagEqualTo("Tags", "t2") }).ToListAsync(); - - // Assert - var collectionExistResult = await sut.CollectionExistsAsync(); - Assert.True(collectionExistResult); - await sut.EnsureCollectionDeletedAsync(); - - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.HotelRating, getResult?.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.LastRenovationDate, getResult?.LastRenovationDate); - Assert.Equal(record.Tags.ToArray(), getResult?.Tags.ToArray()); - Assert.Equal(record.Description, getResult?.Description); - - Assert.Single(searchResults); - var searchResultRecord = searchResults.First().Record; - Assert.Equal(record.HotelId, searchResultRecord?.HotelId); - Assert.Equal(record.HotelName, searchResultRecord?.HotelName); - Assert.Equal(record.HotelCode, searchResultRecord?.HotelCode); - Assert.Equal(record.HotelRating, searchResultRecord?.HotelRating); - Assert.Equal(record.ParkingIncluded, searchResultRecord?.ParkingIncluded); - Assert.Equal(record.LastRenovationDate, searchResultRecord?.LastRenovationDate); - Assert.Equal(record.Tags.ToArray(), searchResultRecord?.Tags.ToArray()); - Assert.Equal(record.Description, searchResultRecord?.Description); - - // Output - output.WriteLine(collectionExistResult.ToString()); - output.WriteLine(getResult?.ToString()); - } - - [Fact] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - var tempCollectionName = "temp-test"; - await fixture.QdrantClient.CreateCollectionAsync( - tempCollectionName, - new VectorParams { Size = 4, Distance = Distance.Cosine }); - - using var sut = new QdrantCollection(fixture.QdrantClient, tempCollectionName, ownsClient: false); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Theory] - [InlineData(true, "singleVectorHotels", false)] - [InlineData(false, "singleVectorHotels", false)] - [InlineData(true, "namedVectorsHotels", true)] - [InlineData(false, "namedVectorsHotels", true)] - public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition, string collectionName, bool hasNamedVectors) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false, options); - - var record = await this.CreateTestHotelAsync(20, fixture.EmbeddingGenerator); - - // Act. - await sut.UpsertAsync(record); - - // Assert. - var getResult = await sut.GetAsync(20, new RecordRetrievalOptions { IncludeVectors = true }); - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.HotelRating, getResult?.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.Tags.ToArray(), getResult?.Tags.ToArray()); - Assert.Equal(record.Description, getResult?.Description); - - // TODO: figure out why original array is different from the one we get back. - //Assert.Equal(record.DescriptionEmbedding?.ToArray(), getResult?.DescriptionEmbedding?.ToArray()); - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Fact] - public async Task ItCanUpsertAndRemoveDocumentWithGuidIdToVectorStoreAsync() - { - // Arrange. - var options = new QdrantCollectionOptions { HasNamedVectors = false }; - using VectorStoreCollection sut = new QdrantCollection(fixture.QdrantClient, "singleVectorGuidIdHotels", ownsClient: false, options); - - var record = new HotelInfoWithGuidId - { - HotelId = Guid.Parse("55555555-5555-5555-5555-555555555555"), - HotelName = "My Hotel 5", - Description = "This is a great hotel.", - DescriptionEmbedding = (await fixture.EmbeddingGenerator.GenerateAsync("This is a great hotel.")).Vector, - }; - - // Act. - await sut.UpsertAsync(record); - - // Assert. - var getResult = await sut.GetAsync(Guid.Parse("55555555-5555-5555-5555-555555555555"), new RecordRetrievalOptions { IncludeVectors = true }); - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.Description, getResult?.Description); - - // Act. - await sut.DeleteAsync(Guid.Parse("55555555-5555-5555-5555-555555555555")); - - // Assert. - Assert.Null(await sut.GetAsync(Guid.Parse("55555555-5555-5555-5555-555555555555"))); - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Theory] - [InlineData(true, true, "singleVectorHotels", false)] - [InlineData(true, false, "singleVectorHotels", false)] - [InlineData(false, true, "singleVectorHotels", false)] - [InlineData(false, false, "singleVectorHotels", false)] - [InlineData(true, true, "namedVectorsHotels", true)] - [InlineData(true, false, "namedVectorsHotels", true)] - [InlineData(false, true, "namedVectorsHotels", true)] - [InlineData(false, false, "namedVectorsHotels", true)] - public async Task ItCanGetDocumentFromVectorStoreAsync(bool useRecordDefinition, bool withEmbeddings, string collectionName, bool hasNamedVectors) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false, options); - - // Act. - var getResult = await sut.GetAsync(11, new RecordRetrievalOptions { IncludeVectors = withEmbeddings }); - - // Assert. - Assert.Equal(11ul, getResult?.HotelId); - Assert.Equal("My Hotel 11", getResult?.HotelName); - Assert.Equal(11, getResult?.HotelCode); - Assert.True(getResult?.ParkingIncluded); - Assert.Equal(4.5f, getResult?.HotelRating); - Assert.Equal(new DateTimeOffset(2025, 2, 10, 5, 10, 15, TimeSpan.Zero), getResult?.LastRenovationDate); - Assert.Equal(2, getResult?.Tags.Count); - Assert.Equal("t11.1", getResult?.Tags[0]); - Assert.Equal("t11.2", getResult?.Tags[1]); - Assert.Equal("This is a great hotel.", getResult?.Description); - if (withEmbeddings) - { - Assert.NotNull(getResult?.DescriptionEmbedding); - } - else - { - Assert.Null(getResult?.DescriptionEmbedding); - } - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanGetDocumentWithGuidIdFromVectorStoreAsync(bool useRecordDefinition, bool withEmbeddings) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = false, - Definition = useRecordDefinition ? fixture.HotelWithGuidIdVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, "singleVectorGuidIdHotels", ownsClient: false, options); - - // Act. - var getResult = await sut.GetAsync(Guid.Parse("11111111-1111-1111-1111-111111111111"), new RecordRetrievalOptions { IncludeVectors = withEmbeddings }); - - // Assert. - Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), getResult?.HotelId); - Assert.Equal("My Hotel 11", getResult?.HotelName); - Assert.Equal("This is a great hotel.", getResult?.Description); - if (withEmbeddings) - { - Assert.NotNull(getResult?.DescriptionEmbedding); - } - else - { - Assert.Null(getResult?.DescriptionEmbedding); - } - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Fact] - public async Task ItCanGetManyDocumentsFromVectorStoreAsync() - { - // Arrange - var options = new QdrantCollectionOptions { HasNamedVectors = true }; - using var sut = new QdrantCollection(fixture.QdrantClient, "namedVectorsHotels", ownsClient: false, options); - - // Act - // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. - var hotels = sut.GetAsync([11, 15, 12], new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(hotels); - var hotelsList = await hotels.ToListAsync(); - Assert.Equal(2, hotelsList.Count); - - // Output - foreach (var hotel in hotelsList) - { - output.WriteLine(hotel?.ToString() ?? "Null"); - } - } - - [Theory] - [InlineData(true, "singleVectorHotels", false)] - [InlineData(false, "singleVectorHotels", false)] - [InlineData(true, "namedVectorsHotels", true)] - [InlineData(false, "namedVectorsHotels", true)] - public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition, string collectionName, bool hasNamedVectors) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false, options); - - await sut.UpsertAsync(await this.CreateTestHotelAsync(20, fixture.EmbeddingGenerator)); - - // Act. - await sut.DeleteAsync(20); - // Also delete a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync(21); - - // Assert. - Assert.Null(await sut.GetAsync(20)); - } - - [Theory] - [InlineData(true, "singleVectorHotels", false)] - [InlineData(false, "singleVectorHotels", false)] - [InlineData(true, "namedVectorsHotels", true)] - [InlineData(false, "namedVectorsHotels", true)] - public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync(bool useRecordDefinition, string collectionName, bool hasNamedVectors) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false, options); - - await sut.UpsertAsync(await this.CreateTestHotelAsync(20, fixture.EmbeddingGenerator)); - - // Act. - // Also delete a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync([20, 21]); - - // Assert. - Assert.Null(await sut.GetAsync(20)); - } - - [Fact] - public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() - { - // Arrange - var options = new QdrantCollectionOptions { HasNamedVectors = false }; - using var sut = new QdrantCollection(fixture.QdrantClient, "singleVectorHotels", ownsClient: false, options); - - // Act & Assert - Assert.Null(await sut.GetAsync(15, new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Theory] - [InlineData(true, "singleVectorHotels", false, "equality")] - [InlineData(false, "singleVectorHotels", false, "equality")] - [InlineData(true, "namedVectorsHotels", true, "equality")] - [InlineData(false, "namedVectorsHotels", true, "equality")] - [InlineData(true, "singleVectorHotels", false, "tagContains")] - [InlineData(false, "singleVectorHotels", false, "tagContains")] - [InlineData(true, "namedVectorsHotels", true, "tagContains")] - [InlineData(false, "namedVectorsHotels", true, "tagContains")] - public async Task ItCanSearchWithFilterAsync(bool useRecordDefinition, string collectionName, bool hasNamedVectors, string filterType) - { - // Arrange. - var options = new QdrantCollectionOptions - { - HasNamedVectors = hasNamedVectors, - Definition = useRecordDefinition ? fixture.HotelVectorStoreRecordDefinition : null - }; - using var sut = new QdrantCollection(fixture.QdrantClient, collectionName, ownsClient: false, options); - - // Act. - var vector = (await fixture.EmbeddingGenerator.GenerateAsync("A great hotel")).Vector; - var filter = filterType == "equality" ? new VectorSearchFilter().EqualTo("HotelName", "My Hotel 13").EqualTo("LastRenovationDate", new DateTimeOffset(2020, 02, 01, 0, 0, 0, TimeSpan.Zero)) : new VectorSearchFilter().AnyTagEqualTo("Tags", "t13.2"); - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() - { - OldFilter = filter - }).ToListAsync(); - - // Assert. - Assert.Single(searchResults); - - var searchResultRecord = searchResults.First().Record; - Assert.Equal(13ul, searchResultRecord?.HotelId); - Assert.Equal("My Hotel 13", searchResultRecord?.HotelName); - Assert.Equal(13, searchResultRecord?.HotelCode); - Assert.Equal(false, searchResultRecord?.ParkingIncluded); - Assert.Equal(new string[] { "t13.1", "t13.2" }, searchResultRecord?.Tags.ToArray()); - Assert.Equal("This is a great hotel.", searchResultRecord?.Description); - } - - [Fact] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new QdrantCollectionOptions - { - Definition = fixture.HotelVectorStoreRecordDefinition - }; - using var sut = new QdrantDynamicCollection(fixture.QdrantClient, "singleVectorHotels", ownsClient: false, options); - - // Act - var baseSetGetResult = await sut.GetAsync(11ul, new RecordRetrievalOptions { IncludeVectors = true }); - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = 40ul, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["HotelCode"] = 40, - ["ParkingIncluded"] = false, - ["HotelRating"] = 3.6f, - ["Tags"] = new List { "dynamic" }, - ["Description"] = "This is a dynamic mapper hotel", - - ["DescriptionEmbedding"] = (await fixture.EmbeddingGenerator.GenerateAsync("This is a dynamic mapper hotel")).Vector - }); - var localGetResult = await sut.GetAsync(40ul, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(baseSetGetResult); - Assert.Equal(11ul, baseSetGetResult["HotelId"]); - Assert.Equal("My Hotel 11", baseSetGetResult["HotelName"]); - Assert.Equal(11, baseSetGetResult["HotelCode"]); - Assert.True((bool)baseSetGetResult["ParkingIncluded"]!); - Assert.Equal(4.5f, baseSetGetResult["HotelRating"]); - Assert.Equal(new[] { "t11.1", "t11.2" }, ((List)baseSetGetResult["Tags"]!).ToArray()); - Assert.Equal("This is a great hotel.", baseSetGetResult["Description"]); - Assert.NotNull(baseSetGetResult["DescriptionEmbedding"]); - Assert.IsType>(baseSetGetResult["DescriptionEmbedding"]); - - Assert.NotNull(localGetResult); - Assert.Equal(40ul, localGetResult["HotelId"]); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal(40, localGetResult["HotelCode"]); - Assert.False((bool)localGetResult["ParkingIncluded"]!); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { "dynamic" }, ((List)localGetResult["Tags"]!).ToArray()); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.NotNull(localGetResult["DescriptionEmbedding"]); - Assert.IsType>(localGetResult["DescriptionEmbedding"]); - } - - private async Task CreateTestHotelAsync(uint hotelId, IEmbeddingGenerator> embeddingGenerator) - { - return new HotelInfo - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelId}", - HotelCode = (int)hotelId, - HotelRating = 4.5f, - ParkingIncluded = true, - LastRenovationDate = new DateTimeOffset(2025, 2, 10, 5, 10, 15, TimeSpan.Zero), - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - DescriptionEmbedding = (await embeddingGenerator.GenerateAsync("This is a great hotel.")).Vector, - }; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs deleted file mode 100644 index e1aabe0bb831..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Connectors.Qdrant; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; - -[Collection("QdrantVectorStoreCollection")] -public class QdrantVectorStoreTests(QdrantVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new QdrantVectorStore(fixture.QdrantClient, OwnsClient)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ - // The client is shared with base class tests. - private const bool OwnsClient = false; - - [Fact] - public async Task ItPassesSettingsFromVectorStoreToCollectionAsync() - { - // Arrange - using QdrantVectorStore sut = new(fixture.QdrantClient, OwnsClient, new() { HasNamedVectors = true }); - - // Act - var collectionFromVS = sut.GetCollection("SettingsPassedCollection"); - await collectionFromVS.EnsureCollectionExistsAsync(); - - using QdrantCollection directCollection = new( - fixture.QdrantClient, "SettingsPassedCollection", OwnsClient, new() { HasNamedVectors = true }); - await directCollection.UpsertAsync(new QdrantVectorStoreFixture.HotelInfo - { - HotelId = 1ul, - HotelName = "My Hotel 1", - HotelCode = 1, - HotelRating = 4.5f, - ParkingIncluded = true, - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - DescriptionEmbedding = new float[1536], - }); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisHashsetVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisHashsetVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 870772772edd..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisHashsetVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Redis; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -/// -/// Inherits common integration tests that should pass for any . -/// -/// Redis setup and teardown. -[Collection("RedisVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Redis tests fail intermittently on build server")] -public class CommonRedisHashsetVectorStoreRecordCollectionTests(RedisVectorStoreFixture fixture) : BaseVectorStoreRecordCollectionTests -{ - protected override string Key1 => "1"; - protected override string Key2 => "2"; - protected override string Key3 => "3"; - protected override string Key4 => "4"; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - return new RedisHashSetCollection(fixture.Database, recordCollectionName + "hashset", new() - { - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - // Excluding DotProductSimilarity from the test even though Redis supports it, because the values that redis returns - // are neither DotProductSimilarity nor NegativeDotProduct, but rather 1 - DotProductSimilarity. - return [DistanceFunction.CosineDistance, DistanceFunction.CosineSimilarity, DistanceFunction.EuclideanSquaredDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisJsonVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisJsonVectorStoreRecordCollectionTests.cs deleted file mode 100644 index f309413d087d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/CommonRedisJsonVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Redis; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -/// -/// Inherits common integration tests that should pass for any . -/// -/// Redis setup and teardown. -[Collection("RedisVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Redis tests fail intermittently on build server")] -public class CommonRedisJsonVectorStoreRecordCollectionTests(RedisVectorStoreFixture fixture) : BaseVectorStoreRecordCollectionTests -{ - protected override string Key1 => "1"; - protected override string Key2 => "2"; - protected override string Key3 => "3"; - protected override string Key4 => "4"; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - return new RedisJsonCollection(fixture.Database, recordCollectionName + "json", new() - { - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - // Excluding DotProductSimilarity from the test even though Redis supports it, because the values that redis returns - // are neither DotProductSimilarity nor NegativeDotProduct, but rather 1 - DotProductSimilarity. - return [DistanceFunction.CosineDistance, DistanceFunction.CosineSimilarity, DistanceFunction.EuclideanSquaredDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 3e6e0cfe77b9..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Redis; -using NRedisStack.RedisStackCommands; -using NRedisStack.Search; -using Xunit; -using Xunit.Abstractions; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Contains tests for the class. -/// -/// Used for logging. -/// Redis setup and teardown. -[Collection("RedisVectorStoreCollection")] -public sealed class RedisHashSetVectorStoreRecordCollectionTests(ITestOutputHelper output, RedisVectorStoreFixture fixture) -{ - // If null, all tests will be enabled - private const string SkipReason = "Redis tests fail intermittently on build server"; - - private const string TestCollectionName = "hashhotels"; - - [Theory(Skip = SkipReason)] - [InlineData(TestCollectionName, true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange. - using var sut = new RedisHashSetCollection(fixture.Database, collectionName); - - // Act. - var actual = await sut.CollectionExistsAsync(); - - // Assert. - Assert.Equal(expectedExists, actual); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDefinition) - { - // Arrange - var record = CreateTestHotel("HUpsert-1", 1); - var collectionNamePostfix = useRecordDefinition ? "WithDefinition" : "WithType"; - var testCollectionName = $"hashsetcreatetest{collectionNamePostfix}"; - - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null - }; - using var sut = new RedisHashSetCollection(fixture.Database, testCollectionName, options); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync("HUpsert-1", new RecordRetrievalOptions { IncludeVectors = true }); - var searchResults = await sut - .SearchAsync( - new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }), - top: 3, - new() { OldFilter = new VectorSearchFilter().EqualTo("HotelCode", 1), IncludeVectors = true }).ToListAsync(); - - // Assert - var collectionExistResult = await sut.CollectionExistsAsync(); - Assert.True(collectionExistResult); - await sut.EnsureCollectionDeletedAsync(); - - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.Rating, getResult?.Rating); - Assert.Equal(record.Description, getResult?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), getResult?.DescriptionEmbedding?.ToArray()); - - Assert.Single(searchResults); - Assert.Equal(1, searchResults.First().Score); - var searchResultRecord = searchResults.First().Record; - Assert.Equal(record.HotelId, searchResultRecord?.HotelId); - Assert.Equal(record.HotelName, searchResultRecord?.HotelName); - Assert.Equal(record.HotelCode, searchResultRecord?.HotelCode); - Assert.Equal(record.ParkingIncluded, searchResultRecord?.ParkingIncluded); - Assert.Equal(record.Rating, searchResultRecord?.Rating); - Assert.Equal(record.Description, searchResultRecord?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), searchResultRecord?.DescriptionEmbedding?.ToArray()); - - // Output - output.WriteLine(collectionExistResult.ToString()); - output.WriteLine(getResult?.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - var tempCollectionName = "temp-test"; - var schema = new Schema(); - schema.AddTextField("HotelName"); - var createParams = new FTCreateParams(); - createParams.AddPrefix(tempCollectionName); - await fixture.Database.FT().CreateAsync(tempCollectionName, createParams, schema); - - using var sut = new RedisHashSetCollection(fixture.Database, tempCollectionName); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null - }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - var record = CreateTestHotel("HUpsert-2", 2); - - // Act. - await sut.UpsertAsync(record); - - // Assert. - var getResult = await sut.GetAsync("HUpsert-2", new RecordRetrievalOptions { IncludeVectors = true }); - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.Rating, getResult?.Rating); - Assert.Equal(record.Description, getResult?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), getResult?.DescriptionEmbedding?.ToArray()); - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null - }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - - // Act. - await sut.UpsertAsync( - [ - CreateTestHotel("HUpsertMany-1", 1), - CreateTestHotel("HUpsertMany-2", 2), - CreateTestHotel("HUpsertMany-3", 3), - ]); - } - - [Theory(Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange. - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null - }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - - // Act. - var getResult = await sut.GetAsync("HBaseSet-1", new RecordRetrievalOptions { IncludeVectors = includeVectors }); - - // Assert. - Assert.Equal("HBaseSet-1", getResult?.HotelId); - Assert.Equal("My Hotel 1", getResult?.HotelName); - Assert.Equal(1, getResult?.HotelCode); - Assert.True(getResult?.ParkingIncluded); - Assert.Equal(3.6, getResult?.Rating); - Assert.Equal("This is a great hotel.", getResult?.Description); - if (includeVectors) - { - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, getResult?.DescriptionEmbedding?.ToArray()); - } - else - { - Assert.Null(getResult?.DescriptionEmbedding); - } - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetManyDocumentsFromVectorStoreAsync() - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - - // Act - // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. - var hotels = sut.GetAsync(["HBaseSet-1", "HBaseSet-5", "HBaseSet-2"], new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(hotels); - var hotelsList = await hotels.ToListAsync(); - Assert.Equal(2, hotelsList.Count); - - // Output - foreach (var hotel in hotelsList) - { - output.WriteLine(hotel?.ToString() ?? "Null"); - } - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null - }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - var record = new RedisBasicFloat32Hotel - { - HotelId = "HRemove-1", - HotelName = "Remove Test Hotel", - HotelCode = 20, - Description = "This is a great hotel.", - DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } - }; - - await sut.UpsertAsync(record); - - // Act. - await sut.DeleteAsync("HRemove-1"); - // Also delete a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync("HRemove-2"); - - // Assert. - Assert.Null(await sut.GetAsync("HRemove-1")); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - await sut.UpsertAsync(CreateTestHotel("HRemoveMany-1", 1)); - await sut.UpsertAsync(CreateTestHotel("HRemoveMany-2", 2)); - await sut.UpsertAsync(CreateTestHotel("HRemoveMany-3", 3)); - - // Act - // Also include a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync(["HRemoveMany-1", "HRemoveMany-2", "HRemoveMany-3", "HRemoveMany-4"]); - - // Assert - Assert.Null(await sut.GetAsync("HRemoveMany-1", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("HRemoveMany-2", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("HRemoveMany-3", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Theory(Skip = SkipReason)] - [InlineData("hotelCode", true)] - [InlineData("hotelName", false)] - public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType, bool includeVectors) - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true } - ; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - var vector = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }); - var filter = filterType == "equality" ? new VectorSearchFilter().EqualTo("HotelCode", 1) : new VectorSearchFilter().EqualTo("HotelName", "My Hotel 1"); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() - { - IncludeVectors = includeVectors, - OldFilter = filter - }).ToListAsync(); - - // Assert - Assert.Single(searchResults); - Assert.Equal(1, searchResults.First().Score); - var searchResult = searchResults.First().Record; - Assert.Equal("HBaseSet-1", searchResult?.HotelId); - Assert.Equal("My Hotel 1", searchResult?.HotelName); - Assert.Equal(1, searchResult?.HotelCode); - Assert.True(searchResult?.ParkingIncluded); - Assert.Equal(3.6, searchResult?.Rating); - Assert.Equal("This is a great hotel.", searchResult?.Description); - if (includeVectors) - { - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, searchResult?.DescriptionEmbedding?.ToArray()); - } - else - { - Assert.Null(searchResult?.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName + "TopSkip", options); - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); - var vector = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - Assert.Equal(3, searchResults.Count); - Assert.True(searchResults.Select(x => x.Record.HotelId).SequenceEqual(["HTopSkip_3", "HTopSkip_4", "HTopSkip_5"])); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName + "Float64", options); - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); - - var vector = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 1, - new() - { - IncludeVectors = includeVectors, - }).ToListAsync(); - - // Assert - Assert.Single(searchResults); - var searchResult = searchResults.First().Record; - Assert.Equal("HFloat64_2", searchResult?.HotelId); - Assert.Equal("2", searchResult?.HotelName); - Assert.Equal("Nice hotel", searchResult?.Description); - if (includeVectors) - { - Assert.Equal([2.0d, 2.1d, 2.2d, 2.3d], searchResult?.DescriptionEmbedding?.ToArray()); - } - else - { - Assert.Null(searchResult?.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() - { - // Arrange - var options = new RedisHashSetCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisHashSetCollection(fixture.Database, TestCollectionName, options); - - // Act & Assert - Assert.Null(await sut.GetAsync("HBaseSet-5", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new RedisHashSetCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = fixture.BasicVectorStoreRecordDefinition - }; - using var sut = new RedisHashSetDynamicCollection(fixture.Database, TestCollectionName, options); - - // Act - var baseSetGetResult = await sut.GetAsync("HBaseSet-1", new RecordRetrievalOptions { IncludeVectors = true }); - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = "HDynamicMapper-1", - - ["HotelName"] = "Dynamic Mapper Hotel", - ["HotelCode"] = 40, - ["ParkingIncluded"] = true, - ["Rating"] = 3.6d, - ["Description"] = "This is a dynamic mapper hotel", - - ["DescriptionEmbedding"] = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }) - }); - var localGetResult = await sut.GetAsync("HDynamicMapper-1", new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(baseSetGetResult); - Assert.Equal("HBaseSet-1", baseSetGetResult["HotelId"]); - Assert.Equal("My Hotel 1", baseSetGetResult["HotelName"]); - Assert.Equal(1, baseSetGetResult["HotelCode"]); - Assert.True((bool)baseSetGetResult["ParkingIncluded"]!); - Assert.Equal(3.6d, baseSetGetResult["Rating"]); - Assert.Equal("This is a great hotel.", baseSetGetResult["Description"]); - Assert.NotNull(baseSetGetResult["DescriptionEmbedding"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)baseSetGetResult["DescriptionEmbedding"]!).ToArray()); - - Assert.NotNull(localGetResult); - Assert.Equal("HDynamicMapper-1", localGetResult["HotelId"]); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal(40, localGetResult["HotelCode"]); - Assert.True((bool)localGetResult["ParkingIncluded"]!); - Assert.Equal(3.6d, localGetResult["Rating"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - private static RedisBasicFloat32Hotel CreateTestHotel(string hotelId, int hotelCode) - { - var record = new RedisBasicFloat32Hotel - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelCode}", - HotelCode = 1, - ParkingIncluded = true, - Rating = 3.6, - Description = "This is a great hotel.", - DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } - }; - return record; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs deleted file mode 100644 index 81e026992a63..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Text.Json.Serialization; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -/// -/// A test model for the vector store that has complex properties as supported by JSON redis mode. -/// -public class RedisHotel -{ - [VectorStoreKey] - public string HotelId { get; init; } - - [VectorStoreData(IsIndexed = true)] - public string HotelName { get; init; } - - [VectorStoreData(IsIndexed = true)] - public int HotelCode { get; init; } - - [VectorStoreData(IsFullTextIndexed = true)] - public string Description { get; init; } - - [VectorStoreVector(4)] - public ReadOnlyMemory? DescriptionEmbedding { get; init; } - -#pragma warning disable CA1819 // Properties should not return arrays - [VectorStoreData(IsIndexed = true)] - public string[] Tags { get; init; } - - [VectorStoreData(IsFullTextIndexed = true)] - public string[] FTSTags { get; init; } -#pragma warning restore CA1819 // Properties should not return arrays - - [JsonPropertyName("parking_is_included")] - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; init; } - - [VectorStoreData] - public DateTimeOffset LastRenovationDate { get; init; } - - [VectorStoreData] - public double Rating { get; init; } - - [VectorStoreData] - public RedisHotelAddress Address { get; init; } -} - -/// -/// A test model for the vector store to simulate a complex type. -/// -public class RedisHotelAddress -{ - public string City { get; init; } - public string Country { get; init; } -} - -/// -/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. -/// -public class RedisBasicHotel -{ - [VectorStoreKey] - public string HotelId { get; init; } - - [VectorStoreData(IsIndexed = true)] - public string HotelName { get; init; } - - [VectorStoreData(IsIndexed = true)] - public int HotelCode { get; init; } - - [VectorStoreData(IsFullTextIndexed = true)] - public string Description { get; init; } - - [VectorStoreVector(4)] - public ReadOnlyMemory? DescriptionEmbedding { get; init; } - - [JsonPropertyName("parking_is_included")] - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; init; } - - [VectorStoreData] - public double Rating { get; init; } -} - -/// -/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. -/// -public class RedisBasicFloat32Hotel : RedisBasicHotel -{ -} - -/// -/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. -/// -public class RedisBasicFloat64Hotel : RedisBasicHotel -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs deleted file mode 100644 index d652fc44547b..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Redis; -using NRedisStack.RedisStackCommands; -using NRedisStack.Search; -using Xunit; -using Xunit.Abstractions; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Contains tests for the class. -/// -/// Used for logging. -/// Redis setup and teardown. -[Collection("RedisVectorStoreCollection")] -public sealed class RedisJsonVectorStoreRecordCollectionTests(ITestOutputHelper output, RedisVectorStoreFixture fixture) -{ - // If null, all tests will be enabled - private const string SkipReason = "Redis tests fail intermittently on build server"; - - private const string TestCollectionName = "jsonhotels"; - - [Theory(Skip = SkipReason)] - [InlineData(TestCollectionName, true)] - [InlineData("nonexistentcollection", false)] - public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) - { - // Arrange. - using var sut = new RedisJsonCollection(fixture.Database, collectionName); - - // Act. - var actual = await sut.CollectionExistsAsync(); - - // Assert. - Assert.Equal(expectedExists, actual); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDefinition) - { - // Arrange - var record = CreateTestHotel("Upsert-10", 10); - var collectionNamePostfix = useRecordDefinition ? "WithDefinition" : "WithType"; - var testCollectionName = $"jsoncreatetest{collectionNamePostfix}"; - - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new RedisJsonCollection(fixture.Database, testCollectionName, options); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync("Upsert-10", new RecordRetrievalOptions { IncludeVectors = true }); - var searchResults = await sut.SearchAsync( - new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }), - top: 3, - new() { OldFilter = new VectorSearchFilter().EqualTo("HotelCode", 10), IncludeVectors = true }).ToListAsync(); - - // Assert - var collectionExistResult = await sut.CollectionExistsAsync(); - Assert.True(collectionExistResult); - await sut.EnsureCollectionDeletedAsync(); - - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.Tags, getResult?.Tags); - Assert.Equal(record.FTSTags, getResult?.FTSTags); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.LastRenovationDate, getResult?.LastRenovationDate); - Assert.Equal(record.Rating, getResult?.Rating); - Assert.Equal(record.Address.Country, getResult?.Address.Country); - Assert.Equal(record.Address.City, getResult?.Address.City); - Assert.Equal(record.Description, getResult?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), getResult?.DescriptionEmbedding?.ToArray()); - - Assert.Single(searchResults); - Assert.Equal(1, searchResults.First().Score); - var searchResultRecord = searchResults.First().Record; - Assert.Equal(record.HotelId, searchResultRecord?.HotelId); - Assert.Equal(record.HotelName, searchResultRecord?.HotelName); - Assert.Equal(record.HotelCode, searchResultRecord?.HotelCode); - Assert.Equal(record.Tags, searchResultRecord?.Tags); - Assert.Equal(record.FTSTags, searchResultRecord?.FTSTags); - Assert.Equal(record.ParkingIncluded, searchResultRecord?.ParkingIncluded); - Assert.Equal(record.LastRenovationDate, searchResultRecord?.LastRenovationDate); - Assert.Equal(record.Rating, searchResultRecord?.Rating); - Assert.Equal(record.Address.Country, searchResultRecord?.Address.Country); - Assert.Equal(record.Address.City, searchResultRecord?.Address.City); - Assert.Equal(record.Description, searchResultRecord?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), searchResultRecord?.DescriptionEmbedding?.ToArray()); - - // Output - output.WriteLine(collectionExistResult.ToString()); - output.WriteLine(getResult?.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - var tempCollectionName = "temp-test"; - var schema = new Schema(); - schema.AddTextField("HotelName"); - var createParams = new FTCreateParams(); - createParams.AddPrefix(tempCollectionName); - await fixture.Database.FT().CreateAsync(tempCollectionName, createParams, schema); - - using var sut = new RedisJsonCollection(fixture.Database, tempCollectionName); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - RedisHotel record = CreateTestHotel("Upsert-2", 2); - - // Act. - await sut.UpsertAsync(record); - - // Assert. - var getResult = await sut.GetAsync("Upsert-2", new RecordRetrievalOptions { IncludeVectors = true }); - Assert.Equal(record.HotelId, getResult?.HotelId); - Assert.Equal(record.HotelName, getResult?.HotelName); - Assert.Equal(record.HotelCode, getResult?.HotelCode); - Assert.Equal(record.Tags, getResult?.Tags); - Assert.Equal(record.FTSTags, getResult?.FTSTags); - Assert.Equal(record.ParkingIncluded, getResult?.ParkingIncluded); - Assert.Equal(record.LastRenovationDate, getResult?.LastRenovationDate); - Assert.Equal(record.Rating, getResult?.Rating); - Assert.Equal(record.Address.Country, getResult?.Address.Country); - Assert.Equal(record.Address.City, getResult?.Address.City); - Assert.Equal(record.Description, getResult?.Description); - Assert.Equal(record.DescriptionEmbedding?.ToArray(), getResult?.DescriptionEmbedding?.ToArray()); - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - - // Act. - await sut.UpsertAsync( - [ - CreateTestHotel("UpsertMany-1", 1), - CreateTestHotel("UpsertMany-2", 2), - CreateTestHotel("UpsertMany-3", 3), - ]); - } - - [Theory(Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange. - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - - // Act. - var getResult = await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = includeVectors }); - - // Assert. - Assert.Equal("BaseSet-1", getResult?.HotelId); - Assert.Equal("My Hotel 1", getResult?.HotelName); - Assert.Equal(1, getResult?.HotelCode); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, getResult?.Tags); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, getResult?.FTSTags); - Assert.True(getResult?.ParkingIncluded); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), getResult?.LastRenovationDate); - Assert.Equal(3.6, getResult?.Rating); - Assert.Equal("Seattle", getResult?.Address.City); - Assert.Equal("This is a great hotel.", getResult?.Description); - if (includeVectors) - { - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, getResult?.DescriptionEmbedding?.ToArray()); - } - else - { - Assert.Null(getResult?.DescriptionEmbedding); - } - - // Output. - output.WriteLine(getResult?.ToString()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetManyDocumentsFromVectorStoreAsync() - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - - // Act - // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. - var hotels = sut.GetAsync(["BaseSet-1", "BaseSet-5", "BaseSet-2"], new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(hotels); - var hotelsList = await hotels.ToListAsync(); - Assert.Equal(2, hotelsList.Count); - - // Output - foreach (var hotel in hotelsList) - { - output.WriteLine(hotel?.ToString() ?? "Null"); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItFailsToGetDocumentsWithInvalidSchemaAsync() - { - // Arrange. - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - - // Act & Assert. - await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-4-Invalid", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) - { - // Arrange. - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null - }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; - var record = new RedisHotel - { - HotelId = "Remove-1", - HotelName = "Remove Test Hotel", - HotelCode = 20, - Description = "This is a great hotel.", - DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } - }; - - await sut.UpsertAsync(record); - - // Act. - await sut.DeleteAsync("Remove-1"); - // Also delete a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync("Remove-2"); - - // Assert. - Assert.Null(await sut.GetAsync("Remove-1")); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - await sut.UpsertAsync(CreateTestHotel("RemoveMany-1", 1)); - await sut.UpsertAsync(CreateTestHotel("RemoveMany-2", 2)); - await sut.UpsertAsync(CreateTestHotel("RemoveMany-3", 3)); - - // Act - // Also include a non-existing key to test that the operation does not fail for these. - await sut.DeleteAsync(["RemoveMany-1", "RemoveMany-2", "RemoveMany-3", "RemoveMany-4"]); - - // Assert - Assert.Null(await sut.GetAsync("RemoveMany-1", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("RemoveMany-2", new RecordRetrievalOptions { IncludeVectors = true })); - Assert.Null(await sut.GetAsync("RemoveMany-3", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Theory(Skip = SkipReason)] - [InlineData("equality")] - [InlineData("tagContains")] - public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType) - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - var vector = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }); - var filter = filterType == "equality" ? new VectorSearchFilter().EqualTo("HotelCode", 1) : new VectorSearchFilter().AnyTagEqualTo("Tags", "pool"); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() { IncludeVectors = true, OldFilter = filter }).ToListAsync(); - - // Assert - Assert.Single(searchResults); - Assert.Equal(1, searchResults.First().Score); - var searchResult = searchResults.First().Record; - Assert.Equal("My Hotel 1", searchResults.First().Record.HotelName); - Assert.Equal("BaseSet-1", searchResult?.HotelId); - Assert.Equal("My Hotel 1", searchResult?.HotelName); - Assert.Equal(1, searchResult?.HotelCode); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, searchResult?.Tags); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, searchResult?.FTSTags); - Assert.True(searchResult?.ParkingIncluded); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), searchResult?.LastRenovationDate); - Assert.Equal(3.6, searchResult?.Rating); - Assert.Equal("Seattle", searchResult?.Address.City); - Assert.Equal("This is a great hotel.", searchResult?.Description); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, searchResult?.DescriptionEmbedding?.ToArray()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName + "TopSkip", options); - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); - await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); - var vector = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 3, - new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - Assert.Equal(3, searchResults.Count); - Assert.True(searchResults.Select(x => x.Record.HotelId).SequenceEqual(["TopSkip_3", "TopSkip_4", "TopSkip_5"])); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName + "Float64", options); - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); - await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); - - var vector = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]); - - // Act - var searchResults = await sut.SearchAsync( - vector, - top: 1, - new() - { - IncludeVectors = includeVectors, - }).ToListAsync(); - - // Assert - Assert.Single(searchResults); - var searchResult = searchResults.First().Record; - Assert.Equal("Float64_2", searchResult?.HotelId); - Assert.Equal("2", searchResult?.HotelName); - Assert.Equal("Nice hotel", searchResult?.Description); - if (includeVectors) - { - Assert.Equal([2.0d, 2.1d, 2.2d, 2.3d], searchResult?.DescriptionEmbedding?.ToArray()); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() - { - // Arrange - var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; - using var sut = new RedisJsonCollection(fixture.Database, TestCollectionName, options); - - // Act & Assert - Assert.Null(await sut.GetAsync("BaseSet-5", new RecordRetrievalOptions { IncludeVectors = true })); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperAsync() - { - // Arrange - var options = new RedisJsonCollectionOptions - { - PrefixCollectionNameToKeyNames = true, - Definition = fixture.VectorStoreRecordDefinition - }; - using var sut = new RedisJsonDynamicCollection(fixture.Database, TestCollectionName, options); - - // Act - var baseSetGetResult = await sut.GetAsync("BaseSet-1", new RecordRetrievalOptions { IncludeVectors = true }); - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = "DynamicMapper-1", - - ["HotelName"] = "Dynamic Mapper Hotel", - ["HotelCode"] = 1, - ["Tags"] = new[] { "dynamic 1", "dynamic 2" }, - ["FTSTags"] = new[] { "dynamic 1", "dynamic 2" }, - ["ParkingIncluded"] = true, - ["LastRenovationDate"] = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - ["Rating"] = 3.6, - ["Address"] = new RedisHotelAddress { City = "Seattle", Country = "USA" }, - ["Description"] = "This is a dynamic mapper hotel", - - ["DescriptionEmbedding"] = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }) - }); - var localGetResult = await sut.GetAsync("DynamicMapper-1", new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(baseSetGetResult); - Assert.Equal("BaseSet-1", baseSetGetResult["HotelId"]); - Assert.Equal("My Hotel 1", baseSetGetResult["HotelName"]); - Assert.Equal(1, baseSetGetResult["HotelCode"]); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, baseSetGetResult["Tags"]); - Assert.Equal(new[] { "pool", "air conditioning", "concierge" }, baseSetGetResult["FTSTags"]); - Assert.True((bool)baseSetGetResult["ParkingIncluded"]!); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), baseSetGetResult["LastRenovationDate"]); - Assert.Equal(3.6, baseSetGetResult["Rating"]); - Assert.Equal("Seattle", ((RedisHotelAddress)baseSetGetResult["Address"]!).City); - Assert.Equal("This is a great hotel.", baseSetGetResult["Description"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)baseSetGetResult["DescriptionEmbedding"]!).ToArray()); - - Assert.NotNull(localGetResult); - Assert.Equal("DynamicMapper-1", localGetResult["HotelId"]); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal(1, localGetResult["HotelCode"]); - Assert.Equal(new[] { "dynamic 1", "dynamic 2" }, localGetResult["Tags"]); - Assert.Equal(new[] { "dynamic 1", "dynamic 2" }, localGetResult["FTSTags"]); - Assert.True((bool)localGetResult["ParkingIncluded"]!); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), localGetResult["LastRenovationDate"]); - Assert.Equal(3.6d, localGetResult["Rating"]); - Assert.Equal("Seattle", ((RedisHotelAddress)localGetResult["Address"]!).City); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - private static RedisHotel CreateTestHotel(string hotelId, int hotelCode) - { - var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; - var record = new RedisHotel - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelCode}", - HotelCode = hotelCode, - Tags = ["air conditioning", "concierge"], - FTSTags = ["air conditioning", "concierge"], - ParkingIncluded = true, - LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - Rating = 3.6, - Address = address, - Description = "This is a great hotel.", - DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f } - }; - return record; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreCollectionFixture.cs deleted file mode 100644 index 1bebd51d8f5f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -[CollectionDefinition("RedisVectorStoreCollection")] -public class RedisVectorStoreCollectionFixture : ICollectionFixture -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs deleted file mode 100644 index 2adb6ca2763c..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Docker.DotNet; -using Docker.DotNet.Models; -using Microsoft.Extensions.VectorData; -using NRedisStack.RedisStackCommands; -using NRedisStack.Search; -using NRedisStack.Search.Literals.Enums; -using StackExchange.Redis; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -/// -/// Does setup and teardown of redis docker container and associated test data. -/// -public class RedisVectorStoreFixture : IAsyncLifetime -{ - /// The docker client we are using to create a redis container with. - private readonly DockerClient _client; - - /// The id of the redis container that we are testing with. - private string? _containerId = null; - - /// - /// Initializes a new instance of the class. - /// - public RedisVectorStoreFixture() - { - using var dockerClientConfiguration = new DockerClientConfiguration(); - this._client = dockerClientConfiguration.CreateClient(); - this.VectorStoreRecordDefinition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("HotelCode", typeof(int)) { IsIndexed = true }, - new VectorStoreDataProperty("Description", typeof(string)) { IsFullTextIndexed = true }, - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4), - new VectorStoreDataProperty("Tags", typeof(string[])) { IsIndexed = true }, - new VectorStoreDataProperty("FTSTags", typeof(string[])) { IsFullTextIndexed = true }, - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("LastRenovationDate", typeof(DateTimeOffset)), - new VectorStoreDataProperty("Rating", typeof(double)), - new VectorStoreDataProperty("Address", typeof(RedisHotelAddress)) - ] - }; - this.BasicVectorStoreRecordDefinition = new VectorStoreCollectionDefinition - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(string)), - new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("HotelCode", typeof(int)) { IsIndexed = true }, - new VectorStoreDataProperty("Description", typeof(string)) { IsFullTextIndexed = true }, - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("Rating", typeof(double)), - ] - }; - } - - /// Gets the redis database connection to use for tests. - public IDatabase Database { get; private set; } - - /// Gets the manually created vector store record definition for our test model. - public VectorStoreCollectionDefinition VectorStoreRecordDefinition { get; private set; } - - /// Gets the manually created vector store record definition for our basic test model. - public VectorStoreCollectionDefinition BasicVectorStoreRecordDefinition { get; private set; } - - /// - /// Create / Recreate redis docker container, create an index and add test data. - /// - /// An async task. - public async Task InitializeAsync() - { - this._containerId = await SetupRedisContainerAsync(this._client); - - // Connect to redis. - ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379,connectTimeout=60000,connectRetry=5"); - this.Database = redis.GetDatabase(); - - // Create a JSON index. - var jsonSchema = new Schema(); - jsonSchema.AddTagField(new FieldName("$.HotelName", "HotelName")); - jsonSchema.AddNumericField(new FieldName("$.HotelCode", "HotelCode")); - jsonSchema.AddTextField(new FieldName("$.Description", "Description")); - jsonSchema.AddTagField(new FieldName("$.Tags", "Tags")); - jsonSchema.AddTextField(new FieldName("$.FTSTags", "FTSTags")); - jsonSchema.AddVectorField(new FieldName("$.DescriptionEmbedding", "DescriptionEmbedding"), Schema.VectorField.VectorAlgo.HNSW, new Dictionary() - { - ["TYPE"] = "FLOAT32", - ["DIM"] = "4", - ["DISTANCE_METRIC"] = "L2" - }); - var jsonCreateParams = new FTCreateParams().AddPrefix("jsonhotels:").On(IndexDataType.JSON); - await this.Database.FT().CreateAsync("jsonhotels", jsonCreateParams, jsonSchema); - - // Create a hashset index. - var hashSchema = new Schema(); - hashSchema.AddTagField(new FieldName("HotelName", "HotelName")); - hashSchema.AddNumericField(new FieldName("HotelCode", "HotelCode")); - hashSchema.AddTextField(new FieldName("Description", "Description")); - hashSchema.AddVectorField(new FieldName("DescriptionEmbedding", "DescriptionEmbedding"), Schema.VectorField.VectorAlgo.HNSW, new Dictionary() - { - ["TYPE"] = "FLOAT32", - ["DIM"] = "4", - ["DISTANCE_METRIC"] = "L2" - }); - - var hashsetCreateParams = new FTCreateParams().AddPrefix("hashhotels:").On(IndexDataType.HASH); - await this.Database.FT().CreateAsync("hashhotels", hashsetCreateParams, hashSchema); - - // Create some test data. - var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; - var embedding = new[] { 30f, 31f, 32f, 33f }; - - // Add JSON test data. - await this.Database.JSON().SetAsync("jsonhotels:BaseSet-1", "$", new - { - HotelName = "My Hotel 1", - HotelCode = 1, - Description = "This is a great hotel.", - DescriptionEmbedding = embedding, - Tags = new[] { "pool", "air conditioning", "concierge" }, - FTSTags = new[] { "pool", "air conditioning", "concierge" }, - parking_is_included = true, - LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - Rating = 3.6, - Address = address - }); - await this.Database.JSON().SetAsync("jsonhotels:BaseSet-2", "$", new { HotelName = "My Hotel 2", HotelCode = 2, Description = "This is a great hotel.", DescriptionEmbedding = embedding, parking_is_included = false }); - await this.Database.JSON().SetAsync("jsonhotels:BaseSet-3", "$", new { HotelName = "My Hotel 3", HotelCode = 3, Description = "This is a great hotel.", DescriptionEmbedding = embedding, parking_is_included = false }); - await this.Database.JSON().SetAsync("jsonhotels:BaseSet-4-Invalid", "$", new { HotelId = "AnotherId", HotelName = "My Invalid Hotel", HotelCode = 4, Description = "This is an invalid hotel.", DescriptionEmbedding = embedding, parking_is_included = false }); - - // Add hashset test data. - await this.Database.HashSetAsync("hashhotels:HBaseSet-1", new HashEntry[] - { - new("HotelName", "My Hotel 1"), - new("HotelCode", 1), - new("Description", "This is a great hotel."), - new("DescriptionEmbedding", MemoryMarshal.AsBytes(new ReadOnlySpan(embedding)).ToArray()), - new("parking_is_included", true), - new("Rating", 3.6) - }); - await this.Database.HashSetAsync("hashhotels:HBaseSet-2", new HashEntry[] - { - new("HotelName", "My Hotel 2"), - new("HotelCode", 2), - new("Description", "This is a great hotel."), - new("DescriptionEmbedding", MemoryMarshal.AsBytes(new ReadOnlySpan(embedding)).ToArray()), - new("parking_is_included", false), - }); - await this.Database.HashSetAsync("hashhotels:HBaseSet-3", new HashEntry[] - { - new("HotelName", "My Hotel 3"), - new("HotelCode", 3), - new("Description", "This is a great hotel."), - new("DescriptionEmbedding", MemoryMarshal.AsBytes(new ReadOnlySpan(embedding)).ToArray()), - new("parking_is_included", false), - }); - await this.Database.HashSetAsync("hashhotels:HBaseSet-4-Invalid", new HashEntry[] - { - new("HotelId", "AnotherId"), - new("HotelName", "My Invalid Hotel"), - new("HotelCode", 4), - new("Description", "This is an invalid hotel."), - new("DescriptionEmbedding", MemoryMarshal.AsBytes(new ReadOnlySpan(embedding)).ToArray()), - new("parking_is_included", false), - }); - } - - /// - /// Delete the docker container after the test run. - /// - /// An async task. - public async Task DisposeAsync() - { - if (this._containerId != null) - { - await this._client.Containers.StopContainerAsync(this._containerId, new ContainerStopParameters()); - await this._client.Containers.RemoveContainerAsync(this._containerId, new ContainerRemoveParameters()); - } - } - - /// - /// Setup the redis container by pulling the image and running it. - /// - /// The docker client to create the container with. - /// The id of the container. - private static async Task SetupRedisContainerAsync(DockerClient client) - { - await client.Images.CreateImageAsync( - new ImagesCreateParameters - { - FromImage = "redis/redis-stack", - Tag = "latest", - }, - null, - new Progress()); - - var container = await client.Containers.CreateContainerAsync(new CreateContainerParameters() - { - Image = "redis/redis-stack", - HostConfig = new HostConfig() - { - PortBindings = new Dictionary> - { - {"6379", new List {new() {HostPort = "6379"}}}, - {"8001", new List {new() {HostPort = "8001"}}} - }, - PublishAllPorts = true - }, - ExposedPorts = new Dictionary - { - { "6379", default }, - { "8001", default } - }, - }); - - await client.Containers.StartContainerAsync( - container.ID, - new ContainerStartParameters()); - - await Task.Delay(1000); - - return container.ID; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs deleted file mode 100644 index e4ed1b17a3a5..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Connectors.Redis; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; - -/// -/// Contains tests for the class. -/// -/// The test fixture. -[Collection("RedisVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Redis tests fail intermittently on build server")] -public class RedisVectorStoreTests(RedisVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new RedisVectorStore(fixture.Database)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteHotel.cs deleted file mode 100644 index 09734a91c17d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteHotel.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.SqliteVec; - -public record SqliteHotel() -{ - /// The key of the record. - [VectorStoreKey] - public TKey? HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreData(StorageName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - /// A data field. - [VectorStoreData] - public string? Description { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.EuclideanDistance)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreCollectionFixture.cs deleted file mode 100644 index cfb4fe923deb..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.SqliteVec; - -[CollectionDefinition("SqliteVectorStoreCollection")] -public class SqliteVectorStoreCollectionFixture : ICollectionFixture -{ } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreFixture.cs deleted file mode 100644 index fdccb8afe875..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreFixture.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.IO; -using Microsoft.SemanticKernel.Connectors.SqliteVec; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.SqliteVec; - -public class SqliteVectorStoreFixture : IDisposable -{ - private readonly string _databasePath = Path.GetTempFileName(); - - public string ConnectionString => $"Data Source={this._databasePath}"; - - public SqliteCollection GetCollection( - string collectionName, - SqliteCollectionOptions? options = default) - where TKey : notnull - where TRecord : class - => new( - this.ConnectionString, - collectionName, - options); - - public SqliteDynamicCollection GetDynamicCollection( - string collectionName, - SqliteCollectionOptions options) - => new( - this.ConnectionString, - collectionName, - options); - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - File.Delete(this._databasePath); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 939d6671dfdc..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.SqliteVec; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.SqliteVec; - -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -/// -/// Integration tests for class. -/// -[Collection("SqliteVectorStoreCollection")] -public sealed class SqliteVectorStoreRecordCollectionTests(SqliteVectorStoreFixture fixture) -{ - private const string? SkipReason = null; - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task CollectionExistsReturnsCollectionStateAsync(bool createCollection) - { - // Arrange - using var sut = fixture.GetCollection>("CollectionExists"); - - if (createCollection) - { - await sut.EnsureCollectionExistsAsync(); - } - - // Act - var collectionExists = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(createCollection, collectionExists); - - // Cleanup - await sut.EnsureCollectionDeletedAsync(); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanEnsureCollectionExistsAsync() - { - // Arrange - using var sut = fixture.GetCollection>("CreateCollection"); - - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanCreateCollectionForSupportedDistanceFunctionsAsync() - { - // Arrange - using var sut = fixture.GetCollection("CreateCollectionForSupportedDistanceFunctions"); - - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - using var sut = fixture.GetCollection>("DeleteCollection"); - - await sut.EnsureCollectionExistsAsync(); - - Assert.True(await sut.CollectionExistsAsync()); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ItCanCreateCollectionUpsertAndGetAsync(bool includeVectors, bool useRecordDefinition) - { - // Arrange - const long HotelId = 5; - - var collectionNamePostfix = useRecordDefinition ? "WithDefinition" : "WithType"; - var collectionName = $"Collection{collectionNamePostfix}"; - - var options = new SqliteCollectionOptions - { - Definition = useRecordDefinition ? GetVectorStoreRecordDefinition() : null - }; - - using var sut = fixture.GetCollection>("DeleteCollection", options); - - var record = CreateTestHotel(HotelId); - - // Act - await sut.EnsureCollectionExistsAsync(); - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = includeVectors }); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - await sut.EnsureCollectionDeletedAsync(); - - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Description, getResult.Description); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetAndDeleteRecordAsync() - { - // Arrange - const long HotelId = 5; - using var sut = fixture.GetCollection>("DeleteRecord"); - - await sut.EnsureCollectionExistsAsync(); - - var record = CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(HotelId); - - getResult = await sut.GetAsync(HotelId); - - // Assert - Assert.Null(getResult); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetUpsertDeleteBatchWithNumericKeyAsync() - { - // Arrange - const long HotelId1 = 1; - const long HotelId2 = 2; - const long HotelId3 = 3; - - using var sut = fixture.GetCollection>("GetUpsertDeleteBatchWithNumericKey"); - - await sut.EnsureCollectionExistsAsync(); - - var record1 = CreateTestHotel(HotelId1); - var record2 = CreateTestHotel(HotelId2); - var record3 = CreateTestHotel(HotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanGetUpsertDeleteBatchWithStringKeyAsync() - { - // Arrange - const string HotelId1 = "11111111-1111-1111-1111-111111111111"; - const string HotelId2 = "22222222-2222-2222-2222-222222222222"; - const string HotelId3 = "33333333-3333-3333-3333-333333333333"; - - using var sut = fixture.GetCollection>("GetUpsertDeleteBatchWithStringKey") as VectorStoreCollection>; - - await sut.EnsureCollectionExistsAsync(); - - var record1 = CreateTestHotel(HotelId1); - var record2 = CreateTestHotel(HotelId2); - var record3 = CreateTestHotel(HotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == HotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == HotelId3)); - - // Act - await sut.DeleteAsync([HotelId1, HotelId2, HotelId3]); - - getResults = await sut.GetAsync([HotelId1, HotelId2, HotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task ItCanGetExistingRecordAsync(bool includeVectors) - { - // Arrange - var collectionNamePostfix = includeVectors ? "WithVectors" : "WithoutVectors"; - var collectionName = $"Collection{collectionNamePostfix}"; - - const long HotelId = 5; - using var sut = fixture.GetCollection>(collectionName); - - await sut.EnsureCollectionExistsAsync(); - - var record = CreateTestHotel(HotelId); - - using var connection = new SqliteConnection(fixture.ConnectionString); - await connection.OpenAsync(); - var commandData = connection.CreateCommand(); - - commandData.CommandText = - $"INSERT INTO {collectionName} " + - "VALUES (@HotelId0, @HotelName0, @HotelCode0, @HotelRating0, @parking_is_included0, @Description0)"; - - commandData.Parameters.AddWithValue("@HotelId0", record.HotelId); - commandData.Parameters.AddWithValue("@HotelName0", record.HotelName); - commandData.Parameters.AddWithValue("@HotelCode0", record.HotelCode); - commandData.Parameters.AddWithValue("@HotelRating0", record.HotelRating); - commandData.Parameters.AddWithValue("@parking_is_included0", record.ParkingIncluded); - commandData.Parameters.AddWithValue("@Description0", record.Description); - - await commandData.ExecuteNonQueryAsync(); - - if (includeVectors) - { - var commandVector = connection.CreateCommand(); - - commandVector.CommandText = - $"INSERT INTO vec_{collectionName} " + - "VALUES (@HotelId0, @DescriptionEmbedding0)"; - - commandVector.Parameters.AddWithValue("@HotelId0", record.HotelId); - commandVector.Parameters.AddWithValue("@DescriptionEmbedding0", GetVectorForStorageModel(record.DescriptionEmbedding!.Value)); - - await commandVector.ExecuteNonQueryAsync(); - } - - // Act - var getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = includeVectors }); - - // Assert - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Description, getResult.Description); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertExistingRecordAsync() - { - // Arrange - const long HotelId = 5; - using var sut = fixture.GetCollection>("UpsertRecord"); - - await sut.EnsureCollectionExistsAsync(); - - var record = CreateTestHotel(HotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(HotelId); - - Assert.NotNull(getResult); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - record.DescriptionEmbedding = new[] { 1f, 2f, 3f, 4f }; - - await sut.UpsertAsync(record); - getResult = await sut.GetAsync(HotelId, new() { IncludeVectors = true }); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task SearchReturnsValidResultsByDefaultAsync(bool includeVectors) - { - // Arrange - var hotel1 = CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = fixture.GetCollection>("VectorizedSearch"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3, new() - { - IncludeVectors = includeVectors - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key1", ids[0]); - Assert.Equal("key2", ids[1]); - Assert.Equal("key3", ids[2]); - - Assert.DoesNotContain("key4", ids); - - Assert.Equal(0, results.First(l => l.Record.HotelId == "key1").Score); - - Assert.Equal(includeVectors, results.All(result => result.Record.DescriptionEmbedding is not null)); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithOffsetAsync() - { - // Arrange - var hotel1 = CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = fixture.GetCollection>("SearchWithOffset"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 2, new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key3", ids[0]); - Assert.Equal("key4", ids[1]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key2", ids); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithFilterAsync() - { - // Arrange - var hotel1 = CreateTestHotel(hotelId: "key1", embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = CreateTestHotel(hotelId: "key2", embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = CreateTestHotel(hotelId: "key3", embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = CreateTestHotel(hotelId: "key4", embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = fixture.GetCollection>("SearchWithFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var results = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3, new() - { - OldFilter = new VectorSearchFilter().EqualTo(nameof(SqliteHotel.HotelName), "My Hotel key2") - }).ToListAsync(); - - // Assert - var ids = results.Select(l => l.Record.HotelId).ToList(); - - Assert.Equal("key2", ids[0]); - - Assert.DoesNotContain("key1", ids); - Assert.DoesNotContain("key3", ids); - Assert.DoesNotContain("key4", ids); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperWithNumericKeyAsync() - { - const long HotelId = 5; - - var options = new SqliteCollectionOptions - { - Definition = GetVectorStoreRecordDefinition() - }; - - using var sut = fixture.GetDynamicCollection("DynamicMapperWithNumericKey", options); - - await sut.EnsureCollectionExistsAsync(); - - var record = CreateTestHotel(HotelId); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = HotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["ParkingIncluded"] = true, - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync(HotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.True((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingTheDynamicMapperWithStringKeyAsync() - { - const string HotelId = "key"; - - var options = new SqliteCollectionOptions - { - Definition = GetVectorStoreRecordDefinition() - }; - - using var sut = fixture.GetDynamicCollection("DynamicMapperWithStringKey", options) - as VectorStoreCollection>; - - await sut.EnsureCollectionExistsAsync(); - - var record = CreateTestHotel(HotelId); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = HotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["ParkingIncluded"] = true, - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync(HotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.True((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - #region - - private static VectorStoreCollectionDefinition GetVectorStoreRecordDefinition() => new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(TKey)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("HotelRating", typeof(float)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.IvfFlat, DistanceFunction = DistanceFunction.CosineDistance } - ] - }; - - private static SqliteHotel CreateTestHotel(TKey hotelId, ReadOnlyMemory? embedding = null) - { - return new SqliteHotel - { - HotelId = hotelId, - HotelName = $"My Hotel {hotelId}", - HotelCode = 42, - HotelRating = 4.5f, - ParkingIncluded = true, - Description = "This is a great hotel.", - DescriptionEmbedding = embedding ?? new[] { 30f, 31f, 32f, 33f }, - }; - } - - private static byte[] GetVectorForStorageModel(ReadOnlyMemory vector) - { - ReadOnlySpan floatSpan = vector.Span; - - byte[] byteArray = new byte[floatSpan.Length * sizeof(float)]; - - MemoryMarshal.AsBytes(floatSpan).CopyTo(byteArray); - - return byteArray; - } - -#pragma warning disable CA1812 - private sealed class RecordWithSupportedDistanceFunctions - { - [VectorStoreKey] - public long Id { get; set; } - - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineDistance)] - public ReadOnlyMemory? Embedding1 { get; set; } - - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.EuclideanDistance)] - public ReadOnlyMemory? Embedding2 { get; set; } - - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.ManhattanDistance)] - public ReadOnlyMemory? Embedding3 { get; set; } - } -#pragma warning restore CA1812 - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreTests.cs deleted file mode 100644 index 1baaf63694ba..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/SqliteVec/SqliteVectorStoreTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.SqliteVec; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.SqliteVec; - -/// -/// Integration tests for class. -/// -[Collection("SqliteVectorStoreCollection")] -public sealed class SqliteVectorStoreTests(SqliteVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests>(new SqliteVectorStore(fixture.ConnectionString)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ - [VectorStoreFact] - public async Task ItCanGetAListOfExistingCollectionNamesWhenRegisteredWithDIAsync() - { - // Arrange - var serviceCollection = new ServiceCollection(); - - serviceCollection.AddSqliteVectorStore(_ => fixture.ConnectionString); - - var provider = serviceCollection.BuildServiceProvider(); - - var sut = provider.GetRequiredService(); - - var collection1 = sut.GetCollection>("ListCollectionNames1"); - var collection2 = sut.GetCollection>("ListCollectionNames2"); - - await collection1.EnsureCollectionExistsAsync(); - await collection2.EnsureCollectionExistsAsync(); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("ListCollectionNames1", collectionNames); - Assert.Contains("ListCollectionNames1", collectionNames); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/CommonWeaviateVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/CommonWeaviateVectorStoreRecordCollectionTests.cs deleted file mode 100644 index 0bdb9d44322d..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/CommonWeaviateVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Weaviate; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -/// -/// Inherits common integration tests that should pass for any . -/// -/// Weaviate setup and teardown. -[Collection("WeaviateVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Weaviate tests are failing on the build server with connection reset errors, but passing locally.")] -public class CommonWeaviateVectorStoreRecordCollectionTests(WeaviateVectorStoreFixture fixture) : BaseVectorStoreRecordCollectionTests -{ - protected override Guid Key1 => new("11111111-1111-1111-1111-111111111111"); - protected override Guid Key2 => new("22222222-2222-2222-2222-222222222222"); - protected override Guid Key3 => new("33333333-3333-3333-3333-333333333333"); - protected override Guid Key4 => new("44444444-4444-4444-4444-444444444444"); - - protected override int DelayAfterUploadInMilliseconds => 1000; - - protected override VectorStoreCollection GetTargetRecordCollection(string recordCollectionName, VectorStoreCollectionDefinition? definition) - { - // Weaviate collection names must start with an upper case letter. - var recordCollectionNameChars = recordCollectionName.ToCharArray(); - recordCollectionNameChars[0] = char.ToUpperInvariant(recordCollectionNameChars[0]); - - return new WeaviateCollection(fixture.HttpClient!, new string(recordCollectionNameChars), new() - { - Definition = definition - }); - } - - protected override HashSet GetSupportedDistanceFunctions() - { - return [DistanceFunction.CosineDistance, DistanceFunction.NegativeDotProductSimilarity, DistanceFunction.EuclideanSquaredDistance, DistanceFunction.HammingDistance, DistanceFunction.ManhattanDistance]; - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateHotel.cs deleted file mode 100644 index bc2249d12b1b..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateHotel.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Microsoft.Extensions.VectorData; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -#pragma warning disable CS8618 - -public sealed record WeaviateHotel -{ - /// The key of the record. - [VectorStoreKey] - public Guid HotelId { get; init; } - - /// A string metadata field. - [VectorStoreData(IsIndexed = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreData(IsIndexed = true)] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreData(IsIndexed = true)] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [JsonPropertyName("parking_is_included")] - [VectorStoreData(IsIndexed = true)] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreData(IsIndexed = true)] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreData(IsFullTextIndexed = true, IsIndexed = true)] - public string Description { get; set; } - - [VectorStoreData(IsIndexed = true)] - public DateTimeOffset Timestamp { get; set; } - - /// A vector field. - [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineDistance, IndexKind = IndexKind.Hnsw)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreCollectionFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreCollectionFixture.cs deleted file mode 100644 index 2f3f67f723a2..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreCollectionFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -[CollectionDefinition("WeaviateVectorStoreCollection")] -public class WeaviateVectorStoreCollectionFixture : ICollectionFixture -{ } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreFixture.cs deleted file mode 100644 index a457af25d3ec..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreFixture.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Docker.DotNet; -using Docker.DotNet.Models; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -public class WeaviateVectorStoreFixture : IAsyncLifetime -{ - /// The Docker client we are using to create a Weaviate container with. - private readonly DockerClient _client; - - /// The id of the Weaviate container that we are testing with. - private string? _containerId = null; - - public HttpClient? HttpClient { get; private set; } - - public WeaviateVectorStoreFixture() - { - using var dockerClientConfiguration = new DockerClientConfiguration(); - this._client = dockerClientConfiguration.CreateClient(); - } - - public async Task InitializeAsync() - { - this._containerId = await SetupWeaviateContainerAsync(this._client); - - this.HttpClient = new HttpClient { BaseAddress = new Uri("http://localhost:8080/v1/") }; - - await WaitForInitializationAsync(this.HttpClient); - } - - public async Task DisposeAsync() - { - if (this._containerId != null) - { - await this._client.Containers.StopContainerAsync(this._containerId, new ContainerStopParameters()); - await this._client.Containers.RemoveContainerAsync(this._containerId, new ContainerRemoveParameters()); - } - } - - #region private - - private static async Task WaitForInitializationAsync(HttpClient httpClient) - { - const int MaxAttemptCount = 10; - const int DelayInterval = 1000; - - int attemptCount = 0; - bool clusterReady = false; - - do - { - await Task.Delay(DelayInterval); - attemptCount++; - clusterReady = await CheckIfClusterReadyAsync(httpClient); - } while (!clusterReady && attemptCount <= MaxAttemptCount); - - if (!clusterReady) - { - throw new InvalidOperationException("Weaviate cluster is not ready for usage."); - } - } - - private static async Task CheckIfClusterReadyAsync(HttpClient httpClient) - { - try - { - var response = await httpClient.GetAsync(new Uri("schema", UriKind.Relative)); - - return response.StatusCode == HttpStatusCode.OK; - } - catch (HttpRequestException) - { - return false; - } - } - - private static async Task SetupWeaviateContainerAsync(DockerClient client) - { - const string Image = "cr.weaviate.io/semitechnologies/weaviate"; - const string Tag = "1.26.4"; - - await client.Images.CreateImageAsync( - new ImagesCreateParameters - { - FromImage = Image, - Tag = Tag, - }, - null, - new Progress()); - - var container = await client.Containers.CreateContainerAsync(new CreateContainerParameters() - { - Image = $"{Image}:{Tag}", - HostConfig = new HostConfig() - { - PortBindings = new Dictionary> - { - { "8080", new List { new() { HostPort = "8080" } } }, - { "50051", new List { new() { HostPort = "50051" } } } - }, - PublishAllPorts = true - }, - ExposedPorts = new Dictionary - { - { "8080", default }, - { "50051", default } - }, - }); - - await client.Containers.StartContainerAsync( - container.ID, - new ContainerStartParameters()); - - return container.ID; - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreRecordCollectionTests.cs deleted file mode 100644 index cc671d0958d8..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreRecordCollectionTests.cs +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.VectorData; -using Microsoft.SemanticKernel.Connectors.Weaviate; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -#pragma warning disable CS0618 // VectorSearchFilter is obsolete - -[Collection("WeaviateVectorStoreCollection")] -public sealed class WeaviateVectorStoreRecordCollectionTests(WeaviateVectorStoreFixture fixture) -{ - private const string SkipReason = "Weaviate tests are failing on the build server with connection reset errors, but passing locally."; - - [Fact(Skip = SkipReason)] - public async Task ItCanEnsureCollectionExistsAsync() - { - // Arrange - using var sut = new WeaviateCollection(fixture.HttpClient!, "TestCreateCollection"); - - // Act - await sut.EnsureCollectionExistsAsync(); - - // Assert - Assert.True(await sut.CollectionExistsAsync()); - } - - [Theory(Skip = SkipReason)] - [InlineData("ExistingCollection", true)] - [InlineData("NonExistentCollection", false)] - public async Task ItCanCheckIfCollectionExistsAsync(string collectionName, bool collectionExists) - { - // Arrange - using var sut = new WeaviateCollection(fixture.HttpClient!, collectionName); - - if (collectionExists) - { - await sut.EnsureCollectionExistsAsync(); - } - - // Act - var result = await sut.CollectionExistsAsync(); - - // Assert - Assert.Equal(collectionExists, result); - } - - [Theory(Skip = SkipReason)] - [InlineData("CollectionWithVectorAndDefinition", true, true)] - [InlineData("CollectionWithVector", true, false)] - [InlineData("CollectionWithDefinition", false, true)] - [InlineData("CollectionWithoutVectorAndDefinition", false, false)] - public async Task ItCanUpsertAndGetRecordAsync(string collectionName, bool includeVectors, bool useRecordDefinition) - { - // Arrange - var hotelId = new Guid("55555555-5555-5555-5555-555555555555"); - - var options = new WeaviateCollectionOptions - { - Definition = useRecordDefinition ? this.GetTestHotelRecordDefinition() : null - }; - - using var sut = new WeaviateCollection(fixture.HttpClient!, collectionName, options); - - var record = this.CreateTestHotel(hotelId); - - // Act && Assert - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync(record); - - var getResult = await sut.GetAsync(hotelId, new() { IncludeVectors = includeVectors }); - - Assert.NotNull(getResult); - - Assert.Equal(record.HotelId, getResult.HotelId); - Assert.Equal(record.HotelName, getResult.HotelName); - Assert.Equal(record.HotelCode, getResult.HotelCode); - Assert.Equal(record.HotelRating, getResult.HotelRating); - Assert.Equal(record.ParkingIncluded, getResult.ParkingIncluded); - Assert.Equal(record.Tags.ToArray(), getResult.Tags.ToArray()); - Assert.Equal(record.Description, getResult.Description); - Assert.Equal(record.Timestamp, getResult.Timestamp); - - if (includeVectors) - { - Assert.NotNull(getResult.DescriptionEmbedding); - Assert.Equal(record.DescriptionEmbedding!.Value.ToArray(), getResult.DescriptionEmbedding.Value.ToArray()); - } - else - { - Assert.Null(getResult.DescriptionEmbedding); - } - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteCollectionAsync() - { - // Arrange - const string CollectionName = "TestDeleteCollection"; - - using var sut = new WeaviateCollection(fixture.HttpClient!, CollectionName); - - await sut.EnsureCollectionExistsAsync(); - - Assert.True(await sut.CollectionExistsAsync()); - - // Act - await sut.EnsureCollectionDeletedAsync(); - - // Assert - Assert.False(await sut.CollectionExistsAsync()); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanDeleteRecordAsync() - { - // Arrange - var hotelId = new Guid("55555555-5555-5555-5555-555555555555"); - - using var sut = new WeaviateCollection(fixture.HttpClient!, "TestDeleteRecord"); - - await sut.EnsureCollectionExistsAsync(); - - var record = this.CreateTestHotel(hotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(hotelId); - - Assert.NotNull(getResult); - - // Act - await sut.DeleteAsync(hotelId); - - getResult = await sut.GetAsync(hotelId); - - // Assert - Assert.Null(getResult); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndGetAndDeleteBatchAsync() - { - // Arrange - var hotelId1 = new Guid("11111111-1111-1111-1111-111111111111"); - var hotelId2 = new Guid("22222222-2222-2222-2222-222222222222"); - var hotelId3 = new Guid("33333333-3333-3333-3333-333333333333"); - - using var sut = new WeaviateCollection(fixture.HttpClient!, "TestBatch"); - - await sut.EnsureCollectionExistsAsync(); - - var record1 = this.CreateTestHotel(hotelId1); - var record2 = this.CreateTestHotel(hotelId2); - var record3 = this.CreateTestHotel(hotelId3); - - await sut.UpsertAsync([record1, record2, record3]); - var getResults = await sut.GetAsync([hotelId1, hotelId2, hotelId3]).ToListAsync(); - - Assert.NotNull(getResults.First(l => l.HotelId == hotelId1)); - Assert.NotNull(getResults.First(l => l.HotelId == hotelId2)); - Assert.NotNull(getResults.First(l => l.HotelId == hotelId3)); - - // Act - await sut.DeleteAsync([hotelId1, hotelId2, hotelId3]); - - getResults = await sut.GetAsync([hotelId1, hotelId2, hotelId3]).ToListAsync(); - - // Assert - Assert.Empty(getResults); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertRecordAsync() - { - // Arrange - var hotelId = new Guid("55555555-5555-5555-5555-555555555555"); - using var sut = new WeaviateCollection(fixture.HttpClient!, "TestUpsert"); - - await sut.EnsureCollectionExistsAsync(); - - var record = this.CreateTestHotel(hotelId); - - await sut.UpsertAsync(record); - var getResult = await sut.GetAsync(hotelId); - - Assert.NotNull(getResult); - - // Act - record.HotelName = "Updated name"; - record.HotelRating = 10; - - await sut.UpsertAsync(record); - getResult = await sut.GetAsync(hotelId); - - // Assert - Assert.NotNull(getResult); - Assert.Equal("Updated name", getResult.HotelName); - Assert.Equal(10, getResult.HotelRating); - } - - [Theory(Skip = SkipReason)] - [InlineData(true)] - [InlineData(false)] - public async Task SearchReturnsValidResultsByDefaultAsync(bool includeVectors) - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: new Guid("11111111-1111-1111-1111-111111111111"), embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: new Guid("22222222-2222-2222-2222-222222222222"), embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: new Guid("33333333-3333-3333-3333-333333333333"), embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: new Guid("44444444-4444-4444-4444-444444444444"), embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new WeaviateCollection(fixture.HttpClient!, "VectorSearchDefault"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 3, new() - { - IncludeVectors = includeVectors - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId.ToString()).ToList(); - - Assert.Equal("11111111-1111-1111-1111-111111111111", ids[0]); - Assert.Equal("22222222-2222-2222-2222-222222222222", ids[1]); - Assert.Equal("33333333-3333-3333-3333-333333333333", ids[2]); - - Assert.DoesNotContain("44444444-4444-4444-4444-444444444444", ids); - - Assert.True( - searchResults[0].Score < searchResults[1].Score && - searchResults[1].Score < searchResults[2].Score); - - Assert.Equal(includeVectors, searchResults.All(l => l.Record.DescriptionEmbedding is not null)); - } - - [Fact(Skip = SkipReason)] - public async Task SearchReturnsValidResultsWithOffsetAsync() - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: new Guid("11111111-1111-1111-1111-111111111111"), embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: new Guid("22222222-2222-2222-2222-222222222222"), embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: new Guid("33333333-3333-3333-3333-333333333333"), embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: new Guid("44444444-4444-4444-4444-444444444444"), embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new WeaviateCollection(fixture.HttpClient!, "VectorSearchWithOffset"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 2, new() - { - Skip = 2 - }).ToListAsync(); - - // Assert - var ids = searchResults.Select(l => l.Record.HotelId.ToString()).ToList(); - - Assert.Equal("33333333-3333-3333-3333-333333333333", ids[0]); - Assert.Equal("44444444-4444-4444-4444-444444444444", ids[1]); - - Assert.DoesNotContain("11111111-1111-1111-1111-111111111111", ids); - Assert.DoesNotContain("22222222-2222-2222-2222-222222222222", ids); - } - - [Theory(Skip = SkipReason)] - [MemberData(nameof(SearchWithFilterData))] - public async Task SearchReturnsValidResultsWithFilterAsync(VectorSearchFilter filter, List expectedIds) - { - // Arrange - var hotel1 = this.CreateTestHotel(hotelId: new Guid("11111111-1111-1111-1111-111111111111"), embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: new Guid("22222222-2222-2222-2222-222222222222"), embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: new Guid("33333333-3333-3333-3333-333333333333"), embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: new Guid("44444444-4444-4444-4444-444444444444"), embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - using var sut = new WeaviateCollection(fixture.HttpClient!, "VectorSearchWithFilter"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([30f, 31f, 32f, 33f]), top: 4, new() - { - OldFilter = filter, - }).ToListAsync(); - - // Assert - var actualIds = searchResults.Select(l => l.Record.HotelId.ToString()).ToList(); - - Assert.Equal(expectedIds, actualIds); - } - - [Theory(Skip = SkipReason)] - [MemberData(nameof(SearchWithFilterAndDifferentDataTypesData))] - public async Task SearchReturnsValidResultsWithFilterAndDifferentDataTypesAsync(VectorSearchFilter filter) - { - // Arrange - var expectedId = "55555555-5555-5555-5555-555555555555"; - - var hotel1 = this.CreateTestHotel(hotelId: new Guid("11111111-1111-1111-1111-111111111111"), embedding: new[] { 30f, 31f, 32f, 33f }); - var hotel2 = this.CreateTestHotel(hotelId: new Guid("22222222-2222-2222-2222-222222222222"), embedding: new[] { 31f, 32f, 33f, 34f }); - var hotel3 = this.CreateTestHotel(hotelId: new Guid("33333333-3333-3333-3333-333333333333"), embedding: new[] { 20f, 20f, 20f, 20f }); - var hotel4 = this.CreateTestHotel(hotelId: new Guid("44444444-4444-4444-4444-444444444444"), embedding: new[] { -1000f, -1000f, -1000f, -1000f }); - - var hotel5 = new WeaviateHotel - { - HotelId = new Guid("55555555-5555-5555-5555-555555555555"), - HotelName = "Test hotel name", - HotelCode = 88, - HotelRating = 7.9f, - ParkingIncluded = false, - Tags = { "tag1", "tag2" }, - Description = "Hotel description", - DescriptionEmbedding = new[] { 40f, 40f, 40f, 40f }, - Timestamp = new DateTime(2024, 9, 22, 15, 59, 42) - }; - - using var sut = new WeaviateCollection(fixture.HttpClient!, "VectorSearchWithFilterAndDataTypes"); - - await sut.EnsureCollectionExistsAsync(); - - await sut.UpsertAsync([hotel4, hotel2, hotel5, hotel3, hotel1]); - - // Act - var searchResults = await sut.SearchAsync(new ReadOnlyMemory([40f, 40f, 40f, 40f]), top: 4, new() - { - OldFilter = filter, - }).ToListAsync(); - - // Assert - var actualIds = searchResults.Select(l => l.Record.HotelId.ToString()).ToList(); - - Assert.Single(actualIds); - - Assert.Equal(expectedId, actualIds[0]); - } - - [Fact(Skip = SkipReason)] - public async Task ItCanUpsertAndRetrieveUsingDynamicMappingAsync() - { - // Arrange - var hotelId = new Guid("55555555-5555-5555-5555-555555555555"); - var options = new WeaviateCollectionOptions - { - Definition = this.GetTestHotelRecordDefinition() - }; - - using var sut = new WeaviateDynamicCollection(fixture.HttpClient!, "TestDynamicMapper", options); - - await sut.EnsureCollectionExistsAsync(); - - // Act - await sut.UpsertAsync(new Dictionary - { - ["HotelId"] = hotelId, - - ["HotelName"] = "Dynamic Mapper Hotel", - ["Description"] = "This is a dynamic mapper hotel", - ["Tags"] = new List { "dynamic" }, - ["ParkingIncluded"] = false, - ["Timestamp"] = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), - ["HotelRating"] = 3.6f, - - ["DescriptionEmbedding"] = new ReadOnlyMemory([30f, 31f, 32f, 33f]) - }); - - var localGetResult = await sut.GetAsync(hotelId, new RecordRetrievalOptions { IncludeVectors = true }); - - // Assert - Assert.NotNull(localGetResult); - Assert.Equal("Dynamic Mapper Hotel", localGetResult["HotelName"]); - Assert.Equal("This is a dynamic mapper hotel", localGetResult["Description"]); - Assert.Equal(new List { "dynamic" }, localGetResult["Tags"]); - Assert.False((bool?)localGetResult["ParkingIncluded"]); - Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), localGetResult["Timestamp"]); - Assert.Equal(3.6f, localGetResult["HotelRating"]); - Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult["DescriptionEmbedding"]!).ToArray()); - } - - public static TheoryData> SearchWithFilterData => new() - { - { - new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.HotelName), "My Hotel 22222222-2222-2222-2222-222222222222"), - ["22222222-2222-2222-2222-222222222222"] - }, - { - new VectorSearchFilter().AnyTagEqualTo(nameof(WeaviateHotel.Tags), "t2"), - [ - "11111111-1111-1111-1111-111111111111", - "22222222-2222-2222-2222-222222222222", - "33333333-3333-3333-3333-333333333333", - "44444444-4444-4444-4444-444444444444" - ] - }, - { - new VectorSearchFilter() - .EqualTo(nameof(WeaviateHotel.HotelName), "My Hotel 22222222-2222-2222-2222-222222222222") - .AnyTagEqualTo(nameof(WeaviateHotel.Tags), "t2"), - ["22222222-2222-2222-2222-222222222222"] - }, - { - new VectorSearchFilter() - .EqualTo(nameof(WeaviateHotel.HotelName), "non-existent-hotel") - .AnyTagEqualTo(nameof(WeaviateHotel.Tags), "non-existent-tag"), - [] - } - }; - - public static TheoryData SearchWithFilterAndDifferentDataTypesData => new() - { - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.HotelId), new Guid("55555555-5555-5555-5555-555555555555")) }, - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.HotelName), "Test hotel name") }, - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.HotelCode), 88) }, - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.HotelRating), 7.9f) }, - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.ParkingIncluded), false) }, - { new VectorSearchFilter().EqualTo(nameof(WeaviateHotel.Timestamp), new DateTimeOffset(new DateTime(2024, 9, 22, 15, 59, 42))) } - }; - - #region private - - private WeaviateHotel CreateTestHotel( - Guid hotelId, - string? hotelName = null, - ReadOnlyMemory? embedding = null) - { - return new WeaviateHotel - { - HotelId = hotelId, - HotelName = hotelName ?? $"My Hotel {hotelId}", - HotelCode = 42, - HotelRating = 4.5f, - ParkingIncluded = true, - Tags = { "t1", "t2" }, - Description = "This is a great hotel.", - DescriptionEmbedding = embedding ?? new[] { 30f, 31f, 32f, 33f }, - Timestamp = new DateTime(2024, 8, 28, 10, 11, 12) - }; - } - - private VectorStoreCollectionDefinition GetTestHotelRecordDefinition() - { - return new() - { - Properties = - [ - new VectorStoreKeyProperty("HotelId", typeof(Guid)), - new VectorStoreDataProperty("HotelName", typeof(string)), - new VectorStoreDataProperty("HotelCode", typeof(int)), - new VectorStoreDataProperty("ParkingIncluded", typeof(bool)) { StorageName = "parking_is_included" }, - new VectorStoreDataProperty("HotelRating", typeof(float)), - new VectorStoreDataProperty("Tags", typeof(List)), - new VectorStoreDataProperty("Description", typeof(string)), - new VectorStoreDataProperty("Timestamp", typeof(DateTimeOffset)), - new VectorStoreVectorProperty("DescriptionEmbedding", typeof(ReadOnlyMemory?), 4) { IndexKind = IndexKind.Hnsw, DistanceFunction = DistanceFunction.CosineDistance } - ] - }; - } - - #endregion -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs deleted file mode 100644 index 15b24ddbbbb0..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.SemanticKernel.Connectors.Weaviate; -using SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; -using Xunit; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; - -[Collection("WeaviateVectorStoreCollection")] -[DisableVectorStoreTests(Skip = "Weaviate tests are failing on the build server with connection reset errors, but passing locally.")] -public sealed class WeaviateVectorStoreTests(WeaviateVectorStoreFixture fixture) -#pragma warning disable CA2000 // Dispose objects before losing scope - : BaseVectorStoreTests(new WeaviateVectorStore(fixture.HttpClient!)) -#pragma warning restore CA2000 // Dispose objects before losing scope -{ - // Weaviate requires each collection name to start with uppercase ASCII letter. - protected override IEnumerable CollectionNames => ["Listcollectionnames1", "Listcollectionnames2", "Listcollectionnames3"]; -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/docker-compose.yml b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/docker-compose.yml deleted file mode 100644 index 9e14a2ba3830..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/docker-compose.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -version: '3.4' -services: - weaviate: - - image: semitechnologies/weaviate:1.21.2 - links: - - "contextionary:contextionary" - ports: - - 8080:8080 - restart: on-failure:0 - environment: - LOG_LEVEL: "debug" - CONTEXTIONARY_URL: contextionary:9999 - QUERY_DEFAULTS_LIMIT: 25 - AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true' - PERSISTENCE_DATA_PATH: "./data" - DEFAULT_VECTORIZER_MODULE: text2vec-contextionary - ENABLE_MODULES: text2vec-contextionary,backup-filesystem,generative-openai - BACKUP_FILESYSTEM_PATH: "/tmp/backups" - CLUSTER_GOSSIP_BIND_PORT: "7100" - CLUSTER_DATA_BIND_PORT: "7101" - AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'my-secret-key' - healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:8080/v1" ] - interval: 1m - timeout: 10s - retries: 5 - start_period: 5s - contextionary: - image: semitechnologies/contextionary:en0.16.0-v1.2.0 - environment: - LOG_LEVEL: "debug" - OCCURRENCE_WEIGHT_LINEAR_FACTOR: 0.75 - EXTENSIONS_STORAGE_MODE: weaviate - EXTENSIONS_STORAGE_ORIGIN: http://weaviate:8080 - NEIGHBOR_OCCURRENCE_IGNORE_PERCENTILE: 5 - ENABLE_COMPOUND_SPLITTING: 'false' diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/DisableVectorStoreTestsAttribute.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/DisableVectorStoreTestsAttribute.cs deleted file mode 100644 index 3f5f849f7db4..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/DisableVectorStoreTestsAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -/// -/// Disable the vector store test in the method or class that this attribute is decorated with. -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public sealed class DisableVectorStoreTestsAttribute : Attribute, ITestCondition -{ - public ValueTask IsMetAsync() - { - return ValueTask.FromResult(false); - } - - public string Skip { get; set; } = "Test disabled due to usage of DisableVectorStoreTestsAttribute"; - - public string SkipReason - => this.Skip; -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/ITestCondition.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/ITestCondition.cs deleted file mode 100644 index ef0f52f21dab..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/ITestCondition.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Threading.Tasks; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public interface ITestCondition -{ - ValueTask IsMetAsync(); - - string SkipReason { get; } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactAttribute.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactAttribute.cs deleted file mode 100644 index 5ad8b176d2e7..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Xunit; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -[AttributeUsage(AttributeTargets.Method)] -[XunitTestCaseDiscoverer("SemanticKernel.IntegrationTests.Connectors.Memory.Xunit.VectorStoreFactDiscoverer", "IntegrationTests")] -public sealed class VectorStoreFactAttribute : FactAttribute; diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactDiscoverer.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactDiscoverer.cs deleted file mode 100644 index 1fb8289362f0..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactDiscoverer.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public class VectorStoreFactDiscoverer(IMessageSink messageSink) : FactDiscoverer(messageSink) -{ - protected override IXunitTestCase CreateTestCase( - ITestFrameworkDiscoveryOptions discoveryOptions, - ITestMethod testMethod, - IAttributeInfo factAttribute) - => new VectorStoreFactTestCase( - this.DiagnosticMessageSink, - discoveryOptions.MethodDisplayOrDefault(), - discoveryOptions.MethodDisplayOptionsOrDefault(), - testMethod); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactTestCase.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactTestCase.cs deleted file mode 100644 index 6b30eb551484..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreFactTestCase.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public sealed class VectorStoreFactTestCase : XunitTestCase -{ - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public VectorStoreFactTestCase() - { - } - - public VectorStoreFactTestCase( - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, - object[]? testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) - { - } - - public override async Task RunAsync( - IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - => await XunitTestCaseExtensions.TrySkipAsync(this, messageBus) - ? new RunSummary { Total = 1, Skipped = 1 } - : await base.RunAsync( - diagnosticMessageSink, - messageBus, - constructorArguments, - aggregator, - cancellationTokenSource); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryAttribute.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryAttribute.cs deleted file mode 100644 index f1577eec104e..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Xunit; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -[AttributeUsage(AttributeTargets.Method)] -[XunitTestCaseDiscoverer("SemanticKernel.IntegrationTests.Connectors.Memory.Xunit.VectorStoreTheoryDiscoverer", "IntegrationTests")] -public sealed class VectorStoreTheoryAttribute : TheoryAttribute; diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryDiscoverer.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryDiscoverer.cs deleted file mode 100644 index f75352941aa8..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryDiscoverer.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public class VectorStoreTheoryDiscoverer(IMessageSink messageSink) : TheoryDiscoverer(messageSink) -{ - protected override IEnumerable CreateTestCasesForTheory( - ITestFrameworkDiscoveryOptions discoveryOptions, - ITestMethod testMethod, - IAttributeInfo theoryAttribute) - { - yield return new VectorStoreTheoryTestCase( - this.DiagnosticMessageSink, - discoveryOptions.MethodDisplayOrDefault(), - discoveryOptions.MethodDisplayOptionsOrDefault(), - testMethod); - } - - protected override IEnumerable CreateTestCasesForDataRow( - ITestFrameworkDiscoveryOptions discoveryOptions, - ITestMethod testMethod, - IAttributeInfo theoryAttribute, - object[] dataRow) - { - yield return new VectorStoreFactTestCase( - this.DiagnosticMessageSink, - discoveryOptions.MethodDisplayOrDefault(), - discoveryOptions.MethodDisplayOptionsOrDefault(), - testMethod, - dataRow); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryTestCase.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryTestCase.cs deleted file mode 100644 index 38e3a3cb76ef..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/VectorStoreTheoryTestCase.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public sealed class VectorStoreTheoryTestCase : XunitTheoryTestCase -{ - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public VectorStoreTheoryTestCase() - { - } - - public VectorStoreTheoryTestCase( - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) - { - } - - public override async Task RunAsync( - IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - => await XunitTestCaseExtensions.TrySkipAsync(this, messageBus) - ? new RunSummary { Total = 1, Skipped = 1 } - : await base.RunAsync( - diagnosticMessageSink, - messageBus, - constructorArguments, - aggregator, - cancellationTokenSource); -} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/XunitTestCaseExtensions.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/XunitTestCaseExtensions.cs deleted file mode 100644 index 312e4197842e..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Xunit/XunitTestCaseExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace SemanticKernel.IntegrationTests.Connectors.Memory.Xunit; - -public static class XunitTestCaseExtensions -{ - private static readonly ConcurrentDictionary> s_typeAttributes = new(); - private static readonly ConcurrentDictionary> s_assemblyAttributes = new(); - - public static async ValueTask TrySkipAsync(XunitTestCase testCase, IMessageBus messageBus) - { - var method = testCase.Method; - var type = testCase.TestMethod.TestClass.Class; - var assembly = type.Assembly; - - var skipReasons = new List(); - var attributes = - s_assemblyAttributes.GetOrAdd( - assembly.Name, - a => assembly.GetCustomAttributes(typeof(ITestCondition)).ToList()) - .Concat( - s_typeAttributes.GetOrAdd( - type.Name, - t => type.GetCustomAttributes(typeof(ITestCondition)).ToList())) - .Concat(method.GetCustomAttributes(typeof(ITestCondition))) - .OfType() - .Select(attributeInfo => (ITestCondition)attributeInfo.Attribute); - - foreach (var attribute in attributes) - { - if (!await attribute.IsMetAsync()) - { - skipReasons.Add(attribute.SkipReason); - } - } - - if (skipReasons.Count > 0) - { - messageBus.QueueMessage( - new TestSkipped(new XunitTest(testCase, testCase.DisplayName), string.Join(Environment.NewLine, skipReasons))); - - return true; - } - - return false; - } -} diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoBsonMappingTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoBsonMappingTests.cs new file mode 100644 index 000000000000..44b99c00d98d --- /dev/null +++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoBsonMappingTests.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosMongoDB.ConformanceTests.Support; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.CosmosMongoDB; +using MongoDB.Bson.Serialization.Attributes; +using VectorData.ConformanceTests.Support; +using VectorData.ConformanceTests.Xunit; +using Xunit; + +namespace CosmosMongoDB.ConformanceTests; + +[CosmosConnectionStringRequired] +public sealed class CosmosMongoBsonMappingTests(CosmosMongoBsonMappingTests.Fixture fixture) + : IClassFixture +{ + [ConditionalFact] + public async Task Upsert_with_bson_model_works() + { + var store = (CosmosMongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonModel"); + + var definition = new VectorStoreCollectionDefinition + { + Properties = + [ + new VectorStoreKeyProperty(nameof(BsonTestModel.Id), typeof(string)), + new VectorStoreDataProperty(nameof(BsonTestModel.HotelName), typeof(string)) + ] + }; + + var model = new BsonTestModel { Id = "key", HotelName = "Test Name" }; + + using var collection = new CosmosMongoCollection( + store.Database, + collectionName, + new() { Definition = definition }); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.Id!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.Id); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalFact] + public async Task Upsert_with_bson_vector_store_model_works() + { + var store = (CosmosMongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonVectorStoreModel"); + + var model = new BsonVectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; + + using var collection = new CosmosMongoCollection(store.Database, collectionName); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.HotelId!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.HotelId); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalFact] + public async Task Upsert_with_bson_vector_store_with_name_model_works() + { + var store = (CosmosMongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonVectorStoreWithNameModel"); + + var model = new BsonVectorStoreWithNameTestModel { Id = "key", HotelName = "Test Name" }; + + using var collection = new CosmosMongoCollection(store.Database, collectionName); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.Id!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.Id); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + public sealed class Fixture : VectorStoreFixture + { + public override TestStore TestStore => CosmosMongoTestStore.Instance; + } + + private sealed class BsonTestModel + { + [BsonId] + public string? Id { get; set; } + + [BsonElement("hotel_name")] + public string? HotelName { get; set; } + } + + private sealed class BsonVectorStoreTestModel + { + [BsonId] + [VectorStoreKey] + public string? HotelId { get; set; } + + [BsonElement("hotel_name")] + [VectorStoreData] + public string? HotelName { get; set; } + } + + private sealed class BsonVectorStoreWithNameTestModel + { + [BsonId] + [VectorStoreKey] + public string? Id { get; set; } + + [BsonElement("bson_hotel_name")] + [VectorStoreData(StorageName = "storage_hotel_name")] + public string? HotelName { get; set; } + } +} diff --git a/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/CosmosNoSqlCollectionOptionsTests.cs b/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/CosmosNoSqlCollectionOptionsTests.cs new file mode 100644 index 000000000000..63a9487dc2bc --- /dev/null +++ b/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/CosmosNoSqlCollectionOptionsTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosNoSql.ConformanceTests.Support; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.CosmosNoSql; +using VectorData.ConformanceTests.Support; +using VectorData.ConformanceTests.Xunit; +using Xunit; + +namespace CosmosNoSql.ConformanceTests; + +[CosmosConnectionStringRequired] +public sealed class CosmosNoSqlCollectionOptionsTests(CosmosNoSqlCollectionOptionsTests.Fixture fixture) + : IClassFixture +{ + [ConditionalFact] + public async Task Collection_supports_partition_key_composite_key() + { + var store = (CosmosNoSqlTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("PartitionKeyCompositeKey"); + + using VectorStoreCollection collection = + new CosmosNoSqlCollection( + store.Database, + collectionName, + new() { PartitionKeyPropertyName = nameof(PartitionedHotel.HotelName) }); + + await collection.EnsureCollectionExistsAsync(); + + try + { + var record = new PartitionedHotel + { + HotelId = "hotel-1", + HotelName = "Hotel A", + Description = "Partitioned", + Embedding = new([1f, 2f, 3f]) + }; + + await collection.UpsertAsync(record); + var key = new CosmosNoSqlCompositeKey(record.HotelId, record.HotelName); + + var fetched = await collection.GetAsync(key, new() { IncludeVectors = true }); + Assert.NotNull(fetched); + + await collection.DeleteAsync(key); + Assert.Null(await collection.GetAsync(key)); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalTheory] + [InlineData(IndexingMode.Consistent)] + [InlineData(IndexingMode.Lazy)] + [InlineData(IndexingMode.None)] + public async Task Collection_supports_indexing_mode(IndexingMode indexingMode) + { + var store = (CosmosNoSqlTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName($"IndexingMode_{indexingMode}"); + + using var collection = new CosmosNoSqlCollection( + store.Database, + collectionName, + new() { IndexingMode = indexingMode, Automatic = indexingMode != IndexingMode.None }); + + await collection.EnsureCollectionExistsAsync(); + + try + { + var record = new IndexingModeHotel + { + HotelId = "hotel-2", + HotelName = "Hotel B", + Embedding = new([1f, 0f, 0f]) + }; + + await collection.UpsertAsync(record); + var fetched = await collection.GetAsync(record.HotelId); + + Assert.NotNull(fetched); + + await collection.DeleteAsync(record.HotelId); + Assert.Null(await collection.GetAsync(record.HotelId)); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + public sealed class Fixture : VectorStoreFixture + { + public override TestStore TestStore => CosmosNoSqlTestStore.Instance; + } + + private sealed class PartitionedHotel + { + [VectorStoreKey] + public string HotelId { get; set; } = string.Empty; + + [VectorStoreData] + public string HotelName { get; set; } = string.Empty; + + [VectorStoreData] + public string? Description { get; set; } + + [VectorStoreVector(Dimensions: 3)] + public ReadOnlyMemory Embedding { get; set; } + } + + private sealed class IndexingModeHotel + { + [VectorStoreKey] + public string HotelId { get; set; } = string.Empty; + + [VectorStoreData] + public string HotelName { get; set; } = string.Empty; + + [VectorStoreVector(Dimensions: 3)] + public ReadOnlyMemory Embedding { get; set; } + } +} diff --git a/dotnet/test/VectorData/MongoDB.ConformanceTests/MongoBsonMappingTests.cs b/dotnet/test/VectorData/MongoDB.ConformanceTests/MongoBsonMappingTests.cs new file mode 100644 index 000000000000..f17caa806825 --- /dev/null +++ b/dotnet/test/VectorData/MongoDB.ConformanceTests/MongoBsonMappingTests.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.MongoDB; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.ConformanceTests.Support; +using VectorData.ConformanceTests.Support; +using VectorData.ConformanceTests.Xunit; +using Xunit; + +namespace MongoDB.ConformanceTests; + +public sealed class MongoBsonMappingTests(MongoBsonMappingTests.Fixture fixture) + : IClassFixture +{ + [ConditionalFact] + public async Task Upsert_with_bson_model_works() + { + var store = (MongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonModel"); + + var definition = new VectorStoreCollectionDefinition + { + Properties = + [ + new VectorStoreKeyProperty(nameof(BsonTestModel.Id), typeof(string)), + new VectorStoreDataProperty(nameof(BsonTestModel.HotelName), typeof(string)) + ] + }; + + var model = new BsonTestModel { Id = "key", HotelName = "Test Name" }; + + using var collection = new MongoCollection( + store.Database, + collectionName, + new() { Definition = definition }); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.Id!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.Id); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalFact] + public async Task Upsert_with_bson_vector_store_model_works() + { + var store = (MongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonVectorStoreModel"); + + var model = new BsonVectorStoreTestModel { HotelId = "key", HotelName = "Test Name" }; + + using var collection = new MongoCollection(store.Database, collectionName); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.HotelId!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.HotelId); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalFact] + public async Task Upsert_with_bson_vector_store_with_name_model_works() + { + var store = (MongoTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("BsonVectorStoreWithNameModel"); + + var model = new BsonVectorStoreWithNameTestModel { Id = "key", HotelName = "Test Name" }; + + using var collection = new MongoCollection(store.Database, collectionName); + + await collection.EnsureCollectionExistsAsync(); + + try + { + await collection.UpsertAsync(model); + var getResult = await collection.GetAsync(model.Id!); + + Assert.NotNull(getResult); + Assert.Equal("key", getResult!.Id); + Assert.Equal("Test Name", getResult.HotelName); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + public sealed class Fixture : VectorStoreFixture + { + public override TestStore TestStore => MongoTestStore.Instance; + } + + private sealed class BsonTestModel + { + [BsonId] + public string? Id { get; set; } + + [BsonElement("hotel_name")] + public string? HotelName { get; set; } + } + + private sealed class BsonVectorStoreTestModel + { + [BsonId] + [VectorStoreKey] + public string? HotelId { get; set; } + + [BsonElement("hotel_name")] + [VectorStoreData] + public string? HotelName { get; set; } + } + + private sealed class BsonVectorStoreWithNameTestModel + { + [BsonId] + [VectorStoreKey] + public string? Id { get; set; } + + [BsonElement("bson_hotel_name")] + [VectorStoreData(StorageName = "storage_hotel_name")] + public string? HotelName { get; set; } + } +} diff --git a/dotnet/test/VectorData/Redis.ConformanceTests/RedisJsonOptionsTests.cs b/dotnet/test/VectorData/Redis.ConformanceTests/RedisJsonOptionsTests.cs new file mode 100644 index 000000000000..4743183b1389 --- /dev/null +++ b/dotnet/test/VectorData/Redis.ConformanceTests/RedisJsonOptionsTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.Redis; +using Redis.ConformanceTests.Support; +using StackExchange.Redis; +using VectorData.ConformanceTests.Support; +using VectorData.ConformanceTests.Xunit; +using Xunit; + +namespace Redis.ConformanceTests; + +public sealed class RedisJsonOptionsTests(RedisJsonOptionsTests.Fixture fixture) + : IClassFixture +{ + [ConditionalFact] + public async Task Json_collection_with_prefix_and_nested_address_roundtrips() + { + var store = (RedisTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("jsonoptions"); + + var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; + using var collection = new RedisJsonCollection(store.Database, collectionName, options); + + await collection.EnsureCollectionExistsAsync(); + + try + { + var record = new RedisJsonHotel + { + HotelId = "hotel-1", + HotelName = "Test Hotel", + ParkingIncluded = true, + Address = new RedisAddress { City = "Seattle", Country = "USA" }, + DescriptionEmbedding = new([30f, 31f, 32f, 33f]) + }; + + await collection.UpsertAsync(record); + var fetched = await collection.GetAsync(record.HotelId, new() { IncludeVectors = true }); + + Assert.NotNull(fetched); + Assert.Equal(record.HotelId, fetched!.HotelId); + Assert.Equal(record.HotelName, fetched.HotelName); + Assert.Equal(record.ParkingIncluded, fetched.ParkingIncluded); + Assert.NotNull(fetched.Address); + Assert.Equal(record.Address.City, fetched.Address.City); + Assert.Equal(record.Address.Country, fetched.Address.Country); + Assert.Equal(record.DescriptionEmbedding.ToArray(), fetched.DescriptionEmbedding.ToArray()); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + [ConditionalFact] + public async Task Json_collection_get_throws_for_invalid_schema() + { + var store = (RedisTestStore)fixture.TestStore; + var collectionName = fixture.TestStore.AdjustCollectionName("jsoninvalidschema"); + + var options = new RedisJsonCollectionOptions { PrefixCollectionNameToKeyNames = true }; + using var collection = new RedisJsonCollection(store.Database, collectionName, options); + + await collection.EnsureCollectionExistsAsync(); + + try + { + var invalidDocument = new + { + HotelId = "another-id", + HotelName = "Invalid Hotel", + ParkingIncluded = false, + DescriptionEmbedding = new[] { 30f, 31f, 32f, 33f }, + Address = new { City = "Seattle", Country = "USA" } + }; + + var key = (RedisKey)$"{collectionName}:invalid"; + var json = JsonSerializer.Serialize(invalidDocument); + await store.Database.ExecuteAsync("JSON.SET", key, "$", json); + + await Assert.ThrowsAsync(async () => + await collection.GetAsync("invalid", new() { IncludeVectors = true })); + } + finally + { + await collection.EnsureCollectionDeletedAsync(); + } + } + + public sealed class Fixture : VectorStoreFixture + { + public override TestStore TestStore => RedisTestStore.JsonInstance; + } + + private sealed class RedisJsonHotel + { + [VectorStoreKey] + public string HotelId { get; set; } = string.Empty; + + [VectorStoreData(IsIndexed = true)] + public string HotelName { get; set; } = string.Empty; + + [VectorStoreData(StorageName = "parking_is_included")] + public bool ParkingIncluded { get; set; } + + [VectorStoreData] + public RedisAddress Address { get; set; } = new(); + + [VectorStoreVector(4)] + public ReadOnlyMemory DescriptionEmbedding { get; set; } + } + + private sealed class RedisAddress + { + public string City { get; set; } = string.Empty; + + public string Country { get; set; } = string.Empty; + } +} diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/BasicModelTests.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/BasicModelTests.cs index 0b4b216f40a6..dec38aff8f1b 100644 --- a/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/BasicModelTests.cs +++ b/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/BasicModelTests.cs @@ -67,6 +67,26 @@ public virtual async Task GetAsync_returns_null_for_missing_key() Assert.Null(await this.Collection.GetAsync(key)); } + [ConditionalFact] + public virtual async Task GetAsync_multiple_records_with_missing_keys_returns_only_existing() + { + var expectedRecords = fixture.TestData.Take(2).ToArray(); + var missingKey = fixture.GenerateNextKey(); + var ids = expectedRecords.Select(record => record.Key).Append(missingKey).ToArray(); + + var received = await this.Collection.GetAsync(ids).ToListAsync(); + + Assert.Equal(2, received.Count); + + foreach (var record in expectedRecords) + { + record.AssertEqual( + received.Single(r => r.Key.Equals(record.Key)), + includeVectors: false, + fixture.TestStore.VectorsComparable); + } + } + [ConditionalFact] public virtual async Task GetAsync_returns_empty_for_empty_keys() {