diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index b282f3a37f95..8d247945db00 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -35,6 +35,7 @@ + diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Connectors.Google.UnitTests.csproj b/dotnet/src/Connectors/Connectors.Google.UnitTests/Connectors.Google.UnitTests.csproj index 4a0ae4032f3e..535cb3e6389a 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Connectors.Google.UnitTests.csproj +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Connectors.Google.UnitTests.csproj @@ -25,6 +25,7 @@ + diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/GoogleAIServiceCollectionExtensionsTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/GoogleAIServiceCollectionExtensionsTests.cs index 844a2341bbc9..399ae13c0d13 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/GoogleAIServiceCollectionExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/GoogleAIServiceCollectionExtensionsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using Google.GenAI; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; @@ -113,4 +114,147 @@ public void GoogleAIEmbeddingGeneratorShouldBeRegisteredInServiceCollection() Assert.NotNull(embeddingsGenerationService); Assert.IsType(embeddingsGenerationService); } + +#if NET + [Fact] + public void GoogleGenAIChatClientShouldBeRegisteredInKernelServicesWithApiKey() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act + kernelBuilder.AddGoogleGenAIChatClient("modelId", "apiKey"); + var kernel = kernelBuilder.Build(); + + // Assert + var chatClient = kernel.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void GoogleGenAIChatClientShouldBeRegisteredInServiceCollectionWithApiKey() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddGoogleGenAIChatClient("modelId", "apiKey"); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var chatClient = serviceProvider.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void GoogleVertexAIChatClientShouldBeRegisteredInKernelServices() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act + kernelBuilder.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1"); + + // Assert - just verify no exception during registration + // Resolution requires real credentials, so skip that in unit tests + var kernel = kernelBuilder.Build(); + Assert.NotNull(kernel.Services); + } + + [Fact] + public void GoogleVertexAIChatClientShouldBeRegisteredInServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1"); + var serviceProvider = services.BuildServiceProvider(); + + // Assert - just verify no exception during registration + // Resolution requires real credentials, so skip that in unit tests + Assert.NotNull(serviceProvider); + } + + [Fact] + public void GoogleAIChatClientShouldBeRegisteredInKernelServicesWithClient() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + using var googleClient = new Client(apiKey: "apiKey"); + + // Act + kernelBuilder.AddGoogleAIChatClient("modelId", googleClient); + var kernel = kernelBuilder.Build(); + + // Assert + var chatClient = kernel.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void GoogleAIChatClientShouldBeRegisteredInServiceCollectionWithClient() + { + // Arrange + var services = new ServiceCollection(); + using var googleClient = new Client(apiKey: "apiKey"); + + // Act + services.AddGoogleAIChatClient("modelId", googleClient); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var chatClient = serviceProvider.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void GoogleGenAIChatClientShouldBeRegisteredWithServiceId() + { + // Arrange + var services = new ServiceCollection(); + const string ServiceId = "test-service-id"; + + // Act + services.AddGoogleGenAIChatClient("modelId", "apiKey", serviceId: ServiceId); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var chatClient = serviceProvider.GetKeyedService(ServiceId); + Assert.NotNull(chatClient); + } + + [Fact] + public void GoogleVertexAIChatClientShouldBeRegisteredWithServiceId() + { + // Arrange + var services = new ServiceCollection(); + const string ServiceId = "test-service-id"; + + // Act + services.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1", serviceId: ServiceId); + var serviceProvider = services.BuildServiceProvider(); + + // Assert - just verify no exception during registration + // Resolution requires real credentials, so skip that in unit tests + Assert.NotNull(serviceProvider); + } + + [Fact] + public void GoogleAIChatClientShouldResolveFromServiceProviderWhenClientNotProvided() + { + // Arrange + var services = new ServiceCollection(); + using var googleClient = new Client(apiKey: "apiKey"); + services.AddSingleton(googleClient); + + // Act + services.AddGoogleAIChatClient("modelId"); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var chatClient = serviceProvider.GetRequiredService(); + Assert.NotNull(chatClient); + } +#endif } diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleGeminiChatClientTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleGeminiChatClientTests.cs new file mode 100644 index 000000000000..91bf5435efdc --- /dev/null +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleGeminiChatClientTests.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All rights reserved. + +#if NET + +using System; +using Google.GenAI; +using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel; +using Xunit; + +namespace SemanticKernel.Connectors.Google.UnitTests.Services; + +public sealed class GoogleGeminiChatClientTests +{ + [Fact] + public void GenAIChatClientShouldBeCreatedWithApiKey() + { + // Arrange + string modelId = "gemini-1.5-pro"; + string apiKey = "test-api-key"; + + // Act + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddGoogleGenAIChatClient(modelId, apiKey); + var kernel = kernelBuilder.Build(); + + // Assert + var chatClient = kernel.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void VertexAIChatClientShouldBeCreated() + { + // Arrange + string modelId = "gemini-1.5-pro"; + + // Act + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddGoogleVertexAIChatClient(modelId, project: "test-project", location: "us-central1"); + var kernel = kernelBuilder.Build(); + + // Assert - just verify no exception during registration + // Resolution requires real credentials, so skip that in unit tests + Assert.NotNull(kernel.Services); + } + + [Fact] + public void ChatClientShouldBeCreatedWithGoogleClient() + { + // Arrange + string modelId = "gemini-1.5-pro"; + using var googleClient = new Client(apiKey: "test-api-key"); + + // Act + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddGoogleAIChatClient(modelId, googleClient); + var kernel = kernelBuilder.Build(); + + // Assert + var chatClient = kernel.GetRequiredService(); + Assert.NotNull(chatClient); + } + + [Fact] + public void GenAIChatClientShouldBeCreatedWithServiceId() + { + // Arrange + string modelId = "gemini-1.5-pro"; + string apiKey = "test-api-key"; + string serviceId = "test-service"; + + // Act + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddGoogleGenAIChatClient(modelId, apiKey, serviceId: serviceId); + var kernel = kernelBuilder.Build(); + + // Assert + var chatClient = kernel.GetRequiredService(serviceId); + Assert.NotNull(chatClient); + } + + [Fact] + public void VertexAIChatClientShouldBeCreatedWithServiceId() + { + // Arrange + string modelId = "gemini-1.5-pro"; + string serviceId = "test-service"; + + // Act + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddGoogleVertexAIChatClient(modelId, project: "test-project", location: "us-central1", serviceId: serviceId); + var kernel = kernelBuilder.Build(); + + // Assert - just verify no exception during registration + // Resolution requires real credentials, so skip that in unit tests + Assert.NotNull(kernel.Services); + } + + [Fact] + public void GenAIChatClientThrowsForNullModelId() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleGenAIChatClient(null!, "apiKey")); + } + + [Fact] + public void GenAIChatClientThrowsForEmptyModelId() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleGenAIChatClient("", "apiKey")); + } + + [Fact] + public void GenAIChatClientThrowsForNullApiKey() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleGenAIChatClient("modelId", null!)); + } + + [Fact] + public void GenAIChatClientThrowsForEmptyApiKey() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleGenAIChatClient("modelId", "")); + } + + [Fact] + public void VertexAIChatClientThrowsForNullModelId() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleVertexAIChatClient(null!, project: "test-project", location: "us-central1")); + } + + [Fact] + public void VertexAIChatClientThrowsForEmptyModelId() + { + // Arrange + var kernelBuilder = Kernel.CreateBuilder(); + + // Act & Assert + Assert.ThrowsAny(() => kernelBuilder.AddGoogleVertexAIChatClient("", project: "test-project", location: "us-central1")); + } +} + +#endif diff --git a/dotnet/src/Connectors/Connectors.Google/Connectors.Google.csproj b/dotnet/src/Connectors/Connectors.Google/Connectors.Google.csproj index e71d80d17a00..7e104ef8b230 100644 --- a/dotnet/src/Connectors/Connectors.Google/Connectors.Google.csproj +++ b/dotnet/src/Connectors/Connectors.Google/Connectors.Google.csproj @@ -24,6 +24,11 @@ + + + + + diff --git a/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIKernelBuilderExtensions.cs index d6ab3768d0e0..72518e91aaf8 100644 --- a/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIKernelBuilderExtensions.cs @@ -118,4 +118,102 @@ public static IKernelBuilder AddGoogleAIEmbeddingGenerator( dimensions: dimensions); return builder; } + +#if NET + /// + /// Add Google GenAI to the . + /// + /// The kernel builder. + /// The model for chat completion. + /// The API key for authentication with the Google GenAI API. + /// The optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated kernel builder. + public static IKernelBuilder AddGoogleGenAIChatClient( + this IKernelBuilder builder, + string modelId, + string apiKey, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(builder); + + builder.Services.AddGoogleGenAIChatClient( + modelId, + apiKey, + serviceId, + openTelemetrySourceName, + openTelemetryConfig); + + return builder; + } + + /// + /// Add Google Vertex AI to the . + /// + /// The kernel builder. + /// The model for chat completion. + /// The Google Cloud project ID. If null, will attempt to use the GOOGLE_CLOUD_PROJECT environment variable. + /// The Google Cloud location (e.g., "us-central1"). If null, will attempt to use the GOOGLE_CLOUD_LOCATION environment variable. + /// The optional for authentication. If null, the client will use its internal discovery implementation to get credentials from the environment. + /// The optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated kernel builder. + public static IKernelBuilder AddGoogleVertexAIChatClient( + this IKernelBuilder builder, + string modelId, + string? project = null, + string? location = null, + Google.Apis.Auth.OAuth2.ICredential? credential = null, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(builder); + + builder.Services.AddGoogleVertexAIChatClient( + modelId, + project, + location, + credential, + serviceId, + openTelemetrySourceName, + openTelemetryConfig); + + return builder; + } + + /// + /// Add Google AI to the . + /// + /// The kernel builder. + /// The model for chat completion. + /// The to use for the service. If null, one must be available in the service provider when this service is resolved. + /// The optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated kernel builder. + public static IKernelBuilder AddGoogleAIChatClient( + this IKernelBuilder builder, + string modelId, + Google.GenAI.Client? googleClient = null, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(builder); + + builder.Services.AddGoogleAIChatClient( + modelId, + googleClient, + serviceId, + openTelemetrySourceName, + openTelemetryConfig); + + return builder; + } +#endif } diff --git a/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIServiceCollectionExtensions.DependencyInjection.cs b/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIServiceCollectionExtensions.DependencyInjection.cs index a45001278e9a..70ca85a913aa 100644 --- a/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIServiceCollectionExtensions.DependencyInjection.cs +++ b/dotnet/src/Connectors/Connectors.Google/Extensions/GoogleAIServiceCollectionExtensions.DependencyInjection.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable IDE0005 // Using directive is unnecessary +using System; +#pragma warning restore IDE0005 // Using directive is unnecessary using System.Net.Http; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; @@ -47,4 +50,146 @@ public static IServiceCollection AddGoogleAIEmbeddingGenerator( loggerFactory: serviceProvider.GetService(), dimensions: dimensions)); } + +#if NET + /// + /// Add Google GenAI to the specified service collection. + /// + /// The service collection to add the Google GenAI Chat Client to. + /// The model for chat completion. + /// The API key for authentication with the Google GenAI API. + /// Optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated service collection. + public static IServiceCollection AddGoogleGenAIChatClient( + this IServiceCollection services, + string modelId, + string apiKey, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(services); + Verify.NotNullOrWhiteSpace(modelId); + Verify.NotNullOrWhiteSpace(apiKey); + + IChatClient Factory(IServiceProvider serviceProvider, object? _) + { + var loggerFactory = serviceProvider.GetService(); + + var googleClient = new Google.GenAI.Client(apiKey: apiKey); + + var builder = googleClient.AsIChatClient(modelId) + .AsBuilder() + .UseKernelFunctionInvocation(loggerFactory) + .UseOpenTelemetry(loggerFactory, openTelemetrySourceName, openTelemetryConfig); + + if (loggerFactory is not null) + { + builder.UseLogging(loggerFactory); + } + + return builder.Build(); + } + + services.AddKeyedSingleton(serviceId, (Func)Factory); + + return services; + } + + /// + /// Add Google Vertex AI to the specified service collection. + /// + /// The service collection to add the Google Vertex AI Chat Client to. + /// The model for chat completion. + /// The Google Cloud project ID. If null, will attempt to use the GOOGLE_CLOUD_PROJECT environment variable. + /// The Google Cloud location (e.g., "us-central1"). If null, will attempt to use the GOOGLE_CLOUD_LOCATION environment variable. + /// The optional for authentication. If null, the client will use its internal discovery implementation to get credentials from the environment. + /// Optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated service collection. + public static IServiceCollection AddGoogleVertexAIChatClient( + this IServiceCollection services, + string modelId, + string? project = null, + string? location = null, + Google.Apis.Auth.OAuth2.ICredential? credential = null, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(services); + Verify.NotNullOrWhiteSpace(modelId); + + IChatClient Factory(IServiceProvider serviceProvider, object? _) + { + var loggerFactory = serviceProvider.GetService(); + + var googleClient = new Google.GenAI.Client(vertexAI: true, credential: credential, project: project, location: location); + + var builder = googleClient.AsIChatClient(modelId) + .AsBuilder() + .UseKernelFunctionInvocation(loggerFactory) + .UseOpenTelemetry(loggerFactory, openTelemetrySourceName, openTelemetryConfig); + + if (loggerFactory is not null) + { + builder.UseLogging(loggerFactory); + } + + return builder.Build(); + } + + services.AddKeyedSingleton(serviceId, (Func)Factory); + + return services; + } + + /// + /// Add Google AI to the specified service collection. + /// + /// The service collection to add the Google AI Chat Client to. + /// The model for chat completion. + /// The to use for the service. If null, one must be available in the service provider when this service is resolved. + /// Optional service ID. + /// An optional name for the OpenTelemetry source. + /// An optional callback that can be used to configure the instance. + /// The updated service collection. + public static IServiceCollection AddGoogleAIChatClient( + this IServiceCollection services, + string modelId, + Google.GenAI.Client? googleClient = null, + string? serviceId = null, + string? openTelemetrySourceName = null, + Action? openTelemetryConfig = null) + { + Verify.NotNull(services); + Verify.NotNullOrWhiteSpace(modelId); + + IChatClient Factory(IServiceProvider serviceProvider, object? _) + { + var loggerFactory = serviceProvider.GetService(); + + var client = googleClient ?? serviceProvider.GetRequiredService(); + + var builder = client.AsIChatClient(modelId) + .AsBuilder() + .UseKernelFunctionInvocation(loggerFactory) + .UseOpenTelemetry(loggerFactory, openTelemetrySourceName, openTelemetryConfig); + + if (loggerFactory is not null) + { + builder.UseLogging(loggerFactory); + } + + return builder.Build(); + } + + services.AddKeyedSingleton(serviceId, (Func)Factory); + + return services; + } +#endif } diff --git a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatClientTests.cs b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatClientTests.cs new file mode 100644 index 000000000000..cf649b24a09f --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatClientTests.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using xRetry; +using Xunit; +using Xunit.Abstractions; + +namespace SemanticKernel.IntegrationTests.Connectors.Google.Gemini; + +public sealed class GeminiGenAIChatClientTests(ITestOutputHelper output) : TestsBase(output) +{ + private const string SkipReason = "This test is for manual verification."; + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientGenerationReturnsValidResponseAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and expand this abbreviation: LLM") + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("Large Language Model", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Brandon", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientStreamingReturnsValidResponseAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and write a long story about my name.") + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + Assert.True(responses.Count > 1); + var message = string.Concat(responses.Select(c => c.Text)); + Assert.False(string.IsNullOrWhiteSpace(message)); + this.Output.WriteLine(message); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientWithSystemMessagesAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.System, "You are helpful assistant. Your name is Roger."), + new ChatMessage(ChatRole.System, "You know ACDD equals 1520"), + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Tell me your name and the value of ACDD.") + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("1520", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Roger", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientStreamingWithSystemMessagesAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.System, "You are helpful assistant. Your name is Roger."), + new ChatMessage(ChatRole.System, "You know ACDD equals 1520"), + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Tell me your name and the value of ACDD.") + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + Assert.True(responses.Count > 1); + var message = string.Concat(responses.Select(c => c.Text)); + this.Output.WriteLine(message); + Assert.Contains("1520", message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Roger", message, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientReturnsUsageDetailsAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and expand this abbreviation: LLM") + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Usage); + this.Output.WriteLine($"Input tokens: {response.Usage.InputTokenCount}"); + this.Output.WriteLine($"Output tokens: {response.Usage.OutputTokenCount}"); + this.Output.WriteLine($"Total tokens: {response.Usage.TotalTokenCount}"); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientWithChatOptionsAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Generate a random number between 1 and 100.") + }; + + var chatOptions = new ChatOptions + { + Temperature = 0.0f, + MaxOutputTokens = 100 + }; + + var sut = this.GetGenAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiFunctionCallingChatClientTests.cs b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiFunctionCallingChatClientTests.cs new file mode 100644 index 000000000000..9173365a60b9 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiFunctionCallingChatClientTests.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel; +using xRetry; +using Xunit; +using Xunit.Abstractions; +using AIFunctionCallContent = Microsoft.Extensions.AI.FunctionCallContent; + +namespace SemanticKernel.IntegrationTests.Connectors.Google.Gemini; + +public sealed class GeminiGenAIFunctionCallingChatClientTests(ITestOutputHelper output) : TestsBase(output) +{ + private const string SkipMessage = "This test is for manual verification."; + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithFunctionCallingReturnsToolCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType(nameof(CustomerPlugin)); + + var sut = this.GetGenAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools + }; + + // Act + var response = await sut.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + + var functionCallContent = response.Messages + .SelectMany(m => m.Contents) + .OfType() + .FirstOrDefault(); + + Assert.NotNull(functionCallContent); + Assert.Contains("GetCustomers", functionCallContent.Name, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientStreamingWithFunctionCallingReturnsToolCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType(nameof(CustomerPlugin)); + + var sut = this.GetGenAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools + }; + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory, chatOptions).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + + var functionCallContent = responses + .SelectMany(r => r.Contents) + .OfType() + .FirstOrDefault(); + + Assert.NotNull(functionCallContent); + Assert.Contains("GetCustomers", functionCallContent.Name, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithAutoInvokeFunctionsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetGenAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var response = await autoInvokingClient.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("John Kowalski", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Anna Nowak", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Steve Smith", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientStreamingWithAutoInvokeFunctionsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetGenAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var responses = await autoInvokingClient.GetStreamingResponseAsync(chatHistory, chatOptions).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + var content = string.Concat(responses.Select(c => c.Text)); + this.Output.WriteLine(content); + Assert.Contains("John Kowalski", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Anna Nowak", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Steve Smith", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithMultipleFunctionCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetGenAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers first and next return age of Anna customer?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var response = await autoInvokingClient.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("28", content, StringComparison.OrdinalIgnoreCase); + } + + public sealed class CustomerPlugin + { + [KernelFunction(nameof(GetCustomers))] + [Description("Get list of customers.")] + [return: Description("List of customers.")] + public string[] GetCustomers() + { + return + [ + "John Kowalski", + "Anna Nowak", + "Steve Smith", + ]; + } + + [KernelFunction(nameof(GetCustomerAge))] + [Description("Get age of customer.")] + [return: Description("Age of customer.")] + public int GetCustomerAge([Description("Name of customer")] string customerName) + { + return customerName switch + { + "John Kowalski" => 35, + "Anna Nowak" => 28, + "Steve Smith" => 42, + _ => throw new ArgumentException("Customer not found."), + }; + } + } + + public sealed class MathPlugin + { + [KernelFunction(nameof(Sum))] + [Description("Sum numbers.")] + public int Sum([Description("Numbers to sum")] int[] numbers) + { + return numbers.Sum(); + } + } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIChatClientTests.cs b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIChatClientTests.cs new file mode 100644 index 000000000000..de51bd66d0d8 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIChatClientTests.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using xRetry; +using Xunit; +using Xunit.Abstractions; + +namespace SemanticKernel.IntegrationTests.Connectors.Google.Gemini; + +public sealed class GeminiVertexAIChatClientTests(ITestOutputHelper output) : TestsBase(output) +{ + private const string SkipReason = "This test is for manual verification."; + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientGenerationReturnsValidResponseAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and expand this abbreviation: LLM") + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("Large Language Model", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Brandon", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientStreamingReturnsValidResponseAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and write a long story about my name.") + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + Assert.True(responses.Count > 1); + var message = string.Concat(responses.Select(c => c.Text)); + Assert.False(string.IsNullOrWhiteSpace(message)); + this.Output.WriteLine(message); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientWithSystemMessagesAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.System, "You are helpful assistant. Your name is Roger."), + new ChatMessage(ChatRole.System, "You know ACDD equals 1520"), + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Tell me your name and the value of ACDD.") + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("1520", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Roger", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientStreamingWithSystemMessagesAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.System, "You are helpful assistant. Your name is Roger."), + new ChatMessage(ChatRole.System, "You know ACDD equals 1520"), + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Tell me your name and the value of ACDD.") + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + Assert.True(responses.Count > 1); + var message = string.Concat(responses.Select(c => c.Text)); + this.Output.WriteLine(message); + Assert.Contains("1520", message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Roger", message, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientReturnsUsageDetailsAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, I'm Brandon, how are you?"), + new ChatMessage(ChatRole.Assistant, "I'm doing well, thanks for asking."), + new ChatMessage(ChatRole.User, "Call me by my name and expand this abbreviation: LLM") + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Usage); + this.Output.WriteLine($"Input tokens: {response.Usage.InputTokenCount}"); + this.Output.WriteLine($"Output tokens: {response.Usage.OutputTokenCount}"); + this.Output.WriteLine($"Total tokens: {response.Usage.TotalTokenCount}"); + } + + [RetryFact(Skip = SkipReason)] + public async Task ChatClientWithChatOptionsAsync() + { + // Arrange + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Generate a random number between 1 and 100.") + }; + + var chatOptions = new ChatOptions + { + Temperature = 0.0f, + MaxOutputTokens = 100 + }; + + var sut = this.GetVertexAIChatClient(); + + // Act + var response = await sut.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIFunctionCallingChatClientTests.cs b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIFunctionCallingChatClientTests.cs new file mode 100644 index 000000000000..7510b1609719 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiVertexAIFunctionCallingChatClientTests.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel; +using xRetry; +using Xunit; +using Xunit.Abstractions; +using AIFunctionCallContent = Microsoft.Extensions.AI.FunctionCallContent; + +namespace SemanticKernel.IntegrationTests.Connectors.Google.Gemini; + +public sealed class GeminiVertexAIFunctionCallingChatClientTests(ITestOutputHelper output) : TestsBase(output) +{ + private const string SkipMessage = "This test is for manual verification."; + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithFunctionCallingReturnsToolCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType(nameof(CustomerPlugin)); + + var sut = this.GetVertexAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools + }; + + // Act + var response = await sut.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + Assert.NotNull(response.Messages); + Assert.NotEmpty(response.Messages); + + var functionCallContent = response.Messages + .SelectMany(m => m.Contents) + .OfType() + .FirstOrDefault(); + + Assert.NotNull(functionCallContent); + Assert.Contains("GetCustomers", functionCallContent.Name, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientStreamingWithFunctionCallingReturnsToolCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType(nameof(CustomerPlugin)); + + var sut = this.GetVertexAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools + }; + + // Act + var responses = await sut.GetStreamingResponseAsync(chatHistory, chatOptions).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + + var functionCallContent = responses + .SelectMany(r => r.Contents) + .OfType() + .FirstOrDefault(); + + Assert.NotNull(functionCallContent); + Assert.Contains("GetCustomers", functionCallContent.Name, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithAutoInvokeFunctionsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetVertexAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var response = await autoInvokingClient.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("John Kowalski", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Anna Nowak", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Steve Smith", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientStreamingWithAutoInvokeFunctionsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetVertexAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var responses = await autoInvokingClient.GetStreamingResponseAsync(chatHistory, chatOptions).ToListAsync(); + + // Assert + Assert.NotEmpty(responses); + var content = string.Concat(responses.Select(c => c.Text)); + this.Output.WriteLine(content); + Assert.Contains("John Kowalski", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Anna Nowak", content, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Steve Smith", content, StringComparison.OrdinalIgnoreCase); + } + + [RetryFact(Skip = SkipMessage)] + public async Task ChatClientWithMultipleFunctionCallsAsync() + { + // Arrange + var kernel = new Kernel(); + kernel.ImportPluginFromType("CustomerPlugin"); + + var sut = this.GetVertexAIChatClient(); + + var chatHistory = new[] + { + new ChatMessage(ChatRole.User, "Hello, could you show me list of customers first and next return age of Anna customer?") + }; + + var tools = kernel.Plugins + .SelectMany(p => p) + .Cast() + .ToList(); + + var chatOptions = new ChatOptions + { + Tools = tools, + ToolMode = ChatToolMode.Auto + }; + + // Use FunctionInvokingChatClient for auto-invoke + using var autoInvokingClient = new FunctionInvokingChatClient(sut); + + // Act + var response = await autoInvokingClient.GetResponseAsync(chatHistory, chatOptions); + + // Assert + Assert.NotNull(response); + var content = string.Join("", response.Messages.Select(m => m.Text)); + this.Output.WriteLine(content); + Assert.Contains("28", content, StringComparison.OrdinalIgnoreCase); + } + + public sealed class CustomerPlugin + { + [KernelFunction(nameof(GetCustomers))] + [Description("Get list of customers.")] + [return: Description("List of customers.")] + public string[] GetCustomers() + { + return + [ + "John Kowalski", + "Anna Nowak", + "Steve Smith", + ]; + } + + [KernelFunction(nameof(GetCustomerAge))] + [Description("Get age of customer.")] + [return: Description("Age of customer.")] + public int GetCustomerAge([Description("Name of customer")] string customerName) + { + return customerName switch + { + "John Kowalski" => 35, + "Anna Nowak" => 28, + "Steve Smith" => 42, + _ => throw new ArgumentException("Customer not found."), + }; + } + } + + public sealed class MathPlugin + { + [KernelFunction(nameof(Sum))] + [Description("Sum numbers.")] + public int Sum([Description("Numbers to sum")] int[] numbers) + { + return numbers.Sum(); + } + } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Google/TestsBase.cs b/dotnet/src/IntegrationTests/Connectors/Google/TestsBase.cs index 723785497ccd..7e6bb8a45f54 100644 --- a/dotnet/src/IntegrationTests/Connectors/Google/TestsBase.cs +++ b/dotnet/src/IntegrationTests/Connectors/Google/TestsBase.cs @@ -3,6 +3,8 @@ using System; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.Google; using Microsoft.SemanticKernel.Embeddings; @@ -65,6 +67,52 @@ protected TestsBase(ITestOutputHelper output) _ => throw new ArgumentOutOfRangeException(nameof(serviceType), serviceType, null) }; + protected IChatClient GetGenAIChatClient(string? overrideModelId = null) + { + var modelId = overrideModelId ?? this.GoogleAI.Gemini.ModelId; + var apiKey = this.GoogleAI.ApiKey; + + var kernel = Kernel.CreateBuilder() + .AddGoogleGenAIChatClient(modelId, apiKey) + .Build(); + + return kernel.GetRequiredService(); + } + + protected IChatClient GetVertexAIChatClient(string? overrideModelId = null) + { + var modelId = overrideModelId ?? this.VertexAI.Gemini.ModelId; + + var kernel = Kernel.CreateBuilder() + .AddGoogleVertexAIChatClient(modelId, project: this.VertexAI.ProjectId, location: this.VertexAI.Location) + .Build(); + + return kernel.GetRequiredService(); + } + + protected IChatClient GetGenAIChatClientWithVision() + { + var modelId = this.GoogleAI.Gemini.VisionModelId; + var apiKey = this.GoogleAI.ApiKey; + + var kernel = Kernel.CreateBuilder() + .AddGoogleGenAIChatClient(modelId, apiKey) + .Build(); + + return kernel.GetRequiredService(); + } + + protected IChatClient GetVertexAIChatClientWithVision() + { + var modelId = this.VertexAI.Gemini.VisionModelId; + + var kernel = Kernel.CreateBuilder() + .AddGoogleVertexAIChatClient(modelId, project: this.VertexAI.ProjectId, location: this.VertexAI.Location) + .Build(); + + return kernel.GetRequiredService(); + } + [Obsolete("Temporary test utility for Obsolete ITextEmbeddingGenerationService")] protected ITextEmbeddingGenerationService GetEmbeddingService(ServiceType serviceType) => serviceType switch { diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index d0e45a75f94f..ec65cb12f288 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -41,6 +41,7 @@ +