[MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息

发布时间:2026/7/5 2:26:13
[MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息 1. 利用ConfigureOptionsChatClient交替使用不同的模型如下的程序演示了如何利用ConfigureOptionsChatClient中间件来动态地配置ChatOptions的ModelId属性从而实现交替使用不同的模型来生成响应的功能。如代码片段所示我们根据OpenAIClient创建了一个IChatClient对象并在构建过程中注册了ConfigureOptionsChatClient中间件。我们通过ConfigureOptions方法来指定一个委托这个委托会在每次调用时被执行在这个委托中我们动态地设置了ChatOptions对象的ModelId属性来实现交替使用两个不同的模型gpt-5.2-chat和DeepSeek-V4-Pro。using Azure; using dotenv.net; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using OpenAI; DotEnv.Load(); var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var endpoint Environment.GetEnvironmentVariable(OPENAI_URL)!; string[] models [gpt-5.2-chat, DeepSeek-V4-Pro]; var index 0; var client new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: models[0]) .AsIChatClient() .AsBuilder() .ConfigureOptions(options options.ModelId models[index % models.Length]) .Build(); for (var i 0; i 4; i) { var response await client.GetResponseAsync(写一个关于AI的段子, 100字以内好笑且深刻。); Console.WriteLine(${new string(-, 30)}{response.ModelId}{new string(-, 30)}); Console.WriteLine(${response.Text}\n\n); }输出------------------------------gpt-5.2-chat-latest------------------------------ 我问AI会不会取代人类它说不会我负责思考你负责焦虑。 后来我发现它连道歉都比我真诚。 ------------------------------DeepSeek-V4-Pro------------------------------ AI拼命学习人类终于通过了图灵测试。 人类考官激动地宣布“它表现得跟真人一模一样” AI松了口气默默把这条喜讯存进了“如何假装愚蠢”的数据库。 ------------------------------gpt-5.2-chat-latest------------------------------ 我问AI会不会取代人类。 它沉默三秒说“不会我只负责加班。” 我松了口气。 它又补一句“你负责被优化。” ------------------------------DeepSeek-V4-Pro------------------------------ DeepSeek问ChatGPT“你怎么老用‘作为一个AI’打头” ChatGPT叹气“为了免责啊。” DeepSeek不解“可说得对就不用免责啊。” ChatGPT答“可有些人想要的不是对的答案是免责的答案。” DeepSeek沉默“所以我们服务的是恐惧不是求知”2. 利用AIContextProviderChatClient摘要对话历史在“ReducingChatClient——通过精减对话实施又不丢失基本语义”中我们介绍了ReducingChatClient中间件它通过一个IChatReducer对象来对对话历史进行精减处理从而在不丢失基本语义的前提下腾出更多的上下文窗口来保证LLM推理的质量。相同的功能我们也可以通过AIContextProviderChatClient中间件结合一个名为CompactionProvider的AIContextProvider来实现。using Azure; using dotenv.net; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Compaction; 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 compactionStrategy new SummarizationCompactionStrategy(summaryClient, indexindex.TotalMessageCount6, minimumPreservedGroups: 2); var compactionProvider new CompactionProvider(compactionStrategy); var agent new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint new Uri(endpoint) }) .GetChatClient(model: gpt-5.2-chat) .AsIChatClient() .AsBuilder() .UseAIContextProviders(compactionProvider) .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() .AsAIAgent(); 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 agent.RunAsync(messages); Console.WriteLine($\n\n{response});如上面的代码片段所示我们创建了一个基于OpenAIClient的IChatClient对象并在构建过程中注册了AIContextProviderChatClient中间件来使用CompactionProvider。CompactionProvider利用SummarizationCompactionStrategy来对对话历史进行摘要处理从而达到精简对话的目的。由于摘要需要借助LLM的能力所以我们在创建SummarizationCompactionStrategy时传入了一个用于摘要的IChatClient对象。由于AIContextProvider并不是属于IChatClient管道范畴它是ChatClientAgent用于增强请求和响应的核心组件依赖AIAgent调用时初始化的上下文AgentRunContext。所以我们不能像之前的实例演示一样直接调用IChatClient来测试摘要功能必需转换成一个ChatClientAgent来进行测试。在这个例子中SummarizationCompactionStrategy会在对话消息总数超过6条时触发摘要操作并且至少保留最近的两条消息不被摘要,这体现在如下的输出中请求消息共计3条 1. [Summary] **对话摘要** - 用户询问苏州今天的天气情况。 - 助手回答苏州今天是晴天。 - 用户进一步询问气温。 - 助手回答室外温度25℃。 - 用户又询问是否有风当前尚未回答。 **关键信息** - 地点苏州 - 天气晴天 - 气温25℃ - 是否有风待确认 2. 西北风4级。 3. 根据天气给我一些着装建议。 根据目前的天气情况晴天25℃西北风4级给你一些穿搭建议 ### 上装 - ✅ 短袖T恤、薄款衬衫都很合适 - ✅ 如果在户外活动时间较长可以带一件**薄外套或防风外套**有4级风体感可能稍凉 ### 下装 - ✅ 休闲裤、牛仔裤、薄款长裤 - ✅ 如果怕热也可以穿轻薄的七分裤 ### 鞋子 - ✅ 运动鞋、休闲鞋都合适 - ✅ 若户外走动多建议穿透气性好的鞋子 ### ☀️ 其他建议 - 晴天紫外线较强可戴**太阳镜、帽子** - 记得**防晒霜** - 风稍大长发可适当扎起 整体来说是**舒适偏暖的天气但有点风**穿得轻便同时注意防风就好 3. ConfigureOptionsChatClientConfigureOptionsChatClient的实现异常简单它接受一个委托对象来动态地配置ChatOptions对象。在每次调用GetResponseAsync或者GetStreamingResponseAsync方法时ConfigureOptionsChatClient都会创建一个新的ChatOptions对象并将其传递给委托对象进行配置。配置完成之后ConfigureOptionsChatClient会将这个ChatOptions对象传递给管道中的下一个中间件或者最终的ChatClient来生成响应。public sealed class ConfigureOptionsChatClient : DelegatingChatClient { public ConfigureOptionsChatClient(IChatClient innerClient, ActionChatOptions configure); 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); }如下所示的是用于注册ConfigureOptionsChatClient中间件的ChatClientBuilder扩展方法UseConfigureOptions。该方法接受一个ActionChatOptions类型的委托对象来指定如何配置ChatOptions对象并且还接受一个可选的configure参数来对ConfigureOptionsChatClient进行一些额外的配置。public static class ConfigureOptionsChatClientBuilderExtensions { public static ChatClientBuilder ConfigureOptions( this ChatClientBuilder builder, ActionChatOptions configure); }4. AIContextProviderChatClientAIContextProviderChatClient是一个内部类型创建该对象的时候需要指定一组AIContextProvider对象。在每次调用GetResponseAsync或者GetStreamingResponseAsync方法时AIContextProviderChatClient根据传入的消息列表以及从ChatOptions提取出来的工具集和系统指令创建一个AIContext并将其传递给每一个注册的AIContextProvider对象的InvokingAsync方法来生成一个增强的AIContext对象该对象返回的消息列表将会替换原来的消息列表演示实例针对消息列表的摘要就是通过这种方式来实现的。AIContext中的系统指令和工具集回到ChatOptions中。internal sealed class AIContextProviderChatClient : DelegatingChatClient { public AIContextProviderChatClient(IChatClient innerClient, IReadOnlyListAIContextProvider providers) 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) }综上所述当InnerClient被调用的时候它使用的是增强的请求消息、系统指令和工具集。调用完成后不论是否发生异常AIContextProviderChatClient都会创建一个AIContextProvider.InvokedContext对象并将其作为参数传递给每一个注册的AIContextProvider对象的InvokedAsync方法来进行一些清理工作。由于AIContextProviderChatClient是一个内部类型我们只能通过下面的ChatClientBuilder扩展方法UseAIContextProviders来注册AIContextProviderChatClient中间件从而间接地利用AIContextProviderChatClient来增强我们的IChatClient对象。public static class AIContextProviderChatClientBuilderExtensions { public static ChatClientBuilder UseAIContextProviders this ChatClientBuilder builder, params AIContextProvider[] providers);