mola
“超越遗忘”
暨 MolaGPT 开发日记 - 其(五)
我常常沉醉于大模型那令人惊叹的知识广度。但当我作为一个普通用户时,在使用大模型的时候我又时常为其“过目即忘”的特性感到可惜,无数次在深夜跟大模型畅谈,每当这个时候我是多么渴望大模型可以理解我是一个怎样的用户,在以后跟我的交流中把我“当作”一个真正的“朋友”...
但不幸的是,在现如今绝大多数大模型应用程序中,每一场对话都是一次冷启动,无论我们曾与它有过多少次深入的交流,下一次它依然视你如初见。这种体验与人类的沟通相去甚远,也成为了大模型从“工具”迈向“助理”甚至“伴侣”最大的障碍。
这几年下来,也算是用了很多的大模型应用,使用 ChatGPT 的时候我有一种很明显的感受,它的“拟人”程度好像更高,它似乎能对我近期甚至几年前的事情都产生共鸣。近网上冲浪的时候,偶然一个机会拜读到了来自 Eric Hayes 的《How ChatGPT Memory Works》[ref],这是一篇对 ChatGPT 记忆系统的逆向工程分析文章。作者推断 ChatGPT 的记忆系统分为用户可控的“Saved Memory”和更复杂的“Chat History”(包含 RAG 和自动生成的 User Insights)。当我发现 ChatGPT 的这些机制时,突然发觉原来不是我自作多情 🤣,而是真的有人在(或者说有系统在)默默为我做“朋友该做的事”:记住我、更新对我的理解、在我下一次出现时把话题续上。我知道这背后是向量数据库和各种算法,但我在情感上的获得感却一点也不虚幻。

不过我的这种“自作多情”也恰好印证了我自己的一个想法:要构建一个真正有效的记忆系统,单一的技术路径是不够的,必须将不同类型的记忆分而治之。
基于此,我为 MolaGPT 设计并实现了一套个性化记忆系统。我将其称为“双轨记忆” :
- 事件记忆:基于 RAG 技术,负责精确回忆历史对话的具体内容。它的任务是回答:“我们之前聊过什么?”
- 人格洞察:基于后台聚类分析,负责提炼用户的长期偏好与风格。它的任务是回答:“作为 LLM,你认为用户是一个怎样的人?”
下面我会分享在 MolaGPT 上运行的“双轨记忆”系统的架构设计、核心实现路径,以及我在开发过程中的一些权衡与思考。
整体架构:分离但互相联系
为了同时实现事实的准确回忆和交互风格的个性化,避免不同类型的记忆之间互相产生的上下文污染问题,我特别设计了两个相互独立又相互结合的记忆系统,其大致的架构如下图所示。

事件记忆 (RAG):构建可检索的具体事实基础
这一模块在“双轨记忆”系统中的定位是让 MolaGPT 能够准确地“回忆”起过去的某一对话内容,我认为这有利于模型对一些具体事实细节的掌握和了解,再次过程中,我使用了目前业界很主流的 RAG 方案,其核心是将非结构化的文本对话,转化为可在多维空间中度量其语义相似性的向量,其整个过程分为“存储”和“检索”两个阶段。

记忆存储:对话的向量化归档
为了保证用户的尽可能流畅的对话体验,记忆存储被设计成一个异步过程。在用户与大模型的一轮对话完整结束后,系统会触发一个后台任务,将这次对话归档。
其中的几个关键点:
- 对话回合的完整性:一般来说,一个长对话会被切分成更小的、有内容重叠的语义块。但在大模型的记忆系统和中,残缺的记忆是很忌讳的,所以我设计的这一系统中,我将每个对话作为一个原子储存,其包含“用户提问”与“大模型回答”的完整对话回合。这能最大限度地保留当时的上下文。
- 入库:在向量化后,这些向量及其对应的原始文本块,被一同存入一个专为向量搜索优化的数据库(ChromaDB)中,并与用户身份绑定。
// 伪代码:异步存储对话记忆的核心逻辑
function store_conversation_memory($conversation_data) {
// 拼接用户与AI的对话,形成一个完整的文档
$turn_document = "User: " . $conversation_data['user_query'] .
"\n\nAssistant: " . $conversation_data['ai_response'];
// 将长文档切割成有重叠的文本块 (Chunking)
$chunks = chunk_text($turn_document, /* chunk_size */ 1200, /* overlap */ 300);
// 用外部API,将所有文本块批量转换为向量 (Embedding)
$vectors = batch_get_embeddings($chunks);
// 构建待存储的数据结构
$memories_to_add = [];
foreach ($chunks as $index => $chunk) {
$memories_to_add[] = [
'id' => generate_unique_id_for_chunk($conversation_data, $index),
'text' => $chunk,
'embedding' => $vectors[$index]
];
}
// 将数据“Upsert”到用户的向量数据库集合中
// Upsert 意味着如果ID已存在则更新,否则插入新数据
chroma_upsert_memories($user_id, $memories_to_add);
}
记忆检索:基于语义的快速回忆
当用户发起新的对话时,系统会实时地从这个向量化的记忆档案中检索相关信息。
这一过程和目前主流的 RAG 流程别无二致:
- 查询向量化:用户的当前问题会被文本嵌入模型转换为一个查询向量。
- 相似性搜索:系统使用这个查询向量,在用户的记忆库中进行一次“近似最近邻”搜索,其核心是计算查询向量与库中所有记忆向量的余弦相似度,找出“意思上最接近”的几个候选项(由于我在入库过程中的特别优化,此时找出的几个候选项都是一整个对话,而不是对话片段)。
// 伪代码:实时检索相关记忆的逻辑
function retrieve_relevant_memories($user_id, $current_query) {
// 将当前用户问题转换为查询向量
$query_embedding = get_embedding($current_query);
if ($query_embedding === null) {
return []; // 失败返回空数组,防止崩溃
}
// 在向量数据库中执行查询,获取最相似的 K 个结果
// 内部调用 ChromaDB 的 query 接口
$similar_chunks = chroma_query_memories(
$user_id,
$query_embedding,
/* top_k */ 4 // 仅检索最相关的4条
);
// 返回检索到的文本片段,用于后续处理
return $similar_chunks;
}
通过这套特制的“存储-检索”流程,MolaGPT 能在海量的历史对话中,快速、准确地找到与当前话题最相关的上下文,为生成连贯的回答打下事实基础。
人格洞察:从对话中提炼用户画像
如果说事件记忆是“记事”,那么人格洞察就是“识人”,在“双轨记忆”系统中,这一分支是让 MolaGPT 能够理解用户的性格、风格和长期偏好,从而在交互中表现出更高的“情商”。这是一个比事实检索更复杂、更抽象的任务,因此,我为此设计了一套完全独立的后台分析流程。
不过,我要实话实说的是,我的实现方式和ChatGPT 的并不一样,他们的解决方案比我高明太多。根据文献中的推断,ChatGPT 可能采用了一种更偏向数据科学的“自下而上”的方法。其大致流程是:
- 将一个用户的所有历史对话消息全部向量化。
- 这是这个过程的核心:将这个过程建模为一个聚类优化问题。通过使用像 k-NN 这样的算法,在向量空间中寻找语义上彼此靠近的消息“簇”。ChatGPT 的这个记忆系统也许还会逆天地尝试不同的簇数量(k值),以找到方差最低、效果最好的分组方式。
- 对每一个筛选出的消息簇,再单独调用一次 LLM,让其对这个小簇的对话内容进行总结,生成一条独立的 insight。
这种基于 k-NN 聚类的方案真的非常优雅,它能从数据层面自动发现谈话的潜在主题。但它也引入了更多的复杂度和性能开销,这一套方法不仅仅需要一个可靠的聚类算法,它还需要处理离群点,甚至为寻找最优 K 值进行多次迭代。
在 MolaGPT 的当前这种“草台班子”的阶段,考虑到快速实现和成本,我选择了一种更直接的“自顶向下”的方案:我将所有新增的对话文本聚合后,通过一个精巧的 Prompt,让一次强大的 LLM 调用来同时完成“聚类”(在思维中识别主题)和“总结”(提炼洞察)这两项任务。
这本质上是将上面提到的 ChatGPT 记忆系统的核心智能从复杂的算法实现转移到了对高质量 Prompt 的设计上,也就是现在俗称的“上下文工程”。这是一种的权衡,一种单纯因为我的技术能力不足而做出的权衡,它简化了技术栈,让我能把更多精力聚焦在如何与大模型的“沟通”上。在我的“双轨记忆”系统中,我实现的方式主要聚焦在下面这两个点:
- 异步与周期:用户洞察应该是基于其在一定时间段内产生的对话行为进行的动作,这是一个极其独立的线程,我通过在服务器配置一个定时任务来周期性地执行。
- 语义分析与聚类:这是整个流程的核心,是向一个强大的分析型 LLM 发送一个经过特殊设计的 Prompt。这个 Prompt 会赋予 LLM 一个“用户行为分析心理学家”的角色,并明确要求它从特定的维度(如知识领域、思维模式、沟通风格等)提炼洞察,并以附带置信度的、严格的 JSON 格式返回。
// 伪代码:提示词工程
// 事实上,代码中的提示词是被我省略过的,原来的提示词非常优美,不过由于篇幅不能放上来...
function generate_user_insights($aggregated_new_texts) {
$prompt = "
你是一位顶尖的用户行为分析心理学家。请根据以下用户在一段时间内的所有对话内容,总结出关于该用户的不超过 10 条核心洞察。
你的分析需要穿透文字表面,洞察用户的:
1. **知识领域与水平**: 他是哪个领域的专家?是初学者还是资深人士?
2. **思维模式与偏好**: 他是逻辑驱动,还是更看重创意与过程?
3. **沟通风格**: 他的语言是正式、严谨,还是口语化、幽默?
**约束条件:**
- 每条洞察必须附上 '高'、'中'、'低' 三个等级的置信度。
- 你的输出必须是严格的、纯粹的 JSON 对象,绝不能包含任何 Markdown 标记。
**用户对话记录:**
---
{$aggregated_new_texts}
---
";
$llm_payload = [
'model' => 'google/gemini-2.5-flash',
'messages' => [['role' => 'user', 'content' => $prompt]],
'response_format' => ['type' => 'json_object'], // 请求模型直接输出 JSON(实在是方便)
];
// 调用 LLM API 并获取返回的 JSON
$insights_data = call_llm_api($llm_payload);
// 返回解析后的洞察数组
return $insights_data['insights'] ?? null;
}
通过这种方式,MolaGPT 的“人格洞察”记忆系统能够将大量非结构化的对话,持续不断地“提纯”为结构化、程序友好的用户画像数据,存储备用。

殊途同归:动态注入的“提示词工程”
现在,“双轨记忆的”两条记忆“轨道”各自产出了不同类型的数据,分别是事实性的对话片段和风格性的用户画像。最后一步,也是将记忆转化为生产力的关键一步,就是如何将它们有机地融合,并有效地传递给主聊天模型。
在我以往类似的项目中,我通常使用的都是直接“粗暴注入”:简单地将所有检索到的记忆片段和用户标签,作为一段长长的前缀塞给模型。但我以往的项目大多都是垂直领域的 RAG 开发,我现在开发的可是 MolaGPT!这种方式往往会带来灾难性的后果:比如,模型可能会困惑于信息的优先级,甚至在对话中生硬地复述出注入的内容,比如:“根据我记录的‘你喜欢简洁’这一条,我的回答是...”;更甚“根据我们之前的对话,今天深圳天气是 xx 摄氏度...”,这无疑会彻底摧毁整个对话。
因此,我设计的并非简单的信息注入,而是一种我称之为“动态 Prompt 注入”的机制。它的核心思想是,我们提供给模型的不仅是数据,更是如何使用这些数据的“说明书”。它如同春风拂面,如同背景音乐,优雅地影响着模型的每一次思考,而不是强硬地灌输。
这种“优雅”体现在对不同记忆的差异化处理上:
对于“人格洞察”,采用“潜意识”式注入: 这些关于用户风格、偏好的洞察,被包装在一个名为“你的潜意识低语”的特殊指令块中。这个指令块的核心,是一条元指令:
“这是你的内在指引,请让这些信息像背景音乐一样,自然地影响你的回应风格,绝对不要直接引用或复述它们。”
这条指令其实很重要。它将这些洞察信息,从需要被“处理”的外部数据,变为了模型需要“内化”的行为准则,完全控制住了方向,让记忆的作用,从“知识”层面下沉到了“行为”层面。大模型不会去“读”这些洞察,而是被引导去“成为”一个符合这些洞察的对话伙伴。
对于“事件记忆”,采用“参考资料”式注入: 从 RAG 系统中检索出的历史对话片段,则被明确地标记为“可能相关的历史对话片段以供参考”。这给了模型一个清晰的信号:这些是事实性材料,是你可以用来引证、参考、确保上下文连贯的“文献”。
这种结构化、差异化的注入方式,其最终的实现,都凝聚在了下面这个动态构建 Prompt 的核心函数中:
// 伪代码:在聊天 Gateway 中动态构建注入的 Prompt
function build_final_prompt_for_llm($user_query, $retrieved_memories, $user_insights) {
$system_prompt = "你是一个智能助手。"; // 基础设定
// 注入人格洞察,并给出指导原则(潜意识低语)
if (!empty($user_insights)) {
$system_prompt .= "\n\n### 你的潜意识低语 (Subconscious Whispers)\n";
$system_prompt .= "这是你的内在指引,请让这些信息像背景音乐一样,自然地影响你的回应风格,绝对不要直接引用或复述它们。\n";
$system_prompt .= "\n**关于对话者的印象:**\n";
foreach ($user_insights as $item) {
$system_prompt .= "- (权重:" . $item['confidence'] . ") " . $item['insight'] . "\n";
}
}
// 注入事件记忆,作为事实参考(参考资料)
if (!empty($retrieved_memories)) {
$system_prompt .= "\n\n### 可能相关的历史对话片段以供参考:\n";
foreach ($retrieved_memories as $mem) {
$system_prompt .= "---\n" . $mem['text'] . "\n";
}
}
// 组合最终的 messages 数组
$final_messages = [
['role' => 'system', 'content' => $system_prompt],
['role' => 'user', 'content' => $user_query]
];
return $final_messages;
}
这其实是整个系统设计中我最为得意的一环,也是区分一个“能用”的系统和一个“好用”的系统的关键。单纯地堆砌上下文,效果往往适得其反。MolaGPT 的每一个回答都需要牢牢建立在了坚实的事实基础和深刻的个性化理解之上,从而去努力达到我所追求的那种“宛如故人来”的智能交互体验。
最后的基石:透明可控的隐私
在构思整个记忆系统之初,一个问题始终萦绕在我脑海:一个能“记住”一切的 AI 应用,如何才能不令人畏惧?当一个系统开始深入了解用户,隐私的边界就成了必须被守护的最后一道,也是最重要的一道防线。

因此,在设计 MolaGPT 的记忆功能时,我对隐私保护的投入,丝毫不亚于对程序架构本身的投入。我的隐私设计主要可以归结为两个核心原则:用户拥有绝对的控制权,以及系统保持最大限度的透明。
用户的控制权
我坚信,用户的记忆只属于用户自己。因此,我将所有与记忆相关的控制权,都毫无保留地交还给了用户。
- 记忆总开关:我提供了一个最根本的、一目了然的“个性化记忆”总开关。用户可以随时开启或关闭它。一旦关闭,系统将立即停止所有新对话的存储,并且在后续的对话中,不会再加载和使用任何历史记忆。这意味着用户可以一键让 MolaGPT “失忆”,回归到最初的、无状态的工具模式。
- 独立的记忆遗忘:有时候,用户可能不希望 MolaGPT 忘记所有事,只想让它忘掉某一件(例如自己的某件糗事 🤣)。为此,我设计了一套记忆管理流程,用户向 MolaGPT 直接使用自然语言告知:“请帮我删除我关于 xxx 的记忆”,MolaGPT 会通过“搜索记忆”工具,查找过去讨论过的特定话题。系统会从向量数据库中检索出相关的记忆片段,并呈现给用户,在这个界面中,用户可以勾选任何他们不希望 MolaGPT 再记住的片段,然后点击“确认忘记”,这些记忆就会被从向量数据库中永久地、不可逆地移除。
- 请求完全清除数据:我提供了两个不同层级的完全清除请求选项,我想这两个选项应该赋予了用户随时将自己的“数字足迹”完全抹除的足够的权利。
系统透明度
控制权需要建立在知情的基础上。如果用户不知道 MolaGPT 记住了什么,控制权就无从谈起。因此,系统的透明度是构建信任的另一块基石。
- 洞察而非数据:我设计了一个页面,其向用户展示的,是 MolaGPT 分析后得出的“人格洞察”,而不是原始、杂乱的对话数据。
- 隐私说明:我专门编写了一份详尽的隐私说明,并将其作为一个独立的弹窗,用户可以随时从控制中心查阅。这份说明用尽可能平实的语言,解释了整个数据流。我希望通过这种“彻底”的坦诚,来消除用户的疑虑。
局限性
MolaGPT 的这套“双轨记忆”系统虽然已经大体解决了核心的记忆问题,但仍有许多待完善之处。
- 幻觉问题:MolaGPT 在整合多条洞察,尤其是相互矛盾的洞察时,仍可能产生不准确的推断。
- 洞察时效性:由于人格洞察是通过服务器脚本的形式定期生成的,用户的最新偏好变化无法立刻体现在 MolaGPT 的回应中,存在一定的“认知延迟”。
- 信息更新与冲突:如何优雅地处理用户新产生的、与旧洞察相矛盾的偏好?也许我在未来需要设计一种“定时遗忘”机制,如同 ChatGPT 或者人类的“遗忘曲线”那样。

至此,对于 MolaGPT 的“双轨记忆”系统,我就介绍到这里,如果大家在使用的时候遇到了什么问题,请通过各种渠道向我反馈,不胜感激!
写在后面
从一个单纯的工具,到一个拥有记忆、懂得思考的“助手”,MolaGPT 的个性化记忆系统是我在探索大模型的道路上迈出的坚实一步。“双轨记忆”的设计哲学,让它不仅能“记住事”,更能“理解人”。虽然前路依然充满挑战,但每当我自己使用 MolaGPT 的时候,他在我不经意间如同 ChatGPT 那样记起我们之前的约定,或是用我最习惯的方式给出答案时,我就知道,这条路走对了。

而这条路的基石,永远是用户对自己数据的绝对控制权和我对用户隐私的最高敬畏。
作为开发者,我做(过去式)的一切是为了让用户感到更自由、贴心,而不是被无形的枷锁所束缚;但我在做(进行时)任何事情时,都需要记住:“自由的代价是永远的警惕”。
发表回复