MAF预定义ChatClient中间件-04]ReducingChatClient——精减对话历史又不丢失基本语义
利用ReducingChatClient摘要对话内容如下的程序演示了如何利用ReducingChatClient来部分对话内容进行摘要保证在不丢失基本语义的前提下腾出更多的上下文窗口。如代码片段所示我们基于OpenAIClient创建了一个IChatClient对象并在此基础上利用ChatClientBuilder注册了ReducingChatClient中间件并指定了一个SummarizingChatReducer对象来提供基于摘要的队对话精减功能。我们在创建SummarizingChatReducer对象的时候传入了一个用于对摘要进行生成的ChatClient对象该对象依然是基于OpenAIClient创建的并且使用了相同的模型来生成摘要。我们还为SummarizingChatReducer对象指定了targetCount和threshold两个参数前者表示我们希望在摘要之后保留多少条消息后者则是一个阈值用于触发摘要操作的阈值超过targetCountthreshold。using Azure; using dotenv.net; using Microsoft.Extensions.AI; using OpenAI; DotEnv.Load(); var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var endpoint Environment.GetEnvironmentVariable(OPENAI_URL)!; var summaryClient new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: DeepSeek-V4-Pro) .AsIChatClient(); var client new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: gpt-5.2-chat) .AsIChatClient() .AsBuilder() .UseChatReducer(reducer: new SummarizingChatReducer(chatClient:summaryClient, targetCount: 3, threshold:1)) .Use((messages,options, next, cancelToken) { Console.WriteLine( $请求消息共计{messages.Count()}条); var index 1; foreach (var message in messages) { Console.WriteLine(${index}. {message}); } return next(messages, options, cancelToken); }) .Build(); ChatMessage[] messages [ new ChatMessage(ChatRole.User, 今天苏州的天气怎么样), new ChatMessage(ChatRole.Assistant, 苏州今天是晴天。), new ChatMessage(ChatRole.User, 气温多少。), new ChatMessage(ChatRole.Assistant, 室外温度25度。), new ChatMessage(ChatRole.User, 有风吗), new ChatMessage(ChatRole.Assistant, 西北风4级。), new ChatMessage(ChatRole.User, 根据天气给我一些着装建议。) ]; var response await client.GetResponseAsync(messages); Console.WriteLine($\n\n{response});为了查看经过ReducingChatClient精减之后的对话历史我们在ChatClientBuilder中注册了一个简单的中间件来输出当前传入的消息列表。IChatClient管道构建成功之后我们调用GetResponseAsync方法并指定了一组消息共7条来模拟一段对话的历史。由于我们在ReducingChatClient中指定了targetCount为3并且threshold为1必然会触发摘要操作。摘要完成后保留了最后三条消息只对对前4条消息进行了摘要这一切体现在如下的输出中请求消息共计4条 1. 用户询问了今天苏州的天气情况助手回答为晴天。随后用户进一步询问气温助手回答室外温度为25度。对话围绕苏州当日的天气状况和具体气温展开内容简洁明确。 2. 有风吗 3. 西北风4级。 4. 根据天气给我一些着装建议。 今天苏州**晴天25℃西北风4级**体感会比较清爽风稍微有点明显。给你一些穿搭建议 ### 上衣 - **短袖T恤、薄衬衫**都可以 - 如果怕风建议带一件**薄外套/防风夹克** ### 下装 - **牛仔裤、休闲裤**都合适 - 不怕冷的话也可以穿**薄款长裙/半裙** ### 鞋子 - 运动鞋、休闲鞋都很舒服 - 风有点大尽量避免太轻薄易飘的穿搭 ### 其他建议 - 晴天紫外线可能偏强出门可以**戴太阳镜、涂防晒** - 风力4级骑车会有点顶风注意安全 整体来说是**舒适偏清爽型天气**穿得轻松一点就好 2. IChatReducerReducingChatClient的核心是IChatReducer接口我们可以称之为精简器。它定义了一个ReduceAsync方法用于对传入的消息列表进行精减处理。我们可以通过实现IChatReducer接口来定义自己的消息精减策略从而满足不同场景下的需求。public interface IChatReducer { TaskIEnumerableChatMessage ReduceAsync( IEnumerableChatMessage messages, CancellationToken cancellationToken); }2.1 SummarizingChatReducerSummarizingChatReducer是IChatReducer接口的一个实现它通过生成摘要的方式来对消息列表进行精减。我们在创建SummarizingChatReducer对象的时候需要传入一个用于生成摘要的IChatClient对象以及targetCount和threshold两个参数。targetCount表示我们希望在摘要之后保留多少条消息threshold表示触发摘要的阈值具体来说当总消息数量targetCountthreshold时摘要会被触发。理想状态下系统会尝试保留最新的targetCount条消息不被摘要将其余的旧消息进行压缩。public sealed class SummarizingChatReducer : IChatReducer { public string SummarizationPrompt{ get; set;} public SummarizingChatReducer(IChatClient chatClient, int targetCount, int? threshold); public async TaskIEnumerableChatMessage ReduceAsync(IEnumerableChatMessage messages, CancellationToken cancellationToken); }为了防止对话上下文被生硬切断系统在确定从哪条消息开始保留时有两条关键的边界保护规则保持工具调用完整性如果切分点刚好处于工具函数调用或返回结果的中间切分点会向前更旧的消息移动确保函数调用消息包含FunctionCallContext与其响应结果消息包含FunctionResultContent完整保留在同一个作用域内不被摘要拆散避免用户问题孤立在缓冲阈值窗口threshold内系统会向前更旧的消息寻找角色为User的消息。一旦找到就会在用户消息之前切断。这样可以确保用户的提问与其后续的LLM回复、工具调用保存在一起避免问题被摘要但答案被保留的孤立现象。我们可以利用SummarizationPrompt属性来指定一个自定义的提示词来控制摘要的生成。默认情况下SummarizingChatReducer会使用一个预定义的提示词来生成摘要这个提示词会指导ChatClient如何对消息列表进行摘要处理从而保证在不丢失基本语义的前提下尽可能地精简消息列表。如下所示的是默认的提示词。**Generate a clear and complete summary of the entire conversation in no more than five sentences.** The summary must always: - Reflect contributions from both the user and the assistant - Preserve context to support ongoing dialogue - Incorporate any previously provided summary - Emphasize the most relevant and meaningful points The summary must never: - Offer critique, correction, interpretation, or speculation - Highlight errors, misunderstandings, or judgments of accuracy - Comment on events or ideas not present in the conversation - Omit any details included in an earlier summary2.2 MessageCountingChatReducer与SummarizingChatReducer不同MessageCountingChatReducer是一个纯轻量级、零AI消耗、基于消息数量进行滑动窗口裁剪Sliding Window的精简器。MessageCountingChatReducer的精简策略简单粗暴直接保留最近的N条消息其中N由targetCount参数指定。public sealed class MessageCountingChatReducer : IChatReducer { public MessageCountingChatReducer(int targetCount); public TaskIEnumerableChatMessage ReduceAsync(IEnumerableChatMessage messages, CancellationToken cancellationToken); }两者选择保留消息的策略会不一样MessageCountingChatReducer它会保留最近的targetCount条消息但不包含FunctionCallContent或FunctionResultContent的消息。整个消息列表包含系统消息第一条最旧的那条系统消息会被保留并置于保留消息的最前端后续的系统消息会被直接抹除。系统消息不占用targetCount的配额也就说最多会有targetCount 1条消息被保留SummarizingChatReducer它不会丢弃工具消息。相反它通过向前更旧的消息移动寻找边界确保只要最新的上下文里触发了工具调用整个工具调用链调用 结果就完整地保留在未摘要的消息列表中对于前面的实例如果我们将ReducingChatClient中使用的精简器从SummarizingChatReducer换成MessageCountingChatReducer那么在输出当前传入的消息列表的时候我们会发现它直接保留了最后的三条消息而没有对前面的消息进行任何摘要处理。using Azure; using dotenv.net; using Microsoft.Extensions.AI; using OpenAI; DotEnv.Load(); var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var endpoint Environment.GetEnvironmentVariable(OPENAI_URL)!; var summaryClient new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: gpt-5.2-chat) .AsIChatClient(); var client new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: gpt-5.2-chat) .AsIChatClient() .AsBuilder() .UseChatReducer(reducer: new MessageCountingChatReducer(targetCount: 3)) .Use((messages,options, next, cancelToken) { Console.WriteLine( $请求消息共计{messages.Count()}条); var index 1; foreach (var message in messages) { Console.WriteLine(${index}. {message}); } return next(messages, options, cancelToken); }) .Build(); ChatMessage[] messages [ new ChatMessage(ChatRole.User, 今天苏州的天气怎么样), new ChatMessage(ChatRole.Assistant, 苏州今天是晴天。), new ChatMessage(ChatRole.User, 气温多少。), new ChatMessage(ChatRole.Assistant, 室外温度25度。), new ChatMessage(ChatRole.User, 有风吗), new ChatMessage(ChatRole.Assistant, 西北风4级。), new ChatMessage(ChatRole.User, 根据天气给我一些着装建议。) ]; var response await client.GetResponseAsync(messages); Console.WriteLine($\n\n{response});请求消息共计3条 1. 有风吗 2. 西北风4级。 3. 根据天气给我一些着装建议。 目前是**西北风4级**风力算是比较明显的体感温度可能会比实际温度低一些。给你一些穿衣建议 - ✅ **外套必备**建议穿一件防风外套、风衣或薄款夹克。 - ✅ **内搭可叠穿**长袖T恤或薄针织衫比较合适方便根据冷热增减。 - ✅ **下装**长裤更舒适避免被风吹得发凉。 - ✅ **怕冷的话**可以加一条薄围巾尤其是西北风通常偏干偏凉。 如果你告诉我现在的气温我可以给你更具体的搭配建议 3. ReducingChatClientReducingChatClient中间件的实现非常简单它在接收到消息列表之后会调用IChatReducer的ReduceAsync方法来对消息列表进行精减处理然后将精减后的消息列表传递给管道中的下一个中间件或者最终的IChatClient来生成响应。通过这种方式ReducingChatClient能够帮助我们精简对话内容从而腾出更多的上下文窗口来保证LLM推理的质量。public sealed class ReducingChatClient : DelegatingChatClient { public ReducingChatClient(IChatClient innerClient, IChatReducer reducer); public override async TaskChatResponse GetResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default); public override async IAsyncEnumerableChatResponseUpdate GetStreamingResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default); }4. UseChatReducer扩展方法UseChatReducer是一个ChatClientBuilder的扩展方法它提供了一种简便的方式来注册ReducingChatClient中间件。我们只需要在构建IChatClient对象的时候调用UseChatReducer方法并传入一个IChatReducer对象来指定我们想要使用的精简器就可以轻松地将ReducingChatClient中间件添加到我们的IChatClient对象中了。除此之外UseChatReducer方法还提供了一个可选的configure参数它允许我们在注册ReducingChatClient中间件的时候对其进行一些额外的配置。public static class ReducingChatClientBuilderExtensions { public static ChatClientBuilder UseChatReducer( this ChatClientBuilder builder, IChatReducer? reducer null, ActionReducingChatClient? configure null); }

相关新闻

基于AScript的SQL脚本语言发布啦!

基于AScript的SQL脚本语言发布啦!

一、介绍 支持SqlServer/MySql基础语法和数据类型: 支持SELECT查询语法:FROM/WHERE/LEFT JOIN/RIGHT JOIN/INNER JOIN/GROUP BY/ORDER BY/LIMIT支持INSERT插入语法支持UPDATE修改语法支持DELETE删除语法支持创建存储过程:Sqlserver/MySql语…

2026/6/29 19:16:02阅读更多 →
SpringBoot自动装配和starter

SpringBoot自动装配和starter

自动装配SpringBootApplication 注解中包含了EnableAutoConfiguration注解,这个注解底层又是import 注解,导入自动配置类导入选择器(AutoConfigurationImportSelecor ),这个选择器会去读取约定配置文件,从文…

2026/6/29 19:16:02阅读更多 →
统计学、数据科学、大数据管理,哪个更适合做数据?2026大学生选方向不迷路

统计学、数据科学、大数据管理,哪个更适合做数据?2026大学生选方向不迷路

很多同学想做数据,却卡在专业选择上:统计学偏理论,数据科学偏技术,大数据管理偏业务与治理。到了 2026 年,企业要的早已不是“会做表”的人,而是能把数据变成决策、产品和增长的人。也正因为这样&#xff0…

2026/6/29 19:16:02阅读更多 →
为什么92%的ChatGPT Plus订阅在第3个月自动降级?国内用户必须知道的OpenAI账户健康度监测协议(含自动续费预警脚本开源)

为什么92%的ChatGPT Plus订阅在第3个月自动降级?国内用户必须知道的OpenAI账户健康度监测协议(含自动续费预警脚本开源)

更多请点击: https://intelliparadigm.com 第一章:ChatGPT Plus国内订阅高降级率的真相解构 国内用户在订阅ChatGPT Plus后出现高频次自动降级现象,并非偶然故障,而是多重技术与策略因素叠加的结果。OpenAI官方未向中国大陆地区提…

2026/6/29 20:31:43阅读更多 →
Rust的std--mem--MaybeUninit:延迟初始化的安全抽象

Rust的std--mem--MaybeUninit:延迟初始化的安全抽象

Rust的std::mem::MaybeUninit:延迟初始化的安全抽象 在Rust中,内存安全是核心设计原则之一,但某些场景需要绕过编译器的初始化检查,比如延迟初始化或与FFI交互。std::mem::MaybeUninit为此提供了安全抽象,允许开发者显…

2026/6/29 20:31:43阅读更多 →
深入解析MSPM0 DEBUGSS调试子系统:从架构原理到安全功耗实战

深入解析MSPM0 DEBUGSS调试子系统:从架构原理到安全功耗实战

1. 项目概述:深入MSPM0的DEBUGSS调试子系统在嵌入式开发这条路上,调试能力的高低,往往直接决定了项目推进的速度和最终产品的质量。我们每天都在和代码、硬件打交道,一个功能强大的调试子系统,就像是给开发者装上了一双…

2026/6/29 20:31:43阅读更多 →
SetDPI深度解析:Windows DPI缩放管理的命令行艺术

SetDPI深度解析:Windows DPI缩放管理的命令行艺术

SetDPI深度解析:Windows DPI缩放管理的命令行艺术 【免费下载链接】SetDPI 项目地址: https://gitcode.com/gh_mirrors/se/SetDPI 在当今高分辨率显示器和多屏工作环境日益普及的时代,DPI缩放管理已成为Windows系统用户面临的核心挑战之一。SetD…

2026/6/29 20:31:43阅读更多 →
终极指南:3大核心功能让原神日常任务效率翻倍

终极指南:3大核心功能让原神日常任务效率翻倍

终极指南:3大核心功能让原神日常任务效率翻倍 【免费下载链接】genshin-impact-script 原神脚本,包含自动钓鱼、自动拾取、自动跳过对话等多项实用功能。A Genshin Impact script includes many useful features such as automatic fishing, automatic i…

2026/6/29 20:31:43阅读更多 →
我为什么研究RAGFlow:RuyiBookCourse遇到复杂文档解析后必须想清楚的事

我为什么研究RAGFlow:RuyiBookCourse遇到复杂文档解析后必须想清楚的事

OK,OK,大家好,欢迎大家来到大鹏 AI 教育,我是张大鹏。 这篇文章讲 RAGFlow。 但我不是为了追热点才研究它。 我研究 RAGFlow,是因为 RuyiBookCourse 正好走到了一个非常现实的位置: 电子书解析不是把文字拿…

2026/6/29 20:26:42阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/6/29 3:27:55阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/6/29 2:19:08阅读更多 →
如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南

如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南

如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南 【免费下载链接】DeepBump Normal & height maps generation from single pictures 项目地址: https://gitcode.com/gh_mirrors/de/DeepBump 还在为3D建模中的纹理制作而烦恼吗?…

2026/6/29 0:01:47阅读更多 →
OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单!

OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单!

OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单! 【免费下载链接】OCAuxiliaryTools Cross-platform GUI management tools for OpenCore(OCAT) 项目地址: https://gitcode.com/gh_mirrors/oc/OCA…

2026/6/29 0:01:47阅读更多 →
终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像

终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像

终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 你是否厌倦了Windows 11系统自带的20…

2026/6/29 0:01:47阅读更多 →