Skip to content

Commit f987064

Browse files
Copilotrogerbarretomarkwallace-microsoft
authored
.Net: Use official Google.GenAI IChatClient implementation (#13404)
### Motivation and Context Google.GenAI 0.11.0 now ships with an official `IChatClient` implementation via `Client.AsIChatClient(modelId)`. This eliminates the need for our custom polyfill. ### Description - **Updated** `Google.GenAI` package from 0.6.0 to 0.11.0 - **Removed** custom `GoogleGenAIChatClient.cs` polyfill (~700 LOC) - **Simplified** extension methods to use the official API ```csharp // Before: Custom polyfill wrapper services.AddSingleton<IChatClient>(new GoogleGenAIChatClient(googleClient, modelId)); // After: Official SDK method services.AddSingleton<IChatClient>(googleClient.AsIChatClient(modelId)); ``` ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > I need to add a new ChatClient service into the Google connector using a reference to the Google.GenAI library. > > this implementation should use internal a polyfill of the `IChatClient` implementation, similar how was done in the sample from Microsoft Agent Framework here: > > https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GeminiChatClient.cs > > and here: > > https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GoogleGenAIExtensions.cs > > Also take note that I need to add some extra Extension methods similiar to the AddGoogleChatCompletion, which in this case will be AddGoogleChatClient extensions to both the IServiceCollection and IKernelBuilder. > > Ensure this new extension methods are unit test covered. > > One extra observation, Google.GenAI currently is only available to NET 8 and there's not support for NetStandard, so this new extensions should only be available for the NET 8+ targets with proper csproj conditionals and #if NET checks. </details> <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/microsoft/semantic-kernel/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
1 parent 72bc025 commit f987064

13 files changed

+1464
-0
lines changed

dotnet/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<PackageVersion Include="FastBertTokenizer" Version="1.0.28" />
3636
<PackageVersion Include="Google.Apis.Auth" Version="1.73.0" />
3737
<PackageVersion Include="Google.Apis.CustomSearchAPI.v1" Version="1.68.0.3520" />
38+
<PackageVersion Include="Google.GenAI" Version="0.11.0" />
3839
<PackageVersion Include="Google.Protobuf" Version="3.33.4" />
3940
<PackageVersion Include="Grpc.AspNetCore" Version="2.76.0" />
4041
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.70.0" />

dotnet/src/Connectors/Connectors.Google.UnitTests/Connectors.Google.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
</PackageReference>
2626
<PackageReference Include="System.Numerics.Tensors" />
2727
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
28+
<PackageReference Include="Google.GenAI" />
2829
</ItemGroup>
2930

3031
<ItemGroup>

dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/GoogleAIServiceCollectionExtensionsTests.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

33
using System;
4+
using Google.GenAI;
45
using Microsoft.Extensions.AI;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.SemanticKernel;
@@ -113,4 +114,147 @@ public void GoogleAIEmbeddingGeneratorShouldBeRegisteredInServiceCollection()
113114
Assert.NotNull(embeddingsGenerationService);
114115
Assert.IsType<GoogleAIEmbeddingGenerator>(embeddingsGenerationService);
115116
}
117+
118+
#if NET
119+
[Fact]
120+
public void GoogleGenAIChatClientShouldBeRegisteredInKernelServicesWithApiKey()
121+
{
122+
// Arrange
123+
var kernelBuilder = Kernel.CreateBuilder();
124+
125+
// Act
126+
kernelBuilder.AddGoogleGenAIChatClient("modelId", "apiKey");
127+
var kernel = kernelBuilder.Build();
128+
129+
// Assert
130+
var chatClient = kernel.GetRequiredService<IChatClient>();
131+
Assert.NotNull(chatClient);
132+
}
133+
134+
[Fact]
135+
public void GoogleGenAIChatClientShouldBeRegisteredInServiceCollectionWithApiKey()
136+
{
137+
// Arrange
138+
var services = new ServiceCollection();
139+
140+
// Act
141+
services.AddGoogleGenAIChatClient("modelId", "apiKey");
142+
var serviceProvider = services.BuildServiceProvider();
143+
144+
// Assert
145+
var chatClient = serviceProvider.GetRequiredService<IChatClient>();
146+
Assert.NotNull(chatClient);
147+
}
148+
149+
[Fact]
150+
public void GoogleVertexAIChatClientShouldBeRegisteredInKernelServices()
151+
{
152+
// Arrange
153+
var kernelBuilder = Kernel.CreateBuilder();
154+
155+
// Act
156+
kernelBuilder.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1");
157+
158+
// Assert - just verify no exception during registration
159+
// Resolution requires real credentials, so skip that in unit tests
160+
var kernel = kernelBuilder.Build();
161+
Assert.NotNull(kernel.Services);
162+
}
163+
164+
[Fact]
165+
public void GoogleVertexAIChatClientShouldBeRegisteredInServiceCollection()
166+
{
167+
// Arrange
168+
var services = new ServiceCollection();
169+
170+
// Act
171+
services.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1");
172+
var serviceProvider = services.BuildServiceProvider();
173+
174+
// Assert - just verify no exception during registration
175+
// Resolution requires real credentials, so skip that in unit tests
176+
Assert.NotNull(serviceProvider);
177+
}
178+
179+
[Fact]
180+
public void GoogleAIChatClientShouldBeRegisteredInKernelServicesWithClient()
181+
{
182+
// Arrange
183+
var kernelBuilder = Kernel.CreateBuilder();
184+
using var googleClient = new Client(apiKey: "apiKey");
185+
186+
// Act
187+
kernelBuilder.AddGoogleAIChatClient("modelId", googleClient);
188+
var kernel = kernelBuilder.Build();
189+
190+
// Assert
191+
var chatClient = kernel.GetRequiredService<IChatClient>();
192+
Assert.NotNull(chatClient);
193+
}
194+
195+
[Fact]
196+
public void GoogleAIChatClientShouldBeRegisteredInServiceCollectionWithClient()
197+
{
198+
// Arrange
199+
var services = new ServiceCollection();
200+
using var googleClient = new Client(apiKey: "apiKey");
201+
202+
// Act
203+
services.AddGoogleAIChatClient("modelId", googleClient);
204+
var serviceProvider = services.BuildServiceProvider();
205+
206+
// Assert
207+
var chatClient = serviceProvider.GetRequiredService<IChatClient>();
208+
Assert.NotNull(chatClient);
209+
}
210+
211+
[Fact]
212+
public void GoogleGenAIChatClientShouldBeRegisteredWithServiceId()
213+
{
214+
// Arrange
215+
var services = new ServiceCollection();
216+
const string ServiceId = "test-service-id";
217+
218+
// Act
219+
services.AddGoogleGenAIChatClient("modelId", "apiKey", serviceId: ServiceId);
220+
var serviceProvider = services.BuildServiceProvider();
221+
222+
// Assert
223+
var chatClient = serviceProvider.GetKeyedService<IChatClient>(ServiceId);
224+
Assert.NotNull(chatClient);
225+
}
226+
227+
[Fact]
228+
public void GoogleVertexAIChatClientShouldBeRegisteredWithServiceId()
229+
{
230+
// Arrange
231+
var services = new ServiceCollection();
232+
const string ServiceId = "test-service-id";
233+
234+
// Act
235+
services.AddGoogleVertexAIChatClient("modelId", project: "test-project", location: "us-central1", serviceId: ServiceId);
236+
var serviceProvider = services.BuildServiceProvider();
237+
238+
// Assert - just verify no exception during registration
239+
// Resolution requires real credentials, so skip that in unit tests
240+
Assert.NotNull(serviceProvider);
241+
}
242+
243+
[Fact]
244+
public void GoogleAIChatClientShouldResolveFromServiceProviderWhenClientNotProvided()
245+
{
246+
// Arrange
247+
var services = new ServiceCollection();
248+
using var googleClient = new Client(apiKey: "apiKey");
249+
services.AddSingleton(googleClient);
250+
251+
// Act
252+
services.AddGoogleAIChatClient("modelId");
253+
var serviceProvider = services.BuildServiceProvider();
254+
255+
// Assert
256+
var chatClient = serviceProvider.GetRequiredService<IChatClient>();
257+
Assert.NotNull(chatClient);
258+
}
259+
#endif
116260
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
#if NET
4+
5+
using System;
6+
using Google.GenAI;
7+
using Microsoft.Extensions.AI;
8+
using Microsoft.SemanticKernel;
9+
using Xunit;
10+
11+
namespace SemanticKernel.Connectors.Google.UnitTests.Services;
12+
13+
public sealed class GoogleGeminiChatClientTests
14+
{
15+
[Fact]
16+
public void GenAIChatClientShouldBeCreatedWithApiKey()
17+
{
18+
// Arrange
19+
string modelId = "gemini-1.5-pro";
20+
string apiKey = "test-api-key";
21+
22+
// Act
23+
var kernelBuilder = Kernel.CreateBuilder();
24+
kernelBuilder.AddGoogleGenAIChatClient(modelId, apiKey);
25+
var kernel = kernelBuilder.Build();
26+
27+
// Assert
28+
var chatClient = kernel.GetRequiredService<IChatClient>();
29+
Assert.NotNull(chatClient);
30+
}
31+
32+
[Fact]
33+
public void VertexAIChatClientShouldBeCreated()
34+
{
35+
// Arrange
36+
string modelId = "gemini-1.5-pro";
37+
38+
// Act
39+
var kernelBuilder = Kernel.CreateBuilder();
40+
kernelBuilder.AddGoogleVertexAIChatClient(modelId, project: "test-project", location: "us-central1");
41+
var kernel = kernelBuilder.Build();
42+
43+
// Assert - just verify no exception during registration
44+
// Resolution requires real credentials, so skip that in unit tests
45+
Assert.NotNull(kernel.Services);
46+
}
47+
48+
[Fact]
49+
public void ChatClientShouldBeCreatedWithGoogleClient()
50+
{
51+
// Arrange
52+
string modelId = "gemini-1.5-pro";
53+
using var googleClient = new Client(apiKey: "test-api-key");
54+
55+
// Act
56+
var kernelBuilder = Kernel.CreateBuilder();
57+
kernelBuilder.AddGoogleAIChatClient(modelId, googleClient);
58+
var kernel = kernelBuilder.Build();
59+
60+
// Assert
61+
var chatClient = kernel.GetRequiredService<IChatClient>();
62+
Assert.NotNull(chatClient);
63+
}
64+
65+
[Fact]
66+
public void GenAIChatClientShouldBeCreatedWithServiceId()
67+
{
68+
// Arrange
69+
string modelId = "gemini-1.5-pro";
70+
string apiKey = "test-api-key";
71+
string serviceId = "test-service";
72+
73+
// Act
74+
var kernelBuilder = Kernel.CreateBuilder();
75+
kernelBuilder.AddGoogleGenAIChatClient(modelId, apiKey, serviceId: serviceId);
76+
var kernel = kernelBuilder.Build();
77+
78+
// Assert
79+
var chatClient = kernel.GetRequiredService<IChatClient>(serviceId);
80+
Assert.NotNull(chatClient);
81+
}
82+
83+
[Fact]
84+
public void VertexAIChatClientShouldBeCreatedWithServiceId()
85+
{
86+
// Arrange
87+
string modelId = "gemini-1.5-pro";
88+
string serviceId = "test-service";
89+
90+
// Act
91+
var kernelBuilder = Kernel.CreateBuilder();
92+
kernelBuilder.AddGoogleVertexAIChatClient(modelId, project: "test-project", location: "us-central1", serviceId: serviceId);
93+
var kernel = kernelBuilder.Build();
94+
95+
// Assert - just verify no exception during registration
96+
// Resolution requires real credentials, so skip that in unit tests
97+
Assert.NotNull(kernel.Services);
98+
}
99+
100+
[Fact]
101+
public void GenAIChatClientThrowsForNullModelId()
102+
{
103+
// Arrange
104+
var kernelBuilder = Kernel.CreateBuilder();
105+
106+
// Act & Assert
107+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleGenAIChatClient(null!, "apiKey"));
108+
}
109+
110+
[Fact]
111+
public void GenAIChatClientThrowsForEmptyModelId()
112+
{
113+
// Arrange
114+
var kernelBuilder = Kernel.CreateBuilder();
115+
116+
// Act & Assert
117+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleGenAIChatClient("", "apiKey"));
118+
}
119+
120+
[Fact]
121+
public void GenAIChatClientThrowsForNullApiKey()
122+
{
123+
// Arrange
124+
var kernelBuilder = Kernel.CreateBuilder();
125+
126+
// Act & Assert
127+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleGenAIChatClient("modelId", null!));
128+
}
129+
130+
[Fact]
131+
public void GenAIChatClientThrowsForEmptyApiKey()
132+
{
133+
// Arrange
134+
var kernelBuilder = Kernel.CreateBuilder();
135+
136+
// Act & Assert
137+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleGenAIChatClient("modelId", ""));
138+
}
139+
140+
[Fact]
141+
public void VertexAIChatClientThrowsForNullModelId()
142+
{
143+
// Arrange
144+
var kernelBuilder = Kernel.CreateBuilder();
145+
146+
// Act & Assert
147+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleVertexAIChatClient(null!, project: "test-project", location: "us-central1"));
148+
}
149+
150+
[Fact]
151+
public void VertexAIChatClientThrowsForEmptyModelId()
152+
{
153+
// Arrange
154+
var kernelBuilder = Kernel.CreateBuilder();
155+
156+
// Act & Assert
157+
Assert.ThrowsAny<ArgumentException>(() => kernelBuilder.AddGoogleVertexAIChatClient("", project: "test-project", location: "us-central1"));
158+
}
159+
}
160+
161+
#endif

dotnet/src/Connectors/Connectors.Google/Connectors.Google.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<ProjectReference Include="..\..\SemanticKernel.Core\SemanticKernel.Core.csproj" />
2525
</ItemGroup>
2626

27+
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0' or '$(TargetFramework)' == 'net10.0'">
28+
<PackageReference Include="Google.GenAI" />
29+
<PackageReference Include="Google.Apis.Auth" />
30+
</ItemGroup>
31+
2732
<ItemGroup>
2833
<InternalsVisibleTo Include="SemanticKernel.Connectors.GoogleVertexAI.UnitTests" />
2934
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />

0 commit comments

Comments
 (0)