★Nemori:让人工智能真正“记住”过去的对话——一份写给开发者的情景记忆系统指南★
Nature-Inspired Episodic Memory for Large Language Models
开场三问
- 🍄
为什么 AI 总是“转头就忘”?
传统做法把对话切成片段,再喂给模型,但人类回忆的是「周五下午 Alice 跟我聊的那次旅行计划」,而不是「第 312 条消息」。Nemori 把聊天数据重新组织成与人类记忆粒度一致的「情景片段」,让模型在关键时刻能精准回溯。 - 🍄
它到底做了什么?
把原始对话 → 探测自然边界 → 生成叙事式摘要 → 建立 BM25 索引 → 一问即得。整个过程只用两条提示词,无需额外的大模型调用。 - 🍄
我能拿来做什么?
个人助理、客服机器人、教育 Agent、长期陪伴型聊天应用……任何需要「记得用户」的场景都能直接嵌入。
1. 情景记忆是什么?
Nemori 把对话重写成第三人称的小故事,保留了因果和时间,却去掉了冗余口水话。
“
效果:当用户问“上次 Alice 说樱花季是几月?”时,模型直接定位到《京都旅行计划》情景,而不是在几百条消息里翻找。
2. 系统全景图
用一张图先看清数据流向,再拆每一步。
graph TD
A[原始数据<br>对话/活动/位置] -->|1. 探测边界| B[情景片段]
B -->|2. 叙事摘要| C[情景记忆对象]
C -->|3. 索引| D[BM25 倒排索引]
D -->|4. 检索| E[用户问题]
E -->|5. 生成答案| F[回复]
3. 动手实践:30 分钟跑通 LoCoMo 基准
3.1 环境准备
# 1. 克隆仓库
git clone https://github.com/nemori-ai/nemori.git && cd nemori
# 2. 安装依赖(使用 uv,一条命令)
uv sync
# 3. 配置大模型(任选其一)
export OPENAI_API_KEY="sk-xxx" # 推荐 gpt-4o-mini
# 或 export ANTHROPIC_API_KEY="sk-ant-xxx"
3.2 两条提示词的秘密
代码片段:
from nemori.llm.providers import OpenAIProvider
from nemori.builders.conversation_builder import ConversationEpisodeBuilder
llm = OpenAIProvider.from_env()
builder = ConversationEpisodeBuilder(llm_provider=llm)
episode = builder.build_episode(raw_conversation, for_owner="alice")
print(episode.title) # 《Alice 与我的京都旅行计划》
3.3 建立 BM25 索引——零额外调用
from nemori.core.retrieval import RetrievalService, RetrievalStrategy
service = RetrievalService(episode_repo)
service.register_provider(RetrievalStrategy.BM25, config={})
service.initialize()
service.add_episode_to_all_providers(episode) # 自动分词、去停用词、建索引
3.4 一问即得
results = service.search(
RetrievalQuery(text="樱花季几月", owner_id="alice", limit=5)
)
print(results.episodes[0].summary)
# 输出:Alice 提议 4 月赏樱,已确认航班 JAL123
4. 架构拆解:四层各司其职
5. 为什么 BM25 就够了?
在 LoCoMo 这个对话记忆基准上,Nemori 只用经典 BM25 就拿到高分,原因有三:
-
叙事摘要减少了噪声
原始对话包含大量“嗯、啊、表情符号”,重写成叙事文本后,关键词密度更纯粹。 -
多字段加权
标题 ×3、摘要 ×2、实体 ×2、内容 ×1 的权重组合,让标题里的“京都”比正文里的“京都”更值钱。 -
用户级索引
每个用户独占索引,避免跨用户污染,召回更准确。
“
如果想进一步提升,可无缝接入向量检索或混合策略,接口已预留。
6. 常见问题 FAQ
6.1 情景记忆会不会太长,占 token?
目前 top-20 情景约 1.5k tokens,与直接带原始 20 条消息相当;把 top-k 降到 10 时 token 减半,性能几乎不变。
6.2 原文细节丢失了怎么办?
情景记忆 + 原文分离存储:情景负责快速定位,原文仓库保留完整消息。后续计划开源“语义记忆补全”模块,自动把关键实体(人名、地点)回填到情景。
6.3 可以处理图片或音频吗?
架构已预留 DataType.MEDIA
。实现思路:用视觉/语音模型先转成文本摘要,再走同样的情景流程。
6.4 如何扩展到百万级用户?
- 🍄
存储层:把 DuckDB 换成 PostgreSQL 或分布式 KV 即可。 - 🍄
检索层:BM25 索引按用户分片,水平扩展无锁竞争。 - 🍄
计算层:情景构建任务可异步队列化,削峰填谷。
7. 三步集成到现有聊天机器人
示例片段:
# 每次收到用户消息
await manager.process_raw_data(
RawEventData(
data_type=DataType.CONVERSATION,
content=[{"user": "alice", "text": "明天去图书馆吗?"}],
source="telegram"
),
owner_id="alice"
)
# 机器人准备回答“明天干什么?”
episodes = await manager.search_episodes("明天计划", owner_id="alice")
context = "\n".join(ep.summary for ep in episodes.episodes)
answer = await llm.generate(f"参考记忆:{context}\n用户问:明天干什么?")
8. 未来路线图(来自官方文档)
9. 一分钟速读总结
- 🍄
核心思想:像人类一样“回忆事件”,而不是“搜索消息”。 - 🍄
技术方案:2 条提示词 + BM25 索引 = 高召回 + 低延迟。 - 🍄
接入成本:Python 3.12 环境,一条 uv sync
,十分钟跑通。 - 🍄
适用场景:任何需要长期记忆的 AI 对话系统。
把 Nemori 当成“记忆乐高”:底层已帮你处理数据、索引、隔离,上层只需关心业务逻辑。剩下的,交给人类记忆的本能。