惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
C
Cisco Blogs
博客园 - 叶小钗
P
Privacy International News Feed
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
IT之家
IT之家
博客园 - 聂微东
Know Your Adversary
Know Your Adversary
Help Net Security
Help Net Security
罗磊的独立博客
I
Intezer
S
Schneier on Security
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
Cisco Talos Blog
Cisco Talos Blog
宝玉的分享
宝玉的分享
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
P
Privacy & Cybersecurity Law Blog
T
The Exploit Database - CXSecurity.com
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
H
Heimdal Security Blog
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
Y
Y Combinator Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
SecWiki News
SecWiki News
The GitHub Blog
The GitHub Blog
A
Arctic Wolf
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

博客园 - Artech

[MAF预定义的AIContextProvider-05]CompactionProvider——采用多种策略压缩对话历史 [MAF预定义的AIContextProvider-04]Mem0Provider——长期记忆基于的云端解决方案 [MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力 [MAF预定义的AIContextProvider-02]AgentSkillsProvider——将Agent Skills引入MAF [MAF预定义的AIContextProvider-01]TextSearchProvider——RAG在MAF中的实现 [MAF预定义ChatClient中间件-09]MessageInjectingChatClient-赋予工具消息注入的能力 [MAF预定义ChatClient中间件-08]OpenTelemetryChatClient-实现链路跟踪和性能监控 [MAF预定义ChatClient中间件-07]PerServiceCallChatHistoryPersistingChatClient——基于ReAct循环的一步一存档 [MAF预定义ChatClient中间件-06]利用ImageGeneratingChatClient开发专业图片生成Agent [MAF预定义ChatClient中间件-04]ReducingChatClient——精减对话历史又不丢失基本语义 [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间 [MAF预定义ChatClient中间件-02]FunctionInvokingChatClient——实现ReAct循环和人机交互的大功臣 [MAF预定义ChatClient中间件-01]LoggingChatClient——在调用LLM前后输出日志 [MAF的Agent管道详解-07]利用AIAgent中间件构建Agent管道 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 - Artech [MAF的Agent管道详解-05]对话历史的持久化和输入输出的增强 [MAF的Agent管道详解-04]如何让LLM按照要求的结构输出数据? [MAF的Agent管道详解-03]连接LLM的IChatClient对象 [MAF的Agent管道详解-02]IChatClient管道如何完美连接大模型? [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 [对比学习LangChain和MAF-04]针对消息的设计 [对比学习LangChain和MAF-03]完全不同的Agent设计哲学 [对比学习LangChain和MAF-02]基本编程模式的差异(下篇) [对比学习LangChain和MAF-01]基本编程模式的差异(上篇) 这是一篇测试文章 我所理解的Python元模型 除了按值和引用,方法参数的第三种传递方式 方法的三种调用形式 可以调用Null的实例方法吗? 自定义Key类型的字典无法序列化的N种解决方案 为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate 深入解析ASP.NET Core MVC的模块化设计[下篇] 深入解析ASP.NET Core MVC的模块化设计[上篇] ASP.NET Core MVC应用模型的构建[4]: Action的选择 ASP.NET Core MVC应用模型的构建[3]: Controller的收集 ASP.NET Core MVC应用模型的构建[2]: 定制应用模型 ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图 基于HTTP2/3的流模式消息交换如何实现? 编写高效的代码,你应该了解Array、Memory、ReadOnlySequence . . . WebAssembly核心编程[4]: Memory WebAssembly核心编程[3]: Module 与 Instance WebAssembly核心编程[2]:类型系统 WebAssembly核心编程[1]:wasm模块实例化的N种方式 WebAssembly入门笔记[4]:利用Global传递全局变量 WebAssembly入门笔记[3]:利用Table传递引用
[MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息
Artech · 2026-06-09 · via 博客园 - Artech

调用IChatClientGetResponseAsync或者GetStreamingResponseAsync方法时,我们通常会传入一个ChatOptions对象来控制运行行为。当我们基于IChatClient构建一个ChatClientAgent对象时,可以指定对应的ChatClientAgentOptionsChatClientAgentOptions携带的ChatOptions会每次应用到针对IChatClient的调用中去,所以这是绑定公共ChatOptions的一个好方式。如果某些调用需要针对性的配置选项,可以显式将其至于作为参数的ChatOptions对象。ConfigureOptionsChatClient提供了第三种方式:利用指定的委托对象来动态设置ChatOptions对象。而另一个AIContextProviderChatClient中间件则可以利用注册的AIContextProvider对象来动态地为每次调用生成一个AIContext对象,该对象可以提供请求消息和ChatOptions工具和系统指令。

1. 利用ConfigureOptionsChatClient交替使用不同的模型

如下的程序演示了如何利用ConfigureOptionsChatClient中间件来动态地配置ChatOptionsModelId属性,从而实现交替使用不同的模型来生成响应的功能。如代码片段所示,我们根据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中间件结合一个名为CompactionProviderAIContextProvider来实现。

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, index=>index.TotalMessageCount>6, 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}");

如上面的代码片段所示,我们创建了一个基于OpenAIClientIChatClient对象,并在构建过程中注册了AIContextProviderChatClient中间件来使用CompactionProviderCompactionProvider利用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. ConfigureOptionsChatClient

ConfigureOptionsChatClient的实现异常简单,它接受一个委托对象来动态地配置ChatOptions对象。在每次调用GetResponseAsync或者GetStreamingResponseAsync方法时,ConfigureOptionsChatClient都会创建一个新的ChatOptions对象,并将其传递给委托对象进行配置。配置完成之后,ConfigureOptionsChatClient会将这个ChatOptions对象传递给管道中的下一个中间件或者最终的ChatClient来生成响应。

public sealed class ConfigureOptionsChatClient : DelegatingChatClient
{
    public ConfigureOptionsChatClient(IChatClient innerClient, Action<ChatOptions> configure);
    public override async Task<ChatResponse> GetResponseAsync(
        IEnumerable<ChatMessage> messages, 
        ChatOptions? options = null, 
        CancellationToken cancellationToken = default);
    public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
        IEnumerable<ChatMessage> messages, 
        ChatOptions? options = null, 
        CancellationToken cancellationToken = default);
}

如下所示的是用于注册ConfigureOptionsChatClient中间件的ChatClientBuilder扩展方法UseConfigureOptions。该方法接受一个Action<ChatOptions>类型的委托对象来指定如何配置ChatOptions对象,并且还接受一个可选的configure参数来对ConfigureOptionsChatClient进行一些额外的配置。

public static class ConfigureOptionsChatClientBuilderExtensions
{
    public static ChatClientBuilder ConfigureOptions(
        this ChatClientBuilder builder, 
        Action<ChatOptions> configure);
}

4. AIContextProviderChatClient

AIContextProviderChatClient是一个内部类型,创建该对象的时候需要指定一组AIContextProvider对象。在每次调用GetResponseAsync或者GetStreamingResponseAsync方法时,AIContextProviderChatClient根据传入的消息列表,以及从ChatOptions提取出来的工具集和系统指令,创建一个AIContext,并将其传递给每一个注册的AIContextProvider对象的InvokingAsync方法来生成一个增强的AIContext对象,该对象返回的消息列表将会替换原来的消息列表,演示实例针对消息列表的摘要就是通过这种方式来实现的。AIContext中的系统指令和工具集回到ChatOptions中。

internal sealed class AIContextProviderChatClient : DelegatingChatClient
{
    public AIContextProviderChatClient(IChatClient innerClient, IReadOnlyList<AIContextProvider> providers);

    public override async Task<ChatResponse> GetResponseAsync(
        IEnumerable<ChatMessage> messages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default);
    public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
        IEnumerable<ChatMessage> 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);
}