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); 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 b70ad9a0fbab..f2605ca7efbd 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;