diff --git a/dotnet/agent-working-dotnet.slnx b/dotnet/agent-working-dotnet.slnx
index 651b185a23..60542e7969 100644
--- a/dotnet/agent-working-dotnet.slnx
+++ b/dotnet/agent-working-dotnet.slnx
@@ -56,6 +56,7 @@
+
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj b/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj
new file mode 100644
index 0000000000..0f9de7c359
--- /dev/null
+++ b/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Program.cs
new file mode 100644
index 0000000000..82a38c538c
--- /dev/null
+++ b/dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Program.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to use a ChatHistoryCompactionPipeline as the ChatReducer for an agent's
+// in-memory chat history. The pipeline chains multiple compaction strategies from gentle to aggressive:
+// 1. ToolResultCompactionStrategy - Collapses old tool-call groups into concise summaries
+// 2. SummarizationCompactionStrategy - LLM-compresses older conversation spans
+// 3. SlidingWindowCompactionStrategy - Keeps only the most recent N user turns
+// 4. TruncationCompactionStrategy - Emergency token-budget backstop
+
+using System.ComponentModel;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Compaction;
+using Microsoft.Extensions.AI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+AzureOpenAIClient openAIClient = new(new Uri(endpoint), new DefaultAzureCredential());
+
+// Create a chat client for the agent and a separate one for the summarization strategy.
+// Using the same model for simplicity; in production, use a smaller/cheaper model for summarization.
+IChatClient agentChatClient = openAIClient.GetChatClient(deploymentName).AsIChatClient();
+IChatClient summarizerChatClient = openAIClient.GetChatClient(deploymentName).AsIChatClient();
+
+// Define a tool the agent can use, so we can see tool-result compaction in action.
+[Description("Look up the current price of a product by name.")]
+static string LookupPrice([Description("The product name to look up.")] string productName) =>
+ productName.ToUpperInvariant() switch
+ {
+ "LAPTOP" => "The laptop costs $999.99.",
+ "KEYBOARD" => "The keyboard costs $79.99.",
+ "MOUSE" => "The mouse costs $29.99.",
+ _ => $"Sorry, I don't have pricing for '{productName}'."
+ };
+
+// Configure the compaction pipeline with one of each strategy, ordered least to most aggressive.
+//const int MaxTokens = 512;
+//const int MaxTurns = 4;
+const int MaxGroups = 2;
+
+PipelineCompactionStrategy compactionPipeline =
+ new(// 1. Gentle: collapse old tool-call groups into short summaries like "[Tool calls: LookupPrice]"
+ //new ToolResultCompactionStrategy(MaxTokens, preserveRecentGroups: 2),
+
+ // 2. Moderate: use an LLM to summarize older conversation spans into a concise message
+ new SummarizationCompactionStrategy(summarizerChatClient, MaxGroups)
+
+ // 3. Aggressive: keep only the last N user turns and their responses
+ //new SlidingWindowCompactionStrategy(MaxTurns),
+
+ // 4. Emergency: drop oldest groups until under the token budget
+ //new TruncationCompactionStrategy(MaxGroups)
+ );
+
+// Create the agent with an in-memory chat history provider whose reducer is the compaction pipeline.
+AIAgent agent =
+ agentChatClient.AsAIAgent(
+ new ChatClientAgentOptions
+ {
+ Name = "ShoppingAssistant",
+ ChatOptions = new()
+ {
+ Instructions =
+ """
+ You are a helpful, but long winded, shopping assistant.
+ Help the user look up prices and compare products.
+ When responding, Be sure to be extra descriptive and use as
+ many words as possible without sounding ridiculous.
+ """,
+ Tools = [AIFunctionFactory.Create(LookupPrice)],
+ },
+ CompactionStrategy = compactionPipeline,
+ });
+
+AgentSession session = await agent.CreateSessionAsync();
+
+// Helper to print chat history size
+void PrintChatHistory()
+{
+ if (session.TryGetInMemoryChatHistory(out var history))
+ {
+ Console.WriteLine($" [Chat history: {history.Count} messages]\n");
+ }
+}
+
+// Run a multi-turn conversation with tool calls to exercise the pipeline.
+string[] prompts =
+[
+ "What's the price of a laptop?",
+ "How about a keyboard?",
+ "And a mouse?",
+ "Which product is the cheapest?",
+ "Can you compare the laptop and the keyboard for me?",
+ "What was the first product I asked about?",
+ "Thank you!",
+];
+
+foreach (string prompt in prompts)
+{
+ Console.WriteLine($"User: {prompt}");
+ Console.WriteLine($"Agent: {await agent.RunAsync(prompt, session)}");
+
+ PrintChatHistory();
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs
index cdd21e9e1c..ad3f3aacfb 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
@@ -270,35 +269,6 @@ public abstract class ChatHistoryProvider
protected virtual ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default) =>
default;
- ///
- /// Compacts the messages in place using the specified compaction strategy before they are stored.
- ///
- /// The messages to compact. This list is mutated in place.
- /// The compaction strategy to apply.
- /// The to monitor for cancellation requests.
- /// A task representing the asynchronous operation. The task result is if compaction occurred.
- ///
- ///
- /// This method organizes the messages into atomic units,
- /// applies the compaction strategy, and replaces the contents of the list with the compacted result.
- /// Tool call groups (assistant message + tool results) are treated as atomic units.
- ///
- ///
- protected static async Task CompactMessagesAsync(List messages, ICompactionStrategy compactionStrategy, CancellationToken cancellationToken = default)
- {
- MessageGroups groups = MessageGroups.Create(messages);
-
- bool compacted = await compactionStrategy.CompactAsync(groups, cancellationToken).ConfigureAwait(false);
-
- if (compacted)
- {
- messages.Clear();
- messages.AddRange(groups.GetIncludedMessages());
- }
-
- return compacted;
- }
-
/// Asks the for an object of the specified type .
/// The type of object being requested.
/// An optional key that can be used to help identify the target service.
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/ICompactionStrategy.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/ICompactionStrategy.cs
index 615317062d..c894dd0d19 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/ICompactionStrategy.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/ICompactionStrategy.cs
@@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace Microsoft.Agents.AI.Compaction;
///
-/// Defines a strategy for compacting a to reduce context size.
+/// Defines a strategy for compacting a to reduce context size.
///
///
///
-/// Compaction strategies operate on instances, which organize messages
+/// Compaction strategies operate on instances, which organize messages
/// into atomic groups that respect the tool-call/result pairing constraint. Strategies mutate the collection
/// in place by marking groups as excluded, removing groups, or replacing message content (e.g., with summaries).
///
@@ -23,7 +23,7 @@ namespace Microsoft.Agents.AI.Compaction;
///
///
///
-/// Multiple strategies can be composed by applying them sequentially to the same .
+/// Multiple strategies can be composed by applying them sequentially to the same .
///
///
public interface ICompactionStrategy
@@ -34,5 +34,5 @@ public interface ICompactionStrategy
/// The message group collection to compact. The strategy mutates this collection in place.
/// The to monitor for cancellation requests.
/// A task representing the asynchronous operation. The task result is if compaction occurred, otherwise.
- Task CompactAsync(MessageGroups groups, CancellationToken cancellationToken = default);
+ Task CompactAsync(MessageIndex groups, CancellationToken cancellationToken = default);
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroup.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroup.cs
index d443b15d87..f8d41a41fb 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroup.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroup.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
+using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Compaction;
@@ -21,8 +22,8 @@ namespace Microsoft.Agents.AI.Compaction;
///
///
/// Each group tracks its , , and
-/// so that can efficiently aggregate totals across all or only included groups.
-/// These values are computed by and passed into the constructor.
+/// so that can efficiently aggregate totals across all or only included groups.
+/// These values are computed by and passed into the constructor.
///
///
public sealed class MessageGroup
@@ -32,7 +33,7 @@ public sealed class MessageGroup
///
///
/// When this key is present with a value of , the message is classified as
- /// by .
+ /// by .
///
public static readonly string SummaryPropertyKey = "_is_summary";
@@ -47,6 +48,7 @@ public sealed class MessageGroup
/// The zero-based user turn this group belongs to, or for groups that precede
/// the first user message (e.g., system messages).
///
+ [JsonConstructor]
public MessageGroup(MessageGroupKind kind, IReadOnlyList messages, int byteCount, int tokenCount, int? turnIndex = null)
{
this.Kind = kind;
@@ -97,7 +99,7 @@ public sealed class MessageGroup
///
///
/// Excluded groups are preserved in the collection for diagnostics or storage purposes
- /// but are not included when calling .
+ /// but are not included when calling .
///
public bool IsExcluded { get; set; }
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroups.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageIndex.cs
similarity index 72%
rename from dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroups.cs
rename to dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageIndex.cs
index 5661c1516a..c018774d91 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageGroups.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/MessageIndex.cs
@@ -13,7 +13,7 @@ namespace Microsoft.Agents.AI.Compaction;
///
///
///
-/// provides structural grouping of messages into logical units that
+/// provides structural grouping of messages into logical units that
/// respect the atomic group preservation constraint: tool call assistant messages and their corresponding
/// tool result messages are always grouped together.
///
@@ -27,9 +27,16 @@ namespace Microsoft.Agents.AI.Compaction;
/// and . The collection provides aggregate properties for both
/// the total (all groups) and included (non-excluded groups only) counts.
///
+///
+/// Instances created via track internal state that enables efficient incremental
+/// updates via . This allows caching a instance and
+/// appending only new messages without reprocessing the entire history.
+///
///
-public sealed class MessageGroups
+public sealed class MessageIndex
{
+ private int _currentTurn;
+
///
/// Gets the list of message groups in this collection.
///
@@ -41,25 +48,43 @@ public sealed class MessageGroups
public Tokenizer? Tokenizer { get; }
///
- /// Initializes a new instance of the class with the specified groups.
+ /// Gets the number of raw messages that have been processed into groups.
+ ///
+ ///
+ /// This value is set by and updated by .
+ /// It is used by to determine which messages are new and need processing.
+ ///
+ public int ProcessedMessageCount { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class with the specified groups.
///
/// The message groups.
/// An optional tokenizer retained for computing token counts when adding new groups.
- public MessageGroups(IList groups, Tokenizer? tokenizer = null)
+ public MessageIndex(IList groups, Tokenizer? tokenizer = null)
{
this.Groups = groups;
this.Tokenizer = tokenizer;
+
+ for (int index = groups.Count - 1; index >= 0; --index)
+ {
+ if (this.Groups[0].TurnIndex.HasValue)
+ {
+ this._currentTurn = this.Groups[0].TurnIndex!.Value;
+ break;
+ }
+ }
}
///
- /// Creates a from a flat list of instances.
+ /// Creates a from a flat list of instances.
///
/// The messages to group.
///
/// An optional for computing token counts on each group.
/// When , token counts are estimated as ByteCount / 4.
///
- /// A new with messages organized into logical groups.
+ /// A new with messages organized into logical groups.
///
/// The grouping algorithm:
///
@@ -70,11 +95,61 @@ public sealed class MessageGroups
/// - Assistant messages without tool calls become groups.
///
///
- public static MessageGroups Create(IList messages, Tokenizer? tokenizer = null)
+ public static MessageIndex Create(IList messages, Tokenizer? tokenizer = null)
{
- List groups = [];
- int index = 0;
- int currentTurn = 0;
+ MessageIndex instance = new(new List(), tokenizer);
+ instance.AppendFromMessages(messages, 0);
+ return instance;
+ }
+
+ ///
+ /// Incrementally updates the groups with new messages from the conversation.
+ ///
+ ///
+ /// The full list of messages for the conversation. This must be the same list (or a replacement with the same
+ /// prefix) that was used to create or last update this instance.
+ ///
+ ///
+ ///
+ /// If the message count exceeds , only the new (delta) messages
+ /// are processed and appended as new groups. Existing groups and their compaction state (exclusions)
+ /// are preserved, allowing compaction strategies to build on previous results.
+ ///
+ ///
+ /// If the message count is less than (e.g., after storage compaction
+ /// replaced messages with summaries), all groups are cleared and rebuilt from scratch.
+ ///
+ ///
+ /// If the message count equals , no work is performed.
+ ///
+ ///
+ public void Update(IList allMessages)
+ {
+ if (allMessages.Count == this.ProcessedMessageCount)
+ {
+ return; // No new messages
+ }
+
+ if (allMessages.Count < this.ProcessedMessageCount)
+ {
+ // Message list shrank (e.g., after storage compaction). Rebuild from scratch.
+ this.ProcessedMessageCount = 0;
+ }
+
+ if (this.ProcessedMessageCount == 0)
+ {
+ // First update on a manually constructed instance — clear any pre-existing groups
+ this.Groups.Clear();
+ this._currentTurn = 0;
+ }
+
+ // Process only the delta messages
+ this.AppendFromMessages(allMessages, this.ProcessedMessageCount);
+ }
+
+ private void AppendFromMessages(IList messages, int startIndex)
+ {
+ int index = startIndex;
while (index < messages.Count)
{
@@ -83,13 +158,13 @@ public sealed class MessageGroups
if (message.Role == ChatRole.System)
{
// System messages are not part of any turn
- groups.Add(CreateGroup(MessageGroupKind.System, [message], tokenizer, turnIndex: null));
+ this.Groups.Add(CreateGroup(MessageGroupKind.System, [message], this.Tokenizer, turnIndex: null));
index++;
}
else if (message.Role == ChatRole.User)
{
- currentTurn++;
- groups.Add(CreateGroup(MessageGroupKind.User, [message], tokenizer, currentTurn));
+ this._currentTurn++;
+ this.Groups.Add(CreateGroup(MessageGroupKind.User, [message], this.Tokenizer, this._currentTurn));
index++;
}
else if (message.Role == ChatRole.Assistant && HasToolCalls(message))
@@ -104,21 +179,21 @@ public sealed class MessageGroups
index++;
}
- groups.Add(CreateGroup(MessageGroupKind.ToolCall, groupMessages, tokenizer, currentTurn));
+ this.Groups.Add(CreateGroup(MessageGroupKind.ToolCall, groupMessages, this.Tokenizer, this._currentTurn));
}
else if (message.Role == ChatRole.Assistant && IsSummaryMessage(message))
{
- groups.Add(CreateGroup(MessageGroupKind.Summary, [message], tokenizer, currentTurn));
+ this.Groups.Add(CreateGroup(MessageGroupKind.Summary, [message], this.Tokenizer, this._currentTurn));
index++;
}
else
{
- groups.Add(CreateGroup(MessageGroupKind.AssistantText, [message], tokenizer, currentTurn));
+ this.Groups.Add(CreateGroup(MessageGroupKind.AssistantText, [message], this.Tokenizer, this._currentTurn));
index++;
}
}
- return new MessageGroups(groups, tokenizer);
+ this.ProcessedMessageCount = messages.Count;
}
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/PipelineCompactionStrategy.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/PipelineCompactionStrategy.cs
index 0c4af67700..2346d03c32 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/PipelineCompactionStrategy.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/Compaction/PipelineCompactionStrategy.cs
@@ -9,7 +9,7 @@ namespace Microsoft.Agents.AI.Compaction;
///
/// A compaction strategy that executes a sequential pipeline of instances
-/// against the same .
+/// against the same .
///
///
///
@@ -28,7 +28,8 @@ public sealed class PipelineCompactionStrategy : ICompactionStrategy
/// Initializes a new instance of the class.
///
/// The ordered sequence of strategies to execute. Must not be empty.
- public PipelineCompactionStrategy(params IEnumerable strategies)
+ ///// An optional cache for instances. When , a default is created.
+ public PipelineCompactionStrategy(params IEnumerable strategies/*, IMessageIndexCache? cache = null*/)
{
this.Strategies = [.. Throw.IfNull(strategies)];
}
@@ -58,7 +59,7 @@ public sealed class PipelineCompactionStrategy : ICompactionStrategy
public int? TargetIncludedGroupCount { get; set; }
///
- public async Task CompactAsync(MessageGroups groups, CancellationToken cancellationToken = default)
+ public async Task CompactAsync(MessageIndex groups, CancellationToken cancellationToken = default)
{
bool anyCompacted = false;
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs
index 4356c1ac3e..5f03ad424a 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
@@ -47,7 +46,6 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
options?.JsonSerializerOptions);
this.ChatReducer = options?.ChatReducer;
this.ReducerTriggerEvent = options?.ReducerTriggerEvent ?? InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval;
- this.CompactionStrategy = options?.CompactionStrategy;
}
///
@@ -63,11 +61,6 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
///
public InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent ReducerTriggerEvent { get; }
- ///
- /// Gets the compaction strategy used to compact stored messages. If , no compaction is applied.
- ///
- public ICompactionStrategy? CompactionStrategy { get; }
-
///
/// Gets the chat messages stored for the specified session.
///
@@ -84,20 +77,21 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
/// is .
public void SetMessages(AgentSession? session, List messages)
{
- _ = Throw.IfNull(messages);
+ Throw.IfNull(messages);
- var state = this._sessionState.GetOrInitializeState(session);
+ State state = this._sessionState.GetOrInitializeState(session);
state.Messages = messages;
}
///
protected override async ValueTask> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
- var state = this._sessionState.GetOrInitializeState(context.Session);
+ State state = this._sessionState.GetOrInitializeState(context.Session);
if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval && this.ChatReducer is not null)
{
- state.Messages = (await this.ChatReducer.ReduceAsync(state.Messages, cancellationToken).ConfigureAwait(false)).ToList();
+ // Apply pre-invocation compaction strategy if configured
+ await this.CompactMessagesAsync(state, cancellationToken).ConfigureAwait(false);
}
return state.Messages;
@@ -106,46 +100,29 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
///
protected override async ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
- var state = this._sessionState.GetOrInitializeState(context.Session);
+ State state = this._sessionState.GetOrInitializeState(context.Session);
// Add request and response messages to the provider
var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
state.Messages.AddRange(allNewMessages);
- if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded && this.ChatReducer is not null)
+ if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded)
{
- state.Messages = (await this.ChatReducer.ReduceAsync(state.Messages, cancellationToken).ConfigureAwait(false)).ToList();
- }
-
- // Apply compaction strategy if configured (pre-write compaction)
- if (this.CompactionStrategy is not null)
- {
- await CompactMessagesAsync(state.Messages, this.CompactionStrategy, cancellationToken).ConfigureAwait(false);
+ // Apply pre-write compaction strategy if configured
+ await this.CompactMessagesAsync(state, cancellationToken).ConfigureAwait(false);
}
}
- ///
- /// Compacts the stored messages for the specified session using the given or configured compaction strategy.
- ///
- /// The agent session whose stored messages should be compacted.
- ///
- /// An optional compaction strategy to use. If , the provider's configured
- /// is used. If neither is available, an is thrown.
- ///
- /// The to monitor for cancellation requests.
- /// A task representing the asynchronous operation. The task result is if compaction occurred.
- /// No compaction strategy is configured or provided.
- ///
- /// This method enables on-demand compaction of stored history, for example as a maintenance operation.
- /// It reads the full stored history, applies the compaction strategy, and writes the compacted result back.
- ///
- public async Task CompactStorageAsync(AgentSession? session, ICompactionStrategy? compactionStrategy = null, CancellationToken cancellationToken = default)
+ private async Task CompactMessagesAsync(State state, CancellationToken cancellationToken = default)
{
- ICompactionStrategy strategy = compactionStrategy ?? this.CompactionStrategy
- ?? throw new InvalidOperationException("No compaction strategy is configured or provided.");
+ if (this.ChatReducer is not null)
+ {
+ // ChatReducer takes precedence, if configured
+ state.Messages = [.. await this.ChatReducer.ReduceAsync(state.Messages, cancellationToken).ConfigureAwait(false)];
+ return;
+ }
- var state = this._sessionState.GetOrInitializeState(session);
- return await CompactMessagesAsync(state.Messages, strategy, cancellationToken).ConfigureAwait(false);
+ // %%% TODO: CONSIDER COMPACTION
}
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProviderOptions.cs
index b30968aa61..5d15bb416b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProviderOptions.cs
@@ -74,21 +74,6 @@ public sealed class InMemoryChatHistoryProviderOptions
///
public Func, IEnumerable>? ProvideOutputMessageFilter { get; set; }
- ///
- /// Gets or sets an optional to apply to stored messages after new messages are added.
- ///
- ///
- ///
- /// When set, this strategy is applied to the full stored message list after new messages have been appended.
- /// This enables pre-write compaction to limit storage size.
- ///
- ///
- /// The compaction strategy organizes messages into atomic groups (preserving tool-call/result pairings)
- /// before applying the strategy logic. See for details.
- ///
- ///
- public ICompactionStrategy? CompactionStrategy { get; set; }
-
///
/// Defines the events that can trigger a reducer in the .
///
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs
index 4e1fe61f12..7deff4056c 100644
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs
@@ -59,10 +59,6 @@ public sealed class ChatClientAgentOptions
/// The strategy organizes messages into atomic groups (preserving tool-call/result pairings)
/// before applying compaction logic. See for details.
///
- ///
- /// This is separate from the compaction strategy on ,
- /// which applies pre-write compaction before storing messages. Both can be used together.
- ///
///
public ICompactionStrategy? CompactionStrategy { get; set; }
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs
index 8b83830afc..a8d57ec3d0 100644
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/CompactingChatClient.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/CompactingChatClient.cs
deleted file mode 100644
index 8e94b840ea..0000000000
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/CompactingChatClient.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Agents.AI.Compaction;
-using Microsoft.Extensions.AI;
-using Microsoft.Shared.Diagnostics;
-
-namespace Microsoft.Agents.AI;
-
-///
-/// A delegating that applies an to the message list
-/// before each call to the inner chat client.
-///
-///
-///
-/// This client is used for in-run compaction during the tool loop. It is inserted into the
-/// pipeline before the so that
-/// compaction is applied before every LLM call, including those triggered by tool call iterations.
-///
-///
-/// The compaction strategy organizes messages into atomic groups (preserving tool-call/result pairings)
-/// before applying compaction logic. Only included messages are forwarded to the inner client.
-///
-///
-internal sealed class CompactingChatClient : DelegatingChatClient
-{
- private readonly ICompactionStrategy _compactionStrategy;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The inner chat client to delegate to.
- /// The compaction strategy to apply before each call.
- public CompactingChatClient(IChatClient innerClient, ICompactionStrategy compactionStrategy)
- : base(innerClient)
- {
- this._compactionStrategy = Throw.IfNull(compactionStrategy);
- }
-
- ///
- public override async Task GetResponseAsync(
- IEnumerable messages,
- ChatOptions? options = null,
- CancellationToken cancellationToken = default)
- {
- List compactedMessages = await this.ApplyCompactionAsync(messages, cancellationToken).ConfigureAwait(false);
- return await base.GetResponseAsync(compactedMessages, options, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public override async IAsyncEnumerable GetStreamingResponseAsync(
- IEnumerable messages,
- ChatOptions? options = null,
- [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- List compactedMessages = await this.ApplyCompactionAsync(messages, cancellationToken).ConfigureAwait(false);
- await foreach (var update in base.GetStreamingResponseAsync(compactedMessages, options, cancellationToken).ConfigureAwait(false))
- {
- yield return update;
- }
- }
-
- private async Task> ApplyCompactionAsync(IEnumerable messages, CancellationToken cancellationToken)
- {
- List messageList = messages as List ?? [.. messages];
- MessageGroups groups = MessageGroups.Create(messageList);
-
- bool compacted = await this._compactionStrategy.CompactAsync(groups, cancellationToken).ConfigureAwait(false);
-
- return compacted ? [.. groups.GetIncludedMessages()] : messageList;
- }
-}
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactingChatClient.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactingChatClient.cs
new file mode 100644
index 0000000000..c7fbf66115
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactingChatClient.cs
@@ -0,0 +1,133 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.Compaction;
+
+///
+/// A delegating that applies an to the message list
+/// before each call to the inner chat client.
+///
+///
+///
+/// This client is used for in-run compaction during the tool loop. It is inserted into the
+/// pipeline before the `FunctionInvokingChatClient` so that
+/// compaction is applied before every LLM call, including those triggered by tool call iterations.
+///
+///
+/// The compaction strategy organizes messages into atomic groups (preserving tool-call/result pairings)
+/// before applying compaction logic. Only included messages are forwarded to the inner client.
+///
+///
+internal sealed class CompactingChatClient : DelegatingChatClient
+{
+ private readonly ICompactionStrategy _compactionStrategy;
+ private readonly ProviderSessionState _sessionState;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The inner chat client to delegate to.
+ /// The compaction strategy to apply before each call.
+ public CompactingChatClient(IChatClient innerClient, ICompactionStrategy compactionStrategy)
+ : base(innerClient)
+ {
+ this._compactionStrategy = Throw.IfNull(compactionStrategy);
+ this._sessionState = new ProviderSessionState(
+ _ => new State(),
+ Convert.ToBase64String(BitConverter.GetBytes(compactionStrategy.GetHashCode())),
+ AgentJsonUtilities.DefaultOptions);
+ }
+
+ ///
+ public override async Task GetResponseAsync(
+ IEnumerable messages,
+ ChatOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ IEnumerable compactedMessages = await this.ApplyCompactionAsync(messages, cancellationToken).ConfigureAwait(false);
+ return await base.GetResponseAsync(compactedMessages, options, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public override async IAsyncEnumerable GetStreamingResponseAsync(
+ IEnumerable messages,
+ ChatOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ IEnumerable compactedMessages = await this.ApplyCompactionAsync(messages, cancellationToken).ConfigureAwait(false);
+ await foreach (var update in base.GetStreamingResponseAsync(compactedMessages, options, cancellationToken).ConfigureAwait(false))
+ {
+ yield return update;
+ }
+ }
+
+ ///
+ public override object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ Throw.IfNull(serviceType);
+
+ return
+ serviceKey is null && serviceType.IsInstanceOfType(typeof(ICompactionStrategy)) ?
+ this._compactionStrategy :
+ base.GetService(serviceType, serviceKey);
+ }
+
+ private async Task> ApplyCompactionAsync(
+ IEnumerable messages, CancellationToken cancellationToken)
+ {
+ List messageList = messages as List ?? [.. messages]; // %%% TODO - LIST COPY
+
+ AgentRunContext? currentAgentContext = AIAgent.CurrentRunContext;
+ if (currentAgentContext is null ||
+ currentAgentContext.Session is null)
+ {
+ // No session available — no reason to compact
+ return messages;
+ }
+
+ State state = this._sessionState.GetOrInitializeState(currentAgentContext.Session);
+
+ MessageIndex messageIndex;
+ if (state.MessageIndex.Count > 0)
+ {
+ // Update existing index
+ messageIndex = new(state.MessageIndex);
+ messageIndex.Update(messageList);
+ }
+ else
+ {
+ // First pass — initialize message index state
+ messageIndex = MessageIndex.Create(messageList);
+ }
+
+ // Apply compaction
+ bool wasCompacted = await this._compactionStrategy.CompactAsync(messageIndex, cancellationToken).ConfigureAwait(false);
+
+ if (wasCompacted)
+ {
+ state.MessageIndex = [.. messageIndex.Groups]; // %%% TODO - LIST COPY
+ }
+
+ return wasCompacted ? messageIndex.GetIncludedMessages() : messageList;
+ }
+
+ ///
+ /// Represents the state of a stored in the .
+ ///
+ public sealed class State
+ {
+ ///
+ /// Gets or sets the message index.
+ ///
+ [JsonPropertyName("messages")]
+ public List MessageIndex { get; set; } = [];
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/SummarizationCompactionStrategy.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/SummarizationCompactionStrategy.cs
index 3255d3f998..51c5b4e224 100644
--- a/dotnet/src/Microsoft.Agents.AI/Compaction/SummarizationCompactionStrategy.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/SummarizationCompactionStrategy.cs
@@ -58,7 +58,7 @@ public sealed class SummarizationCompactionStrategy : ICompactionStrategy
public string SummarizationPrompt { get; }
///
- public async Task CompactAsync(MessageGroups groups, CancellationToken cancellationToken = default)
+ public async Task CompactAsync(MessageIndex groups, CancellationToken cancellationToken = default)
{
int includedCount = groups.IncludedGroupCount;
if (includedCount <= this.MaxGroupsBeforeSummary)
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/TruncationCompactionStrategy.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/TruncationCompactionStrategy.cs
index dbeef544cd..47d729be57 100644
--- a/dotnet/src/Microsoft.Agents.AI/Compaction/TruncationCompactionStrategy.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/TruncationCompactionStrategy.cs
@@ -44,7 +44,7 @@ public sealed class TruncationCompactionStrategy : ICompactionStrategy
public bool PreserveSystemMessages { get; }
///
- public Task CompactAsync(MessageGroups groups, CancellationToken cancellationToken = default)
+ public Task CompactAsync(MessageIndex groups, CancellationToken cancellationToken = default)
{
int includedCount = groups.IncludedGroupCount;
if (includedCount <= this.MaxGroups)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/InMemoryChatHistoryProviderCompactionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/InMemoryChatHistoryProviderCompactionTests.cs
index ae02b85779..d9155e90f0 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/InMemoryChatHistoryProviderCompactionTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/InMemoryChatHistoryProviderCompactionTests.cs
@@ -1,268 +1,269 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Agents.AI.Compaction;
-using Microsoft.Extensions.AI;
-using Moq;
+// %%% SAVE - RE-ANALYZE
+//using System.Collections.Generic;
+//using System.Threading;
+//using System.Threading.Tasks;
+//using Microsoft.Agents.AI.Compaction;
+//using Microsoft.Extensions.AI;
+//using Moq;
-namespace Microsoft.Agents.AI.Abstractions.UnitTests.Compaction;
+//namespace Microsoft.Agents.AI.Abstractions.UnitTests.Compaction;
-///
-/// Contains tests for the compaction integration with .
-///
-public class InMemoryChatHistoryProviderCompactionTests
-{
- private static readonly AIAgent s_mockAgent = new Mock().Object;
+/////
+///// Contains tests for the compaction integration with .
+/////
+//public class InMemoryChatHistoryProviderCompactionTests
+//{
+// private static readonly AIAgent s_mockAgent = new Mock().Object;
- private static AgentSession CreateMockSession() => new Mock().Object;
+// private static AgentSession CreateMockSession() => new Mock().Object;
- [Fact]
- public void Constructor_SetsCompactionStrategy_FromOptions()
- {
- // Arrange
- Mock strategy = new();
+// [Fact]
+// public void Constructor_SetsCompactionStrategy_FromOptions()
+// {
+// // Arrange
+// Mock strategy = new();
- // Act
- InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
- {
- CompactionStrategy = strategy.Object,
- });
+// // Act
+// InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
+// {
+// CompactionStrategy = strategy.Object,
+// });
- // Assert
- Assert.Same(strategy.Object, provider.CompactionStrategy);
- }
+// // Assert
+// Assert.Same(strategy.Object, provider.CompactionStrategy);
+// }
- [Fact]
- public void Constructor_CompactionStrategyIsNull_ByDefault()
- {
- // Arrange & Act
- InMemoryChatHistoryProvider provider = new();
+// [Fact]
+// public void Constructor_CompactionStrategyIsNull_ByDefault()
+// {
+// // Arrange & Act
+// InMemoryChatHistoryProvider provider = new();
- // Assert
- Assert.Null(provider.CompactionStrategy);
- }
+// // Assert
+// Assert.Null(provider.CompactionStrategy);
+// }
- [Fact]
- public async Task StoreChatHistoryAsync_AppliesCompaction_WhenStrategyConfiguredAsync()
- {
- // Arrange — mock strategy that excludes the first included non-system group
- Mock mockStrategy = new();
- mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
- {
- foreach (MessageGroup group in groups.Groups)
- {
- if (!group.IsExcluded && group.Kind != MessageGroupKind.System)
- {
- group.IsExcluded = true;
- group.ExcludeReason = "Mock compaction";
- break;
- }
- }
- })
- .ReturnsAsync(true);
+// [Fact]
+// public async Task StoreChatHistoryAsync_AppliesCompaction_WhenStrategyConfiguredAsync()
+// {
+// // Arrange — mock strategy that excludes the first included non-system group
+// Mock mockStrategy = new();
+// mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+// .Callback((groups, _) =>
+// {
+// foreach (MessageGroup group in groups.Groups)
+// {
+// if (!group.IsExcluded && group.Kind != MessageGroupKind.System)
+// {
+// group.IsExcluded = true;
+// group.ExcludeReason = "Mock compaction";
+// break;
+// }
+// }
+// })
+// .ReturnsAsync(true);
- InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
- {
- CompactionStrategy = mockStrategy.Object,
- });
+// InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
+// {
+// CompactionStrategy = mockStrategy.Object,
+// });
- AgentSession session = CreateMockSession();
+// AgentSession session = CreateMockSession();
- // Pre-populate with some messages
- List existingMessages =
- [
- new ChatMessage(ChatRole.User, "First"),
- new ChatMessage(ChatRole.Assistant, "Response 1"),
- ];
- provider.SetMessages(session, existingMessages);
+// // Pre-populate with some messages
+// List existingMessages =
+// [
+// new ChatMessage(ChatRole.User, "First"),
+// new ChatMessage(ChatRole.Assistant, "Response 1"),
+// ];
+// provider.SetMessages(session, existingMessages);
- // Invoke the store flow with additional messages
- List requestMessages =
- [
- new ChatMessage(ChatRole.User, "Second"),
- ];
- List responseMessages =
- [
- new ChatMessage(ChatRole.Assistant, "Response 2"),
- ];
+// // Invoke the store flow with additional messages
+// List requestMessages =
+// [
+// new ChatMessage(ChatRole.User, "Second"),
+// ];
+// List responseMessages =
+// [
+// new ChatMessage(ChatRole.Assistant, "Response 2"),
+// ];
- ChatHistoryProvider.InvokedContext context = new(s_mockAgent, session, requestMessages, responseMessages);
+// ChatHistoryProvider.InvokedContext context = new(s_mockAgent, session, requestMessages, responseMessages);
- // Act
- await provider.InvokedAsync(context);
+// // Act
+// await provider.InvokedAsync(context);
- // Assert - compaction should have removed one group
- List storedMessages = provider.GetMessages(session);
- Assert.Equal(3, storedMessages.Count);
- mockStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
+// // Assert - compaction should have removed one group
+// List storedMessages = provider.GetMessages(session);
+// Assert.Equal(3, storedMessages.Count);
+// mockStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+// }
- [Fact]
- public async Task StoreChatHistoryAsync_DoesNotCompact_WhenNoStrategyAsync()
- {
- // Arrange
- InMemoryChatHistoryProvider provider = new();
- AgentSession session = CreateMockSession();
+// [Fact]
+// public async Task StoreChatHistoryAsync_DoesNotCompact_WhenNoStrategyAsync()
+// {
+// // Arrange
+// InMemoryChatHistoryProvider provider = new();
+// AgentSession session = CreateMockSession();
- List requestMessages =
- [
- new ChatMessage(ChatRole.User, "Hello"),
- ];
- List responseMessages =
- [
- new ChatMessage(ChatRole.Assistant, "Hi!"),
- ];
+// List requestMessages =
+// [
+// new ChatMessage(ChatRole.User, "Hello"),
+// ];
+// List responseMessages =
+// [
+// new ChatMessage(ChatRole.Assistant, "Hi!"),
+// ];
- ChatHistoryProvider.InvokedContext context = new(s_mockAgent, session, requestMessages, responseMessages);
+// ChatHistoryProvider.InvokedContext context = new(s_mockAgent, session, requestMessages, responseMessages);
- // Act
- await provider.InvokedAsync(context);
+// // Act
+// await provider.InvokedAsync(context);
- // Assert - all messages should be stored
- List storedMessages = provider.GetMessages(session);
- Assert.Equal(2, storedMessages.Count);
- }
+// // Assert - all messages should be stored
+// List storedMessages = provider.GetMessages(session);
+// Assert.Equal(2, storedMessages.Count);
+// }
- [Fact]
- public async Task CompactStorageAsync_CompactsStoredMessagesAsync()
- {
- // Arrange — mock strategy that excludes the two oldest non-system groups
- Mock mockStrategy = new();
- mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
- {
- int excluded = 0;
- foreach (MessageGroup group in groups.Groups)
- {
- if (!group.IsExcluded && group.Kind != MessageGroupKind.System && excluded < 2)
- {
- group.IsExcluded = true;
- excluded++;
- }
- }
- })
- .ReturnsAsync(true);
+// [Fact]
+// public async Task CompactStorageAsync_CompactsStoredMessagesAsync()
+// {
+// // Arrange — mock strategy that excludes the two oldest non-system groups
+// Mock mockStrategy = new();
+// mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+// .Callback((groups, _) =>
+// {
+// int excluded = 0;
+// foreach (MessageGroup group in groups.Groups)
+// {
+// if (!group.IsExcluded && group.Kind != MessageGroupKind.System && excluded < 2)
+// {
+// group.IsExcluded = true;
+// excluded++;
+// }
+// }
+// })
+// .ReturnsAsync(true);
- InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
- {
- CompactionStrategy = mockStrategy.Object,
- });
+// InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
+// {
+// CompactionStrategy = mockStrategy.Object,
+// });
- AgentSession session = CreateMockSession();
- provider.SetMessages(session,
- [
- new ChatMessage(ChatRole.User, "First"),
- new ChatMessage(ChatRole.Assistant, "Response 1"),
- new ChatMessage(ChatRole.User, "Second"),
- new ChatMessage(ChatRole.Assistant, "Response 2"),
- ]);
+// AgentSession session = CreateMockSession();
+// provider.SetMessages(session,
+// [
+// new ChatMessage(ChatRole.User, "First"),
+// new ChatMessage(ChatRole.Assistant, "Response 1"),
+// new ChatMessage(ChatRole.User, "Second"),
+// new ChatMessage(ChatRole.Assistant, "Response 2"),
+// ]);
- // Act
- bool result = await provider.CompactStorageAsync(session);
+// // Act
+// bool result = await provider.CompactStorageAsync(session);
- // Assert
- Assert.True(result);
- List messages = provider.GetMessages(session);
- Assert.Equal(2, messages.Count);
- mockStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
+// // Assert
+// Assert.True(result);
+// List messages = provider.GetMessages(session);
+// Assert.Equal(2, messages.Count);
+// mockStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+// }
- [Fact]
- public async Task CompactStorageAsync_UsesProvidedStrategy_OverDefaultAsync()
- {
- // Arrange
- Mock defaultStrategy = new();
- Mock overrideStrategy = new();
+// [Fact]
+// public async Task CompactStorageAsync_UsesProvidedStrategy_OverDefaultAsync()
+// {
+// // Arrange
+// Mock defaultStrategy = new();
+// Mock overrideStrategy = new();
- overrideStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
- {
- // Exclude all but the last group
- for (int i = 0; i < groups.Groups.Count - 1; i++)
- {
- groups.Groups[i].IsExcluded = true;
- }
- })
- .ReturnsAsync(true);
+// overrideStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+// .Callback((groups, _) =>
+// {
+// // Exclude all but the last group
+// for (int i = 0; i < groups.Groups.Count - 1; i++)
+// {
+// groups.Groups[i].IsExcluded = true;
+// }
+// })
+// .ReturnsAsync(true);
- InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
- {
- CompactionStrategy = defaultStrategy.Object,
- });
+// InMemoryChatHistoryProvider provider = new(new InMemoryChatHistoryProviderOptions
+// {
+// CompactionStrategy = defaultStrategy.Object,
+// });
- AgentSession session = CreateMockSession();
- provider.SetMessages(session,
- [
- new ChatMessage(ChatRole.User, "First"),
- new ChatMessage(ChatRole.User, "Second"),
- new ChatMessage(ChatRole.User, "Third"),
- ]);
+// AgentSession session = CreateMockSession();
+// provider.SetMessages(session,
+// [
+// new ChatMessage(ChatRole.User, "First"),
+// new ChatMessage(ChatRole.User, "Second"),
+// new ChatMessage(ChatRole.User, "Third"),
+// ]);
- // Act
- bool result = await provider.CompactStorageAsync(session, overrideStrategy.Object);
+// // Act
+// bool result = await provider.CompactStorageAsync(session, overrideStrategy.Object);
- // Assert
- Assert.True(result);
- List messages = provider.GetMessages(session);
- Assert.Single(messages);
- Assert.Equal("Third", messages[0].Text);
+// // Assert
+// Assert.True(result);
+// List messages = provider.GetMessages(session);
+// Assert.Single(messages);
+// Assert.Equal("Third", messages[0].Text);
- // Verify the override was used, not the default
- overrideStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- defaultStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Never);
- }
+// // Verify the override was used, not the default
+// overrideStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+// defaultStrategy.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Never);
+// }
- [Fact]
- public async Task CompactStorageAsync_Throws_WhenNoStrategyAvailableAsync()
- {
- // Arrange
- InMemoryChatHistoryProvider provider = new();
- AgentSession session = CreateMockSession();
+// [Fact]
+// public async Task CompactStorageAsync_Throws_WhenNoStrategyAvailableAsync()
+// {
+// // Arrange
+// InMemoryChatHistoryProvider provider = new();
+// AgentSession session = CreateMockSession();
- // Act & Assert
- await Assert.ThrowsAsync(
- () => provider.CompactStorageAsync(session));
- }
+// // Act & Assert
+// await Assert.ThrowsAsync(
+// () => provider.CompactStorageAsync(session));
+// }
- [Fact]
- public async Task CompactStorageAsync_WithCustomStrategy_AppliesCustomLogicAsync()
- {
- // Arrange
- Mock mockStrategy = new();
- mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
- {
- // Exclude all user groups
- foreach (MessageGroup group in groups.Groups)
- {
- if (group.Kind == MessageGroupKind.User)
- {
- group.IsExcluded = true;
- }
- }
- })
- .ReturnsAsync(true);
+// [Fact]
+// public async Task CompactStorageAsync_WithCustomStrategy_AppliesCustomLogicAsync()
+// {
+// // Arrange
+// Mock mockStrategy = new();
+// mockStrategy.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+// .Callback((groups, _) =>
+// {
+// // Exclude all user groups
+// foreach (MessageGroup group in groups.Groups)
+// {
+// if (group.Kind == MessageGroupKind.User)
+// {
+// group.IsExcluded = true;
+// }
+// }
+// })
+// .ReturnsAsync(true);
- InMemoryChatHistoryProvider provider = new();
- AgentSession session = CreateMockSession();
- provider.SetMessages(session,
- [
- new ChatMessage(ChatRole.System, "System"),
- new ChatMessage(ChatRole.User, "User message"),
- new ChatMessage(ChatRole.Assistant, "Response"),
- ]);
+// InMemoryChatHistoryProvider provider = new();
+// AgentSession session = CreateMockSession();
+// provider.SetMessages(session,
+// [
+// new ChatMessage(ChatRole.System, "System"),
+// new ChatMessage(ChatRole.User, "User message"),
+// new ChatMessage(ChatRole.Assistant, "Response"),
+// ]);
- // Act
- bool result = await provider.CompactStorageAsync(session, mockStrategy.Object);
+// // Act
+// bool result = await provider.CompactStorageAsync(session, mockStrategy.Object);
- // Assert
- Assert.True(result);
- List messages = provider.GetMessages(session);
- Assert.Equal(2, messages.Count);
- Assert.Equal(ChatRole.System, messages[0].Role);
- Assert.Equal(ChatRole.Assistant, messages[1].Role);
- }
-}
+// // Assert
+// Assert.True(result);
+// List messages = provider.GetMessages(session);
+// Assert.Equal(2, messages.Count);
+// Assert.Equal(ChatRole.System, messages[0].Role);
+// Assert.Equal(ChatRole.Assistant, messages[1].Role);
+// }
+//}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageGroupsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageIndexTests.cs
similarity index 89%
rename from dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageGroupsTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageIndexTests.cs
index c3dfedd208..5ded224c52 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageGroupsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/MessageIndexTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Microsoft.Agents.AI.Compaction;
@@ -7,9 +7,9 @@ using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Abstractions.UnitTests.Compaction;
///
-/// Contains tests for the class.
+/// Contains tests for the class.
///
-public class MessageGroupsTests
+public class MessageIndexTests
{
[Fact]
public void Create_EmptyList_ReturnsEmptyGroups()
@@ -18,7 +18,7 @@ public class MessageGroupsTests
List messages = [];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Empty(groups.Groups);
@@ -34,7 +34,7 @@ public class MessageGroupsTests
];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -52,7 +52,7 @@ public class MessageGroupsTests
];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -69,7 +69,7 @@ public class MessageGroupsTests
];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -86,7 +86,7 @@ public class MessageGroupsTests
List messages = [assistantMessage, toolResult];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -109,7 +109,7 @@ public class MessageGroupsTests
List messages = [systemMsg, userMsg, assistantToolCall, toolResult, assistantText];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Equal(4, groups.Groups.Count);
@@ -134,7 +134,7 @@ public class MessageGroupsTests
List messages = [assistantToolCall, toolResult1, toolResult2];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -150,7 +150,7 @@ public class MessageGroupsTests
ChatMessage msg2 = new(ChatRole.Assistant, "Response");
ChatMessage msg3 = new(ChatRole.User, "Second");
- MessageGroups groups = MessageGroups.Create([msg1, msg2, msg3]);
+ MessageIndex groups = MessageIndex.Create([msg1, msg2, msg3]);
groups.Groups[1].IsExcluded = true;
// Act
@@ -169,7 +169,7 @@ public class MessageGroupsTests
ChatMessage msg1 = new(ChatRole.User, "First");
ChatMessage msg2 = new(ChatRole.Assistant, "Response");
- MessageGroups groups = MessageGroups.Create([msg1, msg2]);
+ MessageIndex groups = MessageIndex.Create([msg1, msg2]);
groups.Groups[0].IsExcluded = true;
// Act
@@ -183,7 +183,7 @@ public class MessageGroupsTests
public void IncludedGroupCount_ReflectsExclusions()
{
// Arrange
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "A"),
new ChatMessage(ChatRole.Assistant, "B"),
@@ -207,7 +207,7 @@ public class MessageGroupsTests
List messages = [summaryMessage];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Single(groups.Groups);
@@ -227,7 +227,7 @@ public class MessageGroupsTests
List messages = [systemMsg, summaryMsg, userMsg];
// Act
- MessageGroups groups = MessageGroups.Create(messages);
+ MessageIndex groups = MessageIndex.Create(messages);
// Assert
Assert.Equal(3, groups.Groups.Count);
@@ -264,7 +264,7 @@ public class MessageGroupsTests
public void Create_ComputesByteCount_Utf8()
{
// Arrange — "Hello" is 5 UTF-8 bytes
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Assert
Assert.Equal(5, groups.Groups[0].ByteCount);
@@ -274,7 +274,7 @@ public class MessageGroupsTests
public void Create_ComputesByteCount_MultiByteChars()
{
// Arrange — "café" has a multi-byte 'é' (2 bytes in UTF-8) → 5 bytes total
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "café")]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "café")]);
// Assert
Assert.Equal(5, groups.Groups[0].ByteCount);
@@ -286,7 +286,7 @@ public class MessageGroupsTests
// Arrange — ToolCall group: assistant (tool call, null text) + tool result "OK" (2 bytes)
ChatMessage assistantMsg = new(ChatRole.Assistant, [new FunctionCallContent("call1", "fn")]);
ChatMessage toolResult = new(ChatRole.Tool, "OK");
- MessageGroups groups = MessageGroups.Create([assistantMsg, toolResult]);
+ MessageIndex groups = MessageIndex.Create([assistantMsg, toolResult]);
// Assert — single ToolCall group with 2 messages
Assert.Single(groups.Groups);
@@ -298,7 +298,7 @@ public class MessageGroupsTests
public void Create_DefaultTokenCount_IsHeuristic()
{
// Arrange — "Hello world test data!" = 22 UTF-8 bytes → 22 / 4 = 5 estimated tokens
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello world test data!")]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello world test data!")]);
// Assert
Assert.Equal(22, groups.Groups[0].ByteCount);
@@ -311,7 +311,7 @@ public class MessageGroupsTests
// Arrange — message with no text (e.g., pure function call)
ChatMessage msg = new(ChatRole.Assistant, [new FunctionCallContent("call1", "get_weather")]);
ChatMessage tool = new(ChatRole.Tool, string.Empty);
- MessageGroups groups = MessageGroups.Create([msg, tool]);
+ MessageIndex groups = MessageIndex.Create([msg, tool]);
// Assert
Assert.Equal(2, groups.Groups[0].MessageCount);
@@ -323,7 +323,7 @@ public class MessageGroupsTests
public void TotalAggregates_SumAllGroups()
{
// Arrange
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "AAAA"), // 4 bytes
new ChatMessage(ChatRole.Assistant, "BBBB"), // 4 bytes
@@ -342,7 +342,7 @@ public class MessageGroupsTests
public void IncludedAggregates_ExcludeMarkedGroups()
{
// Arrange
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "AAAA"), // 4 bytes
new ChatMessage(ChatRole.Assistant, "BBBB"), // 4 bytes
@@ -369,7 +369,7 @@ public class MessageGroupsTests
ChatMessage assistantMsg = new(ChatRole.Assistant, [new FunctionCallContent("call1", "fn")]);
ChatMessage toolResult = new(ChatRole.Tool, "OK");
- MessageGroups groups = MessageGroups.Create([assistantMsg, toolResult]);
+ MessageIndex groups = MessageIndex.Create([assistantMsg, toolResult]);
// Assert — single group with 2 messages
Assert.Single(groups.Groups);
@@ -383,7 +383,7 @@ public class MessageGroupsTests
public void Create_AssignsTurnIndices_SingleTurn()
{
// Arrange — System (no turn), User + Assistant = turn 1
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "You are helpful."),
new ChatMessage(ChatRole.User, "Hello"),
@@ -402,7 +402,7 @@ public class MessageGroupsTests
public void Create_AssignsTurnIndices_MultiTurn()
{
// Arrange — 3 user turns
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "System prompt."),
new ChatMessage(ChatRole.User, "Q1"),
@@ -429,7 +429,7 @@ public class MessageGroupsTests
ChatMessage assistantToolCall = new(ChatRole.Assistant, [new FunctionCallContent("call1", "get_weather")]);
ChatMessage toolResult = new(ChatRole.Tool, "Sunny");
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "What's the weather?"),
assistantToolCall,
@@ -449,7 +449,7 @@ public class MessageGroupsTests
public void GetTurnGroups_ReturnsGroupsForSpecificTurn()
{
// Arrange
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "System."),
new ChatMessage(ChatRole.User, "Q1"),
@@ -475,7 +475,7 @@ public class MessageGroupsTests
public void IncludedTurnCount_ReflectsExclusions()
{
// Arrange — 2 turns, exclude all groups in turn 1
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
new ChatMessage(ChatRole.Assistant, "A1"),
@@ -495,7 +495,7 @@ public class MessageGroupsTests
public void TotalTurnCount_ZeroWhenNoUserMessages()
{
// Arrange — only system messages
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "System."),
]);
@@ -509,7 +509,7 @@ public class MessageGroupsTests
public void IncludedTurnCount_PartialExclusion_StillCountsTurn()
{
// Arrange — turn 1 has 2 groups, only one excluded
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "Q1"),
new ChatMessage(ChatRole.Assistant, "A1"),
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/PipelineCompactionStrategyTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/PipelineCompactionStrategyTests.cs
index eb5cda0997..c6a842dea4 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/PipelineCompactionStrategyTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Compaction/PipelineCompactionStrategyTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading;
@@ -20,17 +20,17 @@ public class PipelineCompactionStrategyTests
// Arrange
List executionOrder = [];
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.Callback(() => executionOrder.Add("first"))
.ReturnsAsync(false);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.Callback(() => executionOrder.Add("second"))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object);
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Act
await pipeline.CompactAsync(groups);
@@ -44,11 +44,11 @@ public class PipelineCompactionStrategyTests
{
// Arrange
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object);
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ PipelineCompactionStrategy pipeline = new([strategy1.Object]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Act
bool result = await pipeline.CompactAsync(groups);
@@ -62,15 +62,15 @@ public class PipelineCompactionStrategyTests
{
// Arrange
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(true);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object);
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Act
bool result = await pipeline.CompactAsync(groups);
@@ -84,22 +84,22 @@ public class PipelineCompactionStrategyTests
{
// Arrange
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(true);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object);
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Act
await pipeline.CompactAsync(groups);
// Assert — both strategies were called
- strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
@@ -107,8 +107,8 @@ public class PipelineCompactionStrategyTests
{
// Arrange — first strategy reduces to target
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ .Callback((groups, _) =>
{
// Exclude the first group to bring count down
groups.Groups[0].IsExcluded = true;
@@ -116,16 +116,16 @@ public class PipelineCompactionStrategyTests
.ReturnsAsync(true);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object)
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object])
{
EarlyStop = true,
TargetIncludedGroupCount = 2,
};
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "First"),
new ChatMessage(ChatRole.Assistant, "Response"),
@@ -137,8 +137,8 @@ public class PipelineCompactionStrategyTests
// Assert — strategy2 should not have been called
Assert.True(result);
- strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Never);
+ strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
@@ -146,20 +146,20 @@ public class PipelineCompactionStrategyTests
{
// Arrange — first strategy does NOT bring count to target
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object)
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object])
{
EarlyStop = true,
TargetIncludedGroupCount = 1,
};
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.User, "First"),
new ChatMessage(ChatRole.User, "Second"),
@@ -170,8 +170,8 @@ public class PipelineCompactionStrategyTests
await pipeline.CompactAsync(groups);
// Assert — both strategies were called since target was never reached
- strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
@@ -179,27 +179,27 @@ public class PipelineCompactionStrategyTests
{
// Arrange
Mock strategy1 = new();
- strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(true);
Mock strategy2 = new();
- strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ strategy2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(false);
- PipelineCompactionStrategy pipeline = new(strategy1.Object, strategy2.Object)
+ PipelineCompactionStrategy pipeline = new([strategy1.Object, strategy2.Object])
{
EarlyStop = true,
// TargetIncludedGroupCount is null
};
- MessageGroups groups = MessageGroups.Create([new ChatMessage(ChatRole.User, "Hello")]);
+ MessageIndex groups = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]);
// Act
await pipeline.CompactAsync(groups);
// Assert — both strategies called because no target to check against
- strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ strategy2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
@@ -207,8 +207,8 @@ public class PipelineCompactionStrategyTests
{
// Arrange — pipeline: first exclude oldest 2 non-system groups, then exclude 2 more
Mock phase1 = new();
- phase1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
+ phase1.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ .Callback((groups, _) =>
{
int excluded = 0;
foreach (MessageGroup group in groups.Groups)
@@ -223,8 +223,8 @@ public class PipelineCompactionStrategyTests
.ReturnsAsync(true);
Mock phase2 = new();
- phase2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
- .Callback((groups, _) =>
+ phase2.Setup(s => s.CompactAsync(It.IsAny(), It.IsAny()))
+ .Callback((groups, _) =>
{
int excluded = 0;
foreach (MessageGroup group in groups.Groups)
@@ -238,9 +238,9 @@ public class PipelineCompactionStrategyTests
})
.ReturnsAsync(true);
- PipelineCompactionStrategy pipeline = new(phase1.Object, phase2.Object);
+ PipelineCompactionStrategy pipeline = new([phase1.Object, phase2.Object]);
- MessageGroups groups = MessageGroups.Create(
+ MessageIndex groups = MessageIndex.Create(
[
new ChatMessage(ChatRole.System, "You are helpful."),
new ChatMessage(ChatRole.User, "Q1"),
@@ -262,8 +262,8 @@ public class PipelineCompactionStrategyTests
Assert.Equal("You are helpful.", included[0].Text);
Assert.Equal("Q3", included[1].Text);
- phase1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
- phase2.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny()), Times.Once);
+ phase1.Verify(s => s.CompactAsync(It.IsAny(), It.IsAny