当前位置: 首页
AI教程
Agent框架持久化设置对ReduceAsync调用时机的影响

Agent框架持久化设置对ReduceAsync调用时机的影响

热心网友 时间:2026-06-04
转载

本文演示在 Microsoft Agent Framework 中,RequirePerServiceCallChatHistoryPersistence 选项如何影响 ChatReducer 的 ReduceAsync 调用时机,并详细说明在不同应用场景下应当开启或关闭该选项的决策依据。文章发布于

本文演示在 Microsoft Agent Framework 中,RequirePerServiceCallChatHistoryPersistence 选项如何影响 ChatReducerReduceAsync 调用时机,并详细说明在不同应用场景下应当开启或关闭该选项的决策依据。文章发布于 2026 年 6 月 2 日,目前该选项仍属于实验性功能,未来行为可能发生调整。

Microsoft Agent Framework 中 RequirePerServiceCallChatHistoryPersistence 对 ReduceAsync 调用时机的影响

背景

在 Agent Framework 中,ChatHistoryProvider 负责存储和管理对话历史记录。随着对话轮次不断增加,聊天上下文会逐渐累积,最终可能超出 LLM 的 token 限制。为此,框架提供了 IChatReducer 接口,允许开发者在上下文过长时对消息列表进行裁剪,仅保留最核心的部分。

ChatReducer 的核心方法是 ReduceAsync,它接收当前的完整聊天消息列表,并返回缩减后的列表。那么关键问题来了:ReduceAsync 在什么时刻被触发?是在整个对话过程中仅调用一次,还是在每次 LLM 服务调用(包括工具调用)后都会自动执行?若希望在每次工具调用之后都自动压缩上下文,应该如何配置?

答案取决于 RequirePerServiceCallChatHistoryPersistence 选项的设置。

核心概念

RequirePerServiceCallChatHistoryPersistenceChatClientAgentOptions 中的一个布尔选项,其默认值为 false。该选项控制的是:在一次完整的 Agent 运行过程中,是否在每一次 LLM 服务调用完成后立即进行聊天历史的持久化操作。

持久化聊天历史的流程中自然地包含了调用 ReduceAsync。因此,这个选项实际上间接决定了 ReduceAsync 的调用频率。

  • 当设置为 true 时:每次 LLM 服务调用完成后,都会触发一次 ReduceAsync。这意味着在多轮工具调用场景中,每完成一次工具调用往返,上下文就会被裁剪一次,从而有效防止中间消息的过度堆积。
  • 当设置为 false(默认值)时:ReduceAsync 仅在对话流程的初始阶段被调用,不会在每次工具调用后触发。因此在多轮工具调用的过程中,中间消息会持续累积,可能导致上下文膨胀。

为了更直观地展示这一差异,下面搭建一套可控的演示环境。

搭建演示环境

真实 LLM 的工具调用行为具有不确定性,不利于精确对比。本演示采用自定义的 FakeChatClient 来模拟 LLM,从而精确控制返回的工具调用序列。

FakeChatClient 的设计

FakeChatClient 实现了 IChatClient 接口,其核心思路是通过委托注入响应流,使测试代码能够完全控制 LLM 返回的内容。

public sealed class FakeChatClient : IChatClient
{
    public Func, ChatOptions?, CancellationToken, IAsyncEnumerable>? OnGetStreamingResponseAsync { get; set; }
    public IAsyncEnumerable GetStreamingResponseAsync(
        IEnumerable messages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        if (OnGetStreamingResponseAsync is null)
        {
            throw new InvalidOperationException($"{nameof(FakeChatClient)}.{nameof(OnGetStreamingResponseAsync)} has not been configured.");
        }
        return OnGetStreamingResponseAsync(messages, options, cancellationToken);
    }
    // ... 其他接口成员的委托注入实现
}

FakeChatClientIChatClient 的每个核心方法都暴露了一个可设置的委托属性。当外部调用这些方法时,实际执行的是对应的委托。这种设计在单元测试和演示场景中非常实用——开发者可以完全控制 ChatClient 的行为,而无需依赖 Mock 框架。对于非流式调用、GetServiceDispose 等方法,也提供了相应的委托注入点,确保行为完整。

模拟工具调用序列

为了让演示更贴近真实场景,我们设计了一个会触发两次工具调用的 FakeChatClient。核心逻辑封装在 CreateToolCallingFakeChatClient 方法中。

private static FakeChatClient CreateToolCallingFakeChatClient(int maxToolCallRounds)
{
    var callCount = 0;
    var fakeChatClient = new FakeChatClient()
    {
        OnGetStreamingResponseAsync = (messages, options, cancellationToken) => CreateResponseSequenceAsync()
    };
    return fakeChatClient;

    async IAsyncEnumerable CreateResponseSequenceAsync()
    {
        var currentCall = Interlocked.Increment(ref callCount);
        if (currentCall <= maxToolCallRounds)
        {
            var toolName = currentCall == 1 ? nameof(GetWeather) : nameof(GetTime);
            var callId = $"call_{currentCall}";
            yield return new ChatResponseUpdate(
                ChatRole.Assistant,
                new List()
                {
                    new FunctionCallContent(callId, toolName, new Dictionary()
                    {
                        { "location", "深圳" }
                    })
                })
            {
                FinishReason = ChatFinishReason.ToolCalls,
            };
        }
        else
        {
            yield return new ChatResponseUpdate(
                ChatRole.Assistant,
                "根据查询结果,今天深圳是晴天,气温 25°C,现在时间是 " + DateTime.Now.ToString("HH:mm:ss") + "。")
            {
                FinishReason = ChatFinishReason.Stop,
            };
        }
    }
}

这段代码的设计思路:通过闭包变量 callCount 维护调用计数,并使用 Interlocked.Increment 确保线程安全。前 maxToolCallRounds 次(本演示中为 2 次)LLM 请求返回 FinishReasonToolCalls 的响应,分别触发 GetWeatherGetTime 工具调用。第 3 次及之后的 LLM 请求返回 FinishReasonStop 的最终文本回复。这样一条用户消息会触发如下链路:用户消息 → LLM 请求返回“调用 GetWeather”→ 框架执行 GetWeather → LLM 请求返回“调用 GetTime”→ 框架执行 GetTime → LLM 请求返回最终文本。

两个模拟工具方法

GetWeatherGetTime 是两个简单的模拟工具方法,在控制台输出调用信息后返回固定字符串。

private static string GetWeather()
{
    Console.WriteLine($"[工具调用] GetWeather 被执行,返回:晴天 25°C");
    return "晴天,25°C";
}
private static string GetTime()
{
    var time = DateTime.Now.ToString("HH:mm:ss");
    Console.WriteLine($"[工具调用] GetTime 被执行,返回:{time}");
    return time;
}

这两个工具方法通过 AIFunctionFactory.Create 注册到 Agent 的 ChatOptions.Tools 列表中。框架会在收到 FinishReasonToolCalls 的响应后自动查找并执行对应的工具方法。

LoggingChatReducer 的设计

为了观察 ReduceAsync 的调用时机,我们实现了一个带有日志输出的 IChatReducer

sealed class LoggingChatReducer : IChatReducer
{
    private readonly string _name;
    public LoggingChatReducer(string name)
    {
        _name = name;
    }
    public int ReduceCallCount { get; private set; }
    public Task> ReduceAsync(
        IEnumerable messages,
        CancellationToken cancellationToken)
    {
        var messageList = messages.ToList();
        ReduceCallCount++;
        Console.WriteLine($"[{_name}] ReduceAsync 第 {ReduceCallCount} 次被调用,当前消息数:{messageList.Count}");
        if (messageList.Count > 6)
        {
            var reduced = messageList.TakeLast(4).ToList();
            Console.WriteLine($"[{_name}] 上下文消息数从 {messageList.Count} 裁剪为 {reduced.Count}");
            return Task.FromResult>(reduced);
        }
        return Task.FromResult>(messageList);
    }
}

LoggingChatReducer 在两个维度上进行记录:调用次数(通过 ReduceCallCount 属性累计)和裁剪行为(当消息数超过 6 条时只保留最近 4 条,并输出裁剪前后的数量变化)。这里的裁剪策略是简单的“保留最近 N 条”,实际项目中可能需要更智能的策略,例如保留系统消息的同时裁剪历史对话,或基于 token 计数而非消息条数来判断。

演示对比

Main 方法依次运行两个演示,通过相同的 FakeChatClient 和不同的 RequirePerServiceCallChatHistoryPersistence 设置,直观对比 ReduceAsync 的调用频率差异。

演示 1:启用 Persistence

DemonstrateWithPersistenceAsync 方法中,创建 Agent 时显式设置 RequirePerServiceCallChatHistoryPersistencetrue

var agent = fakeChatClient.AsAIAgent(new ChatClientAgentOptions()
{
    ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions()
    {
        ChatReducer = loggingReducer
    }),
    ChatOptions = new ChatOptions()
    {
        Tools =
        [
            AIFunctionFactory.Create(GetWeather, nameof(GetWeather)),
            AIFunctionFactory.Create(GetTime, nameof(GetTime)),
        ],
    },
    RequirePerServiceCallChatHistoryPersistence = true,
});

InMemoryChatHistoryProvider 是框架内置的内存聊天历史提供程序,ChatReducer 属性用于指定裁剪器实例。运行后控制台输出类似如下:

[演示1-Reducer] ReduceAsync 第 1 次被调用,当前消息数:4
[工具调用] GetWeather 被执行,返回:晴天 25°C
[演示1-Reducer] ReduceAsync 第 2 次被调用,当前消息数:6
[工具调用] GetTime 被执行,返回:15:30:42
[演示1-Reducer] ReduceAsync 第 3 次被调用,当前消息数:8
[演示1-Reducer] 上下文消息数从 8 裁剪为 4

可以看到,ReduceAsync 共计被调用了 3 次:初始阶段 1 次,每次工具调用完成后各 1 次。在第三次调用时,消息数量已超过阈值,触发了裁剪。这意味着启用 Persistence 后,每次工具调用返回的结果和下一轮 LLM 响应都会被及时“收拢”,防止上下文在多次工具调用中无节制地增长。

演示 2:不启用 Persistence(默认)

DemonstrateWithoutPersistenceAsync 方法中,不设置 RequirePerServiceCallChatHistoryPersistence(保持默认 false)。

var agent = fakeChatClient.AsAIAgent(new ChatClientAgentOptions()
{
    ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions()
    {
        ChatReducer = loggingReducer
    }),
    ChatOptions = new ChatOptions()
    {
        Tools =
        [
            AIFunctionFactory.Create(GetWeather, nameof(GetWeather)),
            AIFunctionFactory.Create(GetTime, nameof(GetTime)),
        ],
    },
    // RequirePerServiceCallChatHistoryPersistence 默认为 false,不设置
});

运行后控制台输出如下:

[演示2-Reducer] ReduceAsync 第 1 次被调用,当前消息数:3
[工具调用] GetWeather 被执行,返回:晴天 25°C
[工具调用] GetTime 被执行,返回:15:30:42

可以看到,ReduceAsync 仅被调用了 1 次,且发生在对话流程的初始阶段。在后续两次工具调用过程中,ReduceAsync 完全不会被触发。所有中间消息都会累积在 ChatHistoryProvider 中,直到整个对话流程结束。

对比总结

选项值ReduceAsync 调用次数(本演示)调用时机
true3 次初始 + 每次工具调用完成后
false(默认)1 次仅在初始阶段

选择哪种设置取决于具体场景:如果 Agent 可能经历多轮工具调用、上下文容易膨胀,建议开启 RequirePerServiceCallChatHistoryPersistencetrue,让 ReduceAsync 在每次服务调用后及时清理上下文;如果 Agent 工具调用较少、对上下文完整性要求较高,保持默认的 false 可以避免过于频繁的裁剪。

扩展方法:RunStreamingAndLogToConsoleAsync

本演示还定义了一个简化的流式运行扩展方法,将 RunStreamingAsync 的 Text 增量输出到控制台。

public static class AIAgentStreamingExtensions
{
    public static async Task RunStreamingAndLogToConsoleAsync(
        this AIAgent agent,
        IEnumerable messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        await foreach (var update in agent.RunStreamingAsync(messages, session, options, cancellationToken))
        {
            Console.Write(update.Text);
        }
        Console.WriteLine();
    }
}

由于本演示的核心关注点是 ReduceAsync 的调用时机而非流式输出的细节,这里略去了推理内容(Reasoning)的处理,只将文本内容直接打印到控制台。

关于实验性 API

代码开头有一行 #pragma warning disable MAAI001,这是因为 ChatClientAgentOptions 中的部分 API 目前仍处于实验阶段,编译器会发出 MAAI001 警告。在演示代码中抑制此警告是安全的,但生产环境中请关注官方文档,确认相关 API 已经稳定后再使用。

来源:https://cloud.tencent.com.cn/developer/article/2681482

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
跨境物流AI Agent感知决策全链路自动化技术拆解

跨境物流AI Agent感知决策全链路自动化技术拆解

2026年,跨境物流领域正迎来一场根本性变革——从“被动记录”全面迈向“主动执行”,AI智能体正在重塑整个行业格局。 你可能已经深有体会:每天一到岗,光是后台处理货件创建、物流追踪等重复操作,就要耗费数小时之久。行业调研数据显示,超过60%的运营人员每天至少花费2小时在这些机械性劳动上,而手动操作引

时间:2026-07-11 16:50
天学会AI应用开发第十二课:从PDF、Word和网页构建RAG

天学会AI应用开发第十二课:从PDF、Word和网页构建RAG

上一篇文章介绍了如何从TXT文件中读取文本内容,但在日常办公场景中,纯文本文件的使用频率并不高。更常见的需求是处理PDF、Word等文档格式,同时还需要从网页中提取知识信息。 因此,本篇文章将重点讲解如何从PDF、Word以及网页中提取内容,并将其应用于RAG(检索增强生成)系统。 一、从PDF文件

时间:2026-07-11 16:50
Hy3+WorkBuddy组合国产顶级Agent附完整提示词

Hy3+WorkBuddy组合国产顶级Agent附完整提示词

五个 Case 跑完,总结一下整体体验。工具使用能力:能不能自己开网页、找信息、标出处。规划能力:能不能把多约束需求拆成可执行步骤。长程执行能力:跨多步任务时会不会丢状态。复杂推理能力:能不能先推导、再写代码、再执行验证。WorkBuddy+Hy3 的表现完全符合预期。趁着 WorkBuddy 里的

时间:2026-07-11 16:50
AI Agent是什么?一文理解大语言模型、记忆、技能、工具、MCP、工作流与上下文

AI Agent是什么?一文理解大语言模型、记忆、技能、工具、MCP、工作流与上下文

智能体并非单一模型,而是由大语言模型、记忆、工具、工作流等模块协同构成的自主系统。它通过理解目标、检索记忆、调用工具、构建上下文、推理决策,并基于反馈闭环持续迭代,最终自主完成复杂任务。

时间:2026-07-11 16:50
DeepSeek决定自研芯片打造人工智能全新算力芯脏

DeepSeek决定自研芯片打造人工智能全新算力芯脏

被“逼”出来的第三条路。 一直以模型技术见长的DeepSeek,这次在算力供应链上迈出了让所有人侧目的一步。 2026年7月7日,路透社援引三位知情人士消息,DeepSeek正在悄然开发自有AI芯片。值得玩味的是,这颗芯片的定位非常精准——专攻推理,不涉足训练。消息人士称,项目大约启动于一年前,目前

时间:2026-07-11 16:28
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜