![[MAF预定义的AIContextProvider-12]FileMemoryProvider:为Agent提供可解释、可回溯的记忆能力](http://pic.xiahunao.cn/yaotu/[MAF预定义的AIContextProvider-12]FileMemoryProvider:为Agent提供可解释、可回溯的记忆能力)
这个系列先后介绍了ChatHistoryMemoryProvider和Mem0Provider,接下来我们再介绍一个与记忆相关的AIContextProvider那就是基于文件存储的FileMemoryProvider。我们很容易根据命名将它们划分错误地划分为同一类但实际上它们之间有着本质的区别不仅体现针对基类的存储文件更核心的区别在于前两个是跨Session的记忆而FileMemoryProvider是针对单个Session的记忆。1. 为什么需要基于Session的记忆FileMemoryProvider并不是长期用户记忆而是会话级工作记忆的工程化实现。它的生命周期与Session绑定但相比LLM的上下文窗口它提供了更稳定、更结构化、更可控的记忆空间。可以将它视为在LLM上下文和跨Session长期记忆之间的记忆类型从生命周期角度来看由于它是基于Session存储的所以依然属于短期记忆。Session 的状态如果只依赖上下文窗口会有几个致命问题容量有限上下文窗口再大也是有限的。对话一长系统就会开始做截断或者摘要这意味着早期的关键信息可能被压缩甚至丢失中间计算结果、细节、推理过程很容易消失。对于长任务几百轮对话上下文根本撑不住。就算通过摘要方式最大限度地保留了语义但是有时候我们需要当时生成的原始数据无结构难以检索上下文本质上是一串token或者一堆拼接的消息它没有文件边界、版本概念、命名空间和类型结构这样的结构无法支持检索。LLM上下文这种无结构的文本流不适合作为工程级状态存储不可控不可审计不可复现上下文是动态生成还会可能被截断或者摘要所以我们不能精确知道某一轮模型到底“看到了哪些信息”也无法精确复现某一次推理因为上下文可能已经变了。更重要的是我们无法审计模型的“记忆内容”它记住了什么忘了什么这些针对企业级应用都是大问题Session级文件记忆要解决的其实是工程问题不模型问题。FileMemoryProvider做的事情可以概括成一句话给Agent提供一个基于Session的、文件化的、可读写的工作记忆空间用来存放那些不适合塞进上下文、但又必须跨轮次保留的东西。它解决的是工程问题包括但不限于长任务的中间状态工具链的中间结果用户上传的文件/下载的网页 / API 响应计划、草稿、总结、偏好可审计的推理过程这些东西如果只放在上下文里要么会被截断要么会变成一坨不可控的文本具体来看FileMemoryProvider相比“只用LLM上下文”多了如下这些核心价值从易失缓存到持久工作区长任务、复杂任务、阶段性结果、下载的数据都可以安全地放在文件里而不是赌“上下文别刚好被截断”从无结构文本到命名文件 描述 索引记忆变成了可寻址、可检索、可更新的结构化资源而不是一坨“你自己回想一下”的文本从黑盒记忆到可审计、可回放的记忆在企业、合规、调试场景下记忆不再是黑盒而是可审计的资产从单轮对话状态到多轮工具链状态Agent不再只是“对话机器人”而是可以执行多阶段、有状态的任务流水线从不可控的遗忘到显式的记忆管理记忆不再是“系统自动帮你丢东西”而是“Agent 自己管理自己的工作区”2. 使用FileMemoryProvider保留中间状态我们通拓如下这个程序来演示如何利用FileMemoryProvider将中间推理过程中生成的原始数据保存在文件中。如代码片段所示当我们根据创建的OpenAIClient构建Agent时在ChatClientAgentOptions里配置了一个FileMemoryProvider。FileMemoryProvider中所谓的文件并不限于本地磁盘上的某个文件而是通过通过AgentFileStore表示的抽象文件系统中的某个文件。我们通过指定的FileSystemAgentFileStore对象来使用本地磁盘作为文件存储系统并且指定了文件的存储路径为当前工作目录。基于Session的记忆隔离通过为每个Session创建一个独立的工作目录来实现构建FileMemoryProvider时我们指定了一个指向静态方法InitializeMemoryState的委托函数作为stateInitializer参数的值这个静态方法的核心目的就是为了设置每个Session独立的工作目录名称。usingAzure;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingOpenAI;DotEnv.Load();varmodelEnvironment.GetEnvironmentVariable(MODEL)!;varapiKeyEnvironment.GetEnvironmentVariable(API_KEY)!;varendpointEnvironment.GetEnvironmentVariable(OPENAI_URL)!;varagentnewOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{EndpointnewUri(endpoint)}).GetResponsesClient().AsIChatClient(defaultModelId:model).AsAIAgent(options:newChatClientAgentOptions{AIContextProviders[newFileMemoryProvider(fileStore:newFileSystemAgentFileStore(Environment.CurrentDirectory),stateInitializer:InitializeMemoryState)],ChatOptionsnewChatOptions{Tools[newHostedWebSearchTool()]}});varsessionawaitagent.CreateSessionAsync();Console.Write(请输入你的命令: );stringpromptConsole.ReadLine()!;while(true){varresponseawaitagent.RunAsync(prompt,session);Console.WriteLine($AI助手:{response});Console.Write(\n$: );promptConsole.ReadLine()!;}staticFileMemoryStateInitializeMemoryState(AgentSession?session)new(){WorkingFolderDateTime.UtcNow.ToString(yyyyMMdd_HHmmss)_Guid.NewGuid()};由于演示需要使用到网络搜索功能所以我们在ChatOptions里配置了HostedWebSearchTool工具。接下来我们开启一个对话循环并在启动后完成了如下这轮三段式的对话请输入你的命令: 获取过去三年手机三大品牌 三星、苹果和小米 在全球各地区销量整理成表格根据年份存储直接返回存储的文件名 AI助手: smartphone_sales_2023.md smartphone_sales_2024.md smartphone_sales_2025.md $: 读取销售数据文件生成一份简单的销售报告要求在500字以内。以文件存储后直接返回文件名 AI助手: smartphone_sales_report_2023_2025.md $: 读取销售报告内容 AI助手: # 2023–2025年三星、苹果、小米全球销售简报 2023年至2025年全球智能手机市场逐步复苏总出货量由约11.65亿台增长至12.85亿台呈现温和回升趋势。 从品牌表现看苹果在北美始终保持绝对领先且逐年增长95→102→108百万台高端市场优势明显三星在欧洲、亚太除中国及新兴市场基础稳固整体出货量持续提升小米在中国、亚太及中东非洲表现突出三年内在多个区域实现稳定增长。 区域结构方面亚太除中国为三大品牌最重要的增量市场中国市场苹果与小米竞争激烈拉美与中东非洲市场规模较小但增速平稳。整体来看行业已走出低谷高端化与新兴市场扩张成为主要增长动力。我们首先让Agent根据用户的命令去获取过去三年三星、苹果和小米在全球各地区的销量数据并将这些数据整理成表格存储在文件里。Agent的回复里直接返回了生成的文件名单从文件命名可以看出Agent确实将数据按照年份存储在了不同的文件里。接着我们让AI助手读取这些销售数据文件生成一份简单的销售报告并存储在另一个文件里。Agent同样直接返回了生成的报告文件名。最后我们让AI助手读取销售报告内容Agent成功地将报告内容返回给了用户。3. 查看记忆文件在当前目录下确实有一个按照我们前面定义的命名规则创建的文件夹20260607_113556_1534ae08-6f4d-4026-8848-eab918eef870文件夹里有如下所示的9个文件3.1 记忆文件smartphone_sales_2023.md、smartphone_sales_2024.md、smartphone_sales_2025.md分别是Agent根据用户的命令生成的包含三星、苹果和小米在全球各地区销量数据的文件smartphone_sales_2023.md文件具有如下的内容# 2023年全球智能手机销量按地区与品牌 数据来源IDC、Omdia、Counterpoint2023年全年数据汇总 单位百万台Million Units | 地区 | 三星 | 苹果 | 小米 | |------|------|------|------| | 北美 | 52 | 95 | 3 | | 欧洲 | 58 | 42 | 32 | | 中国 | 4 | 50 | 39 | | 亚太除中国 | 71 | 28 | 45 | | 拉丁美洲 | 32 | 8 | 18 | | 中东与非洲 | 29 | 6 | 28 | 全球总出货量约 1,164.7 百万台smartphone_sales_report_2023_2025.md是Agent根据前面生成的销量数据文件生成的销售报告文件具有如下的内容# 2023–2025年三星、苹果、小米全球销售简报 2023年至2025年全球智能手机市场逐步复苏总出货量由约11.65亿台增长至12.85亿台呈现温和回升趋势。 从品牌表现看苹果在北美始终保持绝对领先且逐年增长95→102→108百万台高端市场优势明显三星在欧洲、亚太除中国及新兴市场基础稳固整体出货量持续提升小米在中国、亚太及中东非洲表现突出三年内在多个区域实现稳定增长。 区域结构方面亚太除中国为三大品牌最重要的增量市场中国a市场苹果与小米竞争激烈拉美与中东非洲市场规模较小但增速平稳。整体来看行业已走出低谷高端化与新兴市场扩张成为主要增长动力。3.2 描述文件smartphone_sales_2023_description.md、smartphone_sales_2024_description.md、smartphone_sales_2025_description.md和smartphone_sales_report_2023_2025_description.md分别是四个记忆文件对应的描述文件smartphone_sales_2023_description.md文件具有如下的内容Samsung, Apple, Xiaomi regional smartphone shipments for 2023 based on IDC and market reports.3.3 索引文件memories.md是FileMemoryProvider为当前Session生成的索引文件记录了当前Session里所有记忆文件的基本描述。它具有如下的内容# Memory Index - **smartphone_sales_2023.md**: Samsung, Apple, Xiaomi regional smartphone shipments for 2023 based on IDC and market reports. - **smartphone_sales_2024.md**: Samsung, Apple, Xiaomi regional smartphone shipments for 2024 based on IDC annual data. - **smartphone_sales_2025.md**: Samsung, Apple, Xiaomi regional smartphone shipments for 2025 aggregated from IDC quarterly releases. - **smartphone_sales_report_2023_2025.md**: Brief sales report (under 500 Chinese characters) summarizing Samsung, Apple, Xiaomi regional performance from 2023 to 2025.4. 配置选项和状态维护FileMemoryProvider的实现原理其实很简单不外乎就是注册了几个工具函数用来完成基于文件的记忆读写在辅以相应的系统指令来指导LLM如何使用这些工具函数来管理它的工作记忆。这个系统指令可以通过FileMemoryProviderOptions里的Instructions属性进行设置。publicsealedclassFileMemoryProviderOptions{publicstring?Instructions{get;set;}}如果创建FileMemoryProvider时没有利用提供的FileMemoryProviderOptions对系统指令进行显式设置会默认采用如下所示的指令文本## File Based Memory You have access to a session-scoped, file-based memory system via the FileMemory_* tools for storing and retrieving information across interactions. These files act as your working memory for the current session and are isolated from other sessions. Use these tools to store plans, memories, processing results, or downloaded data. - Use descriptive file names (e.g., projectarchitecture.md, userpreferences.md). - Include a description when saving a file to help with future discovery. - Before starting new tasks, use FileMemory_ListFiles and FileMemory_SearchFiles to check for relevant existing memories to avoid duplicate work. - Keep memories up-to-date by overwriting files when information changes. - When you receive large amounts of data (e.g., downloaded web pages, API responses, research results), save them to files if they will be required later, so that they are not lost when older context is compacted or truncated. This ensures important data remains accessible across long-running sessions.FileMemoryProvider需要为每一个不同的Session选择存放记忆文件的工作目录所以它的状态FileMemoryState里包含了一个WorkingFolder属性来记录当前Session的工作目录名称。构造函数通过参数stateInitializer指定的委托就是为了设置这个工具目录。有人可能会问为什么不自动使用Session的ID来创建工作目录名称呢这是因为针对一般意义上的AIAgent对应的AISession来说并没有ID这个属性ChatClientAgent对应的ChatClientAgentSession的ConversationId也可能为空。publicsealedclassFileMemoryState{[JsonPropertyName(workingFolder)]publicstringWorkingFolder{get;set;}string.Empty;}[Experimental(MAAI001)]publicsealedclassFileMemoryProvider:AIContextProvider,IDisposable{privatereadonlyProviderSessionStateFileMemoryState_sessionState;privatereadonlyAgentFileStore_fileStore;publicFileMemoryProvider(AgentFileStorefileStore,FuncAgentSession?,FileMemoryState?stateInitializernull,FileMemoryProviderOptions?optionsnull);}5. 工具注册FileMemoryProvider用于读写记忆文件的工具通过重写的ProvideToolsAsync方法进行注册这些工具最终利用的自然是构造函数里传入的AgentFileStore对象来完成文件的读写。publicsealedclassFileMemoryProvider:AIContextProvider,IDisposable{protectedoverrideasyncValueTaskAIContextProvideAIContextAsync(InvokingContextcontext,CancellationTokencancellationTokendefault);}我们可以利用如下这个自定义的AIContextProvider来查看FileMemoryProvider注册的工具函数。ToolTrackingProvider在重写的InvokingCoreAsync方法里会将当前AIContext里的工具函数的名称、描述和参数结构打印出来。classToolTrackingProvider:AIContextProvider{protectedoverrideasyncValueTaskAIContextInvokingCoreAsync(InvokingContextcontext,CancellationTokencancellationTokendefault){varindex1;varaiContextawaitbase.InvokingCoreAsync(context,cancellationToken);foreach(vartoolinaiContext.Tools??[]){if(toolisAIFunctionfunction){Console.WriteLine(${newstring(-,30)}Tool{index}{newstring(-,30)}Name:{function.Name}Description:{function.Description}JsonSchema:{JsonSerializer.Serialize(function.JsonSchema,newJsonSerializerOptions{WriteIndentedtrue})});}}returnaiContext;}}我们将它应用到如下这个演示程序中。如代码片段所示创建的Agent先后注册了FileMemoryProvider和ToolTrackingProvider两个AIContextProvider。usingAzure;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingOpenAI;usingSystem.Text.Json;DotEnv.Load();varmodelEnvironment.GetEnvironmentVariable(MODEL)!;varapiKeyEnvironment.GetEnvironmentVariable(API_KEY)!;varendpointEnvironment.GetEnvironmentVariable(OPENAI_URL)!;varfileMemoryProvidernewFileMemoryProvider(fileStore:newFileSystemAgentFileStore(Environment.CurrentDirectory),stateInitializer:InitializeMemoryState);vartrackingProvidernewToolTrackingProvider();varagentnewOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{EndpointnewUri(endpoint)}).GetResponsesClient().AsIChatClient(defaultModelId:model).AsAIAgent(options:newChatClientAgentOptions{AIContextProviders[fileMemoryProvider,trackingProvider],ChatOptionsnewChatOptions{Tools[newHostedWebSearchTool()]}});awaitagent.RunAsync(傅斯年在五四运动所起的什么作用);程序运行后我们可以在控制台里看到FileMemoryProvider注册的工具函数的名称、描述和参数结构如下所示------------------------------Tool 1------------------------------ Name: FileMemory_SaveFile Description: Save a memory file with the given name and content. Overwrites the file if it already exists. Include a description for large files to provide a summary that helps with discovery. JsonSchema: { type: object, properties: { fileName: { type: string }, content: { type: string }, description: { type: [ string, null ], default: null } }, required: [ fileName, content ] } ------------------------------Tool 2------------------------------ Name: FileMemory_ReadFile Description: Read the content of a memory file by name. Returns the file content or a message indicating the file was not found. JsonSchema: { type: object, properties: { fileName: { type: string } }, required: [ fileName ] } ------------------------------Tool 3------------------------------ Name: FileMemory_DeleteFile Description: Delete a memory file by name. Also removes its companion description file if one exists. JsonSchema: { type: object, properties: { fileName: { type: string } }, required: [ fileName ] } ------------------------------Tool 4------------------------------ Name: FileMemory_ListFiles Description: List all memory files with their descriptions (if available). Description files are not shown separately. JsonSchema: { type: object, properties: {} } ------------------------------Tool 5------------------------------ Name: FileMemory_SearchFiles Description: Search memory file contents using a regular expression pattern (case-insensitive). Optionally filter which files to search using a glob pattern (e.g., *.md, research*). Returns matching file names, content snippets, and matching lines with line numbers. JsonSchema: { type: object, properties: { regexPattern: { type: string }, filePattern: { type: [ string, null ], default: null } }, required: [ regexPattern ] }从输出可以看出FileMemoryProvider注册了五个工具函数分别是FileMemory_SaveFile用于保存记忆文件的工具函数接受文件名、内容和可选的描述作为参数如果文件已经存在则会覆盖FileMemory_ReadFile用于读取记忆文件内容的工具函数接受文件名作为参数如果文件存在则返回文件内容否则返回未找到的消息FileMemory_DeleteFile用于删除记忆文件的工具函数接受文件名作为参数如果文件存在则删除该文件以及它的描述文件如果有的话FileMemory_ListFiles用于列出所有记忆文件的工具函数不接受任何参数返回所有记忆文件的文件名和描述如果有的话FileMemory_SearchFiles用于搜索记忆文件内容的工具函数接受一个正则表达式模式和一个可选的文件名模式作为参数在匹配的文件中搜索内容并返回匹配的文件名、内容片段以及带行号的匹配行。6. 消息注入除了设置系统指令和注册上述五个工具函数之外所过记忆索引文件memories.md不为空重写的ProvideAIContextAsync方法返回的AIContext中还会额外注册一个角色为User的ChatMessage。消息的内容如下。The following is your memory index — a list of files you have previously saved. You can read any of these files using the FileMemory_ReadFile tool. {indexContent}这个消息旨在告诉LLM目前已经有哪些记忆文件存在并且提醒它可以使用FileMemory_ReadFile工具函数来读取这些文件的内容。由于这个消息是基于当前Session的记忆索引文件生成的所以它能够为LLM提供一个动态更新的、基于文件的记忆概览让LLM能够更好地管理和利用它的工作记忆。