From 55cd68dc22063d6d93dbc01ad1d627b32133bb9b Mon Sep 17 00:00:00 2001 From: icyca Date: Thu, 23 Oct 2025 14:05:11 +0300 Subject: [PATCH 1/2] Fix #13262: GeminiRequest to handle single turn requests correctly Updated the `CreateGeminiRequest` to ensure that single turn requests end with a "user" role or no role --- .../Core/Gemini/Models/GeminiRequest.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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..042b00a4394d 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs @@ -119,11 +119,19 @@ private static GeminiRequest CreateGeminiRequest(string prompt) private static GeminiRequest CreateGeminiRequest(ChatHistory chatHistory) { + var contents = chatHistory + .Where(message => message.Role != AuthorRole.System) + .Select(CreateGeminiContentFromChatMessage).ToList(); + + // Gemini specific fix: single turn requests must end with "user" role or no role, prevents issue #13262 + if (contents.Count == 1 && contents[0].Role == AuthorRole.Assistant) + { + contents[0].Role = null; + } + GeminiRequest obj = new() { - Contents = chatHistory - .Where(message => message.Role != AuthorRole.System) - .Select(CreateGeminiContentFromChatMessage).ToList(), + Contents = contents, SystemInstruction = CreateSystemMessages(chatHistory) }; return obj; From 7c89b61e28c2c0166866b4b5d1395e9777d52f4e Mon Sep 17 00:00:00 2001 From: icyca Date: Mon, 10 Nov 2025 21:40:47 +0200 Subject: [PATCH 2/2] Add unit tests for GeminiRequest handling of chat history roles --- .../Core/Gemini/GeminiRequestTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) 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 31ce6e9946fd..73082cdc2c5f 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,49 @@ public void FromPromptAndExecutionSettingsWithThinkingConfigReturnsInGenerationC Assert.Equal(executionSettings.ThinkingConfig.ThinkingBudget, request.Configuration?.ThinkingConfig?.ThinkingBudget); } + [Fact] + public void FromChatHistorySingleAssistantMessageSetsRoleToNull() + { + // Arrange - Single assistant message (issue #13262 scenario) + ChatHistory chatHistory = []; + chatHistory.AddAssistantMessage("assistant-message"); + var executionSettings = new GeminiPromptExecutionSettings(); + + // Act + var request = GeminiRequest.FromChatHistoryAndExecutionSettings(chatHistory, executionSettings); + + // Assert - Role should be null to fix issue #13262 (Gemini requires single-turn requests to end with user role or no role) + Assert.Single(request.Contents); + Assert.Null(request.Contents[0].Role); + Assert.Equal("assistant-message", request.Contents[0].Parts![0].Text); + } + + [Fact] + public void FromChatHistoryMultiTurnConversationPreservesAllRoles() + { + // Arrange - Multi-turn conversation should not be affected by the fix + ChatHistory chatHistory = []; + chatHistory.AddUserMessage("user-message-1"); + chatHistory.AddAssistantMessage("assistant-message-1"); + chatHistory.AddUserMessage("user-message-2"); + chatHistory.AddAssistantMessage("assistant-message-2"); + var executionSettings = new GeminiPromptExecutionSettings(); + + // Act + var request = GeminiRequest.FromChatHistoryAndExecutionSettings(chatHistory, executionSettings); + + // Assert - All roles should be preserved in multi-turn conversations + Assert.Equal(4, request.Contents.Count); + Assert.Equal(AuthorRole.User, request.Contents[0].Role); + Assert.Equal(AuthorRole.Assistant, request.Contents[1].Role); + Assert.Equal(AuthorRole.User, request.Contents[2].Role); + Assert.Equal(AuthorRole.Assistant, request.Contents[3].Role); + Assert.Equal("user-message-1", request.Contents[0].Parts![0].Text); + Assert.Equal("assistant-message-1", request.Contents[1].Parts![0].Text); + Assert.Equal("user-message-2", request.Contents[2].Parts![0].Text); + Assert.Equal("assistant-message-2", request.Contents[3].Parts![0].Text); + } + private sealed class DummyContent(object? innerContent, string? modelId = null, IReadOnlyDictionary? metadata = null) : KernelContent(innerContent, modelId, metadata);