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 @@
+