来源:抖音 @永泽
AIGC 作为一个新兴领域,其技术发展日新月异。永泽的AGI Core项目在记忆系统方面做了深入探索和实践,尤其在处理记忆的复杂性和多样性上展现了独特的创新。本文将详细介绍该记忆系统的整体架构和五种记忆形态,帮助读者理解其核心设计理念和运作机制。
整体架构
AGI Core记忆系统的核心在于其分层架构,旨在高效管理和利用不同类型的记忆。该架构包括:
- PromptCTX 编排层:负责调度和编排不同记忆组件,确保信息能够被有效地组织和传递给大模型。
- 六槽位并发填充:通过多线程或异步方式同时填充六种不同类型的上下文槽位,提升效率。
- 双层Token预算:在PromptCTX层定义了两层Token预算机制,以优化Token使用,保证效率与性能平衡。
- 优先級队列约束:通过优先级队列管理信息,确保重要信息得到优先处理,避免上下文溢出。
- STM (Short Term Memory) 短期对话记忆:用于存储最近的对话历史,通过滑动窗口机制管理。
- Pref (Preference) 结构化偏好:采用规则或大模型解析用户偏好,并进行持久化存储。
- LTM (Long Term Memory) 长期语义记忆:存储重要的、持久化的事实性、经验性知识,支持多维度召回。
- GraphMem (GraphEnhance Memory) 图增强记忆:通过知识图谱技术增强记忆,维护实体关系并支持多跳查询。
- TaskMemBuf (Task Memory Buffer) 任务步骤环形缓冲:存储当前任务的执行步骤、工具调用信息和状态,生命周期与任务执行绑定。
- 持久化存储:底层数据存储采用PostgreSQL和Neo4j,分别用于KV存储和图存储,支持持久化和高效查询。
五种记忆形态
AGI Core记忆系统根据不同的使用模式,将所有数据统一为一种表结构,并抽象出五种核心记忆形态:
3.1 ShortTerm(短期对话记忆)
短期对话记忆采用滑动窗口机制来保存最近的对话记录。当对话轮数超过设定的最大限制时,最早的对话记录将被直接丢弃。其结构体定义如下:
type ShortTerm struct {
mu sync.RWMutex
Messages []ConversationMessage
MaxTurns int
}
该机制确保了大模型始终能访问到最新的对话上下文,且不会因历史对话过长而导致Token溢出。
3.2 Preference(结构化偏好)
结构化偏好记忆用于存储用户的个性化信息和喜好,通过双通道写入机制确保及时性和覆盖率:
- 规则路 (同步、零延迟):对于明确的结构化信息(如用户名称、城市等),通过硬编码规则直接写入,无需大模型解析,保证即时一致性。
- LLM 路 (异步、渐进):对于难以通过规则捕获的用户偏好,通过大模型异步解析对话内容,抽取关键信息并以KV键值对形式存储。
这种双通道机制既保证了常用偏好的即时更新,又通过大模型扩展了对复杂偏好的识别能力。
3.3 LongTerm(长期语义记忆)
长期语义记忆用于存储重要的、持久化的语义信息,其核心字段包括:
- Importance (重要性):衡量记忆的重要性,召回时的二级信号,随时间衰减。
- 计算公式:
s = sim * 0.7 + Importance * 0.3,其中sim为语义相似度,importance为重要性。
- 计算公式:
- Category (类别):表示记忆所属的类别,用于在装配阶段进行过滤,避免无关信息干扰。
- SlotHint (槽位提示):建议promptctx在装配时按此进行分流,提高召回准确性。
长期语义记忆的结构体定义如下:
type Item struct {
ID int
Content string
Importance float64 // 0-1, 越高越重要
Embedding []float64
Score float64 // 召回时的综合分(不持久化)
CreatedAt time.Time
LastAccessed time.Time
Category string // identity/preference/fact/episodic/tool_failure/policy
Tags []string
SlotHint string // 建议归属的SlotKind
}
3.4 GraphMemory(图增强层)
图增强记忆将记忆存储为知识图谱中的节点和边,以维护实体之间的深层关系。其节点结构包含记忆ID、内容和重要性,边类型包括:
- FOLLOWS (时序相邻):记录记忆之间的时序关系(前一条 -> 当前),用于按对话顺序召回。
- SIMILAR_TO (语义相似):实时计算记忆之间的语义相似度,并自动创建边。
关键能力在于:
- 1-hop 图扩展:召回时不仅返回向量命中的条目,还会沿边扩展一跳,获取相关背景信息。
- 图中心复合式保护:入度大于等于3的节点免于淘汰,保证重要且关联性高的记忆不会被轻易删除。
3.5 TaskMemBuffer (任务步骤环形缓冲)
任务步骤环形缓冲用于存储任务执行过程中的临时信息,其生命周期与ReAct循环绑定:
- 声明周期:ReAct代理每次新任务开始时清空,任务步骤执行后Push()追加。
- 独立性:与长期记忆完全独立,防止短期任务执行信息污染长期偏好信息。
写入链路
记忆的写入链路是整个系统运作的关键一环,确保了响应信息能被正确存储和分类。
4.1 时序图
用户向assistant发送请求后,系统会进行如下写入流程:
- 短期记忆追加:将大模型回复添加到
ShortTerm.Add,因为它是一个滑动窗口维护的。 - 异步抽取K-V:通过
mem_writer异步抽取大模型回复中的KV结构。 - 分类和分流:
- KV对通过
pref.Save保存用户偏好,并根据内容分类(如个人信息、偏好、工具使用、策略、约束等)。 - 对于长期记忆,通过LLM调用
classifyMemory进行内容分类,并进行向量化嵌入(LLM.Embed(content))。
- KV对通过
- 去重与更新:
- 如果记忆是新增,直接保存(
longterm.210)。 - 如果记忆命中去重但未命中合并(
longterm.184-203),则更新。 - 通过
SyncClassItemPGID同步至Neo4j图数据库,维护节点与边。
- 如果记忆是新增,直接保存(
4.2 写入即分类 (双通道分管线)
写入链路采用双通道管理,同时利用规则层和LLM进行分类:
- 规则层:预设规则迅速判断记忆类别,如包含“我叫”则为身份信息。
- LLM 底气:若规则无法分类,则调用大模型进行兜底分类,返回JSON格式的类别和槽位提示。
这种设计避免了无分类情况下的Top-K全量召回,提高了分类的效率和准确性。不同分类的记忆(如身份信息和任务执行信息)不会相互干扰,保持了独立性。
4.3 写入去重 (隐式合并)
写入去重并非简单地丢弃重复内容,而是通过隐式合并来增强记忆。当多次提到同一事实时,其重要性会自然累积。具体的合并逻辑包括:
- 去重:如果相似度阈值高于0.95,则保留重要性更高的记忆,并移除旧的条目。
- 合并:如果相似度在0.80-0.95之间,则进行合并操作,包括取最高重要性、合并标签和加权平均嵌入向量等。
不调用LLM进行合并的原因是:
- 确定性与效率:规则合并具有确定性,延迟低且可单测。
- 成本:大模型每次改写都会带来显著的成本,且可能生成不确定的结果。
- 长期收益:长期活跃的“用户喜好”会通过
LLM_rewriter进行重写,形成高质量的记忆,抵消手动合并的不足。
4.4 异步建图
图增强层通过异步构建图结构来维护记忆之间的关联。其主要流程为:
- 存储节点:将新记忆作为节点存储到Neo4j中。
- 添加边:
- 根据前一记忆的ID添加
FOLLOWS边,表示时序关联。 - 扫描最近50条记忆,计算余弦相似度,若相似度高于阈值,则创建
SIMILAR_TO边,表示语义关联。
- 根据前一记忆的ID添加
这种方式能够主动联想,用户提到“上次的咖啡”,大模型会召回到“喜欢咖啡”的记忆,通过图层补回“昨晚熬夜了”这类间接关联的记忆,丰富上下文。
召回链路
记忆的召回链路是根据不同策略,从记忆库中检索相关信息的过程。主要分为三种召回策略:
5.1 三种召回策略对应三类槽位
- Profile (用户画像):按
Category分类进行枚举,不计算相似度,用于获取用户身份和偏好。 - Recall (记忆召回):向量相似度 + TF-IDF + 1-hop图扩展,用于召回重要事实和经验。
- TaskMem (任务记忆):环形缓冲,按最近K条取出,用于获取任务步骤和工具状态。
5.2 召回综合分公式
召回综合分通过结合相似度和重要性进行计算:Score = sim * 0.7 + Importance * 0.3。这样设计旨在:
- 相似度 (0.7):作为主信号,响应用户明确的问题(如“你喜欢什么”),召回“喜欢的”结果。
- 重要性 (0.3):次信号,在语义相似的前提下,让指数衰减的更重要记忆排在前面。
- 避免重要性主导:防止很久远的低重要性记忆永远被召回。
5.3 SlotFilter 声明显式过滤
声明显式过滤机制通过结构体RecallFilter struct实现,根据以下条件筛选记忆:
- Categories:包含其中一个即可。
- RequireTags:必须全部包含。
- MinScore:综合分阈值。
- TopK:截断数量。
- MaxAgeHours:年齡硬过滤(创建时间维度)。
5.4 1-hop 图扩展
图扩展通过Neo4j Cypher查询实现,从召回的记忆ID出发,扩展1跳实体关系:
MATCH (m:Memory) WHERE m.mem_id IN $ids
MATCH (m)-[:FOLLOWS|SIMILAR_TO|CAUSES|BELONGS_TO*1..1]->(n:Memory)
WHERE NOT n.mem_id IN $ids
RETURN DISTINCT n.mem_id AS id
注意点:图扩展不是为了直接相似的命中,而是为了主动联想,从相关实体间接补齐信息。
合并链路 (衰减/去重/合并/过期)
合并链路是记忆系统最复杂的部分,涉及对记忆的衰减、去重、合并和过期处理,以维持记忆库的效率和相关性。
6.1 触发器
合并操作由一个计数触发器驱动,而非定时触发,这可以避免在用户不活跃时进行不必要的计算。
- 低活跃期不空转:用户不进行问答时,不触发合并。
- 高活跃期及时清理:当系统活跃度达到一定阈值时,及时清理重复和不必要的记忆条目,防止堆积。
- 异步执行:不阻塞用户响应。
6.2 Phase 1: 指数衰减
记忆的重要性会随时间指数衰减,使用CreatedAt和LastAccessed时间计算,并防止过度衰减(控制PG写入放大):
const minDecayDelta = 0.01
for i := range m.Items {
days := time.Since(m.Items[i].CreatedAt).Hours() / 24
oldImp := m.Items[i].Importance
newImp := oldImp * math.Pow(m.consolidationCfg.DecayRate, days)
if oldImp-newImp >= minDecayDelta {
// ... 更新记忆
}
}
6.3 Phase 2: 去重 + 合并 (双阈值分流)
去重和合并是根据相似度阈值进行分流处理:
- 去重 (sim >= 0.95):保留高重要性记忆,删除低重要性记忆。
- 合并 (0.80 <= sim < 0.95): 合并为新条目,取最高重要性、合并标签和加权平均嵌入向量。
6.4 Phase 3: 双门槛过期淘汰
记忆的淘汰采用双门槛策略:
- 时间门槛 (TTLDays > 30):记忆创建时间超过30天。
- 重要性门槛 (Importance < MinImportance):记忆的重要性衰减到低于0.3。
只有当记忆同时满足这两个门槛时才会被删除,这确保了即使是陈旧但重要的记忆也能被永久保留,避免了“到期就删”的简单策略可能带来的信息丢失。这正是该记忆系统设计中的一个精髓之处。
总结
永泽的AGI Core记忆系统通过精巧的整体架构和多样的记忆形态,实现了对Agent记忆的精细化管理和高效利用。从短期对话到长期语义,从结构化偏好到图谱增强,再到任务步骤的缓冲,每一种记忆都经过精心设计,并通过智能写入和召回链路确保了信息的及时更新和准确检索。这种全面而灵活的记忆系统设计,为构建更智能、更可靠的AI Agent奠定了坚实基础。