
目录查看 Foundry Memory配置 Azure 权限创建 FoundryMemoryProvider确保 Memory Store 已创建写入用户信息等待 Foundry Memory 异步更新开启日志验证长期记忆序列化和恢复 Session在新 Session 中读取记忆清理记忆与隔离测试数据在 Foundry 中查看结果上一节介绍了 ChatHistoryMemoryProvider。这一节继续介绍 Microsoft Foundry 提供的托管 Memory 服务。与直接保存聊天记录不同Foundry Memory 会分析对话内容从中提取具有长期价值的信息例如用户资料、个人偏好、旅行计划以及历史对话摘要并将这些信息保存到云端的 Memory Store 中。FoundryMemoryProvider负责将这项托管服务接入 Agent Framework。即使应用创建了新的AgentSession只要使用相同的 Memory ScopeAgent 仍然可以检索属于同一用户的长期记忆。查看 Foundry Memory首先打开 Microsoft Foundry并进入一个已经创建的 Project。如果当前还没有 Project需要先创建一个。在项目左侧导航栏中找到Memory进入后可以查看当前项目下的 Memory Store。在 Memory 页面中可以查看当前项目下的 Memory Store当前还是Preview版本Memory Store 使用的聊天模型Memory Store 使用的 Embedding 模型Foundry Memory 的处理过程发生在云端。Agent 提交对话后Foundry 会调用聊天模型分析内容再调用 Embedding 模型生成向量最后将提取出的记忆保存到 Memory Store 中。配置 Azure 权限在使用之前我们需要配置一下Azure RBAC权限切换到Memory菜单的时候网页上面也会有相关的提示进入 Azure Portal找到对应的 Foundry Project然后进入Foundry Project → Access control (IAM) → Add role assignment添加以下角色RoleFoundry User Member当前 Project 的 Managed Identity这里选择的是项目的Managed Identity而不是开发者自己的 User 账号。如果权限没有正确配置Memory 更新可能失败并出现401 Authentication等错误。在 Foundry 的 Memory 页面中如果系统检测到权限缺失通常也会显示Resolve可以按照页面提示自动完成部分权限配置。创建 FoundryMemoryProvider首先创建FoundryMemoryProviderFoundryMemoryProvider memoryProvider new( projectClient, memoryStoreName, stateInitializer: _ new FoundryMemoryProvider.State( new FoundryMemoryProviderScope(sample-user-0001)));构造函数中的memoryStoreName表示要使用的 Memory Store 名称。FoundryMemoryProviderScope用于划分记忆空间。这里的sample-user-0001可以理解为用户标识。在实际业务系统中同一个用户可能会创建多个 AgentSession。这些 Session 虽然彼此独立但只要都使用该用户对应的同一个 Scope就可以继续访问这个用户以前保存的长期记忆。 例如用户 0001的多个 Session 都使用 sample-user-0001用户 0002 的多个 Session 都使用 sample-user-0002。这样既能让同一用户跨会话共享记忆也能避免不同用户之间发生记忆混用。随后将FoundryMemoryProvider添加到 Agent 的AIContextProvidersChatClientAgent agent projectClient.AsAIAgent( new ChatClientAgentOptions { Name TravelAssistantWithFoundryMemory, ChatOptions new() { ModelId deploymentName, Instructions 你是一个友好的旅行助手。 在回答时使用已知的用户记忆不要编造细节。 }, AIContextProviders [memoryProvider] });确保 Memory Store 已创建使用 Foundry Memory 前需要确保指定名称的 Memory Store 已经存在。AgentSession session await agent.CreateSessionAsync(); Console.WriteLine(\n 设置 Foundry Memory Store\n); await memoryProvider.EnsureMemoryStoreCreatedAsync( deploymentName, embeddingModelName, 面向旅行助手的 Memory Store);创建 Memory Store 时需要指定两个模型。deploymentName是聊天模型。embeddingModelName是 Embedding 模型。例如var deploymentName gpt-4o; var embeddingModelName text-embedding-3-large;EnsureMemoryStoreCreatedAsync会先检查指定名称的 Memory Store 是否存在。如果不存在就使用传入的聊天模型和 Embedding 模型创建。写入用户信息接下来通过正常的 Agent 对话提交用户信息Console.WriteLine(await agent.RunAsync( 你好我的名字是桂兵兵我计划去北海道旅游。, session)); Console.WriteLine(await agent.RunAsync( 我和朋友一起去希望找一些风景优美的景点。, session));每次调用RunAsync()后FoundryMemoryProvider都会自动将本轮用户消息和 Agent 响应提交给 Foundry Memory。不需要手动调用其他方法才能触发保存。连续调用多次RunAsync()后可以只调用一次await memoryProvider.WhenUpdatesCompletedAsync();例如await agent.RunAsync(我的名字是桂兵兵。, session); await agent.RunAsync(我计划去北海道旅游。, session); await agent.RunAsync(我比较喜欢自然风景。, session); await memoryProvider.WhenUpdatesCompletedAsync();前面的三次RunAsync()都会提交 Memory 更新而WhenUpdatesCompletedAsync()负责等待这些异步更新处理完成。等待 Foundry Memory 异步更新Foundry Memory 的更新过程是异步的。Agent 完成一轮对话后消息会先提交到服务端随后经历以下处理提交对话 → 排队等待 → 提取长期记忆 → 合并或更新已有记忆 → 生成 Embedding → 写入 Memory Store因此RunAsync()返回时不代表长期记忆已经可以立即检索。测试代码可以使用await memoryProvider.WhenUpdatesCompletedAsync();等待当前 Provider 提交的更新完成。这个方法会轮询 Memory Update 状态。日志中可能看到Queued InProgress Completed其中Queued表示更新已经提交但后台任务尚未开始执行InProgress表示服务正在提取和处理记忆Completed表示本次更新已经完成Failed表示处理失败。与固定等待几秒相比等待任务状态更加可靠。// 不推荐仅依赖固定等待时间 await Task.Delay(TimeSpan.FromSeconds(10));固定等待时间无法保证服务已经处理完成。等待时间过短时新记忆可能还没有生成等待时间过长又会增加不必要的延迟。不过在生产环境中也不应该无限等待。建议增加超时和异常处理using CancellationTokenSource cancellationTokenSource new(TimeSpan.FromMinutes(3)); try { await memoryProvider.WhenUpdatesCompletedAsync( pollingInterval: TimeSpan.FromSeconds(5), cancellationToken: cancellationTokenSource.Token); Console.WriteLine(Memory 更新完成。); } catch (OperationCanceledException) { Console.WriteLine(等待 Memory 更新超时。); } catch (Exception ex) { Console.WriteLine($Memory 更新失败{ex.Message}); }Foundry Memory 具有最终一致性。用户刚刚提供的信息可能需要等待一段时间后才能作为长期记忆被后续请求检索到。如果更新长时间停留在Queued应记录日志中的 Memory Update ID。这个 ID 可以帮助排查服务端队列、模型部署、配额和权限问题。开启日志为了观察 Memory 更新状态可以给FoundryMemoryProvider配置日志using ILoggerFactory loggerFactory LoggerFactory.Create(builder { builder .SetMinimumLevel(LogLevel.Debug) .AddSimpleConsole(options { options.SingleLine true; options.TimestampFormat HH:mm:ss ; }); });创建 Provider 时传入loggerFactoryFoundryMemoryProvider memoryProvider new( projectClient, memoryStoreName, stateInitializer: _ new FoundryMemoryProvider.State( new FoundryMemoryProviderScope(sample-user)), options: new FoundryMemoryProviderOptions { EnableSensitiveTelemetryData true }, loggerFactory: loggerFactory);运行后可以看到类似日志15:13:18 FoundryMemoryProvider: Update status: Queued 15:13:26 FoundryMemoryProvider: Update status: InProgress 15:13:35 FoundryMemoryProvider: Update status: CompletedEnableSensitiveTelemetryData适合本地调试它可能会让日志中包含 Scope、用户内容或检索结果等信息。生产环境使用时需要谨慎避免将敏感数据写入日志。验证长期记忆等待更新完成后可以直接询问 AgentConsole.WriteLine(await agent.RunAsync( 你已经知道我即将进行的旅行的哪些信息, session));这次请求执行前FoundryMemoryProvider会根据问题检索相关记忆并将结果补充到当前模型上下文中。Agent 可能回答你叫桂兵兵计划和朋友一起去北海道旅游 并且希望寻找一些风景优美的景点。这说明前面的用户信息已经被 Foundry Memory 提取并保存。序列化和恢复 SessionAgent Framework 还可以序列化当前 SessionJsonElement serializedSession await agent.SerializeSessionAsync(session); AgentSession restoredSession await agent.DeserializeSessionAsync(serializedSession);恢复后可以继续使用该 SessionConsole.WriteLine(await agent.RunAsync( 你能回顾一下你记得的个人信息吗, restoredSession));这里需要区分两种状态。AgentSession保存的是当前 Agent 会话状态序列化后可以在应用重启或其他进程中恢复。FoundryMemoryProviderScope决定长期记忆在云端属于哪个用户或业务范围。即使不恢复原来的 Session只要新 Session 使用相同 Scope也可以访问同一组 Foundry Memory。在新 Session 中读取记忆下面重新创建一个全新的 SessionAgentSession newSession await agent.CreateSessionAsync(); Console.WriteLine(await agent.RunAsync( 总结一下你已经知道的关于我的信息。, newSession));虽然newSession与之前的 Session 不同但 Provider 仍然使用new FoundryMemoryProviderScope(sample-user)因此新 Session 可以检索此前属于sample-user的长期记忆。可以简单理解为AgentSession → 管理一次具体会话的状态 FoundryMemoryProviderScope → 决定长期记忆属于哪个用户只要 Scope 相同多个 Session 就可以共享同一组长期记忆。清理记忆与隔离测试数据为了确保示例每次运行都从干净的数据开始可以删除当前 Scope 下已经保存的记忆await memoryProvider.EnsureStoredMemoriesDeletedAsync(session);这里传入session是因为FoundryMemoryProvider会从 Session 中读取对应的 Provider 状态并确定当前使用的 Memory Scope。它删除的是该 Scope 在云端 Memory Store 中保存的长期记忆而不只是当前 Session 中的聊天记录。EnsureStoredMemoriesDeletedAsync适合演示和自动化测试可以避免以前的数据影响本次运行结果。生产环境不能在普通对话流程中随意调用否则可能误删用户已经积累的长期记忆。在 Foundry 中查看结果程序运行完成后重新打开 Microsoft Foundry并进入当前 Project 的 Memory 页面。继续进入 Memory Store 的记忆列表还可以查看 Foundry 从对话中提取出的信息。这里保存的不是完整聊天记录而是 Foundry 从对话中归纳出的长期信息。这也是FoundryMemoryProvider与ChatHistoryMemoryProvider最明显的区别。ChatHistoryMemoryProvider更偏向保存并检索原始历史消息。FoundryMemoryProvider则由 Foundry 托管服务负责提取、合并和维护长期记忆。通过 Memory Store、Memory Scope 和异步更新机制长期记忆不再依赖某一个AgentSession。即使应用创建新的 SessionAgent 仍然可以继续了解同一用户的资料、偏好和历史信息。源代码地址https://github.com/bingbing-gui/dotnet-agent-playbook/tree/master/src/ai-agent/Agent-Framework/07-StorageConversations/02-FoundryMemoryProvider引入地址