mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Updated
This commit is contained in:
@@ -17,21 +17,18 @@ public enum CompactionApproach
|
||||
/// <summary>
|
||||
/// Applies the lightest available compaction techniques.
|
||||
/// Collapses old tool call groups into concise summaries and uses truncation as an emergency backstop.
|
||||
/// Does not require a summarization <see cref="Microsoft.Extensions.AI.IChatClient"/>.
|
||||
/// </summary>
|
||||
Gentle,
|
||||
|
||||
/// <summary>
|
||||
/// Balances context preservation with compaction efficiency.
|
||||
/// Applies tool result collapsing, LLM-based summarization, and truncation as an emergency backstop.
|
||||
/// Requires a summarization <see cref="Microsoft.Extensions.AI.IChatClient"/>.
|
||||
/// </summary>
|
||||
Balanced,
|
||||
|
||||
/// <summary>
|
||||
/// Applies the most aggressive available compaction techniques.
|
||||
/// Applies tool result collapsing, LLM-based summarization, turn-based sliding window, and truncation.
|
||||
/// Requires a summarization <see cref="Microsoft.Extensions.AI.IChatClient"/>.
|
||||
/// </summary>
|
||||
Aggressive,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Compaction;
|
||||
|
||||
@@ -62,16 +61,12 @@ public abstract partial class CompactionStrategy
|
||||
/// </remarks>
|
||||
/// <param name="approach">
|
||||
/// The compaction approach that controls which strategy or pipeline to use.
|
||||
/// <see cref="CompactionApproach.Gentle"/> does not require a <paramref name="chatClient"/>;
|
||||
/// <see cref="CompactionApproach.Balanced"/> and <see cref="CompactionApproach.Aggressive"/> require one.
|
||||
/// </param>
|
||||
/// <param name="size">
|
||||
/// The context-size profile that controls token and message thresholds.
|
||||
/// </param>
|
||||
/// <param name="chatClient">
|
||||
/// The <see cref="IChatClient"/> used for LLM-based summarization.
|
||||
/// Required when <paramref name="approach"/> is <see cref="CompactionApproach.Balanced"/> or
|
||||
/// <see cref="CompactionApproach.Aggressive"/>; ignored for <see cref="CompactionApproach.Gentle"/>.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="CompactionStrategy"/> configured for the specified approach and size.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
@@ -80,21 +75,16 @@ public abstract partial class CompactionStrategy
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="approach"/> or <paramref name="size"/> is not a defined enum value.
|
||||
/// </exception>
|
||||
public static CompactionStrategy Create(CompactionApproach approach, CompactionSize size, IChatClient? chatClient = null)
|
||||
public static CompactionStrategy Create(CompactionApproach approach, CompactionSize size, IChatClient chatClient)
|
||||
{
|
||||
if (approach is CompactionApproach.Balanced or CompactionApproach.Aggressive)
|
||||
{
|
||||
_ = Throw.IfNull(chatClient);
|
||||
}
|
||||
|
||||
int tokenLimit = GetTokenLimit(size);
|
||||
int messageLimit = GetMessageLimit(size);
|
||||
|
||||
return approach switch
|
||||
{
|
||||
CompactionApproach.Gentle => CreateGentlePipeline(tokenLimit, messageLimit),
|
||||
CompactionApproach.Balanced => CreateBalancedPipeline(tokenLimit, messageLimit, chatClient!),
|
||||
CompactionApproach.Aggressive => CreateAggressivePipeline(tokenLimit, messageLimit, GetTurnLimit(size), chatClient!),
|
||||
CompactionApproach.Gentle => CreateGentlePipeline(tokenLimit, messageLimit, chatClient),
|
||||
CompactionApproach.Balanced => CreateBalancedPipeline(tokenLimit, messageLimit, chatClient),
|
||||
CompactionApproach.Aggressive => CreateAggressivePipeline(tokenLimit, messageLimit, GetTurnLimit(size), chatClient),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(approach), approach, null),
|
||||
};
|
||||
}
|
||||
@@ -123,35 +113,59 @@ public abstract partial class CompactionStrategy
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
|
||||
};
|
||||
|
||||
private static PipelineCompactionStrategy CreateGentlePipeline(int tokenLimit, int messageLimit) =>
|
||||
new(
|
||||
new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(messageLimit)),
|
||||
new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
|
||||
private static PipelineCompactionStrategy CreateGentlePipeline(int tokenLimit, int messageLimit, IChatClient chatClient)
|
||||
{
|
||||
int messageTarget = messageLimit * 4 / 5;
|
||||
int tokenTarget = tokenLimit * 4 / 5;
|
||||
|
||||
return new(
|
||||
new ToolResultCompactionStrategy(
|
||||
trigger: CompactionTriggers.MessagesExceed(messageLimit),
|
||||
target: CompactionTriggers.MessagesBelow(messageTarget)),
|
||||
new SummarizationCompactionStrategy(
|
||||
chatClient,
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)));
|
||||
}
|
||||
|
||||
private static PipelineCompactionStrategy CreateBalancedPipeline(int tokenLimit, int messageLimit, IChatClient chatClient)
|
||||
{
|
||||
// Early stages trigger at two-thirds of the limit so the pipeline has room to compact
|
||||
// incrementally before reaching the emergency truncation backstop at the full limit.
|
||||
int earlyMessageTrigger = messageLimit * 2 / 3;
|
||||
int earlyTokenTrigger = tokenLimit * 2 / 3;
|
||||
int messageTarget = messageLimit * 3 / 4;
|
||||
int tokenTarget = tokenLimit * 4 / 5;
|
||||
|
||||
return new(
|
||||
new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
|
||||
new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
|
||||
new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
|
||||
new ToolResultCompactionStrategy(
|
||||
trigger: CompactionTriggers.MessagesExceed(messageLimit),
|
||||
target: CompactionTriggers.MessagesBelow(messageTarget)),
|
||||
new SummarizationCompactionStrategy(
|
||||
chatClient,
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)),
|
||||
new TruncationCompactionStrategy(
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)));
|
||||
}
|
||||
|
||||
private static PipelineCompactionStrategy CreateAggressivePipeline(int tokenLimit, int messageLimit, int turnLimit, IChatClient chatClient)
|
||||
{
|
||||
// Early stages trigger at half the limit so compaction kicks in sooner and
|
||||
// the sliding window and truncation backstop are reached less often.
|
||||
int earlyMessageTrigger = messageLimit / 2;
|
||||
int earlyTokenTrigger = tokenLimit / 2;
|
||||
int messageTarget = messageLimit * 3 / 4;
|
||||
int tokenTarget = tokenLimit * 3 / 4;
|
||||
|
||||
return new(
|
||||
new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
|
||||
new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
|
||||
new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(turnLimit)),
|
||||
new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
|
||||
new ToolResultCompactionStrategy(
|
||||
trigger: CompactionTriggers.MessagesExceed(messageLimit),
|
||||
target: CompactionTriggers.MessagesBelow(messageTarget)),
|
||||
new SummarizationCompactionStrategy(
|
||||
chatClient,
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)),
|
||||
new SlidingWindowCompactionStrategy(
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)),
|
||||
new TruncationCompactionStrategy(
|
||||
trigger: CompactionTriggers.TokensExceed(tokenLimit),
|
||||
target: CompactionTriggers.TokensBelow(tokenTarget)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,14 @@ public static class CompactionTriggers
|
||||
public static CompactionTrigger TokensExceed(int maxTokens) =>
|
||||
index => index.IncludedTokenCount > maxTokens;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a trigger that fires when the included message count is below the specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="maxMessages">The message threshold.</param>
|
||||
/// <returns>A <see cref="CompactionTrigger"/> that evaluates included message count.</returns>
|
||||
public static CompactionTrigger MessagesBelow(int maxMessages) =>
|
||||
index => index.IncludedMessageCount < maxMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a trigger that fires when the included message count exceeds the specified maximum.
|
||||
/// </summary>
|
||||
|
||||
+12
-19
@@ -33,11 +33,11 @@ public class CompactionStrategyCreateTests
|
||||
{
|
||||
PipelineCompactionStrategy pipeline =
|
||||
Assert.IsType<PipelineCompactionStrategy>(
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact));
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact, CreateMockChatClient()));
|
||||
|
||||
Assert.Equal(2, pipeline.Strategies.Count);
|
||||
Assert.IsType<ToolResultCompactionStrategy>(pipeline.Strategies[0]);
|
||||
Assert.IsType<TruncationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
Assert.IsType<SummarizationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,11 +45,11 @@ public class CompactionStrategyCreateTests
|
||||
{
|
||||
PipelineCompactionStrategy pipeline =
|
||||
Assert.IsType<PipelineCompactionStrategy>(
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate));
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate, CreateMockChatClient()));
|
||||
|
||||
Assert.Equal(2, pipeline.Strategies.Count);
|
||||
Assert.IsType<ToolResultCompactionStrategy>(pipeline.Strategies[0]);
|
||||
Assert.IsType<TruncationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
Assert.IsType<SummarizationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,18 +57,18 @@ public class CompactionStrategyCreateTests
|
||||
{
|
||||
PipelineCompactionStrategy pipeline =
|
||||
Assert.IsType<PipelineCompactionStrategy>(
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Generous));
|
||||
CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Generous, CreateMockChatClient()));
|
||||
|
||||
Assert.Equal(2, pipeline.Strategies.Count);
|
||||
Assert.IsType<ToolResultCompactionStrategy>(pipeline.Strategies[0]);
|
||||
Assert.IsType<TruncationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
Assert.IsType<SummarizationCompactionStrategy>(pipeline.Strategies[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateGentleDoesNotRequireChatClient()
|
||||
{
|
||||
// No chatClient supplied — should succeed without throwing.
|
||||
CompactionStrategy strategy = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate);
|
||||
CompactionStrategy strategy = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate, CreateMockChatClient());
|
||||
Assert.NotNull(strategy);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public class CompactionStrategyCreateTests
|
||||
public void CreateBalancedNullChatClientThrows()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Moderate, null));
|
||||
() => CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Moderate, null!));
|
||||
}
|
||||
|
||||
// ── Aggressive ────────────────────────────────────────────────────────────
|
||||
@@ -164,27 +164,20 @@ public class CompactionStrategyCreateTests
|
||||
Assert.IsType<TruncationCompactionStrategy>(pipeline.Strategies[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateAggressiveNullChatClientThrows()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => CompactionStrategy.Create(CompactionApproach.Aggressive, CompactionSize.Moderate, null));
|
||||
}
|
||||
|
||||
// ── Invalid enum values ───────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void CreateInvalidApproachThrows()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => CompactionStrategy.Create((CompactionApproach)99, CompactionSize.Moderate));
|
||||
() => CompactionStrategy.Create((CompactionApproach)99, CompactionSize.Moderate, CreateMockChatClient()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateInvalidSizeThrows()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => CompactionStrategy.Create(CompactionApproach.Gentle, (CompactionSize)99));
|
||||
() => CompactionStrategy.Create(CompactionApproach.Gentle, (CompactionSize)99, CreateMockChatClient()));
|
||||
}
|
||||
|
||||
// ── Size-threshold behavioral verification ────────────────────────────────
|
||||
@@ -203,8 +196,8 @@ public class CompactionStrategyCreateTests
|
||||
// The configuration preserves a number of most-recent groups, leaving the oldest
|
||||
// tool-call group eligible for collapsing, which makes the behavioral difference
|
||||
// between Compact and Moderate sizes observable in this test.
|
||||
CompactionStrategy compactPipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact);
|
||||
CompactionStrategy moderatePipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate);
|
||||
CompactionStrategy compactPipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact, CreateMockChatClient());
|
||||
CompactionStrategy moderatePipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate, CreateMockChatClient());
|
||||
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user