☰
给AI Agent装上长期记忆:agent-memory核心机制与接入实践
2026/9/26 7:25:20 网站建设 项目流程

你有没有遇到过这种情况:早上跟 AI 助手聊了一上午的方案细节,下午重新开个会话,它一脸茫然地问你“项目背景是什么来着?”——所有上下文清零,像金鱼一样只有七秒记忆。这个问题在 agent 类应用里被无限放大,因为 agent 不是一个只会“你说我听”的聊天框,它要自主拆解任务、调用工具、跟外部系统交互,任何一个中间步骤偏离,最后的结果都可能崩掉。

所以当我在 GitHub 上刷到agent-memory这个开源项目时,第一反应是:这正是 agent 落地最缺的那块拼图。它做的事情很聚焦——给 AI 一个持久化、可检索、带时间衰减的“长期记忆”,让 agent 跨会话、跨任务地记住用户偏好、历史结论和中间决策。项目不大,但设计相当聪明,我把它接进自己的 agent 工作流里跑了两周,今天把这套记忆系统的核心机制、完整接入过程和踩过的坑都整理出来。不管你是在搭个人知识助手、客服机器人,还是搞自动化流程 agent,这篇文章的思路应该都能直接用得上。

1. 为什么 agent 必须有长期记忆——从一次失败的调试说起

1.1 上下文窗口的天花板不是唯一瓶颈

很多人的第一反应是:要长期记忆,把上下文窗口开大不就行了?比如有些模型支持 128K 甚至 1M token,理论上几乎等于无限。但实际跑过 agent 项目的都知道,这条路根本走不通。

我自己的体感是三个层面的问题。第一是成本,每轮请求把历史对话全部塞进去,token 消耗成倍增长,跑几十轮任务下来,费用先撑不住。第二是精度,上下文越长,模型对关键信息的“注意力”就越分散,出现过中间某个没用的日志把核心结论“挤出”attention 范围的情况,表现就是答非所问。第三是时效,用户明明上周已经修改了偏好设置,但旧会话里的原始记录还躺在上下文里,模型反而被历史信息干扰。

所以你真正需要的不是“塞更多”,而是“筛更准”。长期记忆的实质,是把已经发生过的事实、偏好和结论,从原始对话里提炼出来,以结构化的方式存起来,需要时只把最相关的一小部分放回上下文。

1.2 agent 记忆缺失的连锁反应

没有长期记忆的 agent,最典型的表现是“行为漂移”。我举一个自己踩过的真实例子:我搭过一个自动化邮件分类 agent,第一天跑得很稳,能识别“发票”“合同”“催款”三类邮件。第三天用户回来说“这个分类逻辑不对,催款和合同要合并处理”,我改完 prompt 之后没重建会话,结果 agent 在处理下一条邮件时,又按照旧的分类标准给分出去了。

这就是没有记忆的后果——不是模型不聪明,而是它的行为基础每次都在变。prompt 里没有写进去的规则,对 agent 来说就是不存在的。类似的问题还包括:用户明明说过“报告要用中文,表格用英文”,换了个会话就忘了;排查过的问题,下次遇到同样报错又要从头开始定位。这些都是真实的生产力损耗。

1.3 agent-memory 到底做了哪几件事

agent-memory这个项目,如果你去读它的源码和文档,核心其实就是三件事:

  • 分层记忆:把记忆分成短期(session 内)、长期(跨会话)、永久(不可衰减)三个层级,各有各的存取规则。
  • 语义检索:记忆不是靠关键词匹配,而是把每条记忆向量化,用用户当前的问题做相似度检索,只召回真正相关的片段。
  • 时间衰减:记忆有“新鲜度”概念,太久没有被命中的信息权重会下降,让 agent 更关注近期的约定和事实,而不是被陈旧信息带偏。

后面我会把这每一条展开讲。但先记住这个结论:记忆系统的核心不是“存”,而是“取”——怎么在合适的时间点,把合适的历史信息以合适的方式放回模型面前。这才是 agent-memory 设计的真正着力点。

2. 核心设计拆解:三层记忆模型与检索机制

2.1 短期、长期、永久——为什么要分层,而不是一个库存到底

我在读项目源码时,最开始的疑问是:为什么要把记忆拆成三种类型?统一存一个地方不就行了?实际跑完才明白,分层解决的是“生命周期管理”和“检索优先级”两个问题。

项目里对三种记忆的定义大致是这样:

记忆类型存储维度生命周期典型内容
短期记忆当前会话会话结束自动归档或清理刚聊到的临时信息、中间步骤状态
长期记忆跨会话按衰减策略逐步弱化,除非被重复命中用户偏好、已完成任务结论、常用工具配置
永久记忆跨会话永久保留,不参与衰减用户身份信息、全局不可变规则、敏感注意项

这个分层的设计哲学,本质上是在模拟人的记忆机制。我们不会记得昨天中午吃了什么(短期),但会记得某个朋友不吃香菜(长期),更不会忘记自己的身份证号(永久)。如果所有信息都永久保留,检索时噪音就大到没法用;如果所有信息都短期化,跨会话能力就无从谈起。

具体实现上,项目为每条记忆都打了memory_type标签,并且在向量化的同时也保留了结构化字段(比如scope、namespace、created_at、last_accessed_at),这样既能做语义检索,也能按条件做精确过滤。我自己的实践建议是:默认情况下只改长期和永久的配置,短期记忆让框架自己管理,不要手动干预太多,否则会有大量重复记忆写入。

2.2 记忆写入:会话结束后自动提炼,而不是盲目全存

如果每次对话直接把 raw text 全量存进记忆库,那这个系统很快就变成垃圾场。项目里做了一层“写入提炼”的逻辑,我把它拆成了三步:

  1. 触发时机:不是每一轮对话都触发写入,而是等会话结束、或者达到某个边界条件(比如任务完成)后,由系统统一处理。
  2. 信息提取:用一次独立的 LLM 调用,从当前会话中提取“值得长期保存”的信息片段。通常包括:用户明确表达过的偏好、事实性的结论、达成的决策、未完成的事项。
  3. 去重与合并:新记忆写入前,先在现有记忆库里做一次相似度检索,如果跟已存在的记忆高度相似,就更新旧记录而不是新增一条。

这一步非常关键。我见过很多自建的记忆方案,最后死就死在“记忆膨胀”——明明只聊了三次,库里却躺着四百条近乎重复的记录。检索的时候 top-k 里全是同一个意思的不同说法,真正的关键信息反而排不上号。

关于写入提炼的细节,项目里默认用的是一条结构化 prompt,比如“从对话中找出用户可能在未来会话中需要的事实、偏好、约束条件,输出 JSON 列表,包含 content、memory_type、importance 三个字段”。我自己用下来,感觉还可以加一个keywords字段,用来做关键词辅助过滤,配合向量检索,精确度会更高。

2.3 记忆读取:相似度阈值、top-k 与上下文注入

有了库里的记忆,怎么让它在合适的时候被“想起来”,这才是记忆系统的灵魂。项目里的读取流程是:

query → embedding 向量化 → 向量检索(top-k + 相似度阈值) → 重排过滤 → 注入 prompt

top_k决定了每次最多捞多少条记忆出来,min_similarity则是“不够相关宁可不给”的一道闸门。这两个参数我实测下来非常敏感,后面第 4 部分会专门讲怎么调。

注入 prompt 的位置和格式也值得关注。项目默认是把命中记忆放在 system prompt 里,作为“已知背景信息”传给模型,下面是我改过的一版提示模板:

你是一个带有长期记忆能力的 AI 助手。 以下是关于当前用户/任务的已知历史信息,请结合这些信息来回答: [记忆片段] - 【至关重要】用户要求:所有财务类报表必须用英文呈现 - 【长期】用户项目:某智慧农业 SaaS 平台的异常报警模块 - 【长期】上一轮结论:MySQL 慢查询优化方案已确认采用读写分离 [当前用户请求] ...

这样做的原因是,把历史记忆跟当前问题放在同一个上下文层级里,模型更容易把它当作“背景前提”而非“新的用户指令”,减少指令冲突的概率。我试过放到 user 消息里,模型有时会认为这是新指令,反而产生修正行为,效果没有 system 注入稳定。

2.4 存储与向量化的选型方案

存储这块,项目设计成了可插拔结构。默认实现是用 SQLite 加向量检索扩展(比如sqlite-vec),好处是零外部依赖,一个文件全搞定,特别适合边缘设备和本地部署。如果量级大了,也可以把向量库替换成 Chroma、pgvector 或 Qdrant,项目抽象了统一的MemoryBackend接口,切换成本不高。

关于 embedding 模型的选择,我个人的建议是:

  • 本地跑、隐私敏感:bge-m3或bge-large-zh,中文效果不错,且完全离线。
  • 云端、追求效果:text-embedding-3-small,性价比高,维度也适中。
  • 多语言场景:multilingual-e5-large,对中英混排支持较好。

要提醒的是,嵌入模型的选择直接影响检索质量,而且一旦上线换了嵌入模型,历史记忆的向量全部要重新生成,这不是一秒钟能搞定的事。所以选型阶段多花点时间测试中文场景下的召回率,比后面痛苦的 migration 划算得多。

3. 实操:把 agent-memory 接入你自己的 agent 流程

3.1 环境准备与安装

项目本身是 Python 库,pip 直接装就能用。我这里按最常见的方式来:

pip install agent-memory # 如果使用 sqlite-vec 后端,需要额外安装依赖 pip install sqlite-vec

初始化记忆库:

from agent_memory import AgentMemory, MemoryConfig config = MemoryConfig( backend="sqlite+vec", db_path="./agent_memory.db", embedder="bge-m3", # 本地嵌入模型,也可以传 OpenAI 的 embedding 接入 decay_days=30, # 长期记忆的衰减周期 permanent_weight=1.0, # 永久记忆的权重 ) memory = AgentMemory(config) # 写入一条长期记忆 memory.remember( content="用户偏好:所有回复使用中文,技术术语保留英文原词", memory_type="long_term", namespace="user_zhang", importance=0.9, ) # 写入一条永久记忆 memory.remember( content="用户身份:某跨境电商公司的数据分析负责人", memory_type="permanent", namespace="user_zhang", )

这里需要注意namespace字段。如果你的 agent 服务多个用户,务必用 namespace 做隔离,否则用户 A 的偏好会被检索到用户 B 的上下文里,这会是非常严重的隐私事故。我团队在一开始就没注意,结果两个测试账号互相串了记忆,排查了半天才发现是 namespace 当成可选项漏了传。

3.2 检索与注入:最小可用的记忆增强 agent

接下来把记忆接到 agent 的主循环里。最朴素的做法,就是每次收到用户消息时,先去记忆库召回相关内容,然后把它拼进 system prompt。

def build_prompt_with_memory(user_message: str, namespace: str) -> list[dict]: # 1. 召回相关记忆 hits = memory.recall( query=user_message, top_k=5, min_similarity=0.45, namespace=namespace, ) # 2. 将命中记忆格式化为文本片段 memory_text = "\n".join( f"- 【{h.memory_type}】{h.content} (相关性: {h.similarity:.2f})" for h in hits ) # 3. 构建消息列表 return [ { "role": "system", "content": f"你是有长期记忆的 AI 助手。\n历史相关信息:\n{memory_text or '(暂无相关历史记忆)'}", }, {"role": "user", "content": user_message}, ]

这段代码看着简单,但已经把记忆系统的核心链路跑通了:用户说什么 → 语义检索 → 历史信息注入 → 模型生成。我实际测试时最直观的感受是,同样一个问题“帮我改一下上次那个报表模板”,有记忆的 agent 能直接调出之前的模板结构,而没有记忆的 agent 会反问“哪个报表?”。

3.3 更进一步:对接函数调用(tool calling)

如果只是聊天助手,上面的方案够用了。但真正的 agent 应用必然涉及工具调用,而记忆在 tool calling 流程里有另外一层价值:跨任务复用工具执行经验。

举个例子,我的 agent 会调用一个内部 API 查询库存。第一次调用时它不知道要传wh_id这个参数,报错了;我让系统把这次错误经历提炼成一条长期记忆“查询库存接口时,需要额外传入仓库ID,否则返回 400”。下一次再触发这个工具时,这条记忆被召回并注入上下文,agent 就自动知道要先获取仓库 ID。

实现思路是:在 tool calling 循环中,把每次工具调用的入参、报错、重试结果作为一个“事件”缓存到短期记忆,等整个任务结束后,异步提炼出可复用的经验写入长期记忆。这个模式甚至可以扩展到更复杂的工作流,比如多步任务执行后总结经验,agent 越来越像一个熟悉业务的老员工,而不是每次都从零开始试错。

这块项目的官方文档里给了一个比较完整的 workflow 示例,核心代码大致是这样的:

# agent 完成一轮工具调用后,把执行结果写入记忆 for step in agent_execution_trace: if step.success and step.outcome: memory.remember( content=f"工具 {step.tool_name} 的成功调用经验:{step.outcome}", memory_type="long_term", namespace=namespace, tags=[step.tool_name], ) elif step.failed: memory.remember( content=f"工具 {step.tool_name} 的失败教训:{step.error_hint},解决方式:{step.resolution}", memory_type="long_term", namespace=namespace, tags=[step.tool_name], )

这里又是一条关键经验:失败教训的记忆价值往往高于成功经验。因为成功路径可能有很多种,但失败原因通常是少数几个模式。让记忆库优先收藏失败教训,agent 的稳定性提升会非常明显。

4. 避坑指南:长期记忆项目里最常踩的 6 个坑

4.1 相似度阈值设不对,召回结果就没法看

min_similarity这个参数,我一开始设了 0.3,结果检索出来一堆“看起来相关但其实完全无关”的记忆。比如用户问“服务器部署进度”,它把“数据库备份策略”捞了上来,就因为都有“服务器”三个字。后来调到 0.6,又发现经常啥都召不回。

我的最终方案是:默认 0.5 作为底限,按场景动态调整。如果是开放域问答,用 0.45 左右;如果是对精确事实的查询(比如“用户的全名叫什么”),提到 0.65 以上,宁缺毋滥。还有一个技巧:把召回结果按相似度分档,>0.7 的标为“高度相关”,0.5~0.7 的标为“参考信息”,注入 prompt 时对不同档位用不同措辞,可以有效减少模型的误判。

4.2 记忆膨胀:不设上限,三个月后查询必然变慢

我在跑第二周的时候,库里就堆了两万多条记忆。原因很简单:agent 每处理一个任务都会产生多条短期记忆,即便做了提炼,长期记忆的增长仍然比预期快得多。

解决思路有三个:

  1. 数量上限:为每个 namespace 设置记忆条数上限,超过后按“最不常用 + 最不相关”淘汰。
  2. 定期压缩:跑一个离线任务,把相似的记忆合并成概括性的一条,比如把 20 条关于“用户喜欢用 Python 写数据处理”的碎记忆整合成一条。
  3. 生命值机制:每条记忆有一个 freshness 分数,被命中的时候加一点,长期没命中就减一点,减到阈值以下自动清理。这就是项目里“时间衰减”的侧面效果。

我自己是三个一起用,实测下来长期记忆库稳定在五千条以内,检索耗时也能保持在 50ms 以下。

4.3 多用户隔离没做好,就是一场隐私事故

前面提到了 namespace,这里单独拎出来强调。如果你的 agent 是 SaaS 形态,记忆库是所有用户共享的物理存储,那么在每一个remember和recall调用里都必须显式传 namespace。不要依赖默认值,也千万别把用户 A 的召回结果缓存到用户 B 的请求上。

另外,vector 检索本身并不理解“这是谁的数据”,它只看向量相似度。所以过滤条件必须放在检索 SQL 层面,而不是检索出来之后再过滤。否则即使最后一步你 filter 了输出,中间结果可能已经被无关数据污染了,而且效率也差一大截。

项目里recall的namespace参数就是在查询层做过滤的,千万要养成“每次调用必传”的习惯。

4.4 时间衰减参数:衰减太快,agent 变回金鱼;太慢,记忆变成一潭死水

decay_days默认是 30 天。这个数字对大多数场景是合理的,但具体业务差异很大。我的建议是拆开看:

  • 对短期性的任务结论(比如“本周迁移 ETL 流程”),14 天内没被再命中就可以降权。
  • 对用户偏好(比如“报告用中文”),它应该长期稳定存在,衰减周期可以拉到 90 天甚至更久。
  • 对项目背景类的信息,每次被命中都应该“续命”,否则一个活跃项目三个月后就对 agent 失忆了。

我还自己加了一个小改动:把last_accessed_at记下来,当一次会话中有多条记忆命中时,把“记忆续期”写回的操作做一次异步批量更新,而不是同步写,免得每次推理都要多等几十毫秒。

4.5 陈旧记忆与冲突记忆:agent 说“我记得你以前不喜欢这个”

这是整个测试过程中最让我挠头的问题。用户的偏好是会改变的,但记忆库里的旧记录不会自动消失,于是出现了“我记得你上次说不用 Docker”这种尴尬场景。

我的处理方案是“版本演进”:当 agent 发现用户当前指令与召回记忆存在明确冲突时,触发一个“记忆修正流程”——生成一条新的记忆,并把旧记忆标记为superseded_by_new_id,从常规检索里排除。这一步很像代码里的“软删除”,既保留了历史审计痕迹,又不会污染后续检索。

实现上也不复杂,检索时过滤status != 'superseded'即可。但一定要有主动检测机制,不然用户不说“我改主意了”,agent 是没法自己发现的。我目前是在 tool calling 决策层加了一个规则:当用户的显式指令与记忆冲突时,优先听从用户当前指令,并异步触发记忆更新。

4.6 记忆系统的可观测性:黑盒记忆会让你 debug 到怀疑人生

如果说前面五个坑是功能层面的,这个坑是工程层面的。最初我把记忆系统接进去时,完全靠模型输出判断“它有没有记住”,但模型自己也不知道自己调用了什么记忆,经常出现“它答对了但其实是蒙的”这种假阳性。

后来我做了两件事。第一,给recall增加日志,记录每一次召回的 query、top-k 候选、命中记忆 ID、score,在开发模式下输出到本地文件;第二,写了一个简单的可视化面板(用 Gradio 套了个壳),按 namespace 浏览当前记忆库里的所有条目,直接看到每条记忆的memory_type、last_accessed_at、score分布。

有了这两个设施,排查问题就从“猜模型为什么这么回答”变成了“看记忆库里到底有什么”。建议任何往生产环境上线的团队,都不要跳过这一步。

5. 从长期记忆到真正的“智能体记忆系统”——下一步还能怎么玩

5.1 记忆的压缩与抽象:从“流水账”到“心智模型”

目前的 agent-memory 方案,本质上还是“事实型记忆”——存的是用户说过的话、做过的决策。但人的记忆远不止如此,我们会长出对一个人的“整体印象”,比如“这个用户喜欢快速验证想法,不喜欢长篇大论”。

这种抽象型记忆无法靠单条对话记录获得,需要对历史记忆做周期性的再提炼。我目前的玩法是:每周跑一次离线任务,把用户过去 100 条长期记忆丢给 LLM,让它生成一条 200 字左右的“用户画像快照”,存成一条高权重的永久记忆。这样 agent 面对新问题时,即使没有精确的历史事实,也能基于画像做合理默认。

5.2 记忆回放与反思:让 agent 自己“复盘”

另一个方向是让 agent 定期复盘自己的记忆。比如每天的 idle 时段,让 agent 回顾当天新增的记忆,主动发现问题——是不是某类任务反复失败?是不是用户的某项偏好已经发生偏移?然后自动调整记忆的权重和标签。

这种“元认知”机制听着科幻,但实现起来其实就是再加一层定时触发的 agent 任务。我试过一版,效果最明显的场景是:当同一个工具连续报错三次时,反思机制会主动把这个工具标记为“不稳定”,后续任务优先提示人工介入,而不是让 agent 继续拿头撞墙。

5.3 多模态与跨 agent 记忆

目前项目默认存的是纯文本记忆,但实际的 agent 使用场景里,用户可能上传过图片、表格、语音。把这些多模态信息统一向量化后存入同一个记忆库,是延长记忆能力的直接扩展方向。另外,如果你有多个 agent 在并行工作,还可以考虑让它们在共享的“团队记忆”上协作——一个 agent 发现的最优路径,另一个 agent 可以直接复用。

这些玩法目前还是偏实验性质,但底层基础设施都是相通的。先把单 agent 的长期记忆跑稳,再逐步往上叠,是更务实的路径。

5.4 我自己下一步准备怎么做

我已经比较完整地跑通了基于 agent-memory 的记忆增强 agent 流程,接下来准备做三件事:一是把记忆的读写接口封装成内部服务,供多个业务 agent 共用;二是加入前面说的“记忆版本演进”和“周期画像提炼”两个机制,把效果数据再跑一轮;三是把最终的效果评测指标固定下来——比如“跨会话任务成功率”“首次尝试成功率”“用户重复输入率”,用数据说话,而不是靠感觉。

写在最后

如果你也在折腾 agent 开发,我真心建议尽早把长期记忆纳入架构设计,而不是等用户量起来了再补。我个人的体感是:有没有长期记忆,差不多的模型底座,跑出来的体验完全是两个级别的产品。agent-memory这个项目作为起点很合适,代码不复杂,架构清爽,改造成本低。

最后分享一个小技巧:给记忆库的每个条目都打上来源会话ID和创建时间,这样一旦发现某条记忆的内容有误,可以直接追踪到是哪次会话产生的,对应处理起来快得多。我因为在测试阶段跳过这一步,后面回查数据来源时多花了大半天,这口苦水就不多说了。祝你们跑得比我顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询