mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Test update
This commit is contained in:
@@ -20,6 +20,12 @@ public static class CompactionTriggers
|
||||
public static readonly CompactionTrigger Always =
|
||||
_ => true;
|
||||
|
||||
/// <summary>
|
||||
/// Always trigger compaction, regardless of the message index state.
|
||||
/// </summary>
|
||||
public static readonly CompactionTrigger Never =
|
||||
_ => false;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a trigger that fires when the included token count is below the specified maximum.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
@@ -348,7 +347,7 @@ public sealed class CompactingChatClientTests : IDisposable
|
||||
SetRunContext(session);
|
||||
|
||||
// Use an IEnumerable (not a List) to trigger the copy path
|
||||
IEnumerable<ChatMessage> messages = new ChatMessage[] { new(ChatRole.User, "Hello") };
|
||||
IEnumerable<ChatMessage> messages = [new(ChatRole.User, "Hello")];
|
||||
|
||||
// Act
|
||||
ChatResponse response = await client.GetResponseAsync(messages);
|
||||
@@ -380,7 +379,7 @@ public sealed class CompactingChatClientTests : IDisposable
|
||||
AgentRunContext context = new(
|
||||
mockAgent.Object,
|
||||
session,
|
||||
new List<ChatMessage> { new(ChatRole.User, "test") },
|
||||
[new(ChatRole.User, "test")],
|
||||
null);
|
||||
SetCurrentRunContext(context);
|
||||
}
|
||||
|
||||
@@ -800,6 +800,68 @@ public class MessageIndexTests
|
||||
Assert.Equal(4, inserted.TokenCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateWithStandaloneToolMessageGroupsAsAssistantText()
|
||||
{
|
||||
// A Tool message not preceded by an assistant tool-call falls through to the else branch
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.Tool, "Orphaned tool result"),
|
||||
];
|
||||
|
||||
MessageIndex index = MessageIndex.Create(messages);
|
||||
|
||||
// The Tool message should be grouped as AssistantText (the default fallback)
|
||||
Assert.Single(index.Groups);
|
||||
Assert.Equal(MessageGroupKind.AssistantText, index.Groups[0].Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateWithAssistantNonSummaryWithPropertiesFallsToAssistantText()
|
||||
{
|
||||
// Assistant message with AdditionalProperties but NOT a summary
|
||||
ChatMessage assistant = new(ChatRole.Assistant, "Regular response");
|
||||
(assistant.AdditionalProperties ??= [])["someOtherKey"] = "value";
|
||||
|
||||
MessageIndex index = MessageIndex.Create([assistant]);
|
||||
|
||||
Assert.Single(index.Groups);
|
||||
Assert.Equal(MessageGroupKind.AssistantText, index.Groups[0].Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeByteCountHandlesNullAndNonNullText()
|
||||
{
|
||||
// Mix of messages: one with text (non-null), one without (null Text)
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Hello"),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "fn")]),
|
||||
];
|
||||
|
||||
int byteCount = MessageIndex.ComputeByteCount(messages);
|
||||
|
||||
// Only "Hello" contributes bytes (5 bytes UTF-8)
|
||||
Assert.Equal(5, byteCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeTokenCountHandlesNullAndNonNullText()
|
||||
{
|
||||
// Mix: one with text, one without
|
||||
SimpleWordTokenizer tokenizer = new();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Hello world"),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "fn")]),
|
||||
];
|
||||
|
||||
int tokenCount = MessageIndex.ComputeTokenCount(messages, tokenizer);
|
||||
|
||||
// Only "Hello world" contributes tokens (2 words)
|
||||
Assert.Equal(2, tokenCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple tokenizer that counts whitespace-separated words as tokens.
|
||||
/// </summary>
|
||||
|
||||
+26
@@ -221,4 +221,30 @@ public class SlidingWindowCompactionStrategyTests
|
||||
Assert.False(index.Groups[4].IsExcluded); // Q3
|
||||
Assert.False(index.Groups[5].IsExcluded); // A3
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncSkipsExcludedAndSystemGroupsInEnumerationAsync()
|
||||
{
|
||||
// Arrange — includes system and pre-excluded groups that must be skipped
|
||||
SlidingWindowCompactionStrategy strategy = new(
|
||||
CompactionTriggers.TurnsExceed(1),
|
||||
minimumPreserved: 0);
|
||||
|
||||
MessageIndex index = MessageIndex.Create(
|
||||
[
|
||||
new ChatMessage(ChatRole.System, "System prompt"),
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.Assistant, "A1"),
|
||||
new ChatMessage(ChatRole.User, "Q2"),
|
||||
]);
|
||||
// Pre-exclude one group
|
||||
index.Groups[1].IsExcluded = true;
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert — system preserved, pre-excluded skipped
|
||||
Assert.True(result);
|
||||
Assert.False(index.Groups[0].IsExcluded); // System preserved
|
||||
}
|
||||
}
|
||||
|
||||
+111
-1
@@ -246,7 +246,7 @@ public class SummarizationCompactionStrategyTests
|
||||
{
|
||||
// Arrange — 4 non-system groups, preserve 1, target met after 1 exclusion
|
||||
int exclusionCount = 0;
|
||||
CompactionTrigger TargetAfterOne = _ => ++exclusionCount >= 1;
|
||||
bool TargetAfterOne(MessageIndex _) => ++exclusionCount >= 1;
|
||||
|
||||
SummarizationCompactionStrategy strategy = new(
|
||||
CreateMockChatClient("Partial summary."),
|
||||
@@ -297,4 +297,114 @@ public class SummarizationCompactionStrategyTests
|
||||
Assert.Equal("Q2", included[1].Text);
|
||||
Assert.Equal("A2", included[2].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncWithSystemBetweenSummarizableGroupsAsync()
|
||||
{
|
||||
// Arrange — system group between user/assistant groups to exercise skip logic in loop
|
||||
IChatClient mockClient = CreateMockChatClient("[Summary]");
|
||||
SummarizationCompactionStrategy strategy = new(
|
||||
mockClient,
|
||||
CompactionTriggers.Always,
|
||||
minimumPreserved: 1);
|
||||
|
||||
MessageIndex index = MessageIndex.Create(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.System, "System note"),
|
||||
new ChatMessage(ChatRole.Assistant, "A1"),
|
||||
new ChatMessage(ChatRole.User, "Q2"),
|
||||
]);
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert — summary inserted at 0, system group shifted to index 2
|
||||
Assert.True(result);
|
||||
Assert.Equal(MessageGroupKind.Summary, index.Groups[0].Kind);
|
||||
Assert.Equal(MessageGroupKind.System, index.Groups[2].Kind);
|
||||
Assert.False(index.Groups[2].IsExcluded); // System never excluded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncMaxSummarizableBoundsLoopExitAsync()
|
||||
{
|
||||
// Arrange — large MinimumPreserved so maxSummarizable is small, target never stops
|
||||
IChatClient mockClient = CreateMockChatClient("[Summary]");
|
||||
SummarizationCompactionStrategy strategy = new(
|
||||
mockClient,
|
||||
CompactionTriggers.Always,
|
||||
minimumPreserved: 3,
|
||||
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 — should only summarize 6-3 = 3 groups (not all 6)
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert — 3 preserved + 1 summary = 4 included
|
||||
Assert.True(result);
|
||||
Assert.Equal(4, index.IncludedGroupCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncWithPreExcludedGroupAsync()
|
||||
{
|
||||
// Arrange — pre-exclude a group so the count and loop both must skip it
|
||||
IChatClient mockClient = CreateMockChatClient("[Summary]");
|
||||
SummarizationCompactionStrategy strategy = new(
|
||||
mockClient,
|
||||
CompactionTriggers.Always,
|
||||
minimumPreserved: 1);
|
||||
|
||||
MessageIndex index = MessageIndex.Create(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.Assistant, "A1"),
|
||||
new ChatMessage(ChatRole.User, "Q2"),
|
||||
new ChatMessage(ChatRole.Assistant, "A2"),
|
||||
]);
|
||||
index.Groups[0].IsExcluded = true; // Pre-exclude Q1
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(index.Groups[0].IsExcluded); // Still excluded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncWithEmptyTextMessageInGroupAsync()
|
||||
{
|
||||
// Arrange — a message with null text (FunctionCallContent) in a summarized group
|
||||
IChatClient mockClient = CreateMockChatClient("[Summary]");
|
||||
SummarizationCompactionStrategy strategy = new(
|
||||
mockClient,
|
||||
CompactionTriggers.Always,
|
||||
minimumPreserved: 1);
|
||||
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "fn")]),
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.Assistant, "A1"),
|
||||
];
|
||||
|
||||
MessageIndex index = MessageIndex.Create(messages);
|
||||
|
||||
// Act — the tool-call group's message has null text
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert — compaction succeeded despite null text
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -233,4 +233,30 @@ public class ToolResultCompactionStrategyTests
|
||||
|
||||
Assert.Equal(1, collapsedToolGroups);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncSkipsPreExcludedAndSystemGroupsAsync()
|
||||
{
|
||||
// Arrange — pre-excluded and system groups in the enumeration
|
||||
ToolResultCompactionStrategy strategy = new(CompactionTriggers.Always, minimumPreserved: 0);
|
||||
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.System, "System prompt"),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "fn")]),
|
||||
new ChatMessage(ChatRole.Tool, "Result 1"),
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
];
|
||||
|
||||
MessageIndex index = MessageIndex.Create(messages);
|
||||
// Pre-exclude the user group
|
||||
index.Groups[index.Groups.Count - 1].IsExcluded = true;
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(index);
|
||||
|
||||
// Assert — system never excluded, pre-excluded skipped
|
||||
Assert.True(result);
|
||||
Assert.False(index.Groups[0].IsExcluded); // System stays
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -274,4 +274,55 @@ public class TruncationCompactionStrategyTests
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, groups.IncludedGroupCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncLoopExitsWhenMaxRemovableReachedAsync()
|
||||
{
|
||||
// Arrange — target never stops (always false), so the loop must exit via removed >= maxRemovable
|
||||
TruncationCompactionStrategy strategy = new(CompactionTriggers.Always, minimumPreserved: 2, target: CompactionTriggers.Never);
|
||||
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 — only 2 removed (maxRemovable = 4 - 2 = 2), 2 preserved
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, groups.IncludedGroupCount);
|
||||
Assert.True(groups.Groups[0].IsExcluded);
|
||||
Assert.True(groups.Groups[1].IsExcluded);
|
||||
Assert.False(groups.Groups[2].IsExcluded);
|
||||
Assert.False(groups.Groups[3].IsExcluded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncSkipsPreExcludedAndSystemGroupsAsync()
|
||||
{
|
||||
// Arrange — has excluded + system groups that the loop must skip
|
||||
TruncationCompactionStrategy strategy = new(CompactionTriggers.Always, minimumPreserved: 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"),
|
||||
]);
|
||||
// Pre-exclude one group
|
||||
groups.Groups[1].IsExcluded = true;
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(groups);
|
||||
|
||||
// Assert — system preserved, pre-excluded skipped, A1 removed, Q2 preserved
|
||||
Assert.True(result);
|
||||
Assert.False(groups.Groups[0].IsExcluded); // System
|
||||
Assert.True(groups.Groups[1].IsExcluded); // Pre-excluded Q1
|
||||
Assert.True(groups.Groups[2].IsExcluded); // Newly excluded A1
|
||||
Assert.False(groups.Groups[3].IsExcluded); // Preserved Q2
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user