Skip to content

Commit

Permalink
.Net: Migrate AzureOpenAITextToImageService to Azure.AI.OpenAI SDK v2 (
Browse files Browse the repository at this point in the history
…#7097)

### Motivation, Context, and Description
This PR adds service collection and kernel builder extension methods
that register the newly added `AzureOpenAITextToImageService`. The
method signatures remain the same as the current extension methods,
except for those that had an `OpenAIClient` parameter, whose name has
been changed from `openAIClient` to `azureOpenAIClient` and whose type
has been changed to `AzureOpenAIClient`. The breaking change is tracked
in this issue: #7053.

Additionally, this PR adds unit tests for the extension methods and
integration tests for the service.
  • Loading branch information
SergeyMenshykh committed Jul 5, 2024
1 parent 965fe63 commit 6d7434f
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.TextGeneration;
using Microsoft.SemanticKernel.TextToImage;

namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Extensions;

Expand Down Expand Up @@ -88,6 +89,40 @@ public void ServiceCollectionAddAzureOpenAITextEmbeddingGenerationAddsValidServi

#endregion

#region Text to image

[Theory]
[InlineData(InitializationType.ApiKey)]
[InlineData(InitializationType.TokenCredential)]
[InlineData(InitializationType.ClientInline)]
[InlineData(InitializationType.ClientInServiceProvider)]
public void ServiceCollectionExtensionsAddAzureOpenAITextToImageService(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<AzureOpenAIClient>(client);

// Act
IServiceCollection collection = type switch
{
InitializationType.ApiKey => builder.Services.AddAzureOpenAITextToImage("deployment-name", "https://endpoint", "api-key"),
InitializationType.TokenCredential => builder.Services.AddAzureOpenAITextToImage("deployment-name", "https://endpoint", credentials),
InitializationType.ClientInline => builder.Services.AddAzureOpenAITextToImage("deployment-name", client),
InitializationType.ClientInServiceProvider => builder.Services.AddAzureOpenAITextToImage("deployment-name"),
_ => builder.Services
};

// Assert
var service = builder.Build().GetRequiredService<ITextToImageService>();

Assert.True(service is AzureOpenAITextToImageService);
}

#endregion

public enum InitializationType
{
ApiKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.TextGeneration;
using Microsoft.SemanticKernel.TextToImage;

namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Extensions;

Expand All @@ -22,8 +23,8 @@ public sealed class AzureOpenAIServiceKernelBuilderExtensionsTests
[Theory]
[InlineData(InitializationType.ApiKey)]
[InlineData(InitializationType.TokenCredential)]
[InlineData(InitializationType.OpenAIClientInline)]
[InlineData(InitializationType.OpenAIClientInServiceProvider)]
[InlineData(InitializationType.ClientInline)]
[InlineData(InitializationType.ClientInServiceProvider)]
public void KernelBuilderAddAzureOpenAIChatCompletionAddsValidService(InitializationType type)
{
// Arrange
Expand All @@ -38,8 +39,8 @@ public void KernelBuilderAddAzureOpenAIChatCompletionAddsValidService(Initializa
{
InitializationType.ApiKey => builder.AddAzureOpenAIChatCompletion("deployment-name", "https://endpoint", "api-key"),
InitializationType.TokenCredential => builder.AddAzureOpenAIChatCompletion("deployment-name", "https://endpoint", credentials),
InitializationType.OpenAIClientInline => builder.AddAzureOpenAIChatCompletion("deployment-name", client),
InitializationType.OpenAIClientInServiceProvider => builder.AddAzureOpenAIChatCompletion("deployment-name"),
InitializationType.ClientInline => builder.AddAzureOpenAIChatCompletion("deployment-name", client),
InitializationType.ClientInServiceProvider => builder.AddAzureOpenAIChatCompletion("deployment-name"),
_ => builder
};

Expand All @@ -58,8 +59,8 @@ public void KernelBuilderAddAzureOpenAIChatCompletionAddsValidService(Initializa
[Theory]
[InlineData(InitializationType.ApiKey)]
[InlineData(InitializationType.TokenCredential)]
[InlineData(InitializationType.OpenAIClientInline)]
[InlineData(InitializationType.OpenAIClientInServiceProvider)]
[InlineData(InitializationType.ClientInline)]
[InlineData(InitializationType.ClientInServiceProvider)]
public void KernelBuilderAddAzureOpenAITextEmbeddingGenerationAddsValidService(InitializationType type)
{
// Arrange
Expand All @@ -74,8 +75,8 @@ public void KernelBuilderAddAzureOpenAITextEmbeddingGenerationAddsValidService(I
{
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"),
InitializationType.ClientInline => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name", client),
InitializationType.ClientInServiceProvider => builder.AddAzureOpenAITextEmbeddingGeneration("deployment-name"),
_ => builder
};

Expand All @@ -88,12 +89,46 @@ public void KernelBuilderAddAzureOpenAITextEmbeddingGenerationAddsValidService(I

#endregion

#region Text to image

[Theory]
[InlineData(InitializationType.ApiKey)]
[InlineData(InitializationType.TokenCredential)]
[InlineData(InitializationType.ClientInline)]
[InlineData(InitializationType.ClientInServiceProvider)]
public void KernelBuilderExtensionsAddAzureOpenAITextToImageService(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<AzureOpenAIClient>(client);

// Act
builder = type switch
{
InitializationType.ApiKey => builder.AddAzureOpenAITextToImage("deployment-name", "https://endpoint", "api-key"),
InitializationType.TokenCredential => builder.AddAzureOpenAITextToImage("deployment-name", "https://endpoint", credentials),
InitializationType.ClientInline => builder.AddAzureOpenAITextToImage("deployment-name", client),
InitializationType.ClientInServiceProvider => builder.AddAzureOpenAITextToImage("deployment-name"),
_ => builder
};

// Assert
var service = builder.Build().GetRequiredService<ITextToImageService>();

Assert.True(service is AzureOpenAITextToImageService);
}

#endregion

public enum InitializationType
{
ApiKey,
TokenCredential,
OpenAIClientInline,
OpenAIClientInServiceProvider,
OpenAIClientEndpoint,
ClientInline,
ClientInServiceProvider,
ClientEndpoint,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.Http;
using Microsoft.SemanticKernel.TextGeneration;
using Microsoft.SemanticKernel.TextToImage;

#pragma warning disable IDE0039 // Use local function

Expand Down Expand Up @@ -248,6 +249,118 @@ public static IKernelBuilder AddAzureOpenAITextEmbeddingGeneration(

#endregion

#region Images

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="builder">The <see cref="IKernelBuilder"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="credentials">Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc.</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <param name="apiVersion">Azure OpenAI API version</param>
/// <returns>The same instance as <paramref name="builder"/>.</returns>
[Experimental("SKEXP0010")]
public static IKernelBuilder AddAzureOpenAITextToImage(
this IKernelBuilder builder,
string deploymentName,
string endpoint,
TokenCredential credentials,
string? modelId = null,
string? serviceId = null,
string? apiVersion = null)
{
Verify.NotNull(builder);
Verify.NotNullOrWhiteSpace(endpoint);
Verify.NotNull(credentials);

builder.Services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
endpoint,
credentials,
modelId,
HttpClientProvider.GetHttpClient(serviceProvider),
serviceProvider.GetService<ILoggerFactory>(),
apiVersion));

return builder;
}

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="builder">The <see cref="IKernelBuilder"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="apiKey">Azure OpenAI API key</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <param name="apiVersion">Azure OpenAI API version</param>
/// <param name="httpClient">The HttpClient to use with this service.</param>
/// <returns>The same instance as <paramref name="builder"/>.</returns>
[Experimental("SKEXP0010")]
public static IKernelBuilder AddAzureOpenAITextToImage(
this IKernelBuilder builder,
string deploymentName,
string endpoint,
string apiKey,
string? modelId = null,
string? serviceId = null,
string? apiVersion = null,
HttpClient? httpClient = null)
{
Verify.NotNull(builder);
Verify.NotNullOrWhiteSpace(endpoint);
Verify.NotNullOrWhiteSpace(apiKey);

builder.Services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
endpoint,
apiKey,
modelId,
HttpClientProvider.GetHttpClient(httpClient, serviceProvider),
serviceProvider.GetService<ILoggerFactory>(),
apiVersion));

return builder;
}

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="builder">The <see cref="IKernelBuilder"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="azureOpenAIClient"><see cref="AzureOpenAIClient"/> to use for the service. If null, one must be available in the service provider when this service is resolved.</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <returns>The same instance as <paramref name="builder"/>.</returns>
[Experimental("SKEXP0010")]
public static IKernelBuilder AddAzureOpenAITextToImage(
this IKernelBuilder builder,
string deploymentName,
AzureOpenAIClient? azureOpenAIClient = null,
string? modelId = null,
string? serviceId = null)
{
Verify.NotNull(builder);
Verify.NotNullOrWhiteSpace(deploymentName);

builder.Services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
azureOpenAIClient ?? serviceProvider.GetRequiredService<AzureOpenAIClient>(),
modelId,
serviceProvider.GetService<ILoggerFactory>()));

return builder;
}

#endregion

private static AzureOpenAIClient CreateAzureOpenAIClient(string endpoint, AzureKeyCredential credentials, HttpClient? httpClient) =>
new(new Uri(endpoint), credentials, ClientCore.GetAzureOpenAIClientOptions(httpClient));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.Http;
using Microsoft.SemanticKernel.TextGeneration;
using Microsoft.SemanticKernel.TextToImage;

#pragma warning disable IDE0039 // Use local function

Expand Down Expand Up @@ -234,6 +235,109 @@ public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration(

#endregion

#region Images

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="credentials">Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc.</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <param name="apiVersion">Azure OpenAI API version</param>
/// <returns>The same instance as <paramref name="services"/>.</returns>
[Experimental("SKEXP0010")]
public static IServiceCollection AddAzureOpenAITextToImage(
this IServiceCollection services,
string deploymentName,
string endpoint,
TokenCredential credentials,
string? modelId = null,
string? serviceId = null,
string? apiVersion = null)
{
Verify.NotNull(services);
Verify.NotNullOrWhiteSpace(endpoint);
Verify.NotNull(credentials);

return services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
endpoint,
credentials,
modelId,
HttpClientProvider.GetHttpClient(serviceProvider),
serviceProvider.GetService<ILoggerFactory>(),
apiVersion));
}

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="apiKey">Azure OpenAI API key</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="maxRetryCount">Maximum number of attempts to retrieve the text to image operation result.</param>
/// <returns>The same instance as <paramref name="services"/>.</returns>
[Experimental("SKEXP0010")]
public static IServiceCollection AddAzureOpenAITextToImage(
this IServiceCollection services,
string deploymentName,
string endpoint,
string apiKey,
string? serviceId = null,
string? modelId = null,
int maxRetryCount = 5)
{
Verify.NotNull(services);
Verify.NotNullOrWhiteSpace(endpoint);
Verify.NotNullOrWhiteSpace(apiKey);

return services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
endpoint,
apiKey,
modelId,
HttpClientProvider.GetHttpClient(serviceProvider),
serviceProvider.GetService<ILoggerFactory>()));
}

/// <summary>
/// Add the Azure OpenAI text-to-image service to the list.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> instance to augment.</param>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="openAIClient"><see cref="AzureOpenAIClient"/> to use for the service. If null, one must be available in the service provider when this service is resolved.</param>
/// <param name="modelId">Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="serviceId">A local identifier for the given AI service</param>
/// <returns>The same instance as <paramref name="services"/>.</returns>
[Experimental("SKEXP0010")]
public static IServiceCollection AddAzureOpenAITextToImage(
this IServiceCollection services,
string deploymentName,
AzureOpenAIClient? openAIClient = null,
string? modelId = null,
string? serviceId = null)
{
Verify.NotNull(services);
Verify.NotNullOrWhiteSpace(deploymentName);

return services.AddKeyedSingleton<ITextToImageService>(serviceId, (serviceProvider, _) =>
new AzureOpenAITextToImageService(
deploymentName,
openAIClient ?? serviceProvider.GetRequiredService<AzureOpenAIClient>(),
modelId,
serviceProvider.GetService<ILoggerFactory>()));
}

#endregion

private static AzureOpenAIClient CreateAzureOpenAIClient(string endpoint, AzureKeyCredential credentials, HttpClient? httpClient) =>
new(new Uri(endpoint), credentials, ClientCore.GetAzureOpenAIClientOptions(httpClient));

Expand Down
Loading

0 comments on commit 6d7434f

Please sign in to comment.