// Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Agents.AI.Compaction; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.UnitTests.Compaction; /// /// Contains tests for the class. /// public class SlidingWindowCompactionStrategyTests { [Fact] public async Task CompactAsyncBelowMaxTurnsReturnsFalseAsync() { // Arrange SlidingWindowCompactionStrategy strategy = new(maximumTurns: 3); MessageIndex groups = MessageIndex.Create( [ new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.Assistant, "A1"), new ChatMessage(ChatRole.User, "Q2"), new ChatMessage(ChatRole.Assistant, "A2"), ]); // Act bool result = await strategy.CompactAsync(groups); // Assert Assert.False(result); } [Fact] public async Task CompactAsyncExceedsMaxTurnsExcludesOldestTurnsAsync() { // Arrange — keep 2 turns, conversation has 3 SlidingWindowCompactionStrategy strategy = new(maximumTurns: 2); MessageIndex groups = 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(groups); // Assert Assert.True(result); // Turn 1 (Q1 + A1) should be excluded Assert.True(groups.Groups[0].IsExcluded); Assert.True(groups.Groups[1].IsExcluded); // Turn 2 and 3 should remain Assert.False(groups.Groups[2].IsExcluded); Assert.False(groups.Groups[3].IsExcluded); Assert.False(groups.Groups[4].IsExcluded); Assert.False(groups.Groups[5].IsExcluded); } [Fact] public async Task CompactAsyncPreservesSystemMessagesAsync() { // Arrange SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1); MessageIndex groups = MessageIndex.Create( [ new ChatMessage(ChatRole.System, "You are helpful."), new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.Assistant, "A1"), new ChatMessage(ChatRole.User, "Q2"), ]); // Act bool result = await strategy.CompactAsync(groups); // Assert Assert.True(result); Assert.False(groups.Groups[0].IsExcluded); // System preserved Assert.True(groups.Groups[1].IsExcluded); // Turn 1 excluded Assert.True(groups.Groups[2].IsExcluded); // Turn 1 response excluded Assert.False(groups.Groups[3].IsExcluded); // Turn 2 kept } [Fact] public async Task CompactAsyncPreservesToolCallGroupsInKeptTurnsAsync() { // Arrange SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1); MessageIndex groups = MessageIndex.Create( [ new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.Assistant, "A1"), new ChatMessage(ChatRole.User, "Q2"), new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("call1", "search")]), new ChatMessage(ChatRole.Tool, "Results"), ]); // Act bool result = await strategy.CompactAsync(groups); // Assert Assert.True(result); // Turn 1 excluded Assert.True(groups.Groups[0].IsExcluded); Assert.True(groups.Groups[1].IsExcluded); // Turn 2 kept (user + tool call group) Assert.False(groups.Groups[2].IsExcluded); Assert.False(groups.Groups[3].IsExcluded); } [Fact] public async Task CompactAsyncCustomTriggerOverridesDefaultAsync() { // Arrange — custom trigger: only compact when tokens exceed threshold SlidingWindowCompactionStrategy strategy = new(maximumTurns: 99); MessageIndex groups = MessageIndex.Create( [ new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.User, "Q2"), new ChatMessage(ChatRole.User, "Q3"), ]); // Act — tokens are tiny, trigger not met bool result = await strategy.CompactAsync(groups); // Assert Assert.False(result); } [Fact] public async Task CompactAsyncIncludedMessagesContainOnlyKeptTurnsAsync() { // Arrange SlidingWindowCompactionStrategy strategy = new(maximumTurns: 1); MessageIndex groups = MessageIndex.Create( [ new ChatMessage(ChatRole.System, "System"), new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.Assistant, "A1"), new ChatMessage(ChatRole.User, "Q2"), new ChatMessage(ChatRole.Assistant, "A2"), ]); // Act await strategy.CompactAsync(groups); // Assert List included = [.. groups.GetIncludedMessages()]; Assert.Equal(3, included.Count); Assert.Equal("System", included[0].Text); Assert.Equal("Q2", included[1].Text); Assert.Equal("A2", included[2].Text); } [Fact] public async Task CompactAsyncCustomTargetStopsExcludingEarlyAsync() { // Arrange — 4 turns, maxTurns=1 means 3 should be excluded // But custom target stops after removing 1 turn int removeCount = 0; CompactionTrigger targetAfterOne = _ => ++removeCount >= 1; SlidingWindowCompactionStrategy strategy = new( maximumTurns: 1, target: targetAfterOne); 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"), new ChatMessage(ChatRole.User, "Q4"), ]); // Act bool result = await strategy.CompactAsync(index); // Assert — only turn 1 excluded (target stopped after 1 removal) Assert.True(result); Assert.True(index.Groups[0].IsExcluded); // Q1 (turn 1) Assert.True(index.Groups[1].IsExcluded); // A1 (turn 1) Assert.False(index.Groups[2].IsExcluded); // Q2 (turn 2) — kept Assert.False(index.Groups[3].IsExcluded); // A2 (turn 2) } }