4.2. Letta 将 MemGPT 从研究原型变成工程化框架
Letta 将 MemGPT 从研究原型变成工程化框架
2024 年 11 月,MemGPT 的研究团队宣布了一个重要决定:这个曾以“操作系统之于大模型”惊艳学界的项目,正式并入全新平台 Letta。在经历了一年多的论文引用、社区复现和原型打磨之后,MemGPT 不再只是一篇展示虚拟上下文管理思想的论文附件——它变成了一个可安装、可配置、可调用的工程化框架。
如果你读过上一章讨论的 MemGPT 论文,一定记得它用“分页”“中断处理”等 OS 概念管理 LLM 注意力的精妙想法。但论文里的 demo 是硬编码的,你需要手动管理上下文窗口,自行实现记忆换入换出,甚至连 agent 的状态持久化都要自己写。Letta 的使命就是把这种“上下文治理”的思维模型,包装成一组稳定的 SDK 与 API,让你像操作普通微服务一样,创建、恢复、调试一个拥有真实记忆能力的智能体。
这一章正好成为一个关键枢纽:我们不再只是理解概念,而是动手写出第一个用 Letta SDK 构建的、具备持久化状态和记忆块治理能力的 agent。本篇教程将带你从零走完安装、配置、记忆块设计与跨会话对话的全过程——如果你已经厌倦了“每次对话都失忆”的聊天机器人,是时候让它真正记住你了。
你需要什么
- Python 3.10+ 环境(推荐使用虚拟环境)
- 一个 OpenAI API Key(用于访问 GPT-4o-mini 等模型;也可换成其他兼容模型,但本教程以 OpenAI 为例)
- 终端或 VSCode 等代码编辑器
- 预计耗时:约 30 分钟
最终成果
你将得到一个能在多次独立 Python 会话中持续积累用户信息的 Letta agent。它会记住你的饮食偏好、工作习惯,甚至能从一段对话追踪到几天后,无缝延续上下文。更重要的是,你会理解这种能力背后的关键机制——Memory Blocks——并学会如何为不同的长对话场景划分记忆区。
为什么做这个?因为几乎所有复杂的 agent 应用,无论是客服机器人、个人知识管家还是协作式编程助手,都必须解决“跨交互记忆”问题。Letta 提供了迄今为止最干净、最显式的持久化记忆接口,而亲手实现一次,就会懂得“结构化上下文治理”远优于堆积消息历史。
一、安装、配置与第一段代码
步骤 1:安装 Letta SDK 与相关依赖
在终端中执行以下命令,确保拉取的是最新版本的 Letta(截至当前调研素材,SDK 已稳定可用)。
pip install letta-client
预期结果:pip list 中应出现 letta-client 及其依赖项,例如 httpx, pydantic 等,无错误输出。
踩坑提示
有些环境可能需要先升级pip:pip install --upgrade pip。此外,如果之前安装过 MemGPT 的旧包,建议删除虚拟环境后重新创建,避免命名冲突。
步骤 2:设置 OpenAI API Key
Letta SDK 默认使用 OpenAI 作为模型提供方,需要在环境变量中设置密钥。
export OPENAI_API_KEY="sk-你的密钥"
或者在 Python 项目入口处直接写(仅开发测试用,切勿提交到版本库):
import os
os.environ["OPENAI_API_KEY"] = "sk-你的密钥"
预期结果:下一步创建 agent 时不会出现 401 鉴权错误。
步骤 3:创建第一个带文件存储能力的 agent
现在我们利用 Letta Code SDK 创建一个 agent,并为其赋予文本文件记录能力。以下代码完整、可运行。
from letta_client import Letta, AgentCreate
# 初始化客户端(默认连接本地服务,也可以指向远程部署的 Letta Server)
client = Letta(base_url="http://localhost:8283") # 本地 dev server 地址
# 定义 agent 的三个核心记忆块
agent = client.agents.create(
AgentCreate(
name="personal_butler",
model="openai/gpt-4o-mini", # 可使用任何支持的模型名称
embedding="openai/text-embedding-3-small",
context_window_limit=16000, # 控制上下文总长度
memory_blocks=[
{
"label": "persona",
"value": "你是用户的私人管家,彬彬有礼且体贴。"
"你能记住用户的偏好和历史行为,并据此提供个性化建议。",
"description": "定义 agent 的身份和语气,不要修改此块内容。",
"limit": 500 # 该块最大 tokens 数
},
{
"label": "human",
"value": "用户叫托尼,刚搬来本市,正在适应新环境。",
"description": "存储关于用户的信息、偏好和交互历史。",
"limit": 1000
},
{
"label": "scratchpad",
"value": "",
"description": "用作临时工作空间,存放当前任务所需的中间信息。",
"limit": 800
}
],
enable_sleeptime=True, # 开启后台记忆整理
tools=["send_message", "file_read"], # 允许读文件(后面会演示文件存储)
)
)
print(f"Agent 创建成功,ID:{agent.id}")
预期结果:控制台打印出类似 Agent 创建成功,ID:agent-xxxx 的消息。至此,一个拥有 persona、human 和 scratchpad 三块结构化记忆,并且能读取文件的 agent 就在后端就绪了。
重要提示
必须将agent.id保存下来(例如写入本地文件或数据库),这个 ID 是恢复该 agent 全部状态的唯一凭证。丢失 ID 就等于丢失了 agent 积累的所有记忆。
步骤 4:发送第一条消息,观察记忆写入
# 使用 agent.id 恢复默认会话(SDK 会自动保持状态)
session = client.sessions.create(agent_id=agent.id)
# 发送一句话,其中隐含了用户偏好
response = client.sessions.send(
session_id=session.id,
messages=[
{"role": "user", "content": "托尼喜欢在运动后喝椰子水,而且要冰的。"}
]
)
# 打印 agent 的回复,并检查 human 记忆块是否自动更新
print("Agent 回复:", response.messages[-1]["content"])
# 查看记忆块变化
agent_state = client.agents.get(agent.id)
for block in agent_state.memory.blocks:
if block.label == "human":
print("human 块当前内容:", block.value)
预期结果:Agent 会做出礼貌回应,并且在 human 块中,value 字段会新增一条关于“运动后喝冰椰子水”的信息。Letta 框架自动完成了从对话到结构化记忆的萃取与写入——这就是 MemGPT 操作系统思维的具体工程实现。
二、理解 Memory Blocks 的概念与运作
上面这段代码背后,藏着一个容易被忽视的设计抉择,它正是 Letta 与大多数 LLM 框架在上下文治理上的根本差异。
经典做法是把整个对话历史一股脑塞进 context window,然后祈祷模型能“通读并记住一切”。MemGPT 论文已经证明这条路在长程交互中不可靠。而 Letta 给出的方案是:把上下文拆成若干有名称、有用途、有生命周期的记忆块,让模型在每次推理时只加载相关块,并按照块的 description 来决定如何读写。
在我们创建的 agent 里,三个块的角色分别是:
| 记忆块 | 用途 | 读写规则(由 description 引导) |
|---|---|---|
persona |
定义 agent 身份、语气和行为边界 | 不应被 agent 修改,保持稳定 |
human |
存储从对话中学习到的用户信息 | agent 应按需追加和更新 |
scratchpad |
当前任务的工作记忆(类似草稿纸) | agent 可自由读写,任务完成后可清空 |
这些块的写入和读取并非由开发者手动调用 API 完成,而是 agent 在对话循环中,根据每个块的 description 和自身推理结果,决定是否向某个块写入新内容、是否在生成回复前读取某个块的当前值。这意味着你对 description 的编写质量,直接决定了记忆系统的行为是否正确。
举个例子,如果你在 persona 的 description 中忘了写“不要修改”,agent 可能在长期交互中逐渐改变自己的角色定义;或者如果在 human 的 description 中未明确“记录用户偏好”,agent 可能只是礼貌回应,却从不记录任何信息。这是从“代码逻辑治理上下文”向“声明式规则治理上下文”的一次关键跃迁。
另外,每个记忆块的 limit 参数相当于那本“记忆书”的厚度上限,当写入内容超过限制时,Letta 的底层机制会触发总结或遗忘策略,防止无限制膨胀。而 enable_sleeptime=True 会在对话间隙让 agent 自己整理记忆,合并冗余、提取要点,这恰好复现了 MemGPT 论文中的“后台进程”概念,只是这里被封装成了一个配置开关。
可解释性优势
在调试 agent 行为时,你可以随时调用client.agents.get(agent.id)查看所有记忆块的当前值,这与查看“系统配置文件”一样直观。不会有黑盒的向量检索结果混在上下文中,每一段记忆的来源和用途都一目了然。
三、使用 Letta API 进行持久化会话管理
了解记忆块机制后,我们来完成一个更接近真实应用的例子:实现一次跨多天的多轮对话,并验证 agent 的上下文无缝延续。
场景设计
假设托尼在周一向私人管家 agent 提出了一个旅行计划,周三他又回来继续讨论。
周一:提出旅行偏好
周三:直接问“我之前说的那件事怎么样了”,看 agent 是否回忆得起。
步骤 1:模拟第一天的对话(周一)
在 Python 脚本 monday.py 中写入:
from letta_client import Letta, AgentCreate
import json
client = Letta(base_url="http://localhost:8283")
# 创建 agent(同上,简化起见这里写固定代码,或用预先保存的 ID 恢复)
agent = client.agents.create(
AgentCreate(
name="travel_planner",
model="openai/gpt-4o-mini",
embedding="openai/text-embedding-3-small",
context_window_limit=16000,
memory_blocks=[
{"label": "persona", "value": "你是一名细致周到的旅行规划助理。",
"description": "定义角色,请勿修改。"},
{"label": "human", "value": "托尼喜欢深度游,偏爱小众景点和本地美食。",
"description": "学习并保存用户的旅行偏好和已确认的计划。"}
],
enable_sleeptime=True,
)
)
agent_id = agent.id
print("保存此 ID 以便后续恢复:", agent_id)
# 开始第一段会话
session = client.sessions.create(agent_id=agent_id)
response = client.sessions.send(
session_id=session.id,
messages=[{"role": "user", "content": "我想在三月去西班牙旅行,重点看高迪的建筑,并找到塞维利亚最好的 tapas。"}]
)
print(response.messages[-1]["content"])
# 将 agent_id 存储到文件,模拟“跨天”场景
with open("agent_id.json", "w") as f:
json.dump({"agent_id": agent_id}, f)
预期结果:Agent 会给出旅行建议。在对话结束后,human 记忆块应已自动记录下“三月西班牙旅行、高迪建筑、塞维利亚 tapas”等关键信息。
步骤 2:模拟隔日恢复对话(周三)
在另一个脚本 wednesday.py 中写入:
import json
from letta_client import Letta
client = Letta(base_url="http://localhost:8283")
with open("agent_id.json", "r") as f:
agent_id = json.load(f)["agent_id"]
# 直接恢复该 agent 的默认会话(也可创建新会话,但不影响记忆)
session = client.sessions.create(agent_id=agent_id) # 创建新对话线程,但记忆块仍然持久化
# 发送上下文稀疏的消息,考验记忆
response = client.sessions.send(
session_id=session.id,
messages=[{"role": "user", "content": "上次说的那趟西班牙之旅,我想再加两天巴塞罗那的行程。"}]
)
print(response.messages[-1]["content"])
# 检查 human 块是否还记得周一的内容
agent_state = client.agents.get(agent_id)
for block in agent_state.memory.blocks:
if block.label == "human":
print("当前 human 块内容:\n", block.value)
预期结果:Agent 的回复会自然接续上周的对话,比如会说“好的托尼,我会在原有高迪建筑主题的基础上增加巴塞罗那两日行程。”查看 human 块,会发现“三月西班牙旅行、塞维利亚 tapas”等信息依然存在,并可能新增了“加两天巴塞罗那”。整个过程无需重传历史消息,也无需手动拼接上下文。
步骤 3:验证会话隔离
如果你在多用户或多话题场景下担心消息串话,可以主动创建多个会话(Conversation),并分别发送消息,记忆块的内容依然在所有会话间共享(除非你设计了不同的 agent 实例)。
session_a = client.sessions.create(agent_id=agent_id)
session_b = client.sessions.create(agent_id=agent_id)
# 两个会话并发发送消息,后端会自动管理上下文路由
预期结果:Agent 在处理 session_a 的消息时,会看到自己先前在 session_a 中的历史,不会混入 session_b 的消息,但共享的 human/persona 记忆块对所有会话统一生效。这恰好模拟了真实场景中一个用户多端登录的情况:底层记忆一致,上层对话隔离。
架构提示
这种设计意味着agent_id应和一个特定的用户或项目绑定,不要为每一个临时的帮助请求创建新 agent——agent 是记忆的持久化载体,不是无状态函数。如果你需要为一个全新用户服务,再创建另一个 agent。
回顾与行动清单
我们做了什么:
在约 30 分钟内,从安装 Letta SDK 开始,创建了一个拥有 persona、human、scratchpad 三个记忆块的 agent,让它学会从对话中萃取用户信息并持久存储,然后分两次模拟了跨天的多轮对话,验证了上下文的无缝延续。最重要的是,我们理解了 Letta 如何将 MemGPT 的虚拟记忆管理思想工程化为清晰的配置项和 API。
花了多久:核心编码约 15 分钟,大部分时间用于理解记忆块设计和验证跨会话行为。
现在,你可以:
- 在项目里使用 Letta SDK 替代简单的消息历史拼接,实现真正“有记忆”的 agent。
- 根据业务需要,自行设计新的记忆块标签(例如
tasks块用于跟踪待办列表,knowledge块存放领域知识),并用description精细控制 agent 的读写行为。 - 实验
enable_sleeptime和修改context_window_limit,感受上下文回收的实际效果。 - 尝试接入本地模型而非 OpenAI,测试记忆块机制在低算力环境下的表现。
下一章——《Memory Blocks 是实现可解释记忆模块的终极方法》——将沿着本章开启的路径,深入拆解这些记忆块的内部结构与治理逻辑。我们会看到,当记忆被显式地结构化、可审计、可编辑时,agent 就不再只是“更长的上下文窗口”,而是真正向“可信赖的长期伙伴”迈出了实质性一步。别停下,我们马上进入下一段旅途。
上下文治理:AI Agent 系统设计
关于 LearnKu