diff --git a/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs b/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs new file mode 100644 index 0000000..ed93ce5 --- /dev/null +++ b/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs @@ -0,0 +1,131 @@ +#pragma warning disable MEAI001 +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GenerativeAI; +using GenerativeAI.Core; +using GenerativeAI.Types; +using GenerativeAI.Microsoft.Extensions; +using Microsoft.Extensions.AI; + +namespace GenerativeAI.Microsoft; + +/// +/// Implements Microsoft.Extensions.AI.IImageGenerator using the Google_GenerativeAI SDK by +/// creating a GenerateContentRequest that requests image modality and forwarding it to +/// . +/// +public sealed class GenerativeAIImageGenerator : IImageGenerator +{ + /// + /// Underlying GenerativeModel instance. + /// + public GenerativeModel model { get; } + + /// + /// Creates a new instance using an API key and optional model name. + /// + public GenerativeAIImageGenerator(string apiKey, string modelName = GoogleAIModels.Gemini2FlashPreviewImageGeneration) + { + model = new GenerativeModel(apiKey, modelName); + } + + /// + /// Creates a new instance using a platform adapter and optional model name. + /// + public GenerativeAIImageGenerator(IPlatformAdapter adapter, string modelName = GoogleAIModels.Gemini2FlashPreviewImageGeneration) + { + model = new GenerativeModel(adapter, modelName); + } + + /// + public void Dispose() + { + } + + /// + public object? GetService(Type serviceType, object? serviceKey = null) + { + if (serviceKey == null && serviceType?.IsInstanceOfType(this) == true) + return this; + return null; + } + + /// + public async Task GenerateAsync(ImageGenerationRequest request, + ImageGenerationOptions? options = null, CancellationToken cancellationToken = default) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(request); +#else + if (request == null) throw new ArgumentNullException(nameof(request)); +#endif + + var genRequest = ToGenerateContentRequest(request, options); + var resp = await model.GenerateContentAsync(genRequest, cancellationToken).ConfigureAwait(false); + return ToImageGenerationResponse(resp); + } + + // Convert the Microsoft request/options into a model-specific GenerateContentRequest + private GenerateContentRequest ToGenerateContentRequest(ImageGenerationRequest request, ImageGenerationOptions? options) + { + List parts = []; + // Add prompt text (if any) + if (!string.IsNullOrEmpty(request.Prompt)) + { + parts.Add(new(request.Prompt!)); + } + + // If original images provided (image edit scenario), add them as parts + if (request.OriginalImages != null) + { + foreach (var aiContent in request.OriginalImages) + { + parts.Add(aiContent.ToPart()!); + } + } + + // Configure generation to request images + GenerationConfig generationConfig = options?.RawRepresentationFactory?.Invoke(this) as GenerationConfig ?? new(); + generationConfig.CandidateCount = options?.Count ?? 1; + + // We must request both text and image modalities to get images back + generationConfig.ResponseModalities = new List { Modality.TEXT, Modality.IMAGE }; + + if (options != null) + { + if (!string.IsNullOrEmpty(options.MediaType)) + generationConfig.ResponseMimeType = options.MediaType; + + // Map requested image size (basic heuristic) + if (options.ImageSize.HasValue) + { + var sz = options.ImageSize.Value; + if (sz.Width >= 1024 || sz.Height >= 1024) + generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_HIGH; + else if (sz.Width >= 512 || sz.Height >= 512) + generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_MEDIUM; + else + generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_LOW; + } + } + + return new GenerateContentRequest() + { + GenerationConfig = generationConfig, + Contents = [new() { Parts = parts }] + }; + } + + + // Convert the model response to ImageGenerationResponse + private static ImageGenerationResponse ToImageGenerationResponse(GenerateContentResponse? resp) + { + var aiContents = resp?.Candidates?.FirstOrDefault()?.Content?.Parts.ToAiContents(); + return new ImageGenerationResponse(aiContents) { RawRepresentation = resp }; + } +} +#pragma warning restore MEAI001 diff --git a/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs b/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs new file mode 100644 index 0000000..d260a78 --- /dev/null +++ b/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs @@ -0,0 +1,163 @@ +#pragma warning disable MEAI001 +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GenerativeAI; +using GenerativeAI.Core; +using GenerativeAI.Types; +using GenerativeAI.Clients; +using GenerativeAI.Microsoft.Extensions; +using Microsoft.Extensions.AI; + +namespace GenerativeAI.Microsoft; + +/// +/// Implements Microsoft.Extensions.AI.IImageGenerator by creating an ImagenModel via GenAI.CreateImageModel +/// and calling . +/// +public sealed class GenerativeAIImagenGenerator : IImageGenerator +{ + /// + /// Underlying ImagenModel instance created from the provided GenAI factory. + /// + public ImagenModel model { get; } + + /// + /// Creates a new instance using an API key and optional model name. + /// + public GenerativeAIImagenGenerator(string apiKey, string modelName = GoogleAIModels.Imagen.Imagen3Generate002): + this(new GoogleAi(apiKey), modelName) + { } + + + /// + /// Creates a new instance using an existing factory and optional model name. + /// + public GenerativeAIImagenGenerator(GenAI genai, string modelName = GoogleAIModels.Imagen.Imagen3Generate002) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(genai); +#else + if (genai == null) throw new ArgumentNullException(nameof(genai)); +#endif + model = genai.CreateImageModel(modelName); + } + + /// + public void Dispose() + { } + + /// + public object? GetService(Type serviceType, object? serviceKey = null) + { + if (serviceKey == null && serviceType?.IsInstanceOfType(this) == true) + return this; + return null; + } + + /// + public async Task GenerateAsync(ImageGenerationRequest request, + ImageGenerationOptions? options = null, CancellationToken cancellationToken = default) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(request); +#else + if (request == null) throw new ArgumentNullException(nameof(request)); +#endif + + var imgRequest = ToGenerateImageRequest(request, options); + var resp = await model.GenerateImagesAsync(imgRequest, cancellationToken).ConfigureAwait(false); + return ToImageGenerationResponse(resp); + } + + // Convert Microsoft ImageGenerationRequest + options to a GenerateImageRequest + private GenerateImageRequest ToGenerateImageRequest(ImageGenerationRequest request, ImageGenerationOptions? options) + { + var imgRequest = new GenerateImageRequest(); + var instances = new List(); + + if (request.OriginalImages != null && request.OriginalImages.Any()) + { + instances.AddRange(request.OriginalImages.Select(content => new ImageGenerationInstance + { + Prompt = request.Prompt, + Image = ConvertAiContentToImageSource(content) + })); + } + else + { + instances.Add(new ImageGenerationInstance { Prompt = request.Prompt }); + } + + ImageGenerationParameters parameters = options?.RawRepresentationFactory?.Invoke(this) as ImageGenerationParameters ?? new(); + parameters.SampleCount = options?.Count ?? 1; + + if (options != null) + { + if (!string.IsNullOrEmpty(options.MediaType)) + { + parameters.OutputOptions = new OutputOptions { MimeType = options.MediaType }; + } + + if (options.ImageSize.HasValue) + { + var sz = options.ImageSize.Value; + parameters.AspectRatio = $"{sz.Width}:{sz.Height}"; + + } + } + + return new GenerateImageRequest + { + Instances = instances, + Parameters = parameters + }; + } + + // Convert model response to Microsoft ImageGenerationResponse + private static ImageGenerationResponse ToImageGenerationResponse(GenerateImageResponse? resp) + { + var contents = new List(); + if (resp?.Predictions != null) + { + foreach (var pred in resp.Predictions) + { + if (!string.IsNullOrEmpty(pred.BytesBase64Encoded)) + { + var data = Convert.FromBase64String(pred.BytesBase64Encoded); + contents.Add(new DataContent(data, pred.MimeType ?? "image/png")); + } + } + } + + return new ImageGenerationResponse(contents) { RawRepresentation = resp }; + } + + private static ImageSource? ConvertAiContentToImageSource(AIContent content) + { + if (content == null) return null; + + if (content is DataContent dc) + { + return new ImageSource { BytesBase64Encoded = Convert.ToBase64String(dc.Data.ToArray()) }; + } + + if (content is UriContent uc) + { + var uriVal = uc.Uri?.ToString(); + + // Only treat known GCS URIs as storage references for Imagen API. + if (uriVal?.StartsWith("gs://", StringComparison.OrdinalIgnoreCase) == true || + uriVal?.IndexOf("storage.googleapis.com", StringComparison.OrdinalIgnoreCase) >= 0) + { + return new ImageSource { GcsUri = uriVal }; + } + } + + return null; + } +} +#pragma warning restore MEAI001 diff --git a/src/GenerativeAI.Microsoft/README.md b/src/GenerativeAI.Microsoft/README.md index 5fcae61..d364bc1 100644 --- a/src/GenerativeAI.Microsoft/README.md +++ b/src/GenerativeAI.Microsoft/README.md @@ -85,6 +85,66 @@ public class MyChatService } ``` +### 4. Using IImageClient + +Image client can also be used from a service as above. Here's a sample that shows it's capabilities. + +```C# +using System.Diagnostics; +using GenerativeAI.Microsoft; +using Microsoft.Extensions.AI; + +#pragma warning disable MEAI001 +// ImageGen creates high quality initial images +IImageGenerator imageGenerator = new GenerativeAIImagenGenerator( + Environment.GetEnvironmentVariable("GOOGLE_API_KEY"), + "imagen-4.0-fast-generate-001"); + +var response = await imageGenerator.GenerateImagesAsync("A clown fish with orange and black-bordered white stripes."); +var img1 = GetImageContent(response); +SaveImage(img1, "i1.png"); +ShowImage("i1.png"); + +response = await imageGenerator.GenerateImagesAsync("A blue tang fish, blue and black with yellow tipped fin and tail."); +var img2 = GetImageContent(response); +SaveImage(img2, "i2.png"); +ShowImage("i2.png"); + +// Imagen cannot edit, but we can use the gemini model for that. +IImageGenerator imageGeneratorEdit = new GenerativeAIImageGenerator( + Environment.GetEnvironmentVariable("GOOGLE_API_KEY"), + "gemini-2.5-flash-image-preview"); +var request = new ImageGenerationRequest() +{ + Prompt = "Combine the two images into a single scene.", + OriginalImages = new[] { img1, img2 } +}; +response = await imageGeneratorEdit.GenerateAsync(request); +var scene = GetImageContent(response); +SaveImage(scene, "scene.png"); +ShowImage("scene.png"); + +response = await imageGeneratorEdit.EditImageAsync(scene, "Change the setting to a fish tank."); +var edit = GetImageContent(response); +SaveImage(edit, "edit.png"); +ShowImage("edit.png"); + +DataContent GetImageContent(ImageGenerationResponse response) => + response.Contents.OfType().Single(); + +void SaveImage(DataContent content, string fileName) => + File.WriteAllBytes(fileName, content.Data.Span); + +void ShowImage(string fileName) +{ + Process.Start(new ProcessStartInfo + { + FileName = fileName, + UseShellExecute = true + }); +} +``` + ## Dependencies - [Google_GenerativeAI](https://github.com/Google_GenerativeAI) (Unofficial C# Google Generative AI SDK) diff --git a/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs new file mode 100644 index 0000000..36d68ad --- /dev/null +++ b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs @@ -0,0 +1,256 @@ +#pragma warning disable MEAI001 +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using GenerativeAI.Core; +using GenerativeAI.Microsoft; +using GenerativeAI.Microsoft.Extensions; +using GenerativeAI.Tests; +using GenerativeAI.Tests.Base; +using Microsoft.Extensions.AI; +using Shouldly; +using Xunit; + +namespace GenerativeAI.Tests.Microsoft; + +#pragma warning disable MEAI001 + +/// +/// Tests for . +/// Demonstrates a style similar to other Generative AI tests, +/// using xUnit and Shouldly for assertions. +/// +[TestCaseOrderer( + typeof(TestPriorityAttribute))] +public class Microsoft_ImageGenerator_Tests : TestBase +{ + private const string DefaultTestModelName = GoogleAIModels.DefaultGeminiModel; + + public Microsoft_ImageGenerator_Tests(ITestOutputHelper helper) : base(helper) + { + } + + #region Helper Methods + + /// + /// Creates a minimal mock or fake platform adapter for testing. + /// This can be replaced by a more feature-complete mock if needed. + /// + private IPlatformAdapter CreateTestPlatformAdapter() + { + return new GoogleAIPlatformAdapter("test_api_key"); + } + + #endregion + + #region Constructor Tests + + [Fact, TestPriority(1)] + public void ShouldCreateWithBasicConstructor() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + + // Act + var generator = new GenerativeAIImageGenerator(adapter); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + generator.model.Model.ShouldBe(DefaultTestModelName); + Console.WriteLine("GenerativeAIImageGenerator created successfully with the basic constructor."); + } + + [Fact, TestPriority(2)] + public void ShouldCreateWithCustomModelName() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var customModel = "my-custom-model"; + + // Act + var generator = new GenerativeAIImageGenerator(adapter, customModel); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + generator.model.Model.ShouldBe(customModel); + Console.WriteLine($"GenerativeAIImageGenerator created with custom model: {customModel}"); + } + + [Fact, TestPriority(3)] + public void ShouldCreateWithApiKeyConstructor() + { + // Arrange + const string testApiKey = "test_api_key"; + + // Act + var generator = new GenerativeAIImageGenerator(testApiKey); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + generator.model.Model.ShouldBe(DefaultTestModelName); + Console.WriteLine("GenerativeAIImageGenerator created successfully with API key constructor."); + } + + [Fact, TestPriority(4)] + public void ShouldCreateWithApiKeyAndCustomModel() + { + // Arrange + const string testApiKey = "test_api_key"; + const string customModel = "custom-gemini-model"; + + // Act + var generator = new GenerativeAIImageGenerator(testApiKey, customModel); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + generator.model.Model.ShouldBe(customModel); + Console.WriteLine($"GenerativeAIImageGenerator created with API key and custom model: {customModel}"); + } + + #endregion + + #region GenerateAsync Tests + + [Fact, TestPriority(5)] + public async Task ShouldThrowArgumentNullExceptionWhenRequestIsNull() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act & Assert + await Should.ThrowAsync(async () => + { + await generator.GenerateAsync(null!, cancellationToken: TestContext.Current.CancellationToken); + }); + Console.WriteLine("GenerateAsync threw ArgumentNullException as expected when request was null."); + } + + [Fact, TestPriority(6)] + public async Task ShouldReturnImageGenerationResponseOnValidInput() + { + Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage); + + // Arrange + var adapter = GetTestGooglePlatform(); + var generator = new GenerativeAIImageGenerator(adapter); + + var request = new ImageGenerationRequest("Generate an image of a beautiful sunset over mountains"); + var options = new ImageGenerationOptions + { + Count = 1, + MediaType = "image/png" + }; + + // Act + var result = await generator.GenerateAsync(request, options, cancellationToken: TestContext.Current.CancellationToken); + + // Assert + result.ShouldNotBeNull(); + Console.WriteLine("GenerateAsync returned a valid result."); + + // Check if the result has content - either check the raw response or the constructed response + if (result.RawRepresentation != null) + { + Console.WriteLine("Raw representation is available."); + } + } + + #endregion + + #region GetService Tests + + [Fact, TestPriority(7)] + public void ShouldReturnSelfFromGetServiceIfTypeMatches() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act + var service = generator.GetService(typeof(GenerativeAIImageGenerator)); + + // Assert + service.ShouldNotBeNull(); + service.ShouldBeOfType(); + service.ShouldBe(generator); + Console.WriteLine("GetService returned the correct instance when serviceType matches the generator type."); + } + + [Fact, TestPriority(8)] + public void ShouldReturnSelfFromGetServiceForIImageGenerator() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act + var service = generator.GetService(typeof(IImageGenerator)); + + // Assert + service.ShouldNotBeNull(); + service.ShouldBeOfType(); + service.ShouldBe(generator); + Console.WriteLine("GetService returned the correct instance when serviceType is IImageGenerator."); + } + + [Fact, TestPriority(9)] + public void ShouldReturnNullFromGetServiceIfTypeDoesNotMatch() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act + var service = generator.GetService(typeof(string)); + + // Assert + service.ShouldBeNull(); + Console.WriteLine("GetService returned null when the requested serviceType did not match."); + } + + [Fact, TestPriority(10)] + public void ShouldReturnNullFromGetServiceWithServiceKey() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act + var service = generator.GetService(typeof(GenerativeAIImageGenerator), "some_key"); + + // Assert + service.ShouldBeNull(); + Console.WriteLine("GetService returned null when serviceKey was provided."); + } + + #endregion + + #region Dispose Tests + + [Fact, TestPriority(11)] + public void ShouldDisposeWithoutException() + { + // Arrange + var adapter = CreateTestPlatformAdapter(); + var generator = new GenerativeAIImageGenerator(adapter); + + // Act & Assert + Should.NotThrow(() => generator.Dispose()); + Console.WriteLine("Dispose completed without throwing an exception."); + } + + #endregion + + protected override IPlatformAdapter GetTestGooglePlatform() + { + Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage); + return new GoogleAIPlatformAdapter(EnvironmentVariables.GOOGLE_API_KEY); + } +} +#pragma warning restore MEAI001 diff --git a/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs new file mode 100644 index 0000000..eecf7b4 --- /dev/null +++ b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs @@ -0,0 +1,288 @@ +#pragma warning disable MEAI001 +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using GenerativeAI; +using GenerativeAI.Core; +using GenerativeAI.Microsoft; +using GenerativeAI.Microsoft.Extensions; +using GenerativeAI.Tests; +using GenerativeAI.Tests.Base; +using Microsoft.Extensions.AI; +using Shouldly; +using Xunit; + +namespace GenerativeAI.Tests.Microsoft; + +#pragma warning disable MEAI001 + +/// +/// Tests for . +/// Demonstrates a style similar to other Generative AI tests, +/// using xUnit and Shouldly for assertions. +/// +[TestCaseOrderer( + typeof(TestPriorityAttribute))] +public class Microsoft_ImagenGenerator_Tests : TestBase +{ + private const string DefaultTestModelName = GoogleAIModels.Imagen.Imagen3FastGenerate001; + + public Microsoft_ImagenGenerator_Tests(ITestOutputHelper helper) : base(helper) + { + } + + #region Helper Methods + + /// + /// Creates a minimal mock GoogleAi instance for testing. + /// + private GoogleAi CreateTestGoogleAi() + { + return new GoogleAi("test_api_key"); + } + + #endregion + + #region Constructor Tests + + [Fact, TestPriority(1)] + public void ShouldCreateWithApiKeyConstructor() + { + // Arrange + const string testApiKey = "test_api_key"; + + // Act + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + Console.WriteLine("GenerativeAIImagenGenerator created successfully with API key constructor."); + } + + [Fact, TestPriority(2)] + public void ShouldCreateWithApiKeyAndCustomModel() + { + // Arrange + const string testApiKey = "test_api_key"; + const string customModel = "custom-imagen-model"; + + // Act + var generator = new GenerativeAIImagenGenerator(testApiKey, customModel); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + Console.WriteLine($"GenerativeAIImagenGenerator created with API key and custom model: {customModel}"); + } + + [Fact, TestPriority(3)] + public void ShouldCreateWithGenAIConstructor() + { + // Arrange + var genAi = CreateTestGoogleAi(); + + // Act + var generator = new GenerativeAIImagenGenerator(genAi); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + Console.WriteLine("GenerativeAIImagenGenerator created successfully with GenAI constructor."); + } + + [Fact, TestPriority(4)] + public void ShouldCreateWithGenAIAndCustomModel() + { + // Arrange + var genAi = CreateTestGoogleAi(); + const string customModel = "custom-imagen-model"; + + // Act + var generator = new GenerativeAIImagenGenerator(genAi, customModel); + + // Assert + generator.ShouldNotBeNull(); + generator.model.ShouldNotBeNull(); + Console.WriteLine($"GenerativeAIImagenGenerator created with GenAI and custom model: {customModel}"); + } + + [Fact, TestPriority(5)] + public void ShouldThrowArgumentNullExceptionWhenGenAIIsNull() + { + // Arrange & Act & Assert + Should.Throw(() => + { + var generator = new GenerativeAIImagenGenerator((GenAI)null!); + }); + Console.WriteLine("Constructor threw ArgumentNullException as expected when GenAI was null."); + } + + #endregion + + #region GenerateAsync Tests + + [Fact, TestPriority(6)] + public async Task ShouldThrowArgumentNullExceptionWhenRequestIsNull() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act & Assert + await Should.ThrowAsync(async () => + { + await generator.GenerateAsync(null!, cancellationToken: TestContext.Current.CancellationToken); + }); + Console.WriteLine("GenerateAsync threw ArgumentNullException as expected when request was null."); + } + + [Fact, TestPriority(7)] + public async Task ShouldReturnImageGenerationResponseOnValidInput() + { + Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage); + + // Arrange + var genAi = new GoogleAi(EnvironmentVariables.GOOGLE_API_KEY); + var generator = new GenerativeAIImagenGenerator(genAi); + + var request = new ImageGenerationRequest("A beautiful landscape with mountains and a lake"); + var options = new ImageGenerationOptions + { + Count = 1, + MediaType = "image/png" + }; + + // Act + var result = await generator.GenerateAsync(request, options, cancellationToken: TestContext.Current.CancellationToken); + + // Assert + result.ShouldNotBeNull(); + Console.WriteLine("GenerateAsync returned a valid result."); + + // Check if the result has content - either check the raw response or the constructed response + if (result.RawRepresentation != null) + { + Console.WriteLine("Raw representation is available."); + } + } + + [Fact, TestPriority(8)] + public async Task ShouldHandleImageGenerationWithoutOptions() + { + Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage); + + // Arrange + var genAi = new GoogleAi(EnvironmentVariables.GOOGLE_API_KEY); + var generator = new GenerativeAIImagenGenerator(genAi); + + var request = new ImageGenerationRequest("A simple drawing of a cat"); + + // Act + var result = await generator.GenerateAsync(request, null, cancellationToken: TestContext.Current.CancellationToken); + + // Assert + result.ShouldNotBeNull(); + Console.WriteLine("GenerateAsync without options returned a valid result."); + + // Check if the result has content - either check the raw response or the constructed response + if (result.RawRepresentation != null) + { + Console.WriteLine("Raw representation is available."); + } + } + + #endregion + + #region GetService Tests + + [Fact, TestPriority(9)] + public void ShouldReturnSelfFromGetServiceIfTypeMatches() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act + var service = generator.GetService(typeof(GenerativeAIImagenGenerator)); + + // Assert + service.ShouldNotBeNull(); + service.ShouldBeOfType(); + service.ShouldBe(generator); + Console.WriteLine("GetService returned the correct instance when serviceType matches the generator type."); + } + + [Fact, TestPriority(10)] + public void ShouldReturnSelfFromGetServiceForIImageGenerator() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act + var service = generator.GetService(typeof(IImageGenerator)); + + // Assert + service.ShouldNotBeNull(); + service.ShouldBeOfType(); + service.ShouldBe(generator); + Console.WriteLine("GetService returned the correct instance when serviceType is IImageGenerator."); + } + + [Fact, TestPriority(11)] + public void ShouldReturnNullFromGetServiceIfTypeDoesNotMatch() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act + var service = generator.GetService(typeof(string)); + + // Assert + service.ShouldBeNull(); + Console.WriteLine("GetService returned null when the requested serviceType did not match."); + } + + [Fact, TestPriority(12)] + public void ShouldReturnNullFromGetServiceWithServiceKey() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act + var service = generator.GetService(typeof(GenerativeAIImagenGenerator), "some_key"); + + // Assert + service.ShouldBeNull(); + Console.WriteLine("GetService returned null when serviceKey was provided."); + } + + #endregion + + #region Dispose Tests + + [Fact, TestPriority(13)] + public void ShouldDisposeWithoutException() + { + // Arrange + const string testApiKey = "test_api_key"; + var generator = new GenerativeAIImagenGenerator(testApiKey); + + // Act & Assert + Should.NotThrow(() => generator.Dispose()); + Console.WriteLine("Dispose completed without throwing an exception."); + } + + #endregion + + protected override IPlatformAdapter GetTestGooglePlatform() + { + Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage); + return new GoogleAIPlatformAdapter(EnvironmentVariables.GOOGLE_API_KEY); + } +} +#pragma warning restore MEAI001