diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj index 5952d571a09f..a0a695a6719c 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj @@ -8,7 +8,7 @@ true enable false - $(NoWarn);SKEXP0001;SKEXP0010;CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111 + $(NoWarn);SKEXP0001;SKEXP0010;CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,IDE1006 @@ -27,7 +27,7 @@ - + diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceCollectionExtensionsTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceCollectionExtensionsTests.cs index 152a968a6bb1..ca4899258b21 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceCollectionExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceCollectionExtensionsTests.cs @@ -7,6 +7,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Embeddings; using Microsoft.SemanticKernel.TextGeneration; namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Extensions; @@ -21,8 +22,8 @@ public sealed class AzureOpenAIServiceCollectionExtensionsTests [Theory] [InlineData(InitializationType.ApiKey)] [InlineData(InitializationType.TokenCredential)] - [InlineData(InitializationType.OpenAIClientInline)] - [InlineData(InitializationType.OpenAIClientInServiceProvider)] + [InlineData(InitializationType.ClientInline)] + [InlineData(InitializationType.ClientInServiceProvider)] public void ServiceCollectionAddAzureOpenAIChatCompletionAddsValidService(InitializationType type) { // Arrange @@ -37,8 +38,8 @@ public void ServiceCollectionAddAzureOpenAIChatCompletionAddsValidService(Initia { InitializationType.ApiKey => builder.Services.AddAzureOpenAIChatCompletion("deployment-name", "https://endpoint", "api-key"), InitializationType.TokenCredential => builder.Services.AddAzureOpenAIChatCompletion("deployment-name", "https://endpoint", credentials), - InitializationType.OpenAIClientInline => builder.Services.AddAzureOpenAIChatCompletion("deployment-name", client), - InitializationType.OpenAIClientInServiceProvider => builder.Services.AddAzureOpenAIChatCompletion("deployment-name"), + InitializationType.ClientInline => builder.Services.AddAzureOpenAIChatCompletion("deployment-name", client), + InitializationType.ClientInServiceProvider => builder.Services.AddAzureOpenAIChatCompletion("deployment-name"), _ => builder.Services }; @@ -52,12 +53,47 @@ public void ServiceCollectionAddAzureOpenAIChatCompletionAddsValidService(Initia #endregion + #region Text embeddings + + [Theory] + [InlineData(InitializationType.ApiKey)] + [InlineData(InitializationType.TokenCredential)] + [InlineData(InitializationType.ClientInline)] + [InlineData(InitializationType.ClientInServiceProvider)] + public void ServiceCollectionAddAzureOpenAITextEmbeddingGenerationAddsValidService(InitializationType type) + { + // Arrange + var credentials = DelegatedTokenCredential.Create((_, _) => new AccessToken()); + var client = new AzureOpenAIClient(new Uri("http://localhost"), "key"); + var builder = Kernel.CreateBuilder(); + + builder.Services.AddSingleton(client); + + // Act + IServiceCollection collection = type switch + { + InitializationType.ApiKey => builder.Services.AddAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", "api-key"), + InitializationType.TokenCredential => builder.Services.AddAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", credentials), + InitializationType.ClientInline => builder.Services.AddAzureOpenAITextEmbeddingGeneration("deployment-name", client), + InitializationType.ClientInServiceProvider => builder.Services.AddAzureOpenAITextEmbeddingGeneration("deployment-name"), + _ => builder.Services + }; + + // Assert + var service = builder.Build().GetRequiredService(); + + Assert.NotNull(service); + Assert.True(service is AzureOpenAITextEmbeddingGenerationService); + } + + #endregion + public enum InitializationType { ApiKey, TokenCredential, - OpenAIClientInline, - OpenAIClientInServiceProvider, - OpenAIClientEndpoint, + ClientInline, + ClientInServiceProvider, + ClientEndpoint, } } diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceKernelBuilderExtensionsTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceKernelBuilderExtensionsTests.cs index 13c5d31ce427..8c5515516ca5 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceKernelBuilderExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Extensions/AzureOpenAIServiceKernelBuilderExtensionsTests.cs @@ -7,6 +7,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Embeddings; using Microsoft.SemanticKernel.TextGeneration; namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Extensions; @@ -52,6 +53,41 @@ public void KernelBuilderAddAzureOpenAIChatCompletionAddsValidService(Initializa #endregion + #region Text embeddings + + [Theory] + [InlineData(InitializationType.ApiKey)] + [InlineData(InitializationType.TokenCredential)] + [InlineData(InitializationType.OpenAIClientInline)] + [InlineData(InitializationType.OpenAIClientInServiceProvider)] + public void KernelBuilderAddAzureOpenAITextEmbeddingGenerationAddsValidService(InitializationType type) + { + // Arrange + var credentials = DelegatedTokenCredential.Create((_, _) => new AccessToken()); + var client = new AzureOpenAIClient(new Uri("http://localhost"), "key"); + var builder = Kernel.CreateBuilder(); + + builder.Services.AddSingleton(client); + + // Act + builder = type switch + { + InitializationType.ApiKey => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", "api-key"), + InitializationType.TokenCredential => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", credentials), + InitializationType.OpenAIClientInline => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name", client), + InitializationType.OpenAIClientInServiceProvider => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name"), + _ => builder + }; + + // Assert + var service = builder.Build().GetRequiredService(); + + Assert.NotNull(service); + Assert.True(service is AzureOpenAITextEmbeddingGenerationService); + } + + #endregion + public enum InitializationType { ApiKey, diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextEmbeddingGenerationServiceTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextEmbeddingGenerationServiceTests.cs new file mode 100644 index 000000000000..738364429cff --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextEmbeddingGenerationServiceTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.OpenAI; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Services; + +namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Services; + +/// +/// Unit tests for class. +/// +public class AzureOpenAITextEmbeddingGenerationServiceTests +{ + [Fact] + public void ItCanBeInstantiatedAndPropertiesSetAsExpected() + { + // Arrange + var sut = new AzureOpenAITextEmbeddingGenerationService("deployment-name", "https://endpoint", "api-key", modelId: "model", dimensions: 2); + var sutWithAzureOpenAIClient = new AzureOpenAITextEmbeddingGenerationService("deployment-name", new AzureOpenAIClient(new Uri("https://endpoint"), new ApiKeyCredential("apiKey")), modelId: "model", dimensions: 2); + + // Assert + Assert.NotNull(sut); + Assert.NotNull(sutWithAzureOpenAIClient); + Assert.Equal("model", sut.Attributes[AIServiceExtensions.ModelIdKey]); + Assert.Equal("model", sutWithAzureOpenAIClient.Attributes[AIServiceExtensions.ModelIdKey]); + } + + [Fact] + public async Task ItGetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsEmpty() + { + // Arrange + var sut = new AzureOpenAITextEmbeddingGenerationService("deployment-name", "https://endpoint", "api-key"); + + // Act + var result = await sut.GenerateEmbeddingsAsync([], null, CancellationToken.None); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsWhitespace() + { + // Arrange + using HttpMessageHandlerStub handler = new() + { + ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(File.ReadAllText("./TestData/text-embeddings-response.txt")) + } + }; + using HttpClient client = new(handler); + + var sut = new AzureOpenAITextEmbeddingGenerationService("deployment-name", "https://endpoint", "api-key", httpClient: client); + + // Act + var result = await sut.GenerateEmbeddingsAsync(["test"], null, CancellationToken.None); + + // Assert + Assert.Single(result); + Assert.Equal(4, result[0].Length); + } + + [Fact] + public async Task ItThrowsIfNumberOfResultsDiffersFromInputsAsync() + { + // Arrange + using HttpMessageHandlerStub handler = new() + { + ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(File.ReadAllText("./TestData/text-embeddings-multiple-response.txt")) + } + }; + using HttpClient client = new(handler); + + var sut = new AzureOpenAITextEmbeddingGenerationService("deployment-name", "https://endpoint", "api-key", httpClient: client); + + // Act & Assert + await Assert.ThrowsAsync(async () => await sut.GenerateEmbeddingsAsync(["test"], null, CancellationToken.None)); + } +} diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-multiple-response.txt b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-multiple-response.txt new file mode 100644 index 000000000000..46a9581cf0cc --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-multiple-response.txt @@ -0,0 +1,20 @@ +{ + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": "zcyMP83MDEAzM1NAzcyMQA==" + }, + { + "object": "embedding", + "index": 1, + "embedding": "zcyMP83MDEAzM1NAzcyMQA==" + } + ], + "model": "text-embedding-ada-002", + "usage": { + "prompt_tokens": 7, + "total_tokens": 7 + } +} diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-response.txt b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-response.txt new file mode 100644 index 000000000000..c715b851b78c --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-embeddings-response.txt @@ -0,0 +1,15 @@ +{ + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": "zcyMP83MDEAzM1NAzcyMQA==" + } + ], + "model": "text-embedding-ada-002", + "usage": { + "prompt_tokens": 7, + "total_tokens": 7 + } +} diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj index 29fbd3da46d3..35c31788610d 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj @@ -21,18 +21,10 @@ Semantic Kernel connectors for Azure OpenAI. Contains clients for text generation, chat completion, embedding and DALL-E text to image. - - - - - - - - diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Extensions/AzureOpenAIServiceCollectionExtensions.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Extensions/AzureOpenAIServiceCollectionExtensions.cs index f946d09026a0..e25eac02789b 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Extensions/AzureOpenAIServiceCollectionExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Extensions/AzureOpenAIServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Diagnostics.CodeAnalysis; using System.Net.Http; using Azure; using Azure.AI.OpenAI; @@ -9,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Embeddings; using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.TextGeneration; @@ -44,7 +46,6 @@ public static IKernelBuilder AddAzureOpenAIChatCompletion( HttpClient? httpClient = null) { Verify.NotNull(builder); - Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.NotNullOrWhiteSpace(apiKey); @@ -83,7 +84,6 @@ public static IServiceCollection AddAzureOpenAIChatCompletion( string? modelId = null) { Verify.NotNull(services); - Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.NotNullOrWhiteSpace(apiKey); @@ -124,7 +124,6 @@ public static IKernelBuilder AddAzureOpenAIChatCompletion( HttpClient? httpClient = null) { Verify.NotNull(builder); - Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.NotNull(credentials); @@ -163,7 +162,6 @@ public static IServiceCollection AddAzureOpenAIChatCompletion( string? modelId = null) { Verify.NotNull(services); - Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.NotNull(credentials); @@ -241,6 +239,218 @@ public static IServiceCollection AddAzureOpenAIChatCompletion( #endregion + #region Text Embedding + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The HttpClient to use with this service. + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IKernelBuilder AddAzureOpenAITextEmbeddingGeneration( + this IKernelBuilder builder, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + string? modelId = null, + HttpClient? httpClient = null, + int? dimensions = null) + { + Verify.NotNull(builder); + + builder.Services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + endpoint, + apiKey, + modelId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService(), + dimensions)); + + return builder; + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + string? modelId = null, + int? dimensions = null) + { + Verify.NotNull(services); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + endpoint, + apiKey, + modelId, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService(), + dimensions)); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The HttpClient to use with this service. + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IKernelBuilder AddAzureOpenAITextEmbeddingGeneration( + this IKernelBuilder builder, + string deploymentName, + string endpoint, + TokenCredential credential, + string? serviceId = null, + string? modelId = null, + HttpClient? httpClient = null, + int? dimensions = null) + { + Verify.NotNull(builder); + Verify.NotNull(credential); + + builder.Services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + endpoint, + credential, + modelId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService(), + dimensions)); + + return builder; + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + string endpoint, + TokenCredential credential, + string? serviceId = null, + string? modelId = null, + int? dimensions = null) + { + Verify.NotNull(services); + Verify.NotNull(credential); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + endpoint, + credential, + modelId, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService(), + dimensions)); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// to use for the service. If null, one must be available in the service provider when this service is resolved. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IKernelBuilder AddAzureOpenAITextEmbeddingGeneration( + this IKernelBuilder builder, + string deploymentName, + AzureOpenAIClient? azureOpenAIClient = null, + string? serviceId = null, + string? modelId = null, + int? dimensions = null) + { + Verify.NotNull(builder); + + builder.Services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + azureOpenAIClient ?? serviceProvider.GetRequiredService(), + modelId, + serviceProvider.GetService(), + dimensions)); + + return builder; + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// to use for the service. If null, one must be available in the service provider when this service is resolved. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. + /// The same instance as . + [Experimental("SKEXP0010")] + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + AzureOpenAIClient? azureOpenAIClient = null, + string? serviceId = null, + string? modelId = null, + int? dimensions = null) + { + Verify.NotNull(services); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGenerationService( + deploymentName, + azureOpenAIClient ?? serviceProvider.GetRequiredService(), + modelId, + serviceProvider.GetService(), + dimensions)); + } + + #endregion + private static AzureOpenAIClient CreateAzureOpenAIClient(string endpoint, AzureKeyCredential credentials, HttpClient? httpClient) => new(new Uri(endpoint), credentials, ClientCore.GetAzureOpenAIClientOptions(httpClient)); diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs index 9119a9005939..31159da6f0a5 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs @@ -79,13 +79,13 @@ public AzureOpenAITextEmbeddingGenerationService( /// Creates a new client. /// /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource - /// Custom for HTTP requests. + /// Custom for HTTP requests. /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// The to use for logging. If null, no logging will be performed. /// The number of dimensions the resulting output embeddings should have. Only supported in "text-embedding-3" and later models. public AzureOpenAITextEmbeddingGenerationService( string deploymentName, - OpenAIClient openAIClient, + AzureOpenAIClient openAIClient, string? modelId = null, ILoggerFactory? loggerFactory = null, int? dimensions = null) diff --git a/dotnet/src/IntegrationTestsV2/Connectors/AzureOpenAI/AzureOpenAITextEmbeddingTests.cs b/dotnet/src/IntegrationTestsV2/Connectors/AzureOpenAI/AzureOpenAITextEmbeddingTests.cs new file mode 100644 index 000000000000..1dfc39670416 --- /dev/null +++ b/dotnet/src/IntegrationTestsV2/Connectors/AzureOpenAI/AzureOpenAITextEmbeddingTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Embeddings; +using SemanticKernel.IntegrationTests.TestSettings; +using Xunit; + +namespace SemanticKernel.IntegrationTestsV2.Connectors.AzureOpenAI; + +public sealed class AzureOpenAITextEmbeddingTests +{ + public AzureOpenAITextEmbeddingTests() + { + var config = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); + Assert.NotNull(config); + this._azureOpenAIConfiguration = config; + } + + [Theory] + [InlineData("test sentence")] + public async Task AzureOpenAITestAsync(string testInputString) + { + // Arrange + var embeddingGenerator = new AzureOpenAITextEmbeddingGenerationService( + this._azureOpenAIConfiguration.DeploymentName, + this._azureOpenAIConfiguration.Endpoint, + this._azureOpenAIConfiguration.ApiKey); + + // Act + var singleResult = await embeddingGenerator.GenerateEmbeddingAsync(testInputString); + var batchResult = await embeddingGenerator.GenerateEmbeddingsAsync([testInputString, testInputString, testInputString]); + + // Assert + Assert.Equal(AdaVectorLength, singleResult.Length); + Assert.Equal(3, batchResult.Count); + } + + [Theory] + [InlineData(null, 3072)] + [InlineData(1024, 1024)] + public async Task AzureOpenAIWithDimensionsAsync(int? dimensions, int expectedVectorLength) + { + // Arrange + const string TestInputString = "test sentence"; + + var embeddingGenerator = new AzureOpenAITextEmbeddingGenerationService( + "text-embedding-3-large", + this._azureOpenAIConfiguration.Endpoint, + this._azureOpenAIConfiguration.ApiKey, + dimensions: dimensions); + + // Act + var result = await embeddingGenerator.GenerateEmbeddingAsync(TestInputString); + + // Assert + Assert.Equal(expectedVectorLength, result.Length); + } + + private readonly AzureOpenAIConfiguration _azureOpenAIConfiguration; + + private const int AdaVectorLength = 1536; + + private readonly IConfigurationRoot _configuration = new ConfigurationBuilder() + .AddJsonFile(path: "testsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); +} diff --git a/dotnet/src/InternalUtilities/test/AssertExtensions.cs b/dotnet/src/InternalUtilities/test/AssertExtensions.cs index cf201d169366..4caf63589cbc 100644 --- a/dotnet/src/InternalUtilities/test/AssertExtensions.cs +++ b/dotnet/src/InternalUtilities/test/AssertExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using Xunit; +using Assert = Xunit.Assert; namespace SemanticKernel.UnitTests;