6.2 MemGPT 思路:把上下文当内存管理
一句话结论:MemGPT 的核心不是"更大的窗口",而是像操作系统管内存一样管上下文——把模型当 CPU、把上下文当 RAM、把外部存储当硬盘,然后让模型自己决定什么时候读盘、什么时候写盘。
读这篇之前
需要先读 6.1(三层记忆)、5.2(压缩)。这一篇是把"记忆"从"存起来"提升到"管起来"。
一、一个精准的类比
在计算机架构里,CPU 不直接读硬盘上的所有数据,而是通过内存管理在高速内存和低速硬盘之间调度。
MemGPT 把这个结构完整复刻到了 LLM 上:把大模型视为操作系统的内核(Kernel)。 [原文 292]
| 计算机 | MemGPT | 作用 |
|---|---|---|
| CPU | LLM | 执行推理 |
| RAM(内存) | 主上下文(Main Context) | 模型唯一能直接"看到"的区域 |
| 硬盘 | 外部存储(External Storage) | 需要时才读取 |
| MMU(内存管理单元) | 一套函数调用接口 | 决定何时换入换出 |
这个类比之所以贴切,是因为它解释了为什么"更大的窗口"不是答案:内存再大也有上限,操作系统的解法从来不是"无限加内存",而是分页调度。
二、两层存储的具体划分
主上下文(Main Context)= RAM
这是模型在推理时唯一能直接看到的文本区域。它被划分为:
| 区域 | 内容 | 特点 |
|---|---|---|
| 固定区 | 系统提示词、核心设定 | 不变 |
| 动态区 | 当前对话流 | 每轮变化 |
注意:固定区不变这一点,正好符合 5.3 讲的缓存要求——MemGPT 在架构上天然缓存友好。
外部存储(External Storage)= 硬盘
分两种,各自解决不同问题:[原文 292]
| 存储 | 存什么 | 检索方式 |
|---|---|---|
| Recall Storage(回忆库) | 所有历史对话,按时间线 | 按时间/关键词 |
| Archival Storage(档案库) | 非对话类海量资料 | 向量检索 + 全文检索 |
为什么分两种? 因为它们回答的问题不同:
- 回忆库回答"我们之前聊过什么"(时序性)
- 档案库回答"关于 X 的知识是什么"(语义性)
把两者混成一个"向量库",会导致"找历史对话"和"找知识"互相干扰。
三、最硬核的部分:自主触发的中断机制
传统 AI 只能在用户提问后回答。MemGPT 在提示词里内置了一套自我循环逻辑:[原文 292]
| 指令 | 含义 |
|---|---|
| Yield(让步) | 处理完任务后,暂停等待用户输入 |
| Continue(继续) | 当模型意识到需要检索或多步思考时,无需用户干预即可连续运行 |
第二条是关键:模型可以自己决定"我还要再查一次"。
这实际上是把 1.2 讲的"循环控制权",从"代码决定何时继续"部分交给了模型。
四、它靠什么实现:一套记忆操作函数
MemGPT 的运行高度依赖 Function Calling(第 4 章)。它内置了一套标准 API:[原文 292]
| 函数 | 作用 |
|---|---|
send_message |
给用户发消息 |
core_memory_append |
在核心内存(长期偏好)中追加信息 |
core_memory_replace |
修改核心内存中的内容 |
archival_memory_search |
在档案库中做语义搜索 |
archival_memory_insert |
往档案库写入 |
conversation_search |
搜索过去的聊天记录 |
这些函数就是 4.1 讲的标准工具——只不过它们的操作对象是"记忆"而不是"外部世界"。
一个实际场景
用户问:"我去年在上海那家餐厅点的菜叫什么?"
① 模型看到主上下文里没有答案
② 决定调用 conversation_search("上海 餐厅")
③ 拿到检索结果
④ 整理后回复
注意第 ② 步:这是模型自己的决定(3.2 讲的"结构化输出"里,"action" 可以是 call_tool、respond、ask_user)。MemGPT 实际上多了一种 action:继续检索。
五、MemGPT 与 RAG 的本质区别
这是最容易混淆的地方。两者看起来都是"检索后回答",但有四个关键差异:[原文 292]
| 特性 | 传统 RAG | MemGPT |
|---|---|---|
| 数据流向 | 单向(检索 → 生成) | 双向(读与写) |
| 主动性 | 被动,由用户查询驱动 | 主动,模型决定何时检索、何时存储 |
| 状态管理 | 无状态,不记住检索过程 | 有状态,能记住自己之前的思考逻辑 |
| 应用场景 | 静态知识库查询 | 长期个性化助手、复杂任务链 |
核心差异:RAG 像是在查字典,而 MemGPT 像是在写日记并随时翻看日记。MemGPT 能动态地更新自己的记忆,这是 RAG 做不到的。
这个区别的实践含义很具体:
| 场景 | 用 RAG 够吗 |
|---|---|
| 查公司文档 | ✅ 够(静态知识) |
| 记住用户的偏好并逐步完善 | ❌ 不够(需要"写") |
| 跨会话累积项目上下文 | ❌ 不够(需要"写" + 主动读) |
六、它的代价
原文也点出了挑战。补上我的理解:[原文 292]
| 挑战 | 具体表现 | 应对 |
|---|---|---|
| Token 成本 | 每轮要带记忆操作说明 + 可能多次检索 | 精简函数描述 |
| 延迟 | 多次自主循环 = 多轮推理 | 限制最大循环次数 |
| 不可预测 | 模型自己决定读盘,行为难复现 | 加终止条件 + 日志 |
| 记忆污染 | 写入的错误记忆会被长期使用 | 写入前校验、支持手工修正 |
第三条最需要警惕:模型自主决定"再查一次",会让行为不可预测。所以实践中通常会限制最大自主循环次数(如 3 次),避免失控。
七、常见误区
误区一:MemGPT 是"更大的上下文窗口"。
恰恰相反。它不追求更大窗口,而是管理窗口。
误区二:MemGPT 就是高级 RAG。
最大区别在"双向":MemGPT 会写记忆,RAG 只读。
误区三:记忆写进去就不用管了。
写入的错误会被长期使用。需要校验、需要能改(6.4)。
误区四:让模型自主循环越多越好。
越多越不可预测、越慢越贵。要有上限。
误区五:这个思路只适合大项目。
即使是小 Agent,"核心记忆 + 档案库"的分层也能显著提升体验。
八、动手:MemGPT 风格的最小实现
6.2 讲了架构。这里给出一个能跑的最小版本。
核心:把"记忆操作"做成工具
MEMORY_TOOLS = [
{"name": "memory_search",
"description": "在档案库中语义搜索历史事实。当你不确定某事时先搜。",
"parameters": {"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]}},
{"name": "memory_write",
"description": "把一条确定的事实写入长期记忆。只在用户明确说过时使用。",
"parameters": {"type": "object",
"properties": {"key": {"type": "string"},
"value": {"type": "string"}},
"required": ["key", "value"]}},
{"name": "continue_thinking",
"description": "当你需要多步推理或再检索一次时调用,不必等用户输入。",
"parameters": {"type": "object", "properties": {}}},
]
三个工具对应 MemGPT 的三个核心动作:读、写、自主继续。
主循环
def run(task, max_rounds=10):
msgs = [{"role": "user", "content": task}]
for _ in range(max_rounds):
r = llm.chat(msgs, tools=MEMORY_TOOLS)
call = r.tool_calls[0] if r.tool_calls else None
if not call:
return r.content # 正常结束
if call.name == "memory_search":
msgs.append(tool_result(call, memory_search(call.args["query"])))
elif call.name == "memory_write":
# 关键:只有可信来源才能写
if call.args.get("trusted"):
memory_write(call.args["key"], call.args["value"])
msgs.append(tool_result(call, "ok"))
elif call.name == "continue_thinking":
msgs.append(tool_result(call, "continue"))
return "达到最大轮数"
三个必须加的护栏
| 护栏 | 理由 |
|---|---|
max_rounds |
模型自主"继续"可能无限循环(6.2 的代价) |
写入需 trusted |
防外部内容被写进记忆(6.4 的污染) |
| 搜索结果要截断 | 防一次召回 20 条撑爆上下文 |
第二条最容易被省,而它是记忆污染的主要入口。
九、动手:MemGPT 思路的代价测算
6.2 讲了架构,但没算过账。这里算一下。
一个典型任务的成本对比
假设:一次任务需要 3 次"回忆"(检索历史),每次召回 5 条,每条 300 Token。
| 方案 | 每轮输入 | 3 次回忆后 |
|---|---|---|
| 无记忆(全塞历史) | 15000 | 只能装 10 轮,之后崩溃 |
| MemGPT 式(按需检索) | 3000 | 3000 + 3×1500 = 7500 |
MemGPT 式的输入更小,而且不随轮数增长 —— 因为它不把历史全放上下文。
但多花的成本
| 额外开销 | 说明 |
|---|---|
| 每次检索是一次工具调用 | 多一轮推理 |
| 检索本身有延迟 | 向量检索通常 50-200ms |
| 可能检索多次 | 第一次没找到还要再搜 |
所以它是一个权衡:
省下:常驻历史(每轮都付)
多花:按需检索(只在需要时付)
判据:
| 你的任务 | 适合 MemGPT 式吗 |
|---|---|
| 长任务(>15 轮) | ✅ 省得明显 |
| 短任务(<5 轮) | ❌ 检索开销大于省下的 |
| 需要频繁回忆 | ✅ |
| 事实都在上下文里 | ❌ 不需要检索 |
这张表能回答"我要不要做 MemGPT 式架构"这个问题。
一个实测建议
先用最简单的实现(历史 + 压缩)跑一批真实任务,统计:
- 平均轮数
- 出现过几次"模型重复问已问过的事"
- 后几轮的质量是否下降
三个指标里有两个变差,才值得上 MemGPT 式架构 —— 否则就是过度设计(6.5)。
十、动手:不做记忆系统的三种替代方案
6.2 讲了 MemGPT 的代价。这里给出更轻的替代。
替代一:状态栏(最轻)
state = """
【已知信息】
- 用户:小明,后端工程师,用 Java
- 项目:Clarity,Spring Boot + Astro
- 当前任务:修复登录超时
【进度】已完成定位,下一步改配置
"""
成本:几十 Token/轮。
收益:解决 80% 的"它忘了"问题。
适用:单次任务、不需要跨会话。
替代二:CLAUDE.md 式常驻规则
项目约定写在固定文件里,每次会话自动加载
成本:一次配置,之后每轮读。
适用:稳定的项目知识,不是用户个人偏好。
替代三:会话持久化(最简单的"记忆")
# 会话结束存,开始时读
save_session(id, messages)
messages = load_session(id) if session_id else []
成本:一次 IO。
收益:解决"关掉重开就忘了"。
适用:需要跨会话续接,但不需要"跨任务检索"。
三种替代 vs MemGPT 式
| 方案 | 成本 | 解决 |
|---|---|---|
| 状态栏 | 极低 | 单次任务内不忘 |
| CLAUDE.md | 极低 | 项目知识常驻 |
| 会话持久化 | 低 | 跨会话续接 |
| MemGPT 式 | 高 | 跨任务、按需检索 |
判据:
只是"记住这次" → 状态栏
只是"记住这个项目" → CLAUDE.md
只是"下次还能接着聊" → 会话持久化
要"从半年的历史里找相关的事" → 才需要 MemGPT 式
最后一行才是 MemGPT 的真正适用场景 —— 而绝大多数产品并不需要。
十一、动手:检查点与 MemGPT 的分工
很多人把这两者搞混。它们解决不同的"忘"。
| 场景 | 用哪个 |
|---|---|
| 任务跑到第 20 轮,怕进程崩 | 检查点(5.5) |
| 用户换了会话,想问"上月说的事" | MemGPT 式检索 |
| 长任务中需要回忆"第 3 轮查到的结论" | 检查点 + 摘要(5.2) |
| 需要跨任务积累用户画像 | 长期记忆(6.1) |
判据
"忘"的范围:
本次任务内 → 检查点 / 摘要
跨会话 → 会话持久化
跨任务、按需 → 长期记忆 + 检索
一个常见错误
错:用长期记忆存"当前进度"
后果:进度是临时的,存进长期库会污染(6.3)
进度属于检查点,不属于长期记忆。 前者是"任务状态",后者是"用户事实"。
三者的协作
任务开始:从长期记忆检索相关事实(6.1)
任务过程:进度写检查点(5.5)
任务结束:确认的事实写长期记忆(6.4)
三条线各走各的,不混用。
十二、动手:懒检索——什么时候不该检索
6.2 讲的 MemGPT 是"主动检索"。但每次都检索也有代价。
三种不该检索的情况
| 情况 | 为什么 |
|---|---|
| 问题完全在上下文里 | 检索只会引入噪声 |
| 明显是闲聊("你好") | 无信息需求 |
| 刚才已经检索过同样内容 | 重复 |
一个判断函数
def should_retrieve(query, context, last_searches):
# ① 上下文里已有答案?
if answer_in_context(query, context):
return False
# ② 闲聊?
if is_smalltalk(query):
return False
# ③ 最近刚搜过?
if similar_to_recent(query, last_searches, threshold=0.9):
return False
return True
收益与代价
| 每次都检索 | 按需检索 | |
|---|---|---|
| 延迟 | 每次都 +100ms | 只在需要时 |
| 成本 | 每次都多一轮 | 少 |
| 漏检风险 | 低 | 有一点 |
判据:
任务是否需要"外部事实"?
需要 → 检索
上下文里有 → 不检索
"按需检索"是 Agent 化的关键一步 —— 它把检索从"固定流程"变成了"模型判断"(4.5 讲的路由思想)。
参考这个模式:把 memory_search 做成工具而不是固定步骤,模型自己决定什么时候调(6.2 的实现就是这样)。
十三、下一篇
MemGPT 是一套完整的架构。但在实际开发中,你往往从更简单的东西开始——比如 ChatMemory。
从最简单的"记住最近几轮",到生产级的上下文管理,中间有一段很长的演进。下一篇讲这段演进路径。
一句话收尾
MemGPT 的思路值得学,但不必照搬。 学它的"分层 + 按需",用你实际需要的复杂度 —— 多数产品用状态栏加会话持久化就够了。