微软 MAF 1.0 落地初探:用 ASP.NET Core 串起 Agent、工具与技能
4.2 自定义 AIContextProvider 记忆组件
省流总结:
主体功能均通过,至少比笔者自己封装的简陋工具强太多。支持会话、多轮会话、会话保存。目前skill功能疑似未完全完工,部分官方案例类(如ClassSkill)尚未验证成功,但主体已可用。后续计划在项目中实际逻辑进行试水。
本文以笔者本地的 maf-study-moo-sql 示例工程 为线索,说明:在 MAF 1.0 上我们能快速验证哪些能力,以及本仓库里刻意做成「可点 HTTP 就触发」的测试入口分别对应什么场景。工程本身偏「片段化探索(Frag)」,便于对照官方 samples 编号逐步加深理解。
一、前言
微软 Microsoft Agents Framework(常简称 MAF) 为 .NET / Python 提供统一的智能体编程模型:从最简单的「聊天智能体」,到函数工具、会话与记忆、编排与扩展,都可以在相对一致的类型与扩展点上完成。NuGet 上的 Microsoft.Agents.AI 已发布 1.0.0,标志着 API 面与交付节奏进入可跟进的稳定阶段(此前经历多轮 RC / Preview),适合在真实业务或学习仓库里做系统性验证。
MAF v1.0.0正式包发布时间:2026.4.3
原本以为要等到秋天,没想到这么快就发布,找可以测试的案例项目没有找到,就决定用官方项目自带的sample进行测试。

二、工程与依赖一览
- 运行时:
net10.0的 ASP.NET Core Web 项目(apiALdemo)。 - 核心包(与 MAF 1.0 对齐):
Microsoft.Agents.AI1.0.0Microsoft.Agents.AI.OpenAI1.0.0
- 模型接入:通过
OpenAI官方客户端风格的ChatClient,在AICash中读取Configration/llm.json的 Endpoint / ApiKey / ModelId,将请求指向兼容 OpenAI Chat API 的网关(示例中注释与配置意图为阿里云百炼一类服务),从而在不改 MAF 用法的前提下切换模型供应商。
也就是说:MAF 负责「智能体与会话」语义层;底层仍是 IChatClient / ChatClient 生态,与 Microsoft.Extensions.AI 的消息与工具模型衔接。
三、ChatClient 的核心初始化与典型用法
在 MAF 1.0 的 .NET 路径里,智能体(AIAgent)通常由「聊天客户端」扩展而来:项目使用的是 OpenAI 官方 .NET SDK 中的 OpenAI.Chat.ChatClient,在兼容 OpenAI Chat Completions / Responses 语义的网关(如阿里云百炼等)上建连;再通过 Microsoft.Agents.AI.OpenAI 提供的扩展方法 AsAIAgent,把同一个 ChatClient 包装成具备会话、工具、上下文提供器等能力的 MAF 智能体。
3.1 核心初始化方式(本仓库做法)
初始化可以概括为三步:读配置 → 构造 OpenAIClient(密钥 + 可选自定义 Endpoint)→ 按模型 ID 取 ChatClient。本仓库在 AICash.useClient 里用 ConcurrentDictionary 按槽位缓存 ChatClient,避免重复创建连接与重复读配置;配置来自 Configration/llm.json,并开启 reloadOnChange 便于热更新。
public static ChatClient useClient(int position) {
var t= sessionTmp.GetOrAdd(position, (p) =>
{
var config = new ConfigurationBuilder()
.AddJsonFile("Configration/llm.json", optional: false, reloadOnChange: true)
.Build();
var modelProvider = new
{
ApiKey = config["ModelProvider:ApiKey"],
ModelId = config["ModelProvider:ModelId"],
Endpoint = config["ModelProvider:Endpoint"]
};
// 2. 创建 OpenAI 客户端并指向阿里云百炼
var client = new OpenAIClient(
new ApiKeyCredential(modelProvider.ApiKey),
new OpenAIClientOptions
{
Endpoint = new Uri(modelProvider.Endpoint)
}
);
return client.GetChatClient(modelProvider.ModelId);
});
return t;
}
要点简述:
ApiKeyCredential:承载 API Key,与OpenAIClient构造函数配对使用。OpenAIClientOptions.Endpoint:把默认的api.openai.com换成你的兼容网关基地址(须与所选模型供应商文档一致)。GetChatClient(modelId):绑定具体模型名或部署名;后续所有对话请求都走该模型。
llm.json 中一般需提供与上述三个字段对应的 ModelProvider:ApiKey / ModelId / Endpoint,与 MAF 包版本无关,属于部署配置。
3.2 与 MAF 衔接时的典型用法(方法链)
拿到 ChatClient 之后,本仓库中的典型模式不是直接手写底层 HTTP,而是:
-
client.AsAIAgent(...)
使用ChatClientAgentOptions或简便重载,设置Name、Instructions、Tools、AIContextProviders等,得到AIAgent。 -
无状态一轮
RunAsync(string | messages, ...):非流式,一次返回完整AgentResponse。RunStreamingAsync(...):流式输出,适合控制台或后续接 SSE。
-
有状态多轮
CreateSessionAsync:创建AgentSession。RunAsync(..., session, ...)/RunStreamingAsync(..., session, ...):在同一会话中保留上下文。SerializeSessionAsync/DeserializeSessionAsync:会话持久化与恢复(与记忆组件、聊天记录等状态一并考虑)。
-
强类型与工具
RunAsync<T>(...):结构化输出。AIFunctionFactory.Create(...)、ApprovalRequiredAIFunction、子 Agent 的AsAIFunction()等:在AsAIAgent的tools上挂载。
因此,ChatClient 负责「连谁、用哪个模型」;AsAIAgent 之后的方法负责「怎么扮演 Agent、是否带工具与记忆」。第三章后续各节中的 Frag.* 示例,都是在 AICash.useClient(0) 返回的同一个 ChatClient 上重复演示这些典型调用。
四、从「最小智能体」到「可观测的 Run」
public static async void BasicChat() {
var client = AICash.useClient(0);
var agent = client.AsAIAgent(
instructions: "你是一个乐于助人的智能助手。",
name: "千问助手"
);
// 4. 运行智能体并获取流式响应
await foreach (var update in agent.RunStreamingAsync("你好,请介绍一下你自己。"))
{
Console.Write(update);
}
}
4.1 流式对话(Streaming)
Frag.BasicChat 使用 client.AsAIAgent(...) 创建命名智能体,并通过 RunStreamingAsync 把模型输出以流式方式写到控制台。这是验证 MAF + 网关 是否联通的最短路径。
[HttpGet("Heelo/QianWen")]
public async Task<string> GetWenW()
{
Frag.BasicChat();
return "调用结束!";
}
4.2 多轮会话与流式
Frag.ManyChat 演示 AgentSession 在多轮间保留上下文,并再次使用 RunStreamingAsync 做流式输出。适合观察:同一 session 下指令跟随与风格延续是否符合预期。
4.3 多模态(图像)
Frag.BasicImg 构造包含 TextContent 与 DataContent(本地图片)的用户消息,再走流式 Run。用于验证:网关与模型是否支持视觉输入,以及 MAF 侧消息模型的拼法是否正确。
五、会话持久化与「记忆」扩展
5.1 会话序列化 / 反序列化
Frag.memoryChat 与 Frag.PersistedConversationsAsync 都涉及 SerializeSessionAsync / DeserializeSessionAsync:把对话状态(含相关组件状态)落到 JsonElement 再恢复。这在 跨进程、跨请求恢复用户上下文 时是基础能力。
5.2 自定义 AIContextProvider 记忆组件
工程中的 UserInfoMemory 继承 AIContextProvider,在 StoreAIContextAsync 里用结构化抽取(GetResponseAsync<UserInfo>)尝试补全用户名与年龄,在 ProvideAIContextAsync 里把「已知用户信息」写进动态指令。演示了 MAF 里 「记忆不是魔法字符串」,而是可组合的 上下文供应与状态键(StateKeys) 机制。
5.3 聊天记录与归约(探索向)
memorySearchChat(代码在 Frag.Memory.cs)通过 session.TryGetInMemoryChatHistory 观察内存中消息条数,并配合注释说明 归约器触发时机 对「早期笑话是否还能被追问」的影响。这部分非常适合作为 RAG / 长对话裁剪策略 的预习实验。
public static async void memoryChat() {
var client = AICash.useClient(0);
var agent=client.AsAIAgent(
instructions: "你是一个乐于助人的智能助手。",
name: "千问助手"
);
// 为对话创建一个新会话。
AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(">> 使用空白记忆的会话\n");
// 调用智能体并输出文本结果。
Console.WriteLine(await agent.RunAsync("你好,9的平方根是多少?", session));
Console.WriteLine(await agent.RunAsync("我的名字是Ruaidhrí", session));
Console.WriteLine(await agent.RunAsync("我今年20岁", session));
// 我们可以序列化会话。序列化后的状态将包含记忆组件的状态。
JsonElement sesionElement = await agent.SerializeSessionAsync(session);
Console.WriteLine("\n>> 使用反序列化的会话及之前创建的记忆\n");
// 之后我们可以反序列化会话,并使用之前的记忆组件状态继续对话。
var deserializedSession = await agent.DeserializeSessionAsync(sesionElement);
Console.WriteLine(await agent.RunAsync("我叫什么名字?今年多大?", deserializedSession));
Console.WriteLine("\n>> 通过记忆组件读取记忆\n");
// 可以通过智能体的GetService方法访问记忆组件。
var userInfo = agent.GetService<UserInfoMemory>()?.GetUserInfo(deserializedSession);
// 输出记忆组件捕获的用户信息。
Console.WriteLine($"记忆 - 用户名: {userInfo?.UserName}");
Console.WriteLine($"记忆 - 用户年龄: {userInfo?.UserAge}");
Console.WriteLine("\n>> 使用新会话及之前创建的记忆\n");
// 也可以在单个会话上使用记忆组件来设置记忆。
// 如果我们想开始一个新会话,但让它共享之前会话的记忆,这很有用。
var newSession = await agent.CreateSessionAsync();
if (userInfo is not null && agent.GetService<UserInfoMemory>() is UserInfoMemory newSessionMemory)
{
newSessionMemory.SetUserInfo(newSession, userInfo);
}
// 调用智能体并输出文本结果。
// 这次智能体应该记住用户的名字并在响应中使用它。
Console.WriteLine(await agent.RunAsync("我叫什么名字?今年多大?", newSession));
}
六、工具、审批、结构化输出与组合智能体
以下能力与官方 samples/02-agents 中的步骤命名在注释里做了对应,便于读者回到官方仓库核对。
| 能力 | 本仓库入口(控制器路由) | 要点 |
|---|---|---|
| 函数工具 + 流式 | /qw/Heelo/tools |
AIFunctionFactory.Create 将本地方法暴露为工具 |
| 工具调用需人工审批 | /qw/Heelo/agent/step01 |
ApprovalRequiredAIFunction + 循环处理 ToolApprovalRequestContent(演示里自动批准) |
| 结构化输出 | /qw/Heelo/agent/step02 |
RunAsync<T> 得到强类型结果 |
| 会话持久化叙事版 | /qw/Heelo/agent/step03 |
与序列化 API 配套的「笑话续讲」场景 |
| 子 Agent 当作工具 | /qw/Heelo/agent/step09 |
weatherAgent.AsAIFunction() 组合「主助手」 |
| 中间件 | /qw/Heelo/agent/step11 |
AsBuilder().Use(...).Build() 包裹 Run |
| 插件 + DI | /qw/Heelo/agent/step12 |
类方法集 AsAITools(),并把 ServiceProvider 交给 Agent |
这些示例共同说明:MAF 1.0 在「单智能体」之上,已经能较自然地表达工具治理(审批)、组合(Agent-as-tool)与工程化(DI 插件、中间件)。
六、Skills:文件型技能与 Builder
Frag.SkillBasis 使用 AgentSkillsProvider 指向 skills 目录(含 SKILL.md 与参考文档),让模型按「技能包」上下文完成如单位换算类任务;同时代码里还保留了 AgentSkillsProviderBuilder + AgentInlineSkill 的写法(内联资源与脚本),用于对比 「磁盘上的技能」与「代码里装配的技能」。
另外,UnitConverterSkill.cs 中大量继承式 API 被注释,并注明 类定义 Skill 尚不完善——这也是真实探索仓库的价值:把框架边界与演进中的 API 记下来,避免读者踩重复坑。
七、Web 控制器:把探索场景变成「可点的集成测试」
AIChatTestController 将上述 Frag.* 方法挂到 GET /qw/Heelo/...。这类写法的定位是:
- 优点:像集成测试一样,用浏览器或 HTTP 客户端即可触发一条完整链路(建 Client → 建 Agent → Run)。
- 注意:不少方法内部是
Console.WriteLine,在 ASP.NET Core 中输出在宿主控制台,而不是 HTTP 响应体;返回的往往是「调用结束!」这类占位字符串。若要做产品化 API,需要改为返回 JSON / SSE,并把日志迁到ILogger。
路由与官方 Sample 的对应关系在控制器 XML 注释里写得很清楚,例如 step01~step12 的说明,适合作为学习索引。
八、尚未合入主线的 YAML Prompt 工厂
Frag.Create.cs 保留了 YAML 定义 Prompt / outputSchema 的文本,并注释说明 ChatClientPromptAgentFactory 一类 API 在已发包中未找到——这是典型的「跟 RC/正式版变更表」要对齐的地方。1.0 稳定了核心路径,但声明式 YAML 工厂是否独立发包、命名空间如何归位,仍需以官方文档与包为准。
九、小结
- MAF 1.0(
Microsoft.Agents.AI1.0.0) 把智能体、会话、工具、扩展点(上下文、中间件、插件)拉到了同一套 .NET 模型上,适合作为 Semantic Kernel / AutoGen 之后 的新主线做技术储备。 - 当前示例工程的价值在于:用最少路由把「流式、多轮、视觉、记忆、序列化、工具、审批、结构化、子 Agent、中间件、插件、Skills」串成可重复实验,并诚实记录 YAML 工厂与类 Skill 等待办。
- 下一步若走向生产,建议:异步签名统一为
Task、响应体结构化、密钥与配置走环境变量/密钥保管、对长任务与流式输出采用 SSE 或 WebSocket。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)