Skip to content

Commit e0958fa

Browse files
rogerbarretoJavier
authored andcommitted
.Net: BugFix 400 BadRequest for ToolcallBehavior against Google API (microsoft#12365)
# Motivation - Fixes microsoft#11629 As demonstrated in the issue above, attempting to call Google Gemini using the ToolcallBehavior logic (current only one supported in this connector) an unexpected error was raising when sending a complex json schema structure from github MCP tooling, this bugfix removes non supported `additionalParameters` property sent into the schema for the API. - Additionally to the changes we added support for numeric and boolean parameters for function calling. - After this change the example in the Issue should work with `ToolcallBehavior`.
1 parent be3326c commit e0958fa

File tree

3 files changed

+85
-67
lines changed

3 files changed

+85
-67
lines changed

dotnet/src/Connectors/Connectors.Google/Core/Gemini/GeminiPluginCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static bool TryGetFunctionAndArguments(
3434
arguments = [];
3535
foreach (var parameter in functionToolCall.Arguments)
3636
{
37-
arguments[parameter.Key] = parameter.Value?.ToString();
37+
arguments[parameter.Key] = parameter.Value;
3838
}
3939
}
4040

dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,11 @@ internal static JsonElement TransformToOpenApi3Schema(JsonElement jsonElement)
359359

360360
static void TransformOpenApi3Object(JsonObject obj)
361361
{
362+
if (obj.TryGetPropertyValue("additionalProperties", out _))
363+
{
364+
obj.Remove("additionalProperties");
365+
}
366+
362367
if (obj.TryGetPropertyValue("properties", out JsonNode? propsNode) && propsNode is JsonObject properties)
363368
{
364369
foreach (var property in properties)
@@ -386,7 +391,18 @@ static void TransformOpenApi3Object(JsonObject obj)
386391
{
387392
if (propertyObj.TryGetPropertyValue("items", out JsonNode? itemsNode) && itemsNode is JsonObject itemsObj)
388393
{
389-
TransformOpenApi3Object(itemsObj);
394+
// Ensure AnyOf array is considered
395+
if (itemsObj.TryGetPropertyValue("anyOf", out JsonNode? anyOfNode) && anyOfNode is JsonArray anyOfArray)
396+
{
397+
foreach (var anyOfObj in anyOfArray.OfType<JsonObject>())
398+
{
399+
TransformOpenApi3Object(anyOfObj);
400+
}
401+
}
402+
else
403+
{
404+
TransformOpenApi3Object(itemsObj);
405+
}
390406
}
391407
}
392408
}

dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiFunctionCallingTests.cs

Lines changed: 67 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ namespace SemanticKernel.IntegrationTests.Connectors.Google.Gemini;
1616

1717
public sealed class GeminiFunctionCallingTests(ITestOutputHelper output) : TestsBase(output)
1818
{
19-
[RetryTheory]
20-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
21-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
22-
public async Task ChatGenerationEnabledFunctionsShouldReturnFunctionToCallAsync(ServiceType serviceType)
19+
private const string SkipMessage = "This test is for manual verification.";
20+
21+
[RetryTheory(Skip = SkipMessage)]
22+
[InlineData(ServiceType.GoogleAI, true)]
23+
[InlineData(ServiceType.VertexAI, false)]
24+
public async Task ChatGenerationEnabledFunctionsShouldReturnFunctionToCallAsync(ServiceType serviceType, bool isBeta)
2325
{
2426
// Arrange
2527
var kernel = new Kernel();
2628
kernel.ImportPluginFromType<CustomerPlugin>(nameof(CustomerPlugin));
27-
var sut = this.GetChatService(serviceType);
29+
var sut = this.GetChatService(serviceType, isBeta);
2830
var chatHistory = new ChatHistory();
2931
chatHistory.AddUserMessage("Hello, could you show me list of customers?");
3032
var executionSettings = new GeminiPromptExecutionSettings()
@@ -44,15 +46,15 @@ public async Task ChatGenerationEnabledFunctionsShouldReturnFunctionToCallAsync(
4446
item.FullyQualifiedName == $"{nameof(CustomerPlugin)}{GeminiFunction.NameSeparator}{nameof(CustomerPlugin.GetCustomers)}");
4547
}
4648

47-
[RetryTheory]
48-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
49-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
50-
public async Task ChatStreamingEnabledFunctionsShouldReturnFunctionToCallAsync(ServiceType serviceType)
49+
[RetryTheory(Skip = SkipMessage)]
50+
[InlineData(ServiceType.GoogleAI, true)]
51+
[InlineData(ServiceType.VertexAI, false)]
52+
public async Task ChatStreamingEnabledFunctionsShouldReturnFunctionToCallAsync(ServiceType serviceType, bool isBeta)
5153
{
5254
// Arrange
5355
var kernel = new Kernel();
5456
kernel.ImportPluginFromType<CustomerPlugin>(nameof(CustomerPlugin));
55-
var sut = this.GetChatService(serviceType);
57+
var sut = this.GetChatService(serviceType, isBeta);
5658
var chatHistory = new ChatHistory();
5759
chatHistory.AddUserMessage("Hello, could you show me list of customers?");
5860
var executionSettings = new GeminiPromptExecutionSettings()
@@ -74,15 +76,15 @@ public async Task ChatStreamingEnabledFunctionsShouldReturnFunctionToCallAsync(S
7476
item.FullyQualifiedName == $"{nameof(CustomerPlugin)}{GeminiFunction.NameSeparator}{nameof(CustomerPlugin.GetCustomers)}");
7577
}
7678

77-
[RetryTheory]
78-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
79-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
80-
public async Task ChatGenerationAutoInvokeShouldCallOneFunctionAndReturnResponseAsync(ServiceType serviceType)
79+
[RetryTheory(Skip = SkipMessage)]
80+
[InlineData(ServiceType.GoogleAI, true)]
81+
[InlineData(ServiceType.VertexAI, false)]
82+
public async Task ChatGenerationAutoInvokeShouldCallOneFunctionAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
8183
{
8284
// Arrange
8385
var kernel = new Kernel();
8486
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
85-
var sut = this.GetChatService(serviceType);
87+
var sut = this.GetChatService(serviceType, isBeta);
8688
var chatHistory = new ChatHistory();
8789
chatHistory.AddUserMessage("Hello, could you show me list of customers?");
8890
var executionSettings = new GeminiPromptExecutionSettings()
@@ -101,15 +103,15 @@ public async Task ChatGenerationAutoInvokeShouldCallOneFunctionAndReturnResponse
101103
Assert.Contains("Steve Smith", response.Content, StringComparison.OrdinalIgnoreCase);
102104
}
103105

104-
[RetryTheory]
105-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
106-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
107-
public async Task ChatStreamingAutoInvokeShouldCallOneFunctionAndReturnResponseAsync(ServiceType serviceType)
106+
[RetryTheory(Skip = SkipMessage)]
107+
[InlineData(ServiceType.GoogleAI, true)]
108+
[InlineData(ServiceType.VertexAI, false)]
109+
public async Task ChatStreamingAutoInvokeShouldCallOneFunctionAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
108110
{
109111
// Arrange
110112
var kernel = new Kernel();
111113
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
112-
var sut = this.GetChatService(serviceType);
114+
var sut = this.GetChatService(serviceType, isBeta);
113115
var chatHistory = new ChatHistory();
114116
chatHistory.AddUserMessage("Hello, could you show me list of customers?");
115117
var executionSettings = new GeminiPromptExecutionSettings()
@@ -130,15 +132,15 @@ public async Task ChatStreamingAutoInvokeShouldCallOneFunctionAndReturnResponseA
130132
Assert.Contains("Steve Smith", content, StringComparison.OrdinalIgnoreCase);
131133
}
132134

133-
[RetryTheory]
134-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
135-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
136-
public async Task ChatGenerationAutoInvokeShouldCallTwoFunctionsAndReturnResponseAsync(ServiceType serviceType)
135+
[RetryTheory(Skip = SkipMessage)]
136+
[InlineData(ServiceType.GoogleAI, true)]
137+
[InlineData(ServiceType.VertexAI, false)]
138+
public async Task ChatGenerationAutoInvokeShouldCallTwoFunctionsAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
137139
{
138140
// Arrange
139141
var kernel = new Kernel();
140142
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
141-
var sut = this.GetChatService(serviceType);
143+
var sut = this.GetChatService(serviceType, isBeta);
142144
var chatHistory = new ChatHistory();
143145
chatHistory.AddUserMessage("Hello, could you show me list of customers first and next return age of Anna customer?");
144146
var executionSettings = new GeminiPromptExecutionSettings()
@@ -155,15 +157,15 @@ public async Task ChatGenerationAutoInvokeShouldCallTwoFunctionsAndReturnRespons
155157
Assert.Contains("28", response.Content, StringComparison.OrdinalIgnoreCase);
156158
}
157159

158-
[RetryTheory]
159-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
160-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
161-
public async Task ChatStreamingAutoInvokeShouldCallTwoFunctionsAndReturnResponseAsync(ServiceType serviceType)
160+
[RetryTheory(Skip = SkipMessage)]
161+
[InlineData(ServiceType.GoogleAI, true)]
162+
[InlineData(ServiceType.VertexAI, false)]
163+
public async Task ChatStreamingAutoInvokeShouldCallTwoFunctionsAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
162164
{
163165
// Arrange
164166
var kernel = new Kernel();
165167
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
166-
var sut = this.GetChatService(serviceType);
168+
var sut = this.GetChatService(serviceType, isBeta);
167169
var chatHistory = new ChatHistory();
168170
chatHistory.AddUserMessage("Hello, could you show me list of customers first and next return age of Anna customer?");
169171
var executionSettings = new GeminiPromptExecutionSettings()
@@ -182,16 +184,16 @@ public async Task ChatStreamingAutoInvokeShouldCallTwoFunctionsAndReturnResponse
182184
Assert.Contains("28", content, StringComparison.OrdinalIgnoreCase);
183185
}
184186

185-
[RetryTheory]
186-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
187-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
188-
public async Task ChatGenerationAutoInvokeShouldCallFunctionsMultipleTimesAndReturnResponseAsync(ServiceType serviceType)
187+
[RetryTheory(Skip = SkipMessage)]
188+
[InlineData(ServiceType.GoogleAI, true)]
189+
[InlineData(ServiceType.VertexAI, false)]
190+
public async Task ChatGenerationAutoInvokeShouldCallFunctionsMultipleTimesAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
189191
{
190192
// Arrange
191193
var kernel = new Kernel();
192194
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
193195
kernel.ImportPluginFromType<MathPlugin>("MathPlugin");
194-
var sut = this.GetChatService(serviceType);
196+
var sut = this.GetChatService(serviceType, isBeta);
195197
var chatHistory = new ChatHistory();
196198
chatHistory.AddUserMessage(
197199
"Get list of customers and next get customers ages and at the end calculate the sum of ages of all customers.");
@@ -209,16 +211,16 @@ public async Task ChatGenerationAutoInvokeShouldCallFunctionsMultipleTimesAndRet
209211
Assert.Contains("105", response.Content, StringComparison.OrdinalIgnoreCase);
210212
}
211213

212-
[RetryTheory]
213-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
214-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
215-
public async Task ChatStreamingAutoInvokeShouldCallFunctionsMultipleTimesAndReturnResponseAsync(ServiceType serviceType)
214+
[RetryTheory(Skip = SkipMessage)]
215+
[InlineData(ServiceType.GoogleAI, true)]
216+
[InlineData(ServiceType.VertexAI, false)]
217+
public async Task ChatStreamingAutoInvokeShouldCallFunctionsMultipleTimesAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
216218
{
217219
// Arrange
218220
var kernel = new Kernel();
219221
kernel.ImportPluginFromType<CustomerPlugin>("CustomerPlugin");
220222
kernel.ImportPluginFromType<MathPlugin>("MathPlugin");
221-
var sut = this.GetChatService(serviceType);
223+
var sut = this.GetChatService(serviceType, isBeta);
222224
var chatHistory = new ChatHistory();
223225
chatHistory.AddUserMessage(
224226
"Get list of customers and next get customers ages and at the end calculate the sum of ages of all customers.");
@@ -238,14 +240,14 @@ public async Task ChatStreamingAutoInvokeShouldCallFunctionsMultipleTimesAndRetu
238240
Assert.Contains("105", content, StringComparison.OrdinalIgnoreCase);
239241
}
240242

241-
[RetryTheory]
242-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
243-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
244-
public async Task ChatGenerationAutoInvokeNullablePropertiesWorksAsync(ServiceType serviceType)
243+
[RetryTheory(Skip = SkipMessage)]
244+
[InlineData(ServiceType.GoogleAI, true)]
245+
[InlineData(ServiceType.VertexAI, false)]
246+
public async Task ChatGenerationAutoInvokeNullablePropertiesWorksAsync(ServiceType serviceType, bool isBeta)
245247
{
246248
var kernel = new Kernel();
247249
kernel.ImportPluginFromType<NullableTestPlugin>();
248-
var sut = this.GetChatService(serviceType);
250+
var sut = this.GetChatService(serviceType, isBeta);
249251

250252
var executionSettings = new GeminiPromptExecutionSettings()
251253
{
@@ -260,16 +262,16 @@ public async Task ChatGenerationAutoInvokeNullablePropertiesWorksAsync(ServiceTy
260262
Assert.NotNull(response);
261263
}
262264

263-
[RetryTheory]
264-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
265-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
266-
public async Task ChatGenerationAutoInvokeTwoPluginsShouldGetDateAndReturnTasksByDateParamAndReturnResponseAsync(ServiceType serviceType)
265+
[RetryTheory(Skip = SkipMessage)]
266+
[InlineData(ServiceType.GoogleAI, true)]
267+
[InlineData(ServiceType.VertexAI, false)]
268+
public async Task ChatGenerationAutoInvokeTwoPluginsShouldGetDateAndReturnTasksByDateParamAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
267269
{
268270
// Arrange
269271
var kernel = new Kernel();
270272
kernel.ImportPluginFromType<TaskPlugin>(nameof(TaskPlugin));
271273
kernel.ImportPluginFromType<DatePlugin>(nameof(DatePlugin));
272-
var sut = this.GetChatService(serviceType);
274+
var sut = this.GetChatService(serviceType, isBeta);
273275
var chatHistory = new ChatHistory();
274276
chatHistory.AddUserMessage("How many tasks I have to do today? Show me count of tasks for today and date.");
275277
var executionSettings = new GeminiPromptExecutionSettings()
@@ -286,16 +288,16 @@ public async Task ChatGenerationAutoInvokeTwoPluginsShouldGetDateAndReturnTasksB
286288
Assert.Contains("5", response.Content, StringComparison.OrdinalIgnoreCase);
287289
}
288290

289-
[RetryTheory]
290-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
291-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
292-
public async Task ChatStreamingAutoInvokeTwoPluginsShouldGetDateAndReturnTasksByDateParamAndReturnResponseAsync(ServiceType serviceType)
291+
[RetryTheory(Skip = SkipMessage)]
292+
[InlineData(ServiceType.GoogleAI, true)]
293+
[InlineData(ServiceType.VertexAI, false)]
294+
public async Task ChatStreamingAutoInvokeTwoPluginsShouldGetDateAndReturnTasksByDateParamAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
293295
{
294296
// Arrange
295297
var kernel = new Kernel();
296298
kernel.ImportPluginFromType<TaskPlugin>(nameof(TaskPlugin));
297299
kernel.ImportPluginFromType<DatePlugin>(nameof(DatePlugin));
298-
var sut = this.GetChatService(serviceType);
300+
var sut = this.GetChatService(serviceType, isBeta);
299301
var chatHistory = new ChatHistory();
300302
chatHistory.AddUserMessage("How many tasks I have to do today? Show me count of tasks for today and date.");
301303
var executionSettings = new GeminiPromptExecutionSettings()
@@ -314,18 +316,18 @@ public async Task ChatStreamingAutoInvokeTwoPluginsShouldGetDateAndReturnTasksBy
314316
Assert.Contains("5", content, StringComparison.OrdinalIgnoreCase);
315317
}
316318

317-
[RetryTheory]
318-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
319-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
320-
public async Task ChatGenerationAutoInvokeShouldCallFunctionWithEnumParameterAndReturnResponseAsync(ServiceType serviceType)
319+
[RetryTheory(Skip = SkipMessage)]
320+
[InlineData(ServiceType.GoogleAI, true)]
321+
[InlineData(ServiceType.VertexAI, false)]
322+
public async Task ChatGenerationAutoInvokeShouldCallFunctionWithEnumParameterAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
321323
{
322324
// Arrange
323325
var kernel = new Kernel();
324326
var timeProvider = new FakeTimeProvider();
325327
timeProvider.SetUtcNow(new DateTimeOffset(new DateTime(2024, 4, 24))); // Wednesday
326328
var timePlugin = new TimePlugin(timeProvider);
327329
kernel.ImportPluginFromObject(timePlugin, nameof(TimePlugin));
328-
var sut = this.GetChatService(serviceType);
330+
var sut = this.GetChatService(serviceType, isBeta);
329331
var chatHistory = new ChatHistory();
330332
chatHistory.AddUserMessage("When was last friday? Show the date in format DD.MM.YYYY for example: 15.07.2019");
331333
var executionSettings = new GeminiPromptExecutionSettings()
@@ -342,18 +344,18 @@ public async Task ChatGenerationAutoInvokeShouldCallFunctionWithEnumParameterAnd
342344
Assert.Contains("19.04.2024", response.Content, StringComparison.OrdinalIgnoreCase);
343345
}
344346

345-
[RetryTheory]
346-
[InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
347-
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
348-
public async Task ChatStreamingAutoInvokeShouldCallFunctionWithEnumParameterAndReturnResponseAsync(ServiceType serviceType)
347+
[RetryTheory(Skip = SkipMessage)]
348+
[InlineData(ServiceType.GoogleAI, true)]
349+
[InlineData(ServiceType.VertexAI, false)]
350+
public async Task ChatStreamingAutoInvokeShouldCallFunctionWithEnumParameterAndReturnResponseAsync(ServiceType serviceType, bool isBeta)
349351
{
350352
// Arrange
351353
var kernel = new Kernel();
352354
var timeProvider = new FakeTimeProvider();
353355
timeProvider.SetUtcNow(new DateTimeOffset(new DateTime(2024, 4, 24))); // Wednesday
354356
var timePlugin = new TimePlugin(timeProvider);
355357
kernel.ImportPluginFromObject(timePlugin, nameof(TimePlugin));
356-
var sut = this.GetChatService(serviceType);
358+
var sut = this.GetChatService(serviceType, isBeta);
357359
var chatHistory = new ChatHistory();
358360
chatHistory.AddUserMessage("When was last friday? Show the date in format DD.MM.YYYY for example: 15.07.2019");
359361
var executionSettings = new GeminiPromptExecutionSettings()

0 commit comments

Comments
 (0)