mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Checkpoint
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Compaction;
|
||||
|
||||
public partial class ChatHistoryCompactionPipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
public enum Size
|
||||
{
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Compact,
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Adequate,
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Accomodating,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
public enum Approach
|
||||
{
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Aggressive,
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Balanced,
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
Gentle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// %%% COMMENT
|
||||
/// </summary>
|
||||
/// <param name="approach"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="chatClient"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static ChatHistoryCompactionPipeline Create(Approach approach, Size size, IChatClient chatClient) =>
|
||||
approach switch
|
||||
{
|
||||
Approach.Aggressive => CreateAgressive(size, chatClient),
|
||||
Approach.Balanced => CreateBalanced(size),
|
||||
Approach.Gentle => CreateGentle(size),
|
||||
_ => throw new NotImplementedException(), // %%% EXCEPTION
|
||||
};
|
||||
|
||||
private static ChatHistoryCompactionPipeline CreateAgressive(Size size, IChatClient chatClient) =>
|
||||
new(// 1. Gentle: collapse old tool-call groups into short summaries like "[Tool calls: LookupPrice]"
|
||||
new ToolResultCompactionStrategy(MaxTokens(size), preserveRecentGroups: 2),
|
||||
// 2. Moderate: use an LLM to summarize older conversation spans into a concise message
|
||||
new SummarizationCompactionStrategy(chatClient, MaxTokens(size), preserveRecentGroups: 2),
|
||||
// 3. Aggressive: keep only the last N user turns and their responses
|
||||
new SlidingWindowCompactionStrategy(MaxTurns(size)),
|
||||
// 4. Emergency: drop oldest groups until under the token budget
|
||||
new TruncationCompactionStrategy(MaxTokens(size), preserveRecentGroups: 1));
|
||||
|
||||
private static ChatHistoryCompactionPipeline CreateBalanced(Size size) =>
|
||||
new(// 1. Gentle: collapse old tool-call groups into short summaries like "[Tool calls: LookupPrice]"
|
||||
new ToolResultCompactionStrategy(MaxTokens(size), preserveRecentGroups: 2),
|
||||
// 2. Aggressive: keep only the last N user turns and their responses
|
||||
new SlidingWindowCompactionStrategy(MaxTurns(size)));
|
||||
|
||||
private static ChatHistoryCompactionPipeline CreateGentle(Size size) =>
|
||||
new(// 1. Gentle: collapse old tool-call groups into short summaries like "[Tool calls: LookupPrice]"
|
||||
new ToolResultCompactionStrategy(MaxTokens(size), preserveRecentGroups: 2));
|
||||
|
||||
private static int MaxTokens(Size size) =>
|
||||
size switch
|
||||
{
|
||||
Size.Compact => 500,
|
||||
Size.Adequate => 1000,
|
||||
Size.Accomodating => 2000,
|
||||
_ => throw new NotImplementedException(), // %%% EXCEPTION
|
||||
};
|
||||
|
||||
private static int MaxTurns(Size size) =>
|
||||
size switch
|
||||
{
|
||||
Size.Compact => 10,
|
||||
Size.Adequate => 50,
|
||||
Size.Accomodating => 100,
|
||||
_ => throw new NotImplementedException(), // %%% EXCEPTION
|
||||
};
|
||||
}
|
||||
+28
-16
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -24,7 +25,7 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// accepted (e.g., <see cref="InMemoryChatHistoryProviderOptions.ChatReducer"/>).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ChatHistoryCompactionPipeline : IChatReducer
|
||||
public partial class ChatHistoryCompactionPipeline : IChatReducer
|
||||
{
|
||||
private readonly ChatHistoryCompactionStrategy[] _strategies;
|
||||
private readonly IChatHistoryMetricsCalculator _metricsCalculator;
|
||||
@@ -52,7 +53,7 @@ public class ChatHistoryCompactionPipeline : IChatReducer
|
||||
IChatHistoryMetricsCalculator? metricsCalculator,
|
||||
params IEnumerable<ChatHistoryCompactionStrategy> strategies)
|
||||
{
|
||||
this._strategies = Throw.IfNull(strategies).ToArray();
|
||||
this._strategies = [.. Throw.IfNull(strategies)];
|
||||
this._metricsCalculator = metricsCalculator ?? DefaultChatHistoryMetricsCalculator.Instance;
|
||||
}
|
||||
|
||||
@@ -66,9 +67,9 @@ public class ChatHistoryCompactionPipeline : IChatReducer
|
||||
IEnumerable<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<ChatMessage> messageList = messages.ToList(); // %%% HAXX
|
||||
await this.CompactAsync(messageList, cancellationToken).ConfigureAwait(false);
|
||||
return messageList;
|
||||
List<ChatMessage> messageBuffer = messages is List<ChatMessage> messageList ? messageList : [.. messages];
|
||||
await this.CompactAsync(messageBuffer, cancellationToken).ConfigureAwait(false);
|
||||
return messageBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,26 +78,37 @@ public class ChatHistoryCompactionPipeline : IChatReducer
|
||||
/// <param name="messages">The mutable message list to compact.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A <see cref="CompactionPipelineResult"/> with aggregate and per-strategy metrics.</returns>
|
||||
public async ValueTask<CompactionPipelineResult> CompactAsync( // %%% SCOPE
|
||||
IList<ChatMessage> messages,
|
||||
public async ValueTask<CompactionPipelineResult> CompactAsync(
|
||||
List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(messages);
|
||||
|
||||
IReadOnlyList<ChatMessage> readOnlyMessages = messages as IReadOnlyList<ChatMessage> ?? [.. messages]; // %%% TYPE CONSISTENCY
|
||||
CompactionMetric overallBefore = this._metricsCalculator.Calculate(readOnlyMessages);
|
||||
ChatHistoryMetric overallBefore = this._metricsCalculator.Calculate(messages);
|
||||
|
||||
List<CompactionResult> results = new(this._strategies.Length);
|
||||
Debug.WriteLine($"COMPACTION: BEGIN x{overallBefore.MessageCount}/#{overallBefore.UserTurnCount} ({overallBefore.TokenCount} tokens)");
|
||||
|
||||
List<CompactionResult> compactionResults = new(this._strategies.Length);
|
||||
|
||||
Stopwatch timer = new();
|
||||
TimeSpan startTime = TimeSpan.Zero;
|
||||
ChatHistoryMetric overallAfter = overallBefore;
|
||||
ChatHistoryMetric currentBefore = overallBefore;
|
||||
foreach (ChatHistoryCompactionStrategy strategy in this._strategies)
|
||||
{
|
||||
CompactionResult result = await strategy.CompactAsync(messages, this._metricsCalculator, cancellationToken).ConfigureAwait(false);
|
||||
results.Add(result);
|
||||
// %%% VERBOSE - Debug.WriteLine($"COMPACTION: {strategy.Name} START");
|
||||
timer.Start();
|
||||
ChatHistoryCompactionStrategy.s_currentMetrics.Value = currentBefore;
|
||||
CompactionResult strategyResult = await strategy.CompactAsync(messages, this._metricsCalculator, cancellationToken).ConfigureAwait(false);
|
||||
timer.Stop();
|
||||
TimeSpan elapsedTime = timer.Elapsed - startTime;
|
||||
// %%% VERBOSE - Debug.WriteLine($"COMPACTION: {strategy.Name} FINISH [{elapsedTime}]");
|
||||
compactionResults.Add(strategyResult);
|
||||
overallAfter = currentBefore = strategyResult.After;
|
||||
}
|
||||
|
||||
readOnlyMessages = messages as IReadOnlyList<ChatMessage> ?? [.. messages];
|
||||
CompactionMetric overallAfter = this._metricsCalculator.Calculate(readOnlyMessages);
|
||||
Debug.WriteLineIf(overallBefore.TokenCount != overallAfter.TokenCount, $"COMPACTION: TOTAL [{timer.Elapsed}] {overallBefore.TokenCount} => {overallAfter.TokenCount} tokens");
|
||||
|
||||
return new(overallBefore, overallAfter, results);
|
||||
return new(overallBefore, overallAfter, compactionResults);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-28
@@ -2,8 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -21,7 +20,7 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// while the strategy adds:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>A conditional trigger via <see cref="ShouldCompact"/> that decides whether compaction runs.</description></item>
|
||||
/// <item><description>Before/after <see cref="CompactionMetric"/> reporting via <see cref="CompactionResult"/>.</description></item>
|
||||
/// <item><description>Before/after <see cref="ChatHistoryMetric"/> reporting via <see cref="CompactionResult"/>.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -36,7 +35,7 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// </remarks>
|
||||
public abstract class ChatHistoryCompactionStrategy
|
||||
{
|
||||
private static readonly AsyncLocal<CompactionMetric> s_currentMetrics = new();
|
||||
internal static readonly AsyncLocal<ChatHistoryMetric> s_currentMetrics = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryCompactionStrategy"/> class.
|
||||
@@ -48,9 +47,9 @@ public abstract class ChatHistoryCompactionStrategy
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the current <see cref="CompactionMetric"/> for the executing strategy, allowing <see cref="Reducer"/> to make informed decisions.
|
||||
/// Exposes the current <see cref="ChatHistoryMetric"/> for the executing strategy, allowing <see cref="Reducer"/> to make informed decisions.
|
||||
/// </summary>
|
||||
protected static CompactionMetric CurrentMetrics => s_currentMetrics.Value ?? throw new InvalidOperationException($"No active {nameof(ChatHistoryCompactionStrategy)}.");
|
||||
protected static ChatHistoryMetric CurrentMetrics => s_currentMetrics.Value ?? throw new InvalidOperationException($"No active {nameof(ChatHistoryCompactionStrategy)}.");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IChatReducer"/> that performs the actual message compaction.
|
||||
@@ -72,48 +71,50 @@ public abstract class ChatHistoryCompactionStrategy
|
||||
/// <returns>
|
||||
/// <see langword="true"/> to proceed with compaction; <see langword="false"/> to skip.
|
||||
/// </returns>
|
||||
public abstract bool ShouldCompact(CompactionMetric metrics);
|
||||
protected abstract bool ShouldCompact(ChatHistoryMetric metrics);
|
||||
|
||||
/// <summary>
|
||||
/// Execute this strategy: check the trigger, delegate to the <see cref="IChatReducer"/>, and report metrics.
|
||||
/// </summary>
|
||||
/// <param name="messages">The mutable message list to compact.</param>
|
||||
/// <param name="history">The mutable message list to compact.</param>
|
||||
/// <param name="metricsCalculator">The calculator to use for metric snapshots.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A <see cref="CompactionResult"/> reporting the outcome.</returns>
|
||||
public async ValueTask<CompactionResult> CompactAsync(
|
||||
IList<ChatMessage> messages,
|
||||
internal async ValueTask<CompactionResult> CompactAsync(
|
||||
List<ChatMessage> history,
|
||||
IChatHistoryMetricsCalculator metricsCalculator,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
messages = Throw.IfNull(messages);
|
||||
Throw.IfNull(metricsCalculator);
|
||||
Throw.IfNull(history);
|
||||
|
||||
List<ChatMessage>? messageList = messages as List<ChatMessage>;
|
||||
ReadOnlyCollection<ChatMessage> snapshot = messageList is not null ? messageList.AsReadOnly() : new(messages);
|
||||
CompactionMetric before = metricsCalculator.Calculate(snapshot);
|
||||
s_currentMetrics.Value = before;
|
||||
if (!this.ShouldCompact(before))
|
||||
ChatHistoryMetric beforeMetrics = CurrentMetrics;
|
||||
if (!this.ShouldCompact(beforeMetrics))
|
||||
{
|
||||
return CompactionResult.Skipped(this.Name, before);
|
||||
// %%% VERBOSE - Debug.WriteLine($"COMPACTION: {this.Name} - Skipped");
|
||||
return CompactionResult.Skipped(this.Name, beforeMetrics);
|
||||
}
|
||||
|
||||
ChatMessage[] reduced = (await this.Reducer.ReduceAsync(snapshot, cancellationToken).ConfigureAwait(false)).ToArray();
|
||||
Debug.WriteLine($"COMPACTION: {this.Name} - Reducing");
|
||||
|
||||
bool modified = reduced.Length != snapshot.Count;
|
||||
IEnumerable<ChatMessage> reducerResult = await this.Reducer.ReduceAsync(history, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Ensure we have a concrete collection to avoid multiple enumerations of the reducer result, which could be costly if it's an iterator.
|
||||
ChatMessage[] reducedCopy = [.. reducerResult];
|
||||
|
||||
bool modified = reducedCopy.Length != history.Count;
|
||||
if (modified)
|
||||
{
|
||||
messages.Clear();
|
||||
foreach (ChatMessage message in reduced)
|
||||
{
|
||||
messages.Add(message);
|
||||
}
|
||||
history.Clear();
|
||||
history.AddRange(reducedCopy);
|
||||
}
|
||||
|
||||
CompactionMetric after = modified
|
||||
? metricsCalculator.Calculate(reduced)
|
||||
: before;
|
||||
ChatHistoryMetric afterMetrics = modified
|
||||
? metricsCalculator.Calculate(reducedCopy)
|
||||
: beforeMetrics;
|
||||
|
||||
return new(this.Name, applied: modified, before, after);
|
||||
Debug.WriteLine($"COMPACTION: {this.Name} - Tokens {beforeMetrics.TokenCount} => {afterMetrics.TokenCount}");
|
||||
|
||||
return new(this.Name, applied: modified, beforeMetrics, afterMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// <summary>
|
||||
/// Immutable snapshot of conversation metrics used for compaction trigger evaluation and reporting.
|
||||
/// </summary>
|
||||
public sealed class CompactionMetric
|
||||
public sealed class ChatHistoryMetric
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the estimated token count across all messages.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Compaction;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a chat history compaction strategy that uses a condition function to determine when compaction should
|
||||
/// occur.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This strategy evaluates a user-provided condition against compaction metrics to decide whether to
|
||||
/// compact the chat history. It is useful for scenarios where compaction should be triggered based on custom thresholds
|
||||
/// or criteria. Inherits from ChatHistoryCompactionStrategy.
|
||||
/// </remarks>
|
||||
public class ChatReducerCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
{
|
||||
private readonly Func<ChatHistoryMetric, bool> _condition;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatReducerCompactionStrategy"/> class.
|
||||
/// </summary>
|
||||
public ChatReducerCompactionStrategy(
|
||||
IChatReducer reducer,
|
||||
Func<ChatHistoryMetric, bool> condition)
|
||||
: base(reducer)
|
||||
{
|
||||
this._condition = Throw.IfNull(condition);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ShouldCompact(ChatHistoryMetric metrics) => this._condition(metrics);
|
||||
}
|
||||
@@ -18,8 +18,8 @@ public sealed class CompactionPipelineResult
|
||||
/// <param name="after">Metrics of the conversation after all strategies ran.</param>
|
||||
/// <param name="strategyResults">Per-strategy results in execution order.</param>
|
||||
internal CompactionPipelineResult(
|
||||
CompactionMetric before,
|
||||
CompactionMetric after,
|
||||
ChatHistoryMetric before,
|
||||
ChatHistoryMetric after,
|
||||
IReadOnlyList<CompactionResult> strategyResults)
|
||||
{
|
||||
this.Before = Throw.IfNull(before);
|
||||
@@ -30,12 +30,12 @@ public sealed class CompactionPipelineResult
|
||||
/// <summary>
|
||||
/// Gets the conversation metrics before any compaction strategy ran.
|
||||
/// </summary>
|
||||
public CompactionMetric Before { get; }
|
||||
public ChatHistoryMetric Before { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conversation metrics after all compaction strategies ran.
|
||||
/// </summary>
|
||||
public CompactionMetric After { get; }
|
||||
public ChatHistoryMetric After { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the per-strategy results in execution order.
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class CompactionResult
|
||||
/// <param name="applied">Whether the strategy modified the message list.</param>
|
||||
/// <param name="before">Metrics before the strategy ran.</param>
|
||||
/// <param name="after">Metrics after the strategy ran.</param>
|
||||
public CompactionResult(string strategyName, bool applied, CompactionMetric before, CompactionMetric after)
|
||||
public CompactionResult(string strategyName, bool applied, ChatHistoryMetric before, ChatHistoryMetric after)
|
||||
{
|
||||
this.StrategyName = Throw.IfNullOrWhitespace(strategyName);
|
||||
this.Applied = applied;
|
||||
@@ -37,12 +37,12 @@ public sealed class CompactionResult
|
||||
/// <summary>
|
||||
/// Gets the conversation metrics before the strategy executed.
|
||||
/// </summary>
|
||||
public CompactionMetric Before { get; }
|
||||
public ChatHistoryMetric Before { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conversation metrics after the strategy executed.
|
||||
/// </summary>
|
||||
public CompactionMetric After { get; }
|
||||
public ChatHistoryMetric After { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CompactionResult"/> representing a skipped strategy.
|
||||
@@ -50,6 +50,6 @@ public sealed class CompactionResult
|
||||
/// <param name="strategyName">The name of the skipped strategy.</param>
|
||||
/// <param name="metrics">The current conversation metrics.</param>
|
||||
/// <returns>A result indicating no compaction was applied.</returns>
|
||||
internal static CompactionResult Skipped(string strategyName, CompactionMetric metrics)
|
||||
internal static CompactionResult Skipped(string strategyName, ChatHistoryMetric metrics)
|
||||
=> new(strategyName, applied: false, metrics, metrics);
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public sealed class DefaultChatHistoryMetricsCalculator : IChatHistoryMetricsCal
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CompactionMetric Calculate(IReadOnlyList<ChatMessage> messages)
|
||||
public ChatHistoryMetric Calculate(IReadOnlyList<ChatMessage> messages)
|
||||
{
|
||||
if (messages is null || messages.Count == 0)
|
||||
{
|
||||
|
||||
+4
-4
@@ -9,18 +9,18 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
// and whether custom metrics calculators are a realistic extension point.
|
||||
|
||||
/// <summary>
|
||||
/// Computes <see cref="CompactionMetric"/> for a list of messages.
|
||||
/// Computes <see cref="ChatHistoryMetric"/> for a list of messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Token counting is model-specific. Implementations can provide precise tokenization
|
||||
/// (e.g., using tiktoken or a model-specific tokenizer) or use estimation heuristics.
|
||||
/// </remarks>
|
||||
public interface IChatHistoryMetricsCalculator // %%% NEEDED ???
|
||||
public interface IChatHistoryMetricsCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute metrics for the given messages.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to analyze.</param>
|
||||
/// <returns>A <see cref="CompactionMetric"/> snapshot.</returns>
|
||||
CompactionMetric Calculate(IReadOnlyList<ChatMessage> messages);
|
||||
/// <returns>A <see cref="ChatHistoryMetric"/> snapshot.</returns>
|
||||
ChatHistoryMetric Calculate(IReadOnlyList<ChatMessage> messages);
|
||||
}
|
||||
|
||||
+9
-14
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -44,7 +43,7 @@ public class SlidingWindowCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShouldCompact(CompactionMetric metrics) =>
|
||||
protected override bool ShouldCompact(ChatHistoryMetric metrics) =>
|
||||
metrics.UserTurnCount > this._maxTurns;
|
||||
|
||||
/// <summary>
|
||||
@@ -57,25 +56,21 @@ public class SlidingWindowCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
IEnumerable<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
IReadOnlyList<ChatMessage> messageList = [.. messages];
|
||||
IReadOnlyList<ChatMessage> messageList = [.. messages]; // %%% PERFORMANCE
|
||||
IReadOnlyList<ChatMessageGroup> groups = CurrentMetrics.Groups;
|
||||
|
||||
// Find the group-list indices where each user turn starts
|
||||
//int[] turnGroupIndices = groups.Where(group => group.Kind == ChatMessageGroupKind.UserTurn).Select(group => group.StartIndex).ToArray(); // %%% TODO
|
||||
List<int> turnGroupIndices = [];
|
||||
for (int i = 0; i < groups.Count; i++)
|
||||
{
|
||||
if (groups[i].Kind == ChatMessageGroupKind.UserTurn)
|
||||
{
|
||||
turnGroupIndices.Add(i);
|
||||
}
|
||||
}
|
||||
int[] turnGroupIndices =
|
||||
[.. CurrentMetrics.Groups
|
||||
.Select((group, index) => (group, index))
|
||||
.Where(t => t.group.Kind == ChatMessageGroupKind.UserTurn)
|
||||
.Select(t => t.index)];
|
||||
|
||||
// Keep the last maxTurns user turns and everything after the first kept turn
|
||||
int firstKeptTurnIndex = turnGroupIndices.Count - maxTurns;
|
||||
int firstKeptTurnIndex = turnGroupIndices.Length - maxTurns;
|
||||
int firstKeptGroupIndex = turnGroupIndices[firstKeptTurnIndex];
|
||||
|
||||
List<ChatMessage> result = new(messageList.Count);
|
||||
List<ChatMessage> result = new(messageList.Count); // %%% PERFORMANCE
|
||||
for (int gi = 0; gi < groups.Count; gi++)
|
||||
{
|
||||
ChatMessageGroup group = groups[gi];
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ public class SummarizationCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShouldCompact(CompactionMetric metrics) =>
|
||||
protected override bool ShouldCompact(ChatHistoryMetric metrics) =>
|
||||
metrics.TokenCount > this._maxTokens;
|
||||
|
||||
/// <summary>
|
||||
@@ -96,7 +96,7 @@ public class SummarizationCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
IReadOnlyList<ChatMessage> messageList = [.. messages];
|
||||
IReadOnlyList<ChatMessageGroup> groups = CurrentMetrics.Groups;
|
||||
|
||||
List<ChatMessageGroup> nonSystemGroups = groups.Where(g => g.Kind != ChatMessageGroupKind.System).ToList();
|
||||
List<ChatMessageGroup> nonSystemGroups = [.. groups.Where(g => g.Kind != ChatMessageGroupKind.System)];
|
||||
int protectedFromIndex = Math.Max(0, nonSystemGroups.Count - this._preserveRecentGroups);
|
||||
|
||||
if (protectedFromIndex == 0)
|
||||
|
||||
+8
-3
@@ -29,6 +29,11 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// </remarks>
|
||||
public class ToolResultCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value for `preserveRecentGroups` used when constructing <see cref="ToolResultCompactionStrategy"/>.
|
||||
/// </summary>
|
||||
public const int DefaultPreserveRecentGroups = 2;
|
||||
|
||||
private readonly int _maxTokens;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,14 +44,14 @@ public class ToolResultCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
/// The number of most-recent non-system message groups to protect from collapsing.
|
||||
/// Defaults to 2, ensuring the current turn's tool interactions remain visible.
|
||||
/// </param>
|
||||
public ToolResultCompactionStrategy(int maxTokens, int preserveRecentGroups = 2)
|
||||
public ToolResultCompactionStrategy(int maxTokens, int preserveRecentGroups = DefaultPreserveRecentGroups)
|
||||
: base(new ToolResultClearingReducer(preserveRecentGroups))
|
||||
{
|
||||
this._maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShouldCompact(CompactionMetric metrics) =>
|
||||
protected override bool ShouldCompact(ChatHistoryMetric metrics) =>
|
||||
metrics.TokenCount > this._maxTokens && metrics.ToolCallCount > 0;
|
||||
|
||||
/// <summary>
|
||||
@@ -62,7 +67,7 @@ public class ToolResultCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
IReadOnlyList<ChatMessage> messageList = [.. messages];
|
||||
IReadOnlyList<ChatMessageGroup> groups = CurrentMetrics.Groups;
|
||||
|
||||
List<ChatMessageGroup> nonSystemGroups = groups.Where(g => g.Kind != ChatMessageGroupKind.System).ToList();
|
||||
List<ChatMessageGroup> nonSystemGroups = [.. groups.Where(g => g.Kind != ChatMessageGroupKind.System)];
|
||||
int protectedFromIndex = Math.Max(0, nonSystemGroups.Count - preserveRecentGroups);
|
||||
HashSet<int> protectedGroupStarts = [];
|
||||
for (int i = protectedFromIndex; i < nonSystemGroups.Count; i++)
|
||||
|
||||
+4
-4
@@ -41,7 +41,7 @@ public class TruncationCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShouldCompact(CompactionMetric metrics) =>
|
||||
protected override bool ShouldCompact(ChatHistoryMetric metrics) =>
|
||||
metrics.TokenCount > this._maxTokens;
|
||||
|
||||
/// <summary>
|
||||
@@ -56,15 +56,15 @@ public class TruncationCompactionStrategy : ChatHistoryCompactionStrategy
|
||||
{
|
||||
IReadOnlyList<ChatMessage> messageList = [.. messages];
|
||||
|
||||
List<ChatMessageGroup> removableGroups = CurrentMetrics.Groups.Where(g => g.Kind != ChatMessageGroupKind.System).ToList();
|
||||
ChatMessageGroup[] removableGroups = [.. CurrentMetrics.Groups.Where(g => g.Kind != ChatMessageGroupKind.System)];
|
||||
|
||||
if (removableGroups.Count == 0)
|
||||
if (removableGroups.Length == 0)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<ChatMessage>>(messageList);
|
||||
}
|
||||
|
||||
// Remove oldest non-system groups, keeping at least preserveRecentGroups.
|
||||
int maxRemovable = removableGroups.Count - preserveRecentGroups;
|
||||
int maxRemovable = removableGroups.Length - preserveRecentGroups;
|
||||
|
||||
if (maxRemovable <= 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user