From 39a32c9603cacc08a6f967f64611e9408303555b Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:24:45 +0000 Subject: [PATCH] .Net: Migrate from deprecated DALL-E models to gpt-image-1 - Update default image model from dall-e-2 to gpt-image-1 in ClientCore and OpenAITextToImageService - Handle base64 responses in GenerateImageAsync (gpt-image-1 returns base64 only, no URLs) - Add Medium, Low, and Auto quality options to GetGeneratedImageQuality - Update AzureOpenAI integration tests to use api-version 2025-04-01-preview - Remove URL-specific assertions since gpt-image-1 returns base64 only - Update OpenAI integration tests to reference gpt-image-1 - Update testsettings.json defaults from Dalle3/dall-e-2 to gpt-image-1 - Update OpenAITextToImageExecutionSettings XML docs for gpt-image-1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Core/ClientCore.TextToImage.cs | 20 ++++++++++++++---- .../Services/OpenAITextToImageService.cs | 2 +- .../OpenAITextToImageExecutionSettings.cs | 9 ++++---- .../AzureOpenAITextToImageTests.cs | 21 +++++++++++-------- .../OpenAI/OpenAITextToImageTests.cs | 14 ++++++------- dotnet/src/IntegrationTests/testsettings.json | 8 +++---- 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.TextToImage.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.TextToImage.cs index 7d09f0805bb1..33b18f30def2 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.TextToImage.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.TextToImage.cs @@ -37,17 +37,26 @@ internal async Task GenerateImageAsync( var imageOptions = new ImageGenerationOptions() { Size = size, - ResponseFormat = GeneratedImageFormat.Uri }; // The model is not required by the OpenAI API and defaults to the DALL-E 2 server-side - https://platform.openai.com/docs/api-reference/images/create#images-create-model. - // However, considering that the model is required by the OpenAI SDK and the ModelId property is optional, it defaults to DALL-E 2 in the line below. - targetModel = string.IsNullOrEmpty(targetModel) ? "dall-e-2" : targetModel!; + // However, considering that the model is required by the OpenAI SDK and the ModelId property is optional, it defaults to gpt-image-1 in the line below. + targetModel = string.IsNullOrEmpty(targetModel) ? "gpt-image-1" : targetModel!; ClientResult response = await RunRequestAsync(() => this.Client!.GetImageClient(targetModel).GenerateImageAsync(prompt, imageOptions, cancellationToken)).ConfigureAwait(false); var generatedImage = response.Value; - return generatedImage.ImageUri?.ToString() ?? throw new KernelException("The generated image is not in url format"); + if (generatedImage.ImageUri is not null) + { + return generatedImage.ImageUri.ToString(); + } + + if (generatedImage.ImageBytes is not null) + { + return $"data:image/png;base64,{Convert.ToBase64String(generatedImage.ImageBytes.ToArray())}"; + } + + throw new KernelException("The generated image has no valid content."); } /// @@ -113,6 +122,9 @@ internal async Task> GetImageContentsAsync( { "STANDARD" => GeneratedImageQuality.Standard, "HIGH" or "HD" => GeneratedImageQuality.High, + "MEDIUM" => GeneratedImageQuality.Medium, + "LOW" => GeneratedImageQuality.Low, + "AUTO" => GeneratedImageQuality.Auto, _ => throw new NotSupportedException($"The provided quality '{quality}' is not supported.") }; } diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Services/OpenAITextToImageService.cs b/dotnet/src/Connectors/Connectors.OpenAI/Services/OpenAITextToImageService.cs index 4967d87228ff..9e3dce140102 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Services/OpenAITextToImageService.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Services/OpenAITextToImageService.cs @@ -36,7 +36,7 @@ public OpenAITextToImageService( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) { - this._client = new(modelId ?? "dall-e-2", apiKey, organization, null, httpClient, loggerFactory?.CreateLogger(this.GetType())); + this._client = new(modelId ?? "gpt-image-1", apiKey, organization, null, httpClient, loggerFactory?.CreateLogger(this.GetType())); } /// diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Settings/OpenAITextToImageExecutionSettings.cs b/dotnet/src/Connectors/Connectors.OpenAI/Settings/OpenAITextToImageExecutionSettings.cs index 13e8a6b74b1f..92a5d6d2c778 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Settings/OpenAITextToImageExecutionSettings.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Settings/OpenAITextToImageExecutionSettings.cs @@ -19,8 +19,7 @@ public sealed class OpenAITextToImageExecutionSettings : PromptExecutionSettings /// /// /// - /// Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2 model. - /// Must be one of 1024x1024, 1792x1024, 1024x1792 for dall-e-3 model. + /// Must be one of 1024x1024, 1536x1024, 1024x1536, auto for gpt-image-1 model. /// /// public (int Width, int Height)? Size @@ -38,12 +37,12 @@ public sealed class OpenAITextToImageExecutionSettings : PromptExecutionSettings /// The quality of the image that will be generated. /// /// - /// Must be one of standard or hd or high. /// /// standard: creates images with standard quality. This is the default. /// hd OR high: creates images with finer details and greater consistency. + /// medium: creates images with medium quality (supported by gpt-image-1). + /// low: creates images with lower quality for faster generation (supported by gpt-image-1). /// - /// This param is only supported for dall-e-3 model. /// [JsonPropertyName("quality")] public string? Quality @@ -66,7 +65,7 @@ public string? Quality /// vivid: causes the model to lean towards generating hyper-real and dramatic images. /// natural: causes the model to produce more natural, less hyper-real looking images. /// - /// This param is only supported for dall-e-3 model. + /// This param is not supported for gpt-image-1 model. /// [JsonPropertyName("style")] public string? Style diff --git a/dotnet/src/IntegrationTests/Connectors/AzureOpenAI/AzureOpenAITextToImageTests.cs b/dotnet/src/IntegrationTests/Connectors/AzureOpenAI/AzureOpenAITextToImageTests.cs index d2e945e32531..158279013447 100644 --- a/dotnet/src/IntegrationTests/Connectors/AzureOpenAI/AzureOpenAITextToImageTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/AzureOpenAI/AzureOpenAITextToImageTests.cs @@ -25,7 +25,7 @@ public sealed class AzureOpenAITextToImageTests .Build(); [Fact(Skip = "This test is for manual verification.")] - public async Task ItCanReturnImageUrlAsync() + public async Task ItCanReturnImageContentAsync() { // Arrange AzureOpenAIConfiguration? configuration = this._configuration.GetSection("AzureOpenAITextToImage").Get(); @@ -35,7 +35,8 @@ public async Task ItCanReturnImageUrlAsync() .AddAzureOpenAITextToImage( deploymentName: configuration.DeploymentName, endpoint: configuration.Endpoint, - credentials: new AzureCliCredential()) + credentials: new AzureCliCredential(), + apiVersion: "2025-04-01-preview") .Build(); var service = kernel.GetRequiredService(); @@ -45,11 +46,11 @@ public async Task ItCanReturnImageUrlAsync() // Assert Assert.NotNull(result); - Assert.StartsWith("https://", result); + Assert.NotEmpty(result); } [Fact] - public async Task GetImageContentsCanReturnImageUrlAsync() + public async Task GetImageContentsCanReturnImageAsync() { // Arrange AzureOpenAIConfiguration? configuration = this._configuration.GetSection("AzureOpenAITextToImage").Get(); @@ -59,7 +60,8 @@ public async Task GetImageContentsCanReturnImageUrlAsync() .AddAzureOpenAITextToImage( deploymentName: configuration.DeploymentName, endpoint: configuration.Endpoint, - credentials: new AzureCliCredential()) + credentials: new AzureCliCredential(), + apiVersion: "2025-04-01-preview") .Build(); var service = kernel.GetRequiredService(); @@ -70,8 +72,8 @@ public async Task GetImageContentsCanReturnImageUrlAsync() // Assert Assert.NotNull(result); Assert.NotEmpty(result); - Assert.NotEmpty(result[0].Uri!.ToString()); - Assert.StartsWith("https://", result[0].Uri!.ToString()); + var imageContent = result[0]; + Assert.True(imageContent.Uri is not null || imageContent.Data is not null, "Image content should have either a URI or binary data."); } [Fact] @@ -89,6 +91,7 @@ public async Task SemanticKernelVersionHeaderIsSentAsync() deploymentName: configuration.DeploymentName, endpoint: configuration.Endpoint, credentials: new AzureCliCredential(), + apiVersion: "2025-04-01-preview", httpClient: httpClient) .Build(); @@ -100,8 +103,8 @@ public async Task SemanticKernelVersionHeaderIsSentAsync() // Assert Assert.NotNull(result); Assert.NotEmpty(result); - Assert.NotEmpty(result[0].Uri!.ToString()); - Assert.StartsWith("https://", result[0].Uri!.ToString()); + var imageContent = result[0]; + Assert.True(imageContent.Uri is not null || imageContent.Data is not null, "Image content should have either a URI or binary data."); Assert.NotNull(httpHeaderHandler.RequestHeaders); Assert.True(httpHeaderHandler.RequestHeaders.TryGetValues("Semantic-Kernel-Version", out var values)); } diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAITextToImageTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAITextToImageTests.cs index 767e186ba3d0..0fca3b4eaa04 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAITextToImageTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAITextToImageTests.cs @@ -22,8 +22,7 @@ public sealed class OpenAITextToImageTests .Build(); [Theory(Skip = "This test is for manual verification.")] - [InlineData("dall-e-2", 512, 512)] - [InlineData("dall-e-3", 1024, 1024)] + [InlineData("gpt-image-1", 1024, 1024)] public async Task OpenAITextToImageByModelTestAsync(string modelId, int width, int height) { // Arrange @@ -45,7 +44,7 @@ public async Task OpenAITextToImageByModelTestAsync(string modelId, int width, i } [Fact(Skip = "Failing in integration tests pipeline with - HTTP 400 (invalid_request_error: billing_hard_limit_reached) error.")] - public async Task OpenAITextToImageUseDallE2ByDefaultAsync() + public async Task OpenAITextToImageUseDefaultModelAsync() { // Arrange OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAITextToImage").Get(); @@ -58,7 +57,7 @@ public async Task OpenAITextToImageUseDallE2ByDefaultAsync() var service = kernel.GetRequiredService(); // Act - var result = await service.GenerateImageAsync("The sun rises in the east and sets in the west.", 256, 256); + var result = await service.GenerateImageAsync("The sun rises in the east and sets in the west.", 1024, 1024); // Assert Assert.NotNull(result); @@ -66,14 +65,14 @@ public async Task OpenAITextToImageUseDallE2ByDefaultAsync() } [Fact(Skip = "Failing in integration tests pipeline with - HTTP 400 (invalid_request_error: billing_hard_limit_reached) error.")] - public async Task OpenAITextToImageDalle3GetImagesTestAsync() + public async Task OpenAITextToImageGetImagesTestAsync() { // Arrange OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAITextToImage").Get(); Assert.NotNull(openAIConfiguration); var kernel = Kernel.CreateBuilder() - .AddOpenAITextToImage(apiKey: openAIConfiguration.ApiKey, modelId: "dall-e-3") + .AddOpenAITextToImage(apiKey: openAIConfiguration.ApiKey, modelId: "gpt-image-1") .Build(); var service = kernel.GetRequiredService(); @@ -84,6 +83,7 @@ public async Task OpenAITextToImageDalle3GetImagesTestAsync() // Assert Assert.NotNull(result); Assert.NotEmpty(result); - Assert.NotEmpty(result[0].Uri!.ToString()); + var imageContent = result[0]; + Assert.True(imageContent.Uri is not null || imageContent.Data is not null, "Image content should have either a URI or binary data."); } } diff --git a/dotnet/src/IntegrationTests/testsettings.json b/dotnet/src/IntegrationTests/testsettings.json index 0d4b0e6f22ca..34cf9d54be04 100644 --- a/dotnet/src/IntegrationTests/testsettings.json +++ b/dotnet/src/IntegrationTests/testsettings.json @@ -53,13 +53,13 @@ "Endpoint": "" }, "OpenAITextToImage": { - "ServiceId": "dall-e-2", - "ModelId": "dall-e-2", + "ServiceId": "gpt-image-1", + "ModelId": "gpt-image-1", "ApiKey": "" }, "AzureOpenAITextToImage": { - "ServiceId": "azure-dalle3", - "DeploymentName": "Dalle3", + "ServiceId": "azure-gpt-image-1", + "DeploymentName": "gpt-image-1", "Endpoint": "" }, "HuggingFace": {