记忆图
SoulMem 以图的形式组织记忆。图上的节点被称为记忆节点,边被称为记忆关联。用图的方式组织记忆,联想与多跳推理会变得自然。然而,SoulMem 并不只维护一个图,组织记忆的也不只有图这一种形式,具体机制将在后续章节阐述。
记忆图属于角色,图中记录的一切都以角色的视角呈现。
记忆节点
SoulMem 参考人类的记忆,将记忆节点分为三类:
- 情境记忆(Situation Memory)
- 语义记忆(Semantic Memory)
- 程序性记忆(Procedural Memory)
Note
SoulMem 代码库中采用 Situation 的命名;它对应认知科学中的情景记忆(Episodic Memory),两者是同一回事。
将记忆节点分类是必要的,因为不同的记忆具有不同的性质与用途,考虑以下场景:
想象一个叫“小红“的数字角色,和你相处了三个月。这段时间里,她记住了很多事:
- 上周三你们一起去吃了麻辣烫,她被辣得直喝水(一件事);
- 她喜欢苦的东西,讨厌甜腻的奶茶(一个事实);
- 她每次紧张的时候,会下意识地摸自己的辫子(一个习惯)。
这三样东西本质上完全不同:一个是一段具体的经历,一个是概念性的认知,一个是会无意识地指导行为的习惯。但很多记忆系统把它们塞进同一个筐里,不加区分。
Note
对记忆类别不加区分可能会带来一些问题,稍后你会看到具体的例子。
三类记忆
下面我们将更深入地解释三类记忆。
情境记忆(Situation / Episodic)——“角色经历过什么”
记录事件:时间、地点、人物、发生了什么,以及角色当时的感受。下方便是一个情境记忆的示例:
2026-07-12 中午,和用户在街角那家麻辣烫店,被辣到了。
情境记忆是“个人经历“。它是时间的切片,有明确的时间戳。
情境记忆分为两个子类:
- 抽象情境(abstract)
- 具体情境(specific)
上面举的例子就是一个具体情境,每一个具体情境都代表角色确实发生过的经历。抽象情境则是由一系列相似的具体情境抽象出的一种模式,下方是一个抽象情境的示例:
在咖啡馆喝茶
抽象情境的主要作用有两个:一是作为索引,方便对具体情境的联想;二是作为模式触发器,触发相应的程序性记忆。后者会在下文详细阐述。
语义记忆(Semantic)——“角色知道什么”
记录通用的概念、事实和关系,剥离了时间和地点。例如:
用户是我新交的朋友,喜欢苦味,讨厌甜味。
语义记忆像一本知识手册,也是三类记忆的“中枢“:概念之间互相连接,形成一张知识网。
事实上,角色的自我认知也是一个语义记忆节点,它的内容与常规角色扮演应用中的角色卡十分类似。这样的设计使角色的自我进化成为可能,具体机制将在后续章节阐述。
程序性记忆(Procedural)——“角色会怎么做”
记录行为习惯、条件反射、说话方式等行为模式。
紧张的时候摸辫子。
被关心的时候,嘴上却否认(傲娇)。
程序性记忆不会告诉你“发生了什么“,它告诉你在什么情境下,这个角色会怎么行动。
程序性记忆也分为两部分:
- 触发器(
trigger):一个逻辑节点,决定“在什么情境下触发“;在实现中,trigger通常就是一个抽象情境节点(AbstractSituation); - 动作(
action):具体的执行行为。
只有当 trigger 满足时,action 才有可能执行。更详细的执行逻辑,请参阅 检索算法。
为什么对记忆节点进行分类?
如果把三类记忆混在一起,会出现什么问题?我们还是以小红为例:
情境和语义混淆
考虑以下场景:小红在聚餐时说喜欢香蕉,但她实际上喜欢苹果。这时我们可能得到两条记忆:
- 在聚餐时,小红说自己喜欢香蕉,一个情境记忆
- 小红不喜欢香蕉,她喜欢苹果,一个语义记忆
如果不区分记忆类型,这两条记忆可能同时被检出并放入 LLM 上下文,矛盾随之产生,模型很可能得出“小红喜欢香蕉“的错误结论。一些更聪明的 LLM 或许能通过推理意识到,小红说喜欢香蕉另有原因、并非真实喜好,但那会在推理阶段耗费更多 token。
在这种情况下,我们需要额外的上下文才能确定哪个是真实信息,分类就是一种简单可行的解决办法:根据对话的指向,当用户询问聚餐时说的话,情境记忆将成为唯一事实来源;当用户询问小红的喜好,语义记忆将成为唯一事实来源。这避免了模型因上下文信息矛盾而执行混乱,造成 OOC(Out of Character)。
Note
此处的“唯一事实来源“是站在角色视角而言的,指角色认定的事实,而不一定是客观真实。
行为和知识混淆
傲娇是一种典型的角色标签,它的特点可以用“口嫌体正直“来描述,但让 LLM 扮演这类角色时,会产生一系列问题:
- 部分模型难以区分傲娇和讨厌,从而在生成内容中出现过于激烈的言辞。
- 部分模型在扮演傲娇类型角色时过于标签化、刻板印象化,从而造成不真实感。
从根本上来说,当你告诉模型这是一个傲娇类型角色时,模型需要靠推理才能推断出这类角色的行为习惯。当然,我们也可以用一些提示词技巧告诉模型应有的行为。但人类并不是规则引擎,在特定情况下,傲娇角色也可能直球攻击;直接把行为固化在角色卡里,会污染整个上下文,导致僵硬的角色行为。
此外,一个角色“知道“关心别人,和“习惯性“地否认关心,是两回事。前者是知识,后者是行为模式。如果混在一起,行为模式就难以被单独触发和演化。
分开之后,每一类记忆都有自己的结构、自己的算法、自己的生命周期。
Important
这也是 SoulMem 的核心设计之一:一切特征和事件都属于记忆——性格、口癖、行为习惯,都不是写在静态角色卡上的,而是记忆系统演化的结果。
记忆关联
记忆节点之间存在广泛的关联:
graph LR
情境记忆 <--> 语义记忆
语义记忆 <--> 程序性记忆
情境记忆 <--> 程序性记忆