你可能遇到过这么一个场面:用户昨天刚告诉AI代理“回复我尽量简短,不要贴大段代码”,今天换个话题又问同样的事,代理直接把几十行方案糊上来,用户当场想砸键盘。更常见的是,代理在多轮任务里做到一半,突然忘了最初的目标是什么,卡在某个子步骤上反复兜圈子。这不是模型不够聪明,而是代理没有记忆——每一次交互都是“重启”的状态。
LangMem就是冲着这个问题来的。它是一套给AI代理补上长期记忆的方案,让代理能跨会话记住用户偏好、历史事实、操作经验和任务上下文,而不是每次都在上下文窗口里“现找”。这篇文章我不讲宣传语,只说实际搭建和跑通过程中的工作机制、接入代码和坑点,给想自己动手给代理加记忆的读者做一份可复现的参考。
1. 为什么AI代理需要一个记忆系统:从会话失忆说起
1.1 上下文窗口的物理限制
大模型的输入输出能力被上下文窗口卡得死死的。现在的窗口动辄几十万token,听起来很宽裕,但真实对话里塞几个文档、几轮工具返回结果、再加上系统提示词,几万token就没了。更关键的是,上下文窗口再大,也只是“这一轮请求”的临时缓冲区,关掉会话或者发起新session,所有内容都归零。
我一直觉得,把上下文窗口当成代理的“记忆”是个错觉。它更像是人脑的“工作记忆”——临时用来处理当前任务的便签纸,而不是长期存放信息的档案柜。你今天跟代理讨论了一个项目的架构约束,明天新开会话它完全不记得,你得重新把背景讲一遍,这就是典型的“工作记忆”缺陷,跟智商没关系。
这种失忆对纯聊天场景影响还不算致命,但对需要多步执行任务的代理来说,代价非常实际。代理做事靠的是拆解目标、调用工具、观察结果、继续推进,这个循环一旦缺少对“我为什么在做这件事”“我之前做过什么决策”的感知,就会反复做重复动作,甚至做出与用户意图相反的选择。
1.2 失忆的真实代价:不只是“多解释一遍”
我最早做代理原型的时候,踩过一个非常具体的坑:代理需要按照某个字段格式调用内部工具,第一次用户告诉它“日期统一用YYYY-MM-DD,不要带时间”,它当时执行对了。但下一轮任务里它调同一个工具时,又传了带时分秒的格式,工具直接报错。整个链路断在那里,我一开始以为是工具接口的问题,排查了半天才发现,代理压根没把上一轮的经验带给下一轮决策。
这种问题在真实业务里比比皆是。用户偏好(“报告里别放原始数据表格”)、项目约束(“这个模块只允许只读访问”)、工具调用习惯(“查库存前先更新缓存”)——全都是需要跨会话保留的信息,缺了这些,代理永远停留在“看起来会做事,实际上每次都是新手”的状态。
所以给代理加记忆,不是“增强体验”这种锦上添花,而是让代理真正可用的前置条件。没有长期记忆,代理就不是一个持续工作的执行者,只是一个“每次都要重新培训的实习生”。
1.3 记忆的三层划分:工作记忆、语义记忆与情景记忆
说到给代理加记忆,首先得把“记忆”这个词拆开。认知科学里常把记忆分为几类,放到AI代理上也很适用:
- 工作记忆:当前会话内的一组上下文,本质就是transformer的上下文窗口,短、快、易失。
- 语义记忆:关于世界的事实性知识——用户是谁、偏好什么、项目有哪些约束、某个API的调用规范。这类知识相对稳定。
- 情景记忆:特定时间、特定场景下发生了什么——昨天的任务为什么中断、上次部署时哪一步出了问题,属于事件性记录。
LangMem的核心定位,就是补上后两块长期记忆。它会从会话或代理执行过程中自动提炼出值得留存的信息,存进外部存储,并在需要的时候检索回上下文。这样代理看起来就有了“记性”,而不是每次对话都从零开始。
理解这个分层很重要,因为你后续设计记忆策略时,第一件事就是决定“哪些信息值得沉淀成长期记忆”,而不是把整段对话都倒进存储里。
2. LangMem的核心设计:它如何给代理装上记忆
2.1 写入与提取:先想清楚“什么时候记”和“怎么想起来”
LangMem在设计上把记忆拆成了两条路径:写入(write)和提取(retrieve)。写入负责从对话中筛选、提炼出值得长期保存的信息;提取负责在代理决策前把相关记忆召回上下文。两条路径分开,比“整个对话存进去再全量搜索”要合理得多。
这就像你整理笔记。你不会把每天的聊天记录原样抄进笔记本,而是会挑重点、提炼成几条结论;下次查资料的时候,也不会把整本笔记重新读一遍,而是凭关键词快速定位到需要的那一页。LangMem里面的LLM就是这个“整理笔记”的角色,外层存储负责“快速翻页”。
写入路径上,LangMem用LLM做信息提取,把对话内容转成结构化的记忆项。它不会每条都存,而是设定了一个判断机制:只有符合“值得长期记住”标准的内容才会进入存储。提取路径则依赖向量检索,把当前查询转成embedding,在记忆库里做相似度匹配。
2.2 记忆项的存储结构
LangMem里每条记忆不是简单的一句话,而是带元数据的结构化条目。字段大致包括:
| 字段 | 作用 | 说明 |
|---|---|---|
| content | 记忆内容 | 提炼后的自然语言描述,比如“用户偏好使用简体中文回复” |
| embedding | 向量表征 | 用于相似度检索 |
| metadata | 附加信息 | 来源会话ID、创建时间、更新时间、来源类型等 |
| memory_type | 记忆类型 | 语义记忆、程序记忆、情景记忆等 |
| importance | 重要性评分 | 用于后续的合并与淘汰决策 |
我第一次看这个结构觉得有些过度设计,后来才明白这些字段在真实场景里都有用。embedding负责“找得到”,metadata负责“能追溯”,importance评分负责“控制总量”。没有这层结构,记忆库很快就变成一锅粥。
比如你给代理加了记忆之后,某个用户过来说“把我之前那个偏好改一下,以后报告不要发PDF格式了”。这时候你得能定位到旧记忆“用户偏好PDF格式报告”,把它更新掉,而不是让新旧两条冲突的信息同时躺在库里。有了metadata和更新时间,这种更新操作才有抓手。
2.3 自动管理机制:不是所有东西都值得永远保存
LangMem把记忆做成了一套自我管理的循环。它会定期检查记忆库,做去重、合并、更新和淘汰。
- 去重:同一个用户偏好被从多个会话里提炼出来,内容相近的记忆会被合并成一条。
- 合并:半句断开的信息拼接成完整结论。
- 更新:用户明确改变偏好后,旧记忆被覆盖或标记为过期。
- 淘汰:重要性低、长时间未被检索的冷记忆被清理,防止存储和检索成本无限膨胀。
这套机制最关键的一点是“大模型参与决策”。LangMem会使用LLM对记忆做周期性的整理和压缩,而不是纯靠规则和向量距离硬删。LLM判断“这两条能不能合并”比余弦相似度要聪明得多,当然这也是成本所在。后面讲到调优时,我会专门说怎么控制这个成本。
3. 十分钟跑通LangMem:最小可用的记忆代理
3.1 环境准备与依赖逻辑
先说明一下,LangMem在底层接的是LangChain生态的调用抽象,所以不管用哪家大模型API,只要封装成标准的LLM接口,都能接进来。我用的是兼容OpenAI协议的自建模型服务,这样可以绕开多余的配置。
安装很简单:
pip install langmem如果要用向量存储,还需要一个支持embedding的检索后端。本地调试阶段没必要上重型数据库,直接用内存存储或者轻量级向量库就够。先跑通流程,再考虑生产环境的持久化。
我的建议是:第一步不要在“用什么向量库”上纠结。LangMem的核心逻辑在记忆的生成、管理和检索编排上,后端存储是可替换的。用内存版跑通,确认记忆能被写入、能被检索出来,再迁移到正式存储。
3.2 初始化记忆库与记忆管理器
下面是一个最小可运行的配置示例,我用通用占位符代替具体模型实例,你换成自己环境里的模型即可。
from langmem import MemoryManager, SemanticMemory from langchain_core.language_models import BaseChatModel # 假设你已有一个接入好的对话模型实例 llm: BaseChatModel = get_llm() # 换成你自己的实例 manager = MemoryManager( llm=llm, memory_types=[SemanticMemory()], config={ "auto_summarize": True, "max_memories": 500, "retrieval": { "top_k": 5, "score_threshold": 0.35, }, }, )这里SemanticMemory就是语义记忆的类型标记:它管理的是“关于用户和世界的稳定事实”。MemoryManager则承担三条职责:
- 从新的对话记录里提取值得记忆的语义信息;
- 对记忆库做定期去重、合并、更新;
- 根据当前查询返回最相关的历史记忆。
跑通之后,你可以随时打印记忆库里的条目数,确认写入是否真的发生了。我刚开始调试时总怀疑“是不是没写入”,后来发现只是提取频率太低,不是没生效。
3.3 写入记忆:把一次会话沉淀成长期信息
当你有一段新的对话或代理执行日志时,可以这样喂给记忆管理器:
conversation = [ {"role": "user", "content": "以后给我的周报里不要放原始日志,只看汇总数据就好。"}, {"role": "assistant", "content": "明白,我会在周报里优先展示汇总统计,省略原始日志。"}, ] manager.run(conversation)这行调用会触发两件事。第一件,LLM判断这段话里有没有值得长期记忆的信息;第二件,如果有,就提炼成一条结构化记忆并写入存储。在这个例子里,提炼出来的大概是“用户要求周报中展示汇总数据而非原始日志”。
注意,manager.run()默认不是每次调用都会写入大量记忆。它有自己的提取判定逻辑,信息过于琐碎或者与前文重复时,会直接跳过。这是好事,但也会带来一个调试上的难点:你很难判断“没写入”是因为没触发,还是因为信息不值得记。所以我建议你在测试阶段开一个记忆库的日志输出,实际看一眼每次run之后的记忆变化。
另外,manager.run()除了传对话记录,也能传更结构化的数据。比如代理执行过程中工具调用的成功与失败记录,你完全可以组织成一段文本传进去,让LLM提炼成“某个工具的调用注意事项”。这就是前面说的程序性记忆,后面第四个场景会展开讲。
3.4 检索记忆:在代理决策前把记忆拉回来
写入只是第一步。代理真正用上记忆,靠的是检索。最典型的做法就是在构造prompt之前,先查询一次相关记忆,把结果拼进system prompt。
query = "用户对周报格式有什么要求?" relevant_memories = manager.search(query, top_k=5) for m in relevant_memories: print(m.content, m.importance)检索返回的记忆项会带有相关性排序。你可以把它们拼成一段“历史背景”放进系统提示词,代理在决策时就能看到这些约束。
这里有个细节值得多说一句:检索的query质量直接决定召回效果。如果你拿“用户对周报格式有什么要求”这种提问式query去搜,效果通常比拿“周报 格式 原始日志 偏好”这种关键词组合更好。因为embedding训练的时候,语义相近的两个句子会被拉到更近的位置,自然语言的完整问句往往比碎片化关键词更能表达真实意图。这是我多次对比实测后比较确定的经验。
4. 实战:让代理真正“记得你”的三个落地场景
4.1 场景一:跨会话记住用户偏好
最常见的需求就是这个。用户今天说“回复我尽量口语化,别用书面腔”,下一次会话里代理应该自动遵守。
实现的思路是:每次会话结束后,把用户的消息和当前已有的偏好记忆一起丢给manager.run(),让LangMem去判断有没有新的偏好需要写入。下一轮会话开始时,用“该用户的偏好设置”作为query去搜索,把结果注入system prompt。
有一个细节务必注意:用户很可能在之后的某次会话里改变偏好。比如原来要求“口语化”,后来觉得“还是正式一点好”。这时LangMem的更新机制要能识别出来,把旧偏好标记为过期,写入新偏好。我建议在记忆项里保留“更新时间”字段,并在生成system prompt时把“最近的偏好”放在最前面,这样冲突信息同时出现时,代理也会优先遵从最新指令。
做了这个之后,一个直观的改善是:用户不再需要反复重复背景信息。代理面对第二次会话时,开局就能说出“根据您之前提到的偏好,我按口语化风格整理了这份总结”,体感提升非常明显。
4.2 场景二:沉淀工具调用的过程经验
这个场景我觉得是LangMem最具价值的地方,但很多人会忽略。代理在工作过程中会积累大量关于工具使用的经验,比如“调用订单查询接口之前必须先刷新token”“导出报表时如果数据量超过十万,要分页拉取”“某个第三方接口偶尔超时,重试两次基本能成功”。这些经验以前只存在于本次会话的上下文里,下次全忘。
把工具调用的观察结果作为文本喂给记忆管理器,LangMem就能把这些过程经验提炼成程序性记忆。比如每次工具调用后,你可以组织一段日志:
tool_log = ( "工具report_export执行结果: 第一次调用因数据量过大超时," "重试时传入了stream=True参数,成功完成导出。" ) manager.run([{"role": "user", "content": tool_log}])经过一段时间积累,代理就会在记忆中保存一条“导出大数据量报表时使用流式参数”的操作规范。下次面对同类任务,它检索到这条记忆,就会少走弯路。
这个场景的实际收益很难用“体验更好”来形容,它带来的是任务成功率的上升。因为这个机制本质上是在让代理积累“操作手册”,而且是它自己生成、自己使用的手册,比人工预设规则更贴近实际运行环境。
4.3 场景三:动态扩展系统提示词
记忆检索到之后,怎么用也有讲究。我见过不少做法是“把记忆全部塞进对话开头”,这个方案太粗暴。正确做法是把检索到的记忆按照类型和重要度组织成提示词里的“背景约束区”。
假设你的系统提示词原本长这样:
你是一个数据分析助手。你的职责是根据用户请求生成数据分析报告。加入记忆检索后,可以动态拼成:
你是一个数据分析助手。你的职责是根据用户请求生成数据分析报告。 关于用户,请遵循以下已知偏好: 1. 用户偏好使用中文简体回复。 2. 用户的周报仅需汇总数据,不包含原始日志。 3. 用户更倾向看到结论先行,再附详细数据支撑。这样既不干扰对话的即时上下文,又能让代理的关键决策始终受到长期偏好的约束。动态模板里我建议加一个兜底逻辑:如果检索不到相关记忆,就只输出基础系统提示词,不要为了“显得有记忆”而硬编造内容。
这个“硬编造”是个容易踩的隐形坑。LLM在提示词里看到空泛的“用户偏好”字样时,倾向于自行脑补一些内容。所以记忆区必须由检索结果驱动,没有结果就留空,绝不写占位话术。
5. 我踩过的坑与参数调优经验
5.1 记忆写入过于频繁导致Token成本失控
我第一版实现的时候,给记忆管理器设定的触发时机是“每一轮对话结束就run一次”。结果是,一个小时的测试跑下来,记忆库倒是写满了,Token账单也涨得让人心疼。原因很清楚:每次run都是一次LLM调用,用来判断“这段内容是否值得记”,这个判断本身就要消耗Token。对话轮次一多,这个开销就非常可观。
后来我的调整方案是:只在关键节点触发写入,而不是每轮都触发。比如用户明确表达偏好、代理完成一次多步工具任务、会话结束时,这三个节点触发写入;普通的寒暄和过程性对话直接跳过。另外,开启auto_summarize之后,LangMem会周期性地做记忆合并,这个合并操作也是要花钱的,所以合并频率要控制,我通常会设置一个“距离上次合并至少新增20条记忆才触发合并”的阈值,避免频繁做低成本无效操作。
我的经验法则是:记忆写入的成本不能超过它带来的收益。如果一次会话中用户就问了三个问题,没表达任何新偏好,那完全没必要做提炼。等积累了足够的交互密度,再让LLM集中提炼,性价比高得多。
5.2 检索结果太多反而干扰决策
另一个让我头疼的问题是检索数量的选择。一开始我把top_k设成10,想着“多给点上下文总没坏处”,结果代理经常被不相关的旧记忆带偏。比如用户当前问的是“这周销售数据怎么看”,记忆库里却翻出一条三个月前的“用户说过讨厌表格”,代理就莫名其妙地在回答里强调“我不用表格”。
解决方案有两个。第一是把top_k调低,实践中我一般控制在3到5之间。第二是设置相关性阈值,低于阈值的直接丢弃。分数阈值这个参数特别值得花时间测试,不同的embedding模型和不同的记忆内容类型,分数分布差异很大,不要照搬别人的配置。
我记得有一次调试,同一个query在甲embedding模型下记忆相关度高达0.8,换成乙embedding模型直接掉到0.4。后来我挨个试了候选模型,最终选了一个在“中文偏好类记忆”上表现稳定的方案。这个环节没有捷径,只能在你自己的数据分布上逐个测试。
5.3 记忆冲突与陈旧记忆:当用户改变主意
用户改变偏好这件事,处理不好会出大笑话。用户明明说过“以后不要用表格”,后来又发一条“这个对比还是用表格更清楚”,如果两条记忆同时存在,代理就会陷入精神分裂,有时候用表格有时候不用,全看检索时哪条离得近。
LangMem的更新流程能部分解决这个问题,但前提是你传进去的对话要足够明确,让LLM能识别出“这是新的偏好声明,与旧记忆冲突”。我会额外做一层兜底:在检索结果里,把记忆的updated_at字段暴露给代理,让它优先遵从最新记录。如果最新记录与旧记录冲突,以最新为准。
这个方法不完美,但足够实用。至少它保证了“用户最近表达的意愿优先”,不会出现代理还抱着三个月前的旧习惯不放的情况。
5.4 记忆嵌入与检索的工程细节
有几个容易被忽略的小点我集中说一下:
- 检索query不要过长,三五十字以内的自然语言问题效果最佳。太长的query会让向量落到一个模糊的中间位置。
- 写入记忆时,最好保持“一句话一条”的原则。把多个偏好揉进一条长记忆,检索时难以精确命中。
- 记忆库按用户隔离。多用户场景下,这个基础工程不能漏,否则A用户的偏好会被B用户的代理检索到。
- 测试环境用内存存储没问题,生产环境一定要换持久化方案,并做好备份。
第4点尤其重要。我见过一个多租户系统上线后,用户记忆互相串味,代理经常把“其他用户的偏好”当成当前用户的要求,场景非常混乱。后来给所有记忆项加上了user_id字段,检索时强制过滤,才算解决。
6. 记忆能力的边界:什么时候不该依赖LangMem
6.1 记忆不是推理能力
给代理装上记忆之后,它会变得“博闻强识”,但不会因此变得更聪明。检索到的历史经验只是决策时的参考上下文,真正把信息组织成正确行动计划的能力,还是来自底层的模型推理能力。
举个例子。代理记住了“用户喜欢结论先行”,但具体到“这一周销售数据该用哪个指标做结论”时,它依然可能判断失误。记忆负责提供约束背景,推理负责解决问题,两者是不同维度。不要期望加了记忆库之后,模型的逻辑能力也跟着提升。
6.2 隐私与合规:记忆带来了新的责任
这一点聊得少,但实际很关键。记忆系统意味着代理会把用户的个人信息、业务偏好持久化存储。这在带来体验提升的同时,也带来了新的数据合规要求。如果用户要求删除自己的数据,你得有完整的能力把相关记忆项全部清除,而不是在向量库里留下残影。
我在设计记忆方案时加的底线是:用户可随时查看自己被记住了哪些信息,并可一键删除。这个机制看起来增加了一点工作量,但长期来看它既是合规要求,也是产品信任的基石。还有一条:敏感信息(密码、token密钥、身份证号等)在做记忆提取之前就应该被过滤掉,不要给LLM机会把它们写进记忆库。预处理阶段用正则或者基础脱敏规则清一遍,成本很低,收益很大。
6.3 从记忆到个性化:后续可以怎么扩展
LangMem解决的是“记住事实与经验”,但记忆能力的进阶方向还有很多。我自己接下来想尝试的是分级记忆——把记忆分成核心事实、短期偏好、临时状态三个层级,核心事实永不淘汰,短期偏好定期清理,临时状态随会话结束直接消失。
另一个方向是记忆的可视化与审计。给用户展示“代理记住了关于你的哪些信息”,本身就是一种很自然的信任建立方式。如果你在做一个面向真实用户的代理产品,这块值得提前考虑,而不是等到数据问题暴露后再补。
最后还有个思路是把记忆能力从“单代理”扩展到“多代理协作”。团队里不同角色共享一套记忆库,但通过不同的过滤视角使用它,这样避免每个代理都重复学一遍相同的经验。这个方向我目前还在试验中,等有更多数据了再来分享。
我自己做完这一整套下来,最大的感受是:给代理加记忆,难点不在于技术接入,而在于你如何定义“什么值得记住”。LangMem给了框架和工具,但真正让代理变得“聪明且懂事”的,还是你对业务场景里记忆边界的理解。动手试一试吧,第一次跑通“代理记得你是谁”的时刻,你会觉得前面那些坑都值得。