This commit is contained in:
Chris Rickman
2026-03-05 03:10:40 -08:00
Unverified
parent 6ce0447ff6
commit 7e2c5ad4e6
9 changed files with 181 additions and 108 deletions
@@ -15,8 +15,8 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncBelowMaxTurnsReturnsFalseAsync()
{
// Arrange
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 3);
// Arrange — trigger requires > 3 turns, conversation has 2
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(3));
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
@@ -35,8 +35,8 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncExceedsMaxTurnsExcludesOldestTurnsAsync()
{
// Arrange — keep 2 turns, conversation has 3
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 2);
// Arrange — trigger on > 2 turns, conversation has 3
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(2));
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
@@ -65,8 +65,8 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncPreservesSystemMessagesAsync()
{
// Arrange
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1);
// Arrange — trigger on > 1 turn
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(1));
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "You are helpful."),
@@ -89,8 +89,8 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncPreservesToolCallGroupsInKeptTurnsAsync()
{
// Arrange
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1);
// Arrange — trigger on > 1 turn
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(1));
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
@@ -114,10 +114,10 @@ public class SlidingWindowCompactionStrategyTests
}
[Fact]
public async Task CompactAsyncCustomTriggerOverridesDefaultAsync()
public async Task CompactAsyncTriggerNotMetReturnsFalseAsync()
{
// Arrange — custom trigger: only compact when tokens exceed threshold
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 99);
// Arrange — trigger requires > 99 turns
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(99));
MessageIndex groups = MessageIndex.Create(
[
@@ -126,7 +126,7 @@ public class SlidingWindowCompactionStrategyTests
new ChatMessage(ChatRole.User, "Q3"),
]);
// Act — tokens are tiny, trigger not met
// Act
bool result = await strategy.CompactAsync(groups);
// Assert
@@ -136,8 +136,8 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncIncludedMessagesContainOnlyKeptTurnsAsync()
{
// Arrange
SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1);
// Arrange — trigger on > 1 turn
SlidingWindowCompactionStrategy strategy = new(CompactionTriggers.TurnsExceed(1));
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "System"),
@@ -161,13 +161,13 @@ public class SlidingWindowCompactionStrategyTests
[Fact]
public async Task CompactAsyncCustomTargetStopsExcludingEarlyAsync()
{
// Arrange — 4 turns, maxTurns=1 means 3 should be excluded
// But custom target stops after removing 1 turn
// Arrange — trigger on > 1 turn, custom target stops after removing 1 turn
int removeCount = 0;
CompactionTrigger targetAfterOne = _ => ++removeCount >= 1;
SlidingWindowCompactionStrategy strategy = new(
maximumTurns: 1,
CompactionTriggers.TurnsExceed(1),
minimumPreserved: 0,
target: targetAfterOne);
MessageIndex index = MessageIndex.Create(
@@ -191,4 +191,34 @@ public class SlidingWindowCompactionStrategyTests
Assert.False(index.Groups[2].IsExcluded); // Q2 (turn 2) — kept
Assert.False(index.Groups[3].IsExcluded); // A2 (turn 2)
}
[Fact]
public async Task CompactAsyncMinimumPreservedStopsCompactionAsync()
{
// Arrange — always trigger with never-satisfied target, but MinimumPreserved = 2 is hard floor
SlidingWindowCompactionStrategy strategy = new(
CompactionTriggers.TurnsExceed(1),
minimumPreserved: 2,
target: _ => false);
MessageIndex index = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
new ChatMessage(ChatRole.Assistant, "A1"),
new ChatMessage(ChatRole.User, "Q2"),
new ChatMessage(ChatRole.Assistant, "A2"),
new ChatMessage(ChatRole.User, "Q3"),
new ChatMessage(ChatRole.Assistant, "A3"),
]);
// Act
bool result = await strategy.CompactAsync(index);
// Assert — target never says stop, but MinimumPreserved=2 prevents removing the last 2 groups
Assert.True(result);
Assert.Equal(2, index.IncludedGroupCount);
// Last 2 non-system groups must be preserved
Assert.False(index.Groups[4].IsExcluded); // Q3
Assert.False(index.Groups[5].IsExcluded); // A3
}
}
@@ -38,7 +38,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient(),
CompactionTriggers.TokensExceed(100000),
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -61,7 +61,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient("Key facts from earlier."),
AlwaysTrigger,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -92,7 +92,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient(),
AlwaysTrigger,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -119,7 +119,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient("Summary text."),
AlwaysTrigger,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -146,7 +146,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient(" "),
AlwaysTrigger,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -169,7 +169,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient(),
AlwaysTrigger,
preserveRecentGroups: 5);
minimumPreserved: 5);
MessageIndex index = MessageIndex.Create(
[
@@ -202,7 +202,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
mockClient.Object,
AlwaysTrigger,
preserveRecentGroups: 1,
minimumPreserved: 1,
summarizationPrompt: customPrompt);
MessageIndex index = MessageIndex.Create(
@@ -226,7 +226,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient(),
AlwaysTrigger,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex index = MessageIndex.Create(
[
@@ -253,7 +253,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient("Partial summary."),
AlwaysTrigger,
preserveRecentGroups: 1,
minimumPreserved: 1,
target: targetAfterOne);
MessageIndex index = MessageIndex.Create(
@@ -279,7 +279,7 @@ public class SummarizationCompactionStrategyTests
SummarizationCompactionStrategy strategy = new(
CreateMockChatClient("Summary."),
AlwaysTrigger,
preserveRecentGroups: 2);
minimumPreserved: 2);
MessageIndex index = MessageIndex.Create(
[
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -41,7 +41,7 @@ public class ToolResultCompactionStrategyTests
// Arrange — always trigger
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
@@ -71,7 +71,7 @@ public class ToolResultCompactionStrategyTests
// Arrange — protect 2 recent non-system groups (the tool group + Q2)
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 3);
minimumPreserved: 3);
MessageIndex groups = MessageIndex.Create(
[
@@ -94,7 +94,7 @@ public class ToolResultCompactionStrategyTests
// Arrange
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
@@ -119,7 +119,7 @@ public class ToolResultCompactionStrategyTests
// Arrange — assistant calls two tools
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 1);
minimumPreserved: 1);
ChatMessage multiToolCall = new(ChatRole.Assistant,
[
@@ -152,7 +152,7 @@ public class ToolResultCompactionStrategyTests
// Arrange — trigger fires but no tool groups to collapse
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 0);
minimumPreserved: 0);
MessageIndex groups = MessageIndex.Create(
[
@@ -175,7 +175,7 @@ public class ToolResultCompactionStrategyTests
CompactionTriggers.All(
CompactionTriggers.TokensExceed(0),
CompactionTriggers.HasToolCalls()),
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
@@ -201,7 +201,7 @@ public class ToolResultCompactionStrategyTests
ToolResultCompactionStrategy strategy = new(
trigger: _ => true,
preserveRecentGroups: 1,
minimumPreserved: 1,
target: targetAfterOne);
MessageIndex index = MessageIndex.Create(
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using System.Threading.Tasks;
@@ -18,7 +18,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncAlwaysTriggerCompactsToPreserveRecentAsync()
{
// Arrange — always-trigger means always compact
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 1);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "First"),
@@ -39,7 +39,7 @@ public class TruncationCompactionStrategyTests
{
// Arrange — trigger requires > 1000 tokens, conversation is tiny
TruncationCompactionStrategy strategy = new(
preserveRecentGroups: 1,
minimumPreserved: 1,
trigger: CompactionTriggers.TokensExceed(1000));
MessageIndex groups = MessageIndex.Create(
@@ -61,7 +61,7 @@ public class TruncationCompactionStrategyTests
{
// Arrange — trigger on groups > 2
TruncationCompactionStrategy strategy = new(
preserveRecentGroups: 1,
minimumPreserved: 1,
trigger: CompactionTriggers.GroupsExceed(2));
MessageIndex groups = MessageIndex.Create(
@@ -89,7 +89,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncPreservesSystemMessagesAsync()
{
// Arrange
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 1);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "You are helpful."),
@@ -117,7 +117,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncPreservesToolCallGroupAtomicityAsync()
{
// Arrange
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 1);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 1);
ChatMessage assistantToolCall= new(ChatRole.Assistant, [new FunctionCallContent("call1", "get_weather")]);
ChatMessage toolResult = new(ChatRole.Tool, "Sunny");
@@ -141,7 +141,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncSetsExcludeReasonAsync()
{
// Arrange
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 1);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Old"),
@@ -160,7 +160,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncSkipsAlreadyExcludedGroupsAsync()
{
// Arrange
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 1);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Already excluded"),
@@ -180,10 +180,10 @@ public class TruncationCompactionStrategyTests
}
[Fact]
public async Task CompactAsyncPreserveRecentGroupsKeepsMultipleAsync()
public async Task CompactAsyncMinimumPreservedKeepsMultipleAsync()
{
// Arrange — keep 2 most recent
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 2);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 2);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
@@ -207,7 +207,7 @@ public class TruncationCompactionStrategyTests
public async Task CompactAsyncNothingToRemoveReturnsFalseAsync()
{
// Arrange — preserve 5 but only 2 groups
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, preserveRecentGroups: 5);
TruncationCompactionStrategy strategy = new(s_alwaysTrigger, minimumPreserved: 5);
MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Hello"),
@@ -230,7 +230,7 @@ public class TruncationCompactionStrategyTests
TruncationCompactionStrategy strategy = new(
s_alwaysTrigger,
preserveRecentGroups: 1,
minimumPreserved: 1,
target: targetAfterOne);
MessageIndex groups = MessageIndex.Create(
@@ -258,7 +258,7 @@ public class TruncationCompactionStrategyTests
// Arrange — trigger on groups > 2, target is default (inverse of trigger: groups <= 2)
TruncationCompactionStrategy strategy = new(
CompactionTriggers.GroupsExceed(2),
preserveRecentGroups: 1);
minimumPreserved: 1);
MessageIndex groups = MessageIndex.Create(
[