ADD-Only 记忆提取模式
ADD-Only 记忆提取模式
学习目标
读完本章后,你将能够:
- 理解 ADD-Only 策略与传统 ADD/UPDATE/DELETE 三操作模式的区别
- 设计基于 LLM 的单次调用记忆提取流水线
- 实现 Hash 去重和
linked_memory_ids记忆关联机制 - 评估 ADD-Only 模式在记忆膨胀与冲突场景下的表现
核心概念
传统三操作模式 vs ADD-Only
传统记忆系统(如 Mem0 v2)使用三操作模式:
三操作模式的核心问题是 LLM 需要同时判断”提取什么”和”做什么操作”,这增加了 LLM 的认知负担,且 UPDATE/DELETE 操作容易丢失历史信息——例如用户说”我不喜欢 cheese pizza 了,现在喜欢 pepperoni”,UPDATE 会覆盖旧偏好,导致无法追溯用户的历史偏好变化。
ADD-Only 策略的核心思想:只做 ADD,不做 UPDATE/DELETE。所有新提取的记忆都作为新记录插入,通过 linked_memory_ids 与已有记忆建立关联:
8 阶段流水线
Mem0 v3 的 ADD-Only 提取流水线包含 8 个阶段:
| 阶段 | 操作 | 说明 |
|---|---|---|
| Phase 0 | 上下文收集 | 获取最近 10 条历史消息作为上下文 |
| Phase 1 | 现有记忆检索 | 从向量存储检索 top-10 相关记忆 |
| Phase 2 | LLM 单次提取 | 一次 LLM 调用提取所有新记忆(JSON 格式) |
| Phase 3 | 批量 Embedding | 对所有提取的记忆文本进行批量向量化 |
| Phase 4 | Hash 去重 | MD5 Hash 与已有记忆比对,跳过重复项 |
| Phase 5 | 批量插入 | 将所有新记忆批量插入向量存储 |
| Phase 6 | 批量历史日志 | 记录所有 ADD 操作到 SQLite history 表 |
| Phase 7 | 批量实体链接 | 提取实体并关联到 entity store |
| Phase 8 | 保存消息 | 保存原始对话消息到 SQLite messages 表 |
Hash 去重机制
每条记忆文本计算 MD5 Hash:mem_hash = hashlib.md5(text.encode()).hexdigest()。去重逻辑:
- 跨会话去重:与现有记忆的 Hash 集合比对
- 批次内去重:维护
seen_hashes集合,同一批次内相同文本只插入一次 - 语义去重:不在此阶段处理——语义相似但文本不同的记忆作为独立记录保留
记忆链接(linked_memory_ids)
LLM 提取新记忆时,会为其分配 linked_memory_ids 字段,指向相关的现有记忆 UUID。链接条件:
- 同一实体/话题:新事实涉及已有的人物、地点或组织
- 偏好更新:用户改变了之前表达过的观点
- 叙事延续:之前捕获的事件的后续发展
- 矛盾信息:新信息与现有记忆冲突
链接不意味着覆盖——两条矛盾的记忆可以同时存在,下游检索时通过 scoring 机制决定优先级。
设计优势与代价
优势
- 不丢失信息:历史偏好、变更记录完整保留
- 简化 LLM 逻辑:LLM 只需判断”提取什么”,不需要决定 ADD/UPDATE/DELETE
- 天然版本历史:每次 ADD 都带时间戳,可追溯记忆演变
- 批次效率高:批量 Embedding + 批量插入,减少向量存储交互次数
代价
- 存储膨胀:不做删除,记忆量随时间增长
- 冲突并存:矛盾信息同时存在于库中,需要下游检索时解决
- 检索质量依赖排序:top_k 和 threshold 的设置更关键——如果旧矛盾记忆排名高于新记忆,会返回过时信息
替代方案对比
| 策略 | 信息保留 | LLM 复杂度 | 存储量 | 检索质量 |
|---|---|---|---|---|
| ADD/UPDATE/DELETE | 低(旧值被覆盖) | 高(4 分类判断) | 低 | 中(最新值优先) |
| ADD-Only + Hash 去重 | 高 | 低(只提取) | 高 | 依赖排序 |
| ADD-Only + 语义去重 | 高 | 中(需判断语义相似) | 中 | 较好 |
| 时间衰减加权 | 中(旧值降权) | 中 | 中 | 较好(新值优先) |
陷阱与对策
| 陷阱 | 对策 |
|---|---|
| 记忆膨胀导致检索变慢 | 设置合理的 top_k 和 threshold,或使用 reranker 精排 |
| 矛盾记忆返回错误结果 | 在检索结果中按 created_at 排序,优先返回最新记忆 |
| LLM 提取过多无关信息 | 在提示词中设置提取质量标准和 Few-Shot 示例 |
| Hash 碰撞(极罕见) | MD5 碰撞概率约 1/2^128,实际可忽略;若需要更高安全性可用 SHA-256 |
补充:延迟反思模式(Deferred Reflection)
来源:TradingAgents(TauricResearch/TradingAgents)tradingagents/agents/utils/memory.py、tradingagents/graph/reflection.py、tradingagents/graph/trading_graph.py,commit 61522e1
ADD-Only 策略关注写入时不覆盖,而延迟反思模式关注何时写入 REFLECTION——它解决了一个关键问题:Agent 做出决策后,结果是未知的,无法立即生成有意义的反思。
两阶段生命周期
关键设计要素
| 要素 | 说明 |
|---|---|
| Pending 标记 | 每条决策初始标记为 pending,包含日期、Ticker、5 级评级 |
| 延迟解析 | 下次 run 时 fetch 实际回报(raw_return + alpha vs 基准),为 pending 条目生成反思 |
| 跨实体注入 | PM 提示词注入同 Ticker 最近 5 条 + 跨 Ticker 最近 3 条已解析记忆 |
| 原子写入 | tmp 文件 + os.replace() 确保崩溃不损坏日志 |
| 旋转淘汰 | memory_log_max_entries 限制已解析条目数,保护 pending 条目不被淘汰 |
| 分隔符设计 | <!-- ENTRY_END --> HTML 注释作为条目分隔符——不可能出现在 LLM 输出中 |
为什么需要延迟反思?
传统反思模式在决策后立即执行(Agent 评估自己的决策),但交易决策的评估需要等待市场结果。延迟反思将 REFLECTION 的生成推迟到结果已知时,确保反思基于真实数据而非假设。
与标准 ADD-Only 的区别:
- ADD-Only 关注”不覆盖旧记忆”,延迟反思关注”在正确的时机写入”
- 延迟反思中的 pending 条目是一个待办工作队列,不仅仅是历史记录
- 反思 LLM 的提示词被精简为 2-4 句(“正确性 → 成败原因 → 可执行教训”),因为反思会被注入到未来 Agent 的提示词中,必须节省 Token
陷阱
| 陷阱 | 表现 | 对策 |
|---|---|---|
| 价格数据延迟 | 刚 run 完第二天 fetch 回报,价格还没更新 | 跳过该 pending 条目,下次 run 再试 |
| Ticker 退市 | 永久无法获取价格数据 | pending 条目永久累积(需要手动清理) |
| 仅同 Ticker 解析 | 其他 Ticker 的 pending 条目永不解析 | Trade-off:简单但跨 Ticker 延迟大 |
补充:记忆库即插件——可插拔的增/查记忆工具模式
来源:JeecgBoot v3.9.2,AiragKnowledgeServiceImpl.getPluginMemory()、buildAddMemoryTool()、buildQueryMemoryTool(),commit 3f826c4
核心概念
在传统 AI Agent 中,记忆系统通常是硬编码在 Agent 核心循环中的。JeecgBoot 提出了一种记忆库即插件(Memory-as-Plugin)模式:将记忆库转换为标准的 MCP/插件格式,让 AI 流程通过工具调用的方式读写记忆。
转换机制
记忆库(AiragKnowledge, type=memory) ↓getPluginMemory(memoryId) ↓转换为 AiragMcp(MCP 插件格式)├── name: "memory"├── category: "plugin"├── tools: [│ add_memory: POST /api/airag/knowledge/addMemory│ query_memory: POST /api/airag/knowledge/queryMemory│ ]└── metadata: { token, tenantId, authType } ↓注册到 AIChatParams.tools ↓LLM 自主决定何时调用 add_memory / query_memory工具定义设计
add_memory 工具:
- 描述前缀:“【自动触发】向记忆库添加长期信息。范围:{记忆库描述}”
- 参数:knowledgeId(固定值)、content(格式模板:“在 yyyy年MM月dd日 HH:mm分,用户[行为],assistant[回答]”)
- 触发策略:LLM 检测到用户个人信息时自动调用,无需用户指令
query_memory 工具:
- 描述:“查询记忆库中的长期信息。当需要了解用户偏好、历史交互时调用”
- 参数:knowledgeId(固定值)、query(用户问题)
设计优势
| 维度 | 硬编码记忆 | 记忆库即插件 |
|---|---|---|
| 可配置性 | 记忆逻辑写死在代码中 | 通过界面配置记忆库,绑定到 AI 应用 |
| 多记忆库 | 单一记忆系统 | 多个记忆库,每个绑定不同应用场景 |
| 工具可见性 | LLM 隐式使用记忆 | LLM 显式通过工具调用,可观察 |
| 扩展性 | 修改记忆逻辑需改代码 | 新增记忆库只需在界面操作 |
自动触发机制
add_memory 工具描述中的 “【自动触发】” 前缀是关键设计:它告诉 LLM “当检测到相关信息时立即调用,无需等待用户指令”。这与传统工具(需要用户明确要求才调用)不同,是一种主动式工具调用模式。
实现要点:
- 工具描述中明确记忆范围(基于记忆库描述动态生成)
- 内容格式模板确保记忆结构化存储
- 自动触发不需要额外的系统提示,通过工具描述即可驱动
与通用 ADD-Only 模式的关系
ADD-Only 关注”记忆写入策略”(只添加、不覆盖),记忆库即插件关注”记忆访问方式”(通过工具调用而非硬编码)。两者互补:
ADD-Only 策略 → 确保记忆持久化不被覆盖记忆库即插件 → 确保记忆可配置、可观察、可扩展