实现一个简单的长期记忆DEMO
要理解智能体的长期记忆,可以先看两个问题:一轮对话结束后,系统怎样判断哪些信息值得留下;之后遇到相关问题时,又怎样把这些信息找回来。本文通过一个 Java Demo 展开这两个环节:回答完成后,模型提出记忆操作候选,由 Java 程序校验并保存;下一轮提问时,程序检索相关记忆,连同近期对话一起提供给模型。
一、长期记忆在 Demo 中保存什么
Demo 把长期记忆按用户归属,使用 userId 区分不同用户;同一用户的记忆可以在不同 threadId 会话中召回。短期对话消息仍按 userId + threadId 隔离。
long_term_memories ├─ id 记忆 ID ├─ user_id 记忆所属用户 ├─ memory_type semantic / episodic / procedural ├─ content 记忆正文 ├─ source_thread_id 来源会话 └─ updated_at 最近更新时间
三类记忆分别用于保存事实、经历、偏好或规则。这里的重点是长期记忆的管理流程;数据库保存完整记录,而本轮只把检索到的少量候选交给模型。
二、从问答到长期记忆变更:整体流程
关键边界:提取模型只返回候选 JSON,不直接连接数据库。Java 应用负责检查候选并执行存储操作;提取调用失败不会撤销已经生成并保存的本轮回答。
三、模型提出的操作 JSON
extractMemoryOperations() 把提取规则、近期用户发言和最多 100 条已有记忆交给模型,请它返回以下结构。Assistant 的回复不会被当作用户事实。
{
"operations": [
{
"action": "ADD",
"type": "procedural",
"content": "用户偏好简洁的中文回答,少用术语。",
"target_id": null,
"confidence": 0.96
}
]
}
| 字段 | 含义 | 示例或规则 |
|---|---|---|
action | 模型建议执行的动作 | ADD、UPDATE、DELETE、IGNORE |
type | 记忆类别 | semantic 事实、episodic 经历、procedural 偏好或规则 |
content | 精炼后的记忆正文 | 新增或更新后要保存的内容 |
target_id | 待更新或删除的已有记录 ID | ADD 通常为 null;UPDATE、DELETE 指向已有记录 |
confidence | 模型按提示给出的自评数值 | 当前程序忽略低于 0.85 的候选;不是校准概率 |
四、applyMemoryOperations():校验后再执行
该函数位于 LongTermMemoryDemo.java。它接收提取模型的 JSON、当前用户和线程信息、最新用户消息以及提取前的记忆快照,返回成功变更的条数。
- 读取 operations:如果缺少操作数组或结构不正确,则不修改记忆。
- 过滤:置信度低于 0.85 或动作为
IGNORE时直接跳过。 - 处理 DELETE:最新用户消息必须包含明确的遗忘意图;目标 ID 还必须存在于当前用户的记忆快照中。
- 验证内容:ADD 和 UPDATE 的记忆类别必须有效;正文长度为 4–400 个字符,且不能命中敏感信息检查。
- 执行 ADD:调用
upsertMemory()写入;数据库用用户、类别和正文的唯一约束避免重复。 - 执行 UPDATE:目标记录必须属于当前用户,且类别匹配,然后调用
updateMemory()。 - 执行 DELETE:调用
deleteMemory(userId, id);SQL 同时限定用户 ID 和记忆 ID。
五、长期记忆的查询与手动管理
自动召回
每次普通提问时,findRelevantMemories(userId, question, 5) 先从当前用户的记录中取候选,按关键词重合数量由高到低排序;同分时优先较新记录,最多取 5 条。命中的记忆加入 system 提示,近期对话则以各自的 role 加入请求消息。
用户命令
| 命令 | 行为 | 相关存储函数 |
|---|---|---|
/memories | 列出当前 userId 的全部长期记忆及来源线程。 | listMemories(userId) |
/forget <记忆ID> | 删除当前用户指定的记忆;不属于当前用户的 ID 不会删除。 | deleteMemory(userId, id) |
| 普通问题 | 自动检索相关记忆并将少量结果提供给模型。 | findRelevantMemories(...) |
六、Demo 和实际项目的差距
这个 Demo 把长期记忆的核心闭环放在一个 Java 程序里,便于看清每一步由谁负责。真正上线后,数据量、用户规模和可靠性要求都会提高,因此下面这些地方通常还需要进一步设计。
1. 记忆提取会增加一次模型调用
Demo 在每次成功问答后同步调用提取模型。这样流程直观,但用户需要多等一次模型响应,也会增加 API 成本;若提取接口故障,记忆本轮就没有更新。实际系统可以把提取工作放到后台队列异步执行,配置重试和失败告警,同时确保主问答不被拖慢。
2. 模型给出的 confidence 不能当作真实概率
这里的 confidence 是模型根据提示自行生成的分数,0.9 并不代表经过统计校准后有 90% 的正确率。Demo 用阈值做一层简单筛选;生产系统还应结合明确的规则校验、来源证据、冲突检测,必要时对高风险记忆要求用户确认,并通过评估数据检查误写和漏写。
3. 关键词检索的语义能力有限
当前实现把问题和记忆拆成词,再按重合数量排序,优点是逻辑简单、结果容易解释,也不需要额外的 Embedding 调用。但“别太啰嗦”和“回答简洁”可能用词不同却意思接近,关键词检索未必能匹配。记忆数量或问法变化增多时,可考虑全文检索与向量检索混合召回,再重排候选,并用检索评估集验证效果。
4. 自动写入需要完整的记忆生命周期管理
只把内容写进去还不够:重复记忆要合并,用户更正要处理旧值与新值的冲突,过时信息要能更新或过期,用户要求遗忘时也要确保真正删除。Demo 只用有限规则演示这些动作;实际项目通常还要记录来源和更新时间,制定保留策略,并提供查看、纠正、删除和审计能力。
5. 存储与权限要适应真实部署
SQLite 适合单机学习 Demo,数据量和并发上升后,生产服务一般会考虑集中式数据库、备份恢复、迁移和并发控制。每次读取、更新或删除都必须校验用户身份和数据归属;敏感信息还需要明确的保存范围、访问控制和删除策略,不能只依赖模型提示词来保护。
总结
通过这个 Demo,可以看到长期记忆并不是把所有旧对话一股脑交给模型。每次提问时,程序先从属于当前用户的记忆中找出少量相关内容,和近期对话一起提供给回答模型;回答完成后,再由提取模型判断用户这轮有没有表达值得以后保留的信息。
提取模型给出的只是操作建议。是否新增、更新或删除,最终由 Java 程序依据规则和用户归属进行检查,再写入 SQLite。这样,模型负责理解内容,应用负责把关并管理数据。
这版实现用关键词排序和简单校验展示了基本思路。真实项目还需要随着用户规模和使用要求,逐步完善记忆检索、异步提取、数据治理、权限与评估。理解这个基本闭环后,我们就有了继续改进长期记忆系统的起点。