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 056ac691dfa4..a0a695a6719c 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj
@@ -30,14 +30,6 @@
-
-
-
-
-
-
-
-
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextToImageServiceTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextToImageServiceTests.cs
index b0d44113febb..d384df3d627c 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextToImageServiceTests.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAITextToImageServiceTests.cs
@@ -3,17 +3,20 @@
using System;
using System.IO;
using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Threading.Tasks;
+using Azure.AI.OpenAI;
+using Azure.Core;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Services;
using Moq;
-using OpenAI;
namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests.Services;
///
-/// Unit tests for class.
+/// Unit tests for class.
///
public sealed class AzureOpenAITextToImageServiceTests : IDisposable
{
@@ -35,25 +38,21 @@ public AzureOpenAITextToImageServiceTests()
}
[Fact]
- public void ConstructorWorksCorrectly()
+ public void ConstructorsAddRequiredMetadata()
{
- // Arrange & Act
- var sut = new AzureOpenAITextToImageServiceTests("model", "api-key", "organization");
-
- // Assert
- Assert.NotNull(sut);
- Assert.Equal("organization", sut.Attributes[ClientCore.OrganizationKey]);
+ // Case #1
+ var sut = new AzureOpenAITextToImageService("deployment", "https://api-host/", "api-key", "model");
+ Assert.Equal("deployment", sut.Attributes[ClientCore.DeploymentNameKey]);
Assert.Equal("model", sut.Attributes[AIServiceExtensions.ModelIdKey]);
- }
- [Fact]
- public void OpenAIClientConstructorWorksCorrectly()
- {
- // Arrange
- var sut = new AzureOpenAITextToImageServiceTests("model", new OpenAIClient("apikey"));
+ // Case #2
+ sut = new AzureOpenAITextToImageService("deployment", "https://api-hostapi/", new Mock().Object, "model");
+ Assert.Equal("deployment", sut.Attributes[ClientCore.DeploymentNameKey]);
+ Assert.Equal("model", sut.Attributes[AIServiceExtensions.ModelIdKey]);
- // Assert
- Assert.NotNull(sut);
+ // Case #3
+ sut = new AzureOpenAITextToImageService("deployment", new AzureOpenAIClient(new Uri("https://api-host/"), "api-key"), "model");
+ Assert.Equal("deployment", sut.Attributes[ClientCore.DeploymentNameKey]);
Assert.Equal("model", sut.Attributes[AIServiceExtensions.ModelIdKey]);
}
@@ -69,34 +68,20 @@ public void OpenAIClientConstructorWorksCorrectly()
public async Task GenerateImageWorksCorrectlyAsync(int width, int height, string modelId)
{
// Arrange
- var sut = new AzureOpenAITextToImageServiceTests(modelId, "api-key", httpClient: this._httpClient);
- Assert.Equal(modelId, sut.Attributes["ModelId"]);
+ var sut = new AzureOpenAITextToImageService("deployment", "https://api-host", "api-key", modelId, this._httpClient);
// Act
var result = await sut.GenerateImageAsync("description", width, height);
// Assert
Assert.Equal("https://image-url/", result);
- }
- [Fact]
- public async Task GenerateImageDoesLogActionAsync()
- {
- // Assert
- var modelId = "dall-e-2";
- var logger = new Mock>();
- logger.Setup(l => l.IsEnabled(It.IsAny())).Returns(true);
-
- this._mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(logger.Object);
-
- // Arrange
- var sut = new AzureOpenAITextToImageServiceTests(modelId, "apiKey", httpClient: this._httpClient, loggerFactory: this._mockLoggerFactory.Object);
-
- // Act
- await sut.GenerateImageAsync("description", 256, 256);
-
- // Assert
- logger.VerifyLog(LogLevel.Information, $"Action: {nameof(AzureOpenAITextToImageServiceTests.GenerateImageAsync)}. OpenAI Model ID: {modelId}.", Times.Once());
+ var request = JsonSerializer.Deserialize(this._messageHandlerStub.RequestContent); // {"prompt":"description","model":"deployment","response_format":"url","size":"179x124"}
+ Assert.NotNull(request);
+ Assert.Equal("description", request["prompt"]?.ToString());
+ Assert.Equal("deployment", request["model"]?.ToString());
+ Assert.Equal("url", request["response_format"]?.ToString());
+ Assert.Equal($"{width}x{height}", request["size"]?.ToString());
}
public void Dispose()
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-to-image-response.txt b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-to-image-response.txt
new file mode 100644
index 000000000000..1d6f2150b1d5
--- /dev/null
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/TestData/text-to-image-response.txt
@@ -0,0 +1,9 @@
+{
+ "created": 1702575371,
+ "data": [
+ {
+ "revised_prompt": "A photo capturing the diversity of the Earth's landscapes.",
+ "url": "https://image-url/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj
index 720cd1cf71f5..35c31788610d 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj
@@ -21,20 +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/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.ChatCompletion.cs
index e118a4b440e9..14b8c6a38ae0 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.ChatCompletion.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.ChatCompletion.cs
@@ -23,7 +23,7 @@
namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
///
-/// Base class for AI clients that provides common functionality for interacting with OpenAI services.
+/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services.
///
internal partial class ClientCore
{
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.Embeddings.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.Embeddings.cs
index cc7f6ffdda04..6f1aaaf16efc 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.Embeddings.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.Embeddings.cs
@@ -9,7 +9,7 @@
namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
///
-/// Base class for AI clients that provides common functionality for interacting with OpenAI services.
+/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services.
///
internal partial class ClientCore
{
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToImage.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToImage.cs
index b6490a058fb9..46335d6289de 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToImage.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToImage.cs
@@ -8,7 +8,7 @@
namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
///
-/// Base class for AI clients that provides common functionality for interacting with OpenAI services.
+/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services.
///
internal partial class ClientCore
{
@@ -36,9 +36,8 @@ internal async Task GenerateImageAsync(
ResponseFormat = GeneratedImageFormat.Uri
};
- ClientResult response = await RunRequestAsync(() => this.Client.GetImageClient(this.ModelId).GenerateImageAsync(prompt, imageOptions, cancellationToken)).ConfigureAwait(false);
- var generatedImage = response.Value;
+ ClientResult response = await RunRequestAsync(() => this.Client.GetImageClient(this.DeploymentOrModelName).GenerateImageAsync(prompt, imageOptions, cancellationToken)).ConfigureAwait(false);
- return generatedImage.ImageUri?.ToString() ?? throw new KernelException("The generated image is not in url format");
+ return response.Value.ImageUri?.ToString() ?? throw new KernelException("The generated image is not in url format");
}
}
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.cs
index dc45fdaea59d..571d24d95c3b 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.cs
@@ -17,7 +17,7 @@
namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
///
-/// Base class for AI clients that provides common functionality for interacting with OpenAI services.
+/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services.
///
internal partial class ClientCore
{
@@ -135,13 +135,13 @@ internal ClientCore(
/// Gets options to use for an OpenAIClient
/// Custom for HTTP requests.
+ /// Optional API version.
/// An instance of .
- internal static AzureOpenAIClientOptions GetAzureOpenAIClientOptions(HttpClient? httpClient)
+ internal static AzureOpenAIClientOptions GetAzureOpenAIClientOptions(HttpClient? httpClient, AzureOpenAIClientOptions.ServiceVersion? serviceVersion = null)
{
- AzureOpenAIClientOptions options = new()
- {
- ApplicationId = HttpHeaderConstant.Values.UserAgent,
- };
+ AzureOpenAIClientOptions options = serviceVersion is not null
+ ? new(serviceVersion.Value) { ApplicationId = HttpHeaderConstant.Values.UserAgent }
+ : new() { ApplicationId = HttpHeaderConstant.Values.UserAgent };
options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(ClientCore))), PipelinePosition.PerCall);
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs
index 103f1bbcf3ca..8908c9291220 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextEmbeddingGenerationService.cs
@@ -79,18 +79,18 @@ 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,
- AzureOpenAIClient openAIClient,
+ AzureOpenAIClient azureOpenAIClient,
string? modelId = null,
ILoggerFactory? loggerFactory = null,
int? dimensions = null)
{
- this._core = new(deploymentName, openAIClient, loggerFactory?.CreateLogger(typeof(AzureOpenAITextEmbeddingGenerationService)));
+ this._core = new(deploymentName, azureOpenAIClient, loggerFactory?.CreateLogger(typeof(AzureOpenAITextEmbeddingGenerationService)));
this._core.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToImageService.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToImageService.cs
index a48b3177ebee..4b1ebe7aafa5 100644
--- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToImageService.cs
+++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToImageService.cs
@@ -6,14 +6,16 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Azure.AI.OpenAI;
+using Azure.Core;
using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TextToImage;
-using OpenAI;
namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
///
-/// OpenAI text to image service.
+/// Azure OpenAI text to image service.
///
[Experimental("SKEXP0010")]
public class AzureOpenAITextToImageService : ITextToImageService
@@ -26,41 +28,111 @@ public class AzureOpenAITextToImageService : ITextToImageService
///
/// Initializes a new instance of the class.
///
- /// The model to use for image generation.
- /// OpenAI API key, see https://platform.openai.com/account/api-keys
- /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations.
- /// Non-default endpoint for the OpenAI API.
+ /// 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
+ /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource
/// Custom for HTTP requests.
/// The to use for logging. If null, no logging will be performed.
+ /// Azure OpenAI service API version, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart
public AzureOpenAITextToImageService(
- string modelId,
- string? apiKey = null,
- string? organizationId = null,
- Uri? endpoint = null,
+ string deploymentName,
+ string endpoint,
+ string apiKey,
+ string? modelId,
HttpClient? httpClient = null,
- ILoggerFactory? loggerFactory = null)
+ ILoggerFactory? loggerFactory = null,
+ string? apiVersion = null)
{
- this._client = new(modelId, apiKey, organizationId, endpoint, httpClient, loggerFactory?.CreateLogger(this.GetType()));
+ Verify.NotNullOrWhiteSpace(apiKey);
+
+ var connectorEndpoint = !string.IsNullOrWhiteSpace(endpoint) ? endpoint! : httpClient?.BaseAddress?.AbsoluteUri;
+ if (connectorEndpoint is null)
+ {
+ throw new ArgumentException($"The {nameof(httpClient)}.{nameof(HttpClient.BaseAddress)} and {nameof(endpoint)} are both null or empty. Please ensure at least one is provided.");
+ }
+
+ var options = ClientCore.GetAzureOpenAIClientOptions(
+ httpClient,
+ AzureOpenAIClientOptions.ServiceVersion.V2024_05_01_Preview); // DALL-E 3 is supported in the latest API releases - https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#image-generation
+
+ var azureOpenAIClient = new AzureOpenAIClient(new Uri(connectorEndpoint), apiKey, options);
+
+ this._client = new(deploymentName, azureOpenAIClient, loggerFactory?.CreateLogger(this.GetType()));
+
+ if (modelId is not null)
+ {
+ this._client.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
+ }
}
///
/// Initializes a new instance of the class.
///
- /// Model name
- /// Custom for HTTP requests.
+ /// 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.
+ /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource
+ /// Custom for HTTP requests.
/// The to use for logging. If null, no logging will be performed.
+ /// Azure OpenAI service API version, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart
public AzureOpenAITextToImageService(
- string modelId,
- OpenAIClient openAIClient,
+ string deploymentName,
+ string endpoint,
+ TokenCredential credential,
+ string? modelId,
+ HttpClient? httpClient = null,
+ ILoggerFactory? loggerFactory = null,
+ string? apiVersion = null)
+ {
+ Verify.NotNull(credential);
+
+ var connectorEndpoint = !string.IsNullOrWhiteSpace(endpoint) ? endpoint! : httpClient?.BaseAddress?.AbsoluteUri;
+ if (connectorEndpoint is null)
+ {
+ throw new ArgumentException($"The {nameof(httpClient)}.{nameof(HttpClient.BaseAddress)} and {nameof(endpoint)} are both null or empty. Please ensure at least one is provided.");
+ }
+
+ var options = ClientCore.GetAzureOpenAIClientOptions(
+ httpClient,
+ AzureOpenAIClientOptions.ServiceVersion.V2024_05_01_Preview); // DALL-E 3 is supported in the latest API releases - https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#image-generation
+
+ var azureOpenAIClient = new AzureOpenAIClient(new Uri(connectorEndpoint), credential, options);
+
+ this._client = new(deploymentName, azureOpenAIClient, loggerFactory?.CreateLogger(this.GetType()));
+
+ if (modelId is not null)
+ {
+ this._client.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource
+ /// Custom .
+ /// 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.
+ public AzureOpenAITextToImageService(
+ string deploymentName,
+ AzureOpenAIClient azureOpenAIClient,
+ string? modelId,
ILoggerFactory? loggerFactory = null)
{
- this._client = new(modelId, openAIClient, loggerFactory?.CreateLogger(typeof(OpenAITextEmbeddingGenerationService)));
+ Verify.NotNull(azureOpenAIClient);
+
+ this._client = new(deploymentName, azureOpenAIClient, loggerFactory?.CreateLogger(this.GetType()));
+
+ if (modelId is not null)
+ {
+ this._client.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
+ }
}
///
public Task GenerateImageAsync(string description, int width, int height, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
- this._client.LogActionDetails();
return this._client.GenerateImageAsync(description, width, height, cancellationToken);
}
}