From 6f303959a66814198eaa6baf1ac1e84ca9dc034b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:35:21 +0000 Subject: [PATCH 1/2] Initial plan From 3d1a5db84fa4d27c495d84efc7bf66a564209303 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:48:41 +0000 Subject: [PATCH 2/2] Fix Google Gemini API format validation by stripping unsupported format fields Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> --- .../Core/Gemini/GeminiRequestTests.cs | 80 +++++++++++++++++++ .../Core/Gemini/Models/GeminiRequest.cs | 20 ++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs index 877b80debf67..6d74f3a91e7e 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs @@ -694,6 +694,86 @@ public void FromPromptAndExecutionSettingsWithThinkingConfigReturnsInGenerationC Assert.Equal(executionSettings.ThinkingConfig.ThinkingBudget, request.Configuration?.ThinkingConfig?.ThinkingBudget); } + [Fact] + public void ResponseSchemaStripsUnsupportedFormatFields() + { + // Arrange + var prompt = "prompt-example"; + var schemaWithUnsupportedFormats = """ + { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "unique identifier" + }, + "email": { + "type": "string", + "format": "email", + "description": "user email" + }, + "status": { + "type": "string", + "format": "enum", + "enum": ["active", "inactive"], + "description": "user status" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "creation timestamp" + }, + "phoneNumber": { + "type": "string", + "format": "phone", + "description": "phone number" + } + } + } + """; + + var executionSettings = new GeminiPromptExecutionSettings + { + ResponseMimeType = "application/json", + ResponseSchema = JsonSerializer.Deserialize(schemaWithUnsupportedFormats) + }; + + // Act + var request = GeminiRequest.FromPromptAndExecutionSettings(prompt, executionSettings); + + // Assert + Assert.NotNull(request.Configuration?.ResponseSchema); + var properties = request.Configuration.ResponseSchema.Value.GetProperty("properties"); + + // UUID format should be stripped + var idProperty = properties.GetProperty("id"); + Assert.Equal("string", idProperty.GetProperty("type").GetString()); + Assert.False(idProperty.TryGetProperty("format", out _)); + + // Email format should be stripped + var emailProperty = properties.GetProperty("email"); + Assert.Equal("string", emailProperty.GetProperty("type").GetString()); + Assert.False(emailProperty.TryGetProperty("format", out _)); + + // Enum format should be preserved + var statusProperty = properties.GetProperty("status"); + Assert.Equal("string", statusProperty.GetProperty("type").GetString()); + Assert.True(statusProperty.TryGetProperty("format", out var statusFormat)); + Assert.Equal("enum", statusFormat.GetString()); + + // Date-time format should be preserved + var createdAtProperty = properties.GetProperty("createdAt"); + Assert.Equal("string", createdAtProperty.GetProperty("type").GetString()); + Assert.True(createdAtProperty.TryGetProperty("format", out var createdAtFormat)); + Assert.Equal("date-time", createdAtFormat.GetString()); + + // Phone format should be stripped + var phoneProperty = properties.GetProperty("phoneNumber"); + Assert.Equal("string", phoneProperty.GetProperty("type").GetString()); + Assert.False(phoneProperty.TryGetProperty("format", out _)); + } + private sealed class DummyContent(object? innerContent, string? modelId = null, IReadOnlyDictionary? metadata = null) : KernelContent(innerContent, modelId, metadata); diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs index 5d4b917ee1e7..ad933e0cfd52 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs @@ -375,7 +375,23 @@ static void TransformOpenApi3Object(JsonObject obj) { propertyObj["type"] = JsonValue.Create("string"); } - else if (propertyObj.TryGetPropertyValue("type", out JsonNode? typeNode)) + + // Strip unsupported format fields for string types + // Google API only supports "enum" and "date-time" formats for string types + if (propertyObj.TryGetPropertyValue("type", out JsonNode? typeNode) && + typeNode is JsonValue typeValue && + typeValue.GetValue() == "string" && + propertyObj.TryGetPropertyValue("format", out JsonNode? formatNode) && + formatNode is JsonValue formatValue) + { + var format = formatValue.GetValue(); + if (format != "enum" && format != "date-time") + { + propertyObj.Remove("format"); + } + } + + if (propertyObj.TryGetPropertyValue("type", out typeNode)) { if (typeNode is JsonArray typeArray) { @@ -387,7 +403,7 @@ static void TransformOpenApi3Object(JsonObject obj) propertyObj["nullable"] = JsonValue.Create(true); } } - else if (typeNode is JsonValue typeValue && typeValue.GetValue() == "array") + else if (typeNode is JsonValue typeVal && typeVal.GetValue() == "array") { if (propertyObj.TryGetPropertyValue("items", out JsonNode? itemsNode) && itemsNode is JsonObject itemsObj) {