mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into copilot/add-preconfigured-compaction-strategy
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
@@ -30,6 +31,12 @@ namespace Microsoft.Agents.AI.Compaction;
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A custom <see cref="ToolCallFormatter"/> can be supplied to override the default YAML-like
|
||||
/// summary format. The formatter receives the <see cref="CompactionMessageGroup"/> being collapsed
|
||||
/// and must return the replacement summary string. <see cref="DefaultToolCallFormatter"/> is the
|
||||
/// built-in default and can be reused inside a custom formatter when needed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="MinimumPreservedGroups"/> is a hard floor: even if the <see cref="CompactionStrategy.Target"/>
|
||||
/// has not been reached, compaction will not touch the last <see cref="MinimumPreservedGroups"/> non-system groups.
|
||||
/// </para>
|
||||
@@ -62,7 +69,10 @@ public sealed class ToolResultCompactionStrategy : CompactionStrategy
|
||||
/// An optional target condition that controls when compaction stops. When <see langword="null"/>,
|
||||
/// defaults to the inverse of the <paramref name="trigger"/> — compaction stops as soon as the trigger would no longer fire.
|
||||
/// </param>
|
||||
public ToolResultCompactionStrategy(CompactionTrigger trigger, int minimumPreservedGroups = DefaultMinimumPreserved, CompactionTrigger? target = null)
|
||||
public ToolResultCompactionStrategy(
|
||||
CompactionTrigger trigger,
|
||||
int minimumPreservedGroups = DefaultMinimumPreserved,
|
||||
CompactionTrigger? target = null)
|
||||
: base(trigger, target)
|
||||
{
|
||||
this.MinimumPreservedGroups = EnsureNonNegative(minimumPreservedGroups);
|
||||
@@ -74,6 +84,13 @@ public sealed class ToolResultCompactionStrategy : CompactionStrategy
|
||||
/// </summary>
|
||||
public int MinimumPreservedGroups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional custom formatter that converts a <see cref="CompactionMessageGroup"/> into a summary string.
|
||||
/// When <see langword="null"/>, <see cref="DefaultToolCallFormatter"/> is used, which produces a YAML-like
|
||||
/// block listing each tool name and its results.
|
||||
/// </summary>
|
||||
public Func<CompactionMessageGroup, string>? ToolCallFormatter { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ValueTask<bool> CompactCoreAsync(CompactionMessageIndex index, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -120,7 +137,7 @@ public sealed class ToolResultCompactionStrategy : CompactionStrategy
|
||||
int idx = eligibleIndices[e] + offset;
|
||||
CompactionMessageGroup group = index.Groups[idx];
|
||||
|
||||
string summary = BuildToolCallSummary(group);
|
||||
string summary = (this.ToolCallFormatter ?? DefaultToolCallFormatter).Invoke(group);
|
||||
|
||||
// Exclude the original group and insert a collapsed replacement
|
||||
group.IsExcluded = true;
|
||||
@@ -145,14 +162,18 @@ public sealed class ToolResultCompactionStrategy : CompactionStrategy
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a concise summary string for a tool call group, including tool names,
|
||||
/// The default formatter that produces a YAML-like summary of tool call groups, including tool names,
|
||||
/// results, and deduplication counts for repeated tool names.
|
||||
/// </summary>
|
||||
private static string BuildToolCallSummary(CompactionMessageGroup group)
|
||||
/// <remarks>
|
||||
/// This is the formatter used when no custom <see cref="ToolCallFormatter"/> is supplied.
|
||||
/// It can be referenced directly in a custom formatter to augment or wrap the default output.
|
||||
/// </remarks>
|
||||
public static string DefaultToolCallFormatter(CompactionMessageGroup group)
|
||||
{
|
||||
// Collect function calls (callId, name) and results (callId → result text)
|
||||
List<(string CallId, string Name)> functionCalls = [];
|
||||
Dictionary<string, string> resultsByCallId = new();
|
||||
Dictionary<string, string> resultsByCallId = [];
|
||||
List<string> plainTextResults = [];
|
||||
|
||||
foreach (ChatMessage message in group.Messages)
|
||||
@@ -187,7 +208,7 @@ public sealed class ToolResultCompactionStrategy : CompactionStrategy
|
||||
// grouping by tool name while preserving first-seen order.
|
||||
int plainTextIdx = 0;
|
||||
List<string> orderedNames = [];
|
||||
Dictionary<string, List<string>> groupedResults = new();
|
||||
Dictionary<string, List<string>> groupedResults = [];
|
||||
|
||||
foreach ((string callId, string name) in functionCalls)
|
||||
{
|
||||
|
||||
+87
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Compaction;
|
||||
@@ -348,4 +349,90 @@ public class ToolResultCompactionStrategyTests
|
||||
List<ChatMessage> included = [.. groups.GetIncludedMessages()];
|
||||
Assert.Equal("[Tool Calls]\nget_weather:\n - Sunny\n - Rainy\nsearch_docs:\n - Found 3 docs", included[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncUsesCustomFormatterAsync()
|
||||
{
|
||||
// Arrange — custom formatter that produces a collapsed message count
|
||||
static string CustomFormatter(CompactionMessageGroup group) =>
|
||||
$"[Collapsed: {group.Messages.Count} messages]";
|
||||
|
||||
ToolResultCompactionStrategy strategy = new(
|
||||
trigger: _ => true,
|
||||
minimumPreservedGroups: 1)
|
||||
{
|
||||
ToolCallFormatter = CustomFormatter,
|
||||
};
|
||||
|
||||
CompactionMessageIndex groups = CompactionMessageIndex.Create(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "get_weather")]),
|
||||
new ChatMessage(ChatRole.Tool, "Sunny"),
|
||||
new ChatMessage(ChatRole.User, "Q2"),
|
||||
]);
|
||||
|
||||
// Act
|
||||
bool result = await strategy.CompactAsync(groups);
|
||||
|
||||
// Assert — custom formatter output used instead of default YAML-like format
|
||||
Assert.True(result);
|
||||
List<ChatMessage> included = [.. groups.GetIncludedMessages()];
|
||||
Assert.Equal("[Collapsed: 2 messages]", included[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolCallFormatterPropertyIsNullWhenNoneProvided()
|
||||
{
|
||||
// Arrange
|
||||
ToolResultCompactionStrategy strategy = new(CompactionTriggers.Always);
|
||||
|
||||
// Assert — ToolCallFormatter is null when no custom formatter is provided
|
||||
Assert.Null(strategy.ToolCallFormatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolCallFormatterPropertyReturnsCustomFormatterWhenProvided()
|
||||
{
|
||||
// Arrange
|
||||
Func<CompactionMessageGroup, string> customFormatter = static _ => "custom";
|
||||
ToolResultCompactionStrategy strategy = new(
|
||||
CompactionTriggers.Always)
|
||||
{
|
||||
ToolCallFormatter = customFormatter
|
||||
};
|
||||
|
||||
// Assert — ToolCallFormatter is the injected custom function
|
||||
Assert.Same(customFormatter, strategy.ToolCallFormatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompactAsyncCustomFormatterCanDelegateToDefaultAsync()
|
||||
{
|
||||
// Arrange — custom formatter that wraps the default output
|
||||
static string WrappingFormatter(CompactionMessageGroup group) =>
|
||||
$"CUSTOM_PREFIX\n{ToolResultCompactionStrategy.DefaultToolCallFormatter(group)}";
|
||||
|
||||
ToolResultCompactionStrategy strategy = new(
|
||||
trigger: _ => true,
|
||||
minimumPreservedGroups: 1)
|
||||
{
|
||||
ToolCallFormatter = WrappingFormatter
|
||||
};
|
||||
|
||||
CompactionMessageIndex groups = CompactionMessageIndex.Create(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "Q1"),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("c1", "fn")]),
|
||||
new ChatMessage(ChatRole.Tool, "result"),
|
||||
new ChatMessage(ChatRole.User, "Q2"),
|
||||
]);
|
||||
|
||||
// Act
|
||||
await strategy.CompactAsync(groups);
|
||||
|
||||
// Assert — wrapped default output
|
||||
List<ChatMessage> included = [.. groups.GetIncludedMessages()];
|
||||
Assert.Equal("CUSTOM_PREFIX\n[Tool Calls]\nfn:\n - result", included[1].Text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user