Mini Agent 源码解析——3 会话和记忆
Mini Agent 源码解析——3 会话和记忆
导言
在前面两篇里,我们花了大量篇幅讨论 Mini Agent
的工具系统:从基础工具的直接调用,到 base.py 中的抽象基类和
schema 导出,再到 BashTool
内部如何完成命令执行与进程管理。这些内容帮助我们理解了一个核心问题——Agent
是如何借助工具与外部世界交互的。
但交互只是问题的一面。另一面同样关键的是:Agent 是如何在一次会话中保持上下文、记住之前说过什么、以及如何把历史信息融入当前推理的?
我们来看一个常见的场景:用户先让 Agent 写了一个函数,后来又问“我刚才写的那个函数能不能改一下”。如果 Agent 完全不记得之前的内容,这个多轮对话就无法完成。事实上,真正可用的 Agent 系统几乎都必须处理三类信息:
- 短期记忆:当前对话上下文中的多轮交互历史
- 长期记忆:跨越会话积累下来的持久化知识或经验
- 工作内存:当前任务执行过程中正在使用的临时状态
对于一个基于大模型的 Agent 来说,模型本身并不天然具备记忆能力——每一次新的请求对模型而言都是从零开始。因此,框架必须主动承担起“管理对话历史”和“维护会话状态”的职责,在模型需要的时候把相关信息注入上下文。
这篇我们就来聚焦这个话题:Mini Agent 是如何组织会话结构、如何管理对话历史的,以及它的记忆机制在整个 Agent 执行流程中是如何配合工作的。
代码案例
我们结合 examples/03_session_notes.py 来看看 Mini Agent
的记忆机制是如何通过工具层面的设计来落地的。这个示例主要展示了两件事:一个是
SessionNoteTool 和 RecallNoteTool
的直接用法,另一个是将它们接入 Agent
后如何实现跨会话记忆。整个示例的核心思路并不复杂:把“记忆”本身也当成一种工具能力来暴露。
1. 直接使用 SessionNoteTool 与 RecallNoteTool
先看第一个演示函数
demo_direct_note_usage(),它展示了两个工具如何被独立调用:
1 | |
这里有一个很关键的设计细节:记忆不是存在内存里,而是写入一个 JSON 文件。这意味着即使用户关闭了程序,下次重新启动时只要指向同一个文件,记忆就能被恢复。这种方式非常直接——不依赖任何外部存储服务,只要有个文件路径就能工作。
再看 record_tool.execute() 的参数:content
是笔记的正文内容,category
则允许调用方给笔记打上分类标签。这个分类在后续检索时会非常有用,因为
Agent
并不总是需要把所有记忆都翻出来,有时候只需要定位某一类信息,比如“用户偏好”或“项目信息”。
记录完之后,就可以用 RecallNoteTool 把内容取回来:
1 | |
execute() 不传参数时默认返回所有笔记;传入
category
则做过滤。这种简单的设计其实非常贴近实际使用场景——Agent 可以在 system
prompt
的引导下自行决定什么时候该全量召回,什么时候只需要特定类别的记忆。
2. 记忆文件长什么样
在 examples/03_session_notes.py 的第 67 到 71
行,有一个展示记忆文件内容的片段:
1 | |
如果运行过示例,实际的 JSON 大致是这样一个结构:
1 | |
每条笔记都是一个包含内容、分类和时间戳的简单对象。这种格式非常适合直接塞进 prompt 里让模型理解,因为它是结构化的、人类可读的,模型也能很容易地推断出哪些记忆和当前任务相关。
3. 把记忆工具接入 Agent
单独使用工具是一回事,更有意思的是让 Agent
自己决定什么时候该记录、什么时候该召回。demo_agent_with_notes()
展示了完整的做法。
第一步是准备工具列表,把记忆相关的工具和其他工具一起注册进去:
1 | |
这里把 SessionNoteTool 和 RecallNoteTool
和普通文件工具放在了同一层级,说明在 Mini Agent
的框架设计里,记忆工具和其他工具是完全平等的——它们都有统一的
name、description、parameters 和
execute() 接口。
第二步是在 system prompt 里写清楚使用指南:
1 | |
这一步其实很关键。Agent 本身并不“知道”自己有记忆能力,它只是看到自己有两个工具可用。因此,system prompt 实际上承担了“告诉 Agent 应该如何使用记忆系统”的职责。这不是框架自动做的,而是开发者通过 prompt 注入的领域知识。从这个角度看,Mini Agent 的记忆系统其实是“工具 + prompt 引导”共同配合的结果,而不是某个全知全能的内置智能。
4. 跨会话记忆如何实现的
整个示例最有价值的地方,在于它演示了“第一次会话记录、第二次会话召回”这个跨进程的记忆能力。
第一次会话中,Agent 收到了这样一条任务:
1 | |
在处理这个任务的过程中,如果 Agent 主动调用了
record_note,这些信息就会被写入
.agent_memory.json。到了第二次会话,虽然是重新实例化的
Agent
对象,但由于工具指向的是同一个文件,RecallNoteTool
仍然能读到之前写入的数据:
1 | |
这个场景听起来简单,但背后的意义很深:Agent 并不需要在两次会话之间保持进程,它只需要一个共享的存储介质——这里是 JSON 文件——就能延续记忆。这和人类依赖笔记本或笔记应用的场景非常类似。
5. 为什么说这是“工具化”的记忆
到这里可以回头重新审视一下这个设计的思路。
在 Mini Agent
里,记忆并不是框架内置的一个“黑盒智能功能”,而是被明确地抽象成了两个工具:record_note
负责写入,recall_notes
负责读取。它们遵循和其他工具完全一样的接口约定,暴露同样的
name、description 和 parameters
属性,并且可以被 Agent 自动选择和调用。
这种做法有几个显而易见的好处:
第一,可组合性强。 记忆工具和其他文件工具、bash 工具处于同一套工具体系里,Agent 在做决策时不需要区分“你是系统内置的还是我自己加的”,调用方式完全统一。
第二,可控性高。 记忆内容存在哪个文件、格式是什么、什么时候写入,都由开发者决定。框架本身不做隐式的记忆管理,所有记忆行为都是显式的工具调用。
第三,容易扩展。 如果未来想把记忆从 JSON
文件换成数据库或者向量存储,只需要改 SessionNoteTool 和
RecallNoteTool 的内部实现,对外接口保持不变,Agent
代码无需修改。
这也再次呼应了我们在第二篇里讨论过的设计哲学:Mini Agent 始终倾向于把复杂能力拆解成标准化的可调用单元,记忆系统也不例外。
工具类源码解析
有了代码示例的使用场景做铺垫,接下来我们直接从源码层面理解这两个工具的实现逻辑。mini_agent/tools/note_tool.py
总共只有两百多行,结构也非常清晰,两个类分别对应两个工具,每个工具都遵循
Tool 基类定义的统一接口。
1. SessionNoteTool:把笔记写入持久化存储
先看 SessionNoteTool 的几个核心属性:
1 | |
这段实现和之前 BashTool 等工具的思路完全一致:工具名是
"record_note",描述清晰地说明了它的用途,参数定义只要求必填的
content 和可选的 category。注意到
category 虽然是可选参数,但它在 prompt
里的描述已经给出了一个参考分类体系(user_preference、project_info、decision
等),这本质上是在引导模型使用更结构化的方式组织记忆。
然后看 execute() 的实现:
1 | |
整个写入流程非常简洁:先从文件加载已有笔记列表,追加新笔记(含时间戳),再写回文件。值得注意的是,它是先把新笔记追加到列表尾部,而不是整块重写,这在笔记量不大的时候是完全够用的。
2. 惰性初始化:文件何时真正被创建
在 __init__ 和 _save_to_file
里藏着一个很实用的设计细节:
1 | |
__init__
里并没有立即创建文件或目录,而是把路径存好,等真正有笔记要写入时才通过
_save_to_file() 创建。这叫惰性初始化(lazy
initialization)。
这样做有两层好处:第一,工具实例化时完全不需要关心目标路径是否存在,不会因为目录不存在就报错;第二,只有在第一次记录笔记时才会真正创建文件,节省了无谓的
IO 操作。结合 _load_from_file()
里“文件不存在就返回空列表”的设计,整个工具在没有笔记的时候几乎是零成本的。
3. RecallNoteTool:按条件检索已记录的笔记
和 SessionNoteTool 配对使用的是
RecallNoteTool,它负责把笔记从文件里读回来:
1 | |
注意它的参数只有一个可选的
category,不传参数时默认返回全部笔记。description
里特意提到了"from earlier in the session or previous agent execution
chains",这是在向模型说明这个工具可以召回跨会话的历史信息。
再看 execute() 的核心逻辑:
1 | |
召回的流程分几层:首先检查文件是否存在,其次检查是否有笔记内容,然后按 category 过滤(如果传了的话),最后把结果格式化成人类可读的文本块返回。
这里有一个细节值得注意:formatted 的输出格式是
"序号. [分类] 内容\n (recorded at 时间戳)"
的形式。这种格式对人类可读很友好,对模型来说也容易解析,因为每条笔记的结构是固定的,模型如果要在后续推理中提取某一条笔记的关键信息,完全可以从这个格式里稳定地抽出来。
4. 与 Tool 基类的对应关系
把这两个工具的实现和前面第二篇里分析的 Tool
基类对照起来看,可以更清楚地理解 Mini Agent 工具系统的一致性。
SessionNoteTool 和 RecallNoteTool
都完整实现了
name、description、parameters 和
execute()
四个必需成员。它们不需要关心自己是被哪个模型调用的,也不需要关心调用结果最终会交给谁处理。框架只需要保证:
- 工具可以被序列化成一个 schema(通过
to_schema()或to_openai_schema()) - 工具执行后返回
ToolResult(成功/失败/内容/错误) - 工具的元信息(名称、描述、参数定义)被准确传递给模型
这四个条件它们都满足了,所以它们可以无缝融入 Mini Agent
的工具体系,和 WriteTool、BashTool
等没有任何区别。
总结
这一篇我们围绕 Mini Agent 的会话与记忆机制,从使用场景到源码实现做了一个完整的梳理。
首先是代码案例部分。通过
examples/03_session_notes.py,我们看到了
SessionNoteTool 和 RecallNoteTool
是如何被直接调用的,以及它们接入 Agent
之后如何实现跨会话的记忆持久化。最值得关注的设计思路是:Mini Agent
并不在框架层面内置一个“记忆模块”,而是把记忆本身也抽象成工具——record_note
负责写入,recall_notes
负责读取,二者都遵循和其他工具完全一致的接口约定。这种“工具化”的做法让记忆系统具备了高度的可组合性和可扩展性。
然后是源码解析部分。mini_agent/tools/note_tool.py
中的两个工具类实现都非常直接:写入时通过 JSON
文件追加记录,并附上时间戳和分类信息;召回时支持全部返回和按分类过滤,最终把结构化数据格式化成人类可读、模型也容易解析的文本。惰性初始化的设计让工具实例化几乎是零成本的,而
JSON 文件作为存储介质既简单又足够满足跨会话持久化的需求。
把这两部分串起来看,可以更清晰地理解 Mini Agent 对“记忆”这件事的整体思路:它没有选择把记忆做成框架内置的隐式功能,而是选择了更透明、更可控的工具模式。这样做的好处是,所有记忆行为都是显式的工具调用,开发者完全可以控制记忆存在哪里、什么时候写入、召回后如何使用。同时,由于工具都遵循统一的抽象接口,未来把记忆后端从 JSON 文件换成数据库或者向量检索服务,也只需要改动工具的内部实现,不需要动框架代码。