diff --git a/dotnet/src/SemanticKernel.Core/AI/ChatCompletion/ChatHistoryReducerExtensions.cs b/dotnet/src/SemanticKernel.Core/AI/ChatCompletion/ChatHistoryReducerExtensions.cs
index 2a01909a2c5c..4b8fa61b4511 100644
--- a/dotnet/src/SemanticKernel.Core/AI/ChatCompletion/ChatHistoryReducerExtensions.cs
+++ b/dotnet/src/SemanticKernel.Core/AI/ChatCompletion/ChatHistoryReducerExtensions.cs
@@ -104,7 +104,8 @@ public static int LocateSafeReductionIndex(
int offsetCount = 0,
bool hasSystemMessage = false)
{
- targetCount -= hasSystemMessage ? 1 : 0;
+ // The system message counts toward the target, but at least one other message is always retained
+ targetCount = Math.Max(1, targetCount - (hasSystemMessage ? 1 : 0));
// Compute the index of the truncation threshold
int thresholdIndex = chatHistory.Count - (thresholdCount ?? 0) - targetCount;
diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistorySummarizationReducerTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistorySummarizationReducerTests.cs
index 0588055efc19..5db41bc01dd1 100644
--- a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistorySummarizationReducerTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistorySummarizationReducerTests.cs
@@ -245,6 +245,27 @@ public async Task VerifySystemMessageIsNotReducedAsync()
Assert.Contains(messages, m => m.Role == AuthorRole.System);
}
+ ///
+ /// Validate history summarized and the latest message retained when the target count is one and a system message is present.
+ ///
+ [Fact]
+ public async Task VerifySystemMessageWithSingleTargetCountAsync()
+ {
+ // Arrange
+ Mock mockCompletionService = this.CreateMockCompletionService();
+ IReadOnlyList sourceHistory = MockChatHistoryGenerator.CreateSimpleHistory(20, includeSystemMessage: true).ToArray();
+ ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 1);
+
+ // Act
+ IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory);
+
+ // Assert
+ ChatMessageContent[] messages = VerifyReducedHistory(reducedHistory, 3);
+ Assert.Equal(AuthorRole.System, messages[0].Role);
+ VerifySummarization(messages[1]);
+ Assert.Same(sourceHistory[^1], messages[2]);
+ }
+
private static ChatMessageContent[] VerifyReducedHistory(IEnumerable? reducedHistory, int expectedCount)
{
Assert.NotNull(reducedHistory);
diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistoryTruncationReducerTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistoryTruncationReducerTests.cs
index 650648dadc17..73fe57d688d7 100644
--- a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistoryTruncationReducerTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatHistoryTruncationReducerTests.cs
@@ -155,6 +155,25 @@ public async Task VerifySystemMessageIsNotReducedAsync()
Assert.Contains(reducedHistory!, m => m.Role == AuthorRole.System);
}
+ ///
+ /// Validate history reduced to the system message and the latest message when the target count is one.
+ ///
+ [Fact]
+ public async Task VerifySystemMessageWithSingleTargetCountAsync()
+ {
+ // Arrange
+ IReadOnlyList sourceHistory = MockChatHistoryGenerator.CreateSimpleHistory(20, includeSystemMessage: true).ToArray();
+ ChatHistoryTruncationReducer reducer = new(1);
+
+ // Act
+ IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory);
+
+ // Assert
+ VerifyReducedHistory(reducedHistory, 2);
+ Assert.Equal(AuthorRole.System, reducedHistory!.First().Role);
+ Assert.Same(sourceHistory[^1], reducedHistory!.Last());
+ }
+
private static void VerifyReducedHistory(IEnumerable? reducedHistory, int expectedCount)
{
Assert.NotNull(reducedHistory);