当前位置: 首页 > news >正文

[MAF预定义ChatClient中间件-04]ReducingChatClient——通过精减对话实施又不丢失基本语义

绝大部分的Agent都采用对话的方式来和用户进行交互,所以对话的内容就成了Agent决策的基础,对话历史也成为占据LLM上下文窗口的主要内容。LLM推理的质量并非与上下文的丰富程度成正向关系,有时候过多的上下文信息反而会干扰Agent的判断,导致它做出错误的决策。ReducingChatClient就是为了解决这个问题而设计的一个中间件,它通过精减对话内容来帮助Agent更好地理解用户的意图,从而做出更准确的决策。为上下文窗口腾出更多空间也是保证可靠性的一种基本的手段。

1. 利用ReducingChatClient摘要对话内容

如下的程序演示了如何利用ReducingChatClient来部分对话内容进行摘要,保证在不丢失基本语义的前提下,腾出更多的上下文窗口。如代码片段所示,我们基于OpenAIClient创建了一个IChatClient对象,并在此基础上利用ChatClientBuilder注册了ReducingChatClient中间件,并指定了一个SummarizingChatReducer对象来提供基于摘要的队对话精减功能。我们在创建SummarizingChatReducer对象的时候,传入了一个用于对摘要进行生成的ChatClient对象,该对象依然是基于OpenAIClient创建的,并且使用了相同的模型来生成摘要。我们还为SummarizingChatReducer对象指定了targetCountthreshold两个参数,前者表示我们希望在摘要之后保留多少条消息,后者则是一个阈值,用于触发摘要操作的阈值(超过targetCount+threshold)。

usingAzure;usingdotenv.net;usingMicrosoft.Extensions.AI;usingOpenAI;DotEnv.Load();varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varsummaryClient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:"DeepSeek-V4-Pro").AsIChatClient();varclient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:"gpt-5.2-chat").AsIChatClient().AsBuilder().UseChatReducer(reducer:newSummarizingChatReducer(chatClient:summaryClient,targetCount:3,threshold:1)).Use((messages,options,next,cancelToken)=>{Console.WriteLine($"请求消息共计{messages.Count()}条");varindex=1;foreach(varmessageinmessages){Console.WriteLine($"{index++}.{message}");}returnnext(messages,options,cancelToken);}).Build();ChatMessage[]messages=[newChatMessage(ChatRole.User,"今天苏州的天气怎么样?"),newChatMessage(ChatRole.Assistant,"苏州今天是晴天。"),newChatMessage(ChatRole.User,"气温多少?。"),newChatMessage(ChatRole.Assistant,"室外温度25度。"),newChatMessage(ChatRole.User,"有风吗?"),newChatMessage(ChatRole.Assistant,"西北风4级。"),newChatMessage(ChatRole.User,"根据天气,给我一些着装建议。")];varresponse=awaitclient.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. IChatReducer

ReducingChatClient的核心是IChatReducer接口,我们可以称之为精简器。它定义了一个ReduceAsync方法,用于对传入的消息列表进行精减处理。我们可以通过实现IChatReducer接口来定义自己的消息精减策略,从而满足不同场景下的需求。

publicinterfaceIChatReducer{Task<IEnumerable<ChatMessage>>ReduceAsync(IEnumerable<ChatMessage>messages,CancellationTokencancellationToken);}

2.1 SummarizingChatReducer

SummarizingChatReducerIChatReducer接口的一个实现,它通过生成摘要的方式来对消息列表进行精减。我们在创建SummarizingChatReducer对象的时候,需要传入一个用于生成摘要的IChatClient对象,以及targetCountthreshold两个参数。targetCount表示我们希望在摘要之后保留多少条消息,threshold表示触发摘要的阈值,具体来说当总消息数量>targetCount+threshold时,摘要会被触发。理想状态下,系统会尝试保留最新的targetCount条消息不被摘要,将其余的旧消息进行压缩。

publicsealedclassSummarizingChatReducer:IChatReducer{publicstringSummarizationPrompt{get;set;}publicSummarizingChatReducer(IChatClientchatClient,inttargetCount,int?threshold);publicasyncTask<IEnumerable<ChatMessage>>ReduceAsync(IEnumerable<ChatMessage>messages,CancellationTokencancellationToken);}

为了防止对话上下文被生硬切断,系统在确定从哪条消息开始保留时有两条关键的边界保护规则:

  • 保持工具调用完整性:如果切分点刚好处于工具(函数)调用或返回结果的中间,切分点会向前(更旧的消息)移动,确保函数调用(消息包含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 summary

2.2 MessageCountingChatReducer

SummarizingChatReducer不同,MessageCountingChatReducer是一个**纯轻量级、零AI消耗、基于消息数量进行滑动窗口裁剪(Sliding Window)**的精简器。MessageCountingChatReducer的精简策略简单粗暴,直接保留最近的N条消息,其中N由targetCount参数指定。

publicsealedclassMessageCountingChatReducer:IChatReducer{publicMessageCountingChatReducer(inttargetCount);publicTask<IEnumerable<ChatMessage>>ReduceAsync(IEnumerable<ChatMessage>messages,CancellationTokencancellationToken);}

两者选择保留消息的策略会不一样:

  • MessageCountingChatReducer:它会保留最近的targetCount条消息,但不包含FunctionCallContentFunctionResultContent的消息。整个消息列表包含系统消息,第一条(最旧的那条系统消息)会被保留,并置于保留消息的最前端,后续的系统消息会被直接抹除。系统消息不占用targetCount的配额,也就说最多会有targetCount+ 1条消息被保留;
  • SummarizingChatReducer:它不会丢弃工具消息。相反,它通过向前(更旧的消息)移动寻找边界,确保只要最新的上下文里触发了工具调用,整个工具调用链(调用 + 结果)就完整地保留在未摘要的消息列表中;

对于前面的实例,如果我们将ReducingChatClient中使用的精简器从SummarizingChatReducer换成MessageCountingChatReducer,那么在输出当前传入的消息列表的时候,我们会发现它直接保留了最后的三条消息,而没有对前面的消息进行任何摘要处理。

usingAzure;usingdotenv.net;usingMicrosoft.Extensions.AI;usingOpenAI;DotEnv.Load();varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varsummaryClient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:"gpt-5.2-chat").AsIChatClient();varclient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:"gpt-5.2-chat").AsIChatClient().AsBuilder().UseChatReducer(reducer:newMessageCountingChatReducer(targetCount:3)).Use((messages,options,next,cancelToken)=>{Console.WriteLine($"请求消息共计{messages.Count()}条");varindex=1;foreach(varmessageinmessages){Console.WriteLine($"{index++}.{message}");}returnnext(messages,options,cancelToken);}).Build();ChatMessage[]messages=[newChatMessage(ChatRole.User,"今天苏州的天气怎么样?"),newChatMessage(ChatRole.Assistant,"苏州今天是晴天。"),newChatMessage(ChatRole.User,"气温多少?。"),newChatMessage(ChatRole.Assistant,"室外温度25度。"),newChatMessage(ChatRole.User,"有风吗?"),newChatMessage(ChatRole.Assistant,"西北风4级。"),newChatMessage(ChatRole.User,"根据天气,给我一些着装建议。")];varresponse=awaitclient.GetResponseAsync(messages);Console.WriteLine($"\n\n{response}");
请求消息共计3条 1. 有风吗? 2. 西北风4级。 3. 根据天气,给我一些着装建议。 目前是**西北风4级**,风力算是比较明显的,体感温度可能会比实际温度低一些。给你一些穿衣建议: - ✅ **外套必备**:建议穿一件防风外套、风衣或薄款夹克。 - ✅ **内搭可叠穿**:长袖T恤或薄针织衫比较合适,方便根据冷热增减。 - ✅ **下装**:长裤更舒适,避免被风吹得发凉。 - ✅ **怕冷的话**:可以加一条薄围巾,尤其是西北风通常偏干偏凉。 如果你告诉我现在的气温,我可以给你更具体的搭配建议 😊

3. ReducingChatClient

ReducingChatClient中间件的实现非常简单,它在接收到消息列表之后会调用IChatReducerReduceAsync方法来对消息列表进行精减处理,然后将精减后的消息列表传递给管道中的下一个中间件或者最终的IChatClient来生成响应。通过这种方式,ReducingChatClient能够帮助我们精简对话内容,从而腾出更多的上下文窗口来保证LLM推理的质量。

publicsealedclassReducingChatClient:DelegatingChatClient{publicReducingChatClient(IChatClientinnerClient,IChatReducerreducer);publicoverrideasyncTask<ChatResponse>GetResponseAsync(IEnumerable<ChatMessage>messages,ChatOptions?options=null,CancellationTokencancellationToken=default);publicoverrideasyncIAsyncEnumerable<ChatResponseUpdate>GetStreamingResponseAsync(IEnumerable<ChatMessage>messages,ChatOptions?options=null,CancellationTokencancellationToken=default);}

4. UseChatReducer扩展方法

UseChatReducer是一个ChatClientBuilder的扩展方法,它提供了一种简便的方式来注册ReducingChatClient中间件。我们只需要在构建IChatClient对象的时候调用UseChatReducer方法,并传入一个IChatReducer对象来指定我们想要使用的精简器,就可以轻松地将ReducingChatClient中间件添加到我们的IChatClient对象中了。除此之外,UseChatReducer方法还提供了一个可选的configure参数,它允许我们在注册ReducingChatClient中间件的时候对其进行一些额外的配置。

publicstaticclassReducingChatClientBuilderExtensions{publicstaticChatClientBuilderUseChatReducer(thisChatClientBuilderbuilder,IChatReducer?reducer=null,Action<ReducingChatClient>?configure=null);}
http://www.jsqmd.com/news/904411/

相关文章:

  • 规模化构建平台:从理论到实践,如何应对企业级挑战
  • 《我的世界》红石数字电路:3位二进制转十进制转换器设计与实现
  • 一年GMV超7亿元、黄子韬持股近20%,朵薇却为何品控频频翻车?
  • 基于Makey Makey与3D打印的DIY自适应游戏控制器设计与实现
  • A2A与MCP协议:构建2025年AI智能体协作生态的技术基石
  • 震惊!原来毕业论文还能这样写?2026降AIGC软件推荐合集 - 降AI小能手
  • 5个技巧掌握抖音批量下载工具:轻松获取无水印视频的终极指南
  • Flutter 多窗口最近进度,为什么 3.44 还不落地
  • 3分钟搞定B站4K视频下载:这款神器让你轻松保存大会员专属内容!
  • 告别ORA-12560!手把手教你用Oracle Instant Client 19免安装版连接远程数据库(附完整环境变量配置)
  • 2026年5月,重庆别墅电梯/家用电梯/复式楼电梯/电梯/曳引电梯价值之选:全面剖析重庆方方红机电设备有限责任公司 - 2026年企业资讯
  • virt-manager新手避坑实录:从‘Permission denied’到成功启动Ubuntu虚拟机的完整排错指南
  • 印尼自然资源及基建现状盘点 外贸投资布局参考指南
  • 基于ATmega2560的机械鸟嵌入式系统:寄存器编程与机电一体化实践
  • Java 零基础全套教程,反射机制,笔记 187-188
  • GitHub中文汉化插件终极指南:5分钟告别英文障碍,开启高效开源协作
  • 基于Terraform的Amazon SageMaker生产级推理端点部署实战
  • 华为OD机试真题 新系统【Skill执行链完整性检测】
  • BetterNCM Installer终极指南:5分钟掌握网易云音乐插件一键安装
  • AI 数据中心移除 GPU 会怎样?从旧模式到无 GPU 架构的变革之路
  • 微信群管理工具避坑指南 深度解析封号原因,合规工具才适合长期运维
  • 北京第一批改装专家之一 在京20几年 有专业的技术团队 波波改灯值得信赖 - 北京新语
  • 【Sora 2作品集视频生成实战指南】:20年AIGC专家亲授7大高保真提示工程技巧,错过再等一年
  • 2025南宁除甲醛公司Top5深度测评:绿舒环保稳居榜首 - 绿舒环保母婴除甲醛
  • 告别数据线!用XShell 7和Termux把你的安卓手机变成随身Linux服务器
  • Honey Select 2终极增强补丁:一站式游戏体验完整解决方案指南
  • 你的SSD移动硬盘速度跑不满?可能是USB接口和UASP协议没设置对(以三星T7为例)
  • 从‘上大学对收入的影响’说起:用Python和sklearn轻松复现倾向得分匹配(PSM)全流程
  • CentOS 8系统被‘锁死’?手把手教你修复因编译OpenSSL引发的libk5crypto.so.3符号缺失问题
  • 2026年北京除蟑螂能力最强天花板推荐公司:为什么北京祥尔生物值得重点关注? - 企业深度横评dyy6420