第一记忆模式和第二记忆模式:Kapy 的记忆系统设计
Kapy (short for Kapybara) 是我春节假期做的一个通用 agent,由于我略长于别人的春节假期 (thanks to dify.ai) 就要结束了,先把 devlog 写了,精装修的 GitHub repo 之后再发。
我思考记忆系统的设计很久了,我想每一个做过 chatbot 的人应该都思考过。但我一直没有形成清晰的结论,接着这个机会逼迫自己一把,算是把我的答卷做出来了。其实做 Kapy 是我近期手写代码最多的时候,手写代码就像雕刻家一样(我想象中的雕刻家,因为我也不知道真正的雕刻家什么样),是在打磨的过程中完善思想的过程。
统一式架构设计
进入正题。这个记忆系统打一开始我就决定要做统一式的,不做基于 thread 的拆分,以原始信息作为 single source of truth。此时包括 bub 在内的 agent 已经把这条路跑通了,因此这个决策对我来说不仅是想法,是一个完全明确的方向。
统一式的架构定了,应用层总得有 compact 吧?我一直报有 turn 重要于 thread 的想法:
- 很多任务是以 turn 为单位的,比如搜索任务大概率不会有追问;
- Agent 语境下的 turn 不仅是输入-输出对,而是:输入 -> tool call loop -> 输出
- 之前在推特上我就表达过,tool call loop 不是记忆,而是工作日志。agent-human 之间的交互记忆和 tool call loop 的细节几乎没有关联。
于是 compact 的方案也自然敲定了:以一个 turn 里的 tool call loop 为最小单位做 compact,形成一个 compact node。那记忆的基本单位就成型了:一个 memory record 对应一个 turn,包含其原始工作 log 和 compact node。
DAG 与因果链串联
那既然不要 thread,每个 turn 之间怎么串联起来呢?我立即想到了 git,也就是一个 DAG。这个形状非常合理:连边表示时序和因果,一个 memory record 上接 parents 不就构成因果链了吗?
那 parents 怎么接上呢?我试过不同方案,比如就用 thread 强制串起来?但是 agent 如果自主去查询自己的记忆,不仅限于最近的记忆了,那不也构成因果了吗?最终我认为这里大可交给 agent 自己决定:完成任务后你自己告诉我你参考了哪些过去的记忆,我帮你把这个 DAG 结构落库就好了。
读写逻辑与召回机制
很好,写入逻辑敲定了,读出呢?首先最基本的近期记忆肯定是要的,然后 agent 要自主查询它可能感兴趣的过去的记忆、包括过去的具体工作日志。这时候我已经意识到基于文件系统做事很爽,我把 memory record 的存储落在文件系统里,然后写了一个小脚本。
这个脚本有三路召回:
- Thread 维度:按 thread 筛选召回最近的 memory records,自然起到最近语境上下文的作用。
- User 维度:Kapy 会识别不同的 human user,进行召回。
- Keyword 维度:依赖 agent 自己传进来的 keywords 参数,按 keywords 检索过去的记忆。
这一个脚本虽然是 agent 自己调用的,但其实是半固定注入:我要求 agent 在每一个 turn 的起始第一个 tool call 必须使用这个脚本。
有了这个基础记忆,agent 至少能理解语境了。接下来,agent 或许会想起来有价值的细节需要探查:比如看完 compact 还得查一下原始日志,或者出现了新的关键词要在过去的记忆里查一查。还记得 memory records 都落在文件系统上吗?你自己用 rg sed 之类的去查吧。记得最后把 referenced memories id 告诉我就行。
两种记忆模式的并存
至此,Kapy 的记忆系统就完成了,当然中间我也或多或少地调整了设计,总之十数天的使用下来证明它 work 的非常好。
总而言之,这个系统的原则在于两种记忆模式的并存:
- 模式一:固定式注入的记忆。这个记忆是宽泛的,大体的,把握全局的,以 compact 信息为主的;
- 模式二:agent 自主的记忆查询。这个记忆是具体的、有明确目标的,agent 知道自己在寻找什么。
实现细节与未来展望
实现细节上,目前自然有点粗糙,但总之要符合上述两种模式:
- 对第一个模式要提供一个高级的 API,让 agent 从零上下文的状态下就搞明白我是谁我在哪我要干什么。这个高级 API 的参数要尽量少,能写死的就写死,调用也是强制或半强制的;
- 对第二个模式则要提供一个低级的 API。Agent 此时已经明确知道自己想要什么信息,要给它尽可能大的灵活性去找到它要的信息。
比如 Kapy 当前版本的第一记忆模式目前是三路检索,然后加上 DAG 因果链上的最近祖先们;第二记忆模式基于文件系统,虽然够低级,但 rg 毕竟只是基于关键字,要是加上语义检索全文检索的能力,总归能给它多两只手。
说到这里其实可以引出一个新的问题:第二记忆模式、知识库、skills,都是 agent 利用一个工具从外部去查询,它们之间是什么关系,有什么区别,是统一的同一件东西吗?还是应该区分,有不同的索引结构?我也还没想好,留作后话。
(Nowledge Mem 从记忆的方法论的角度做了一些工作,我觉得很有价值)