当前位置: 首页 > news >正文

我加了 `InMemorySaver`,为什么 Agent 还是没记忆?

Agent 能调工具之后,我开始做一件看起来很基础但很容易踩坑的事:让 Agent 记住上下文

我按照文档加了checkpointer=InMemorySaver(),满心期待 Agent 能像 ChatGPT 一样记得上一句。结果第二次运行脚本,它又问"这是我们的第一次对话吗?"

我一度怀疑InMemorySaver是个假的记忆组件。后来才搞明白:不是没记忆,是我的用法让它没法记忆。


一、我当时想干什么

我想实现最简单的多轮对话:

  1. 第一轮:我问"旧金山天气怎么样?"
  2. 第二轮:我问"那明天呢?"
  3. Agent 应该能理解"明天"指的是"旧金山明天的天气"

我写的代码:

fromlangchain.agentsimportcreate_agentfromlangchain_core.toolsimporttoolfromlangchain_core.utils.uuidimportuuid7fromlangchain_openaiimportChatOpenAIfromlanggraph.checkpoint.memoryimportInMemorySaver@tooldefsearch(query:str)->str:"""搜索工具"""returnf"搜索结果:{query}"llm=ChatOpenAI(model="deepseek-chat",temperature=0,openai_api_key="你的 api key",openai_api_base="https://api.deepseek.com/v1",)agent=create_agent(model=llm,tools=[search],checkpointer=InMemorySaver(),)config={"configurable":{"thread_id":str(uuid7())}}result=agent.invoke({"messages":[{"role":"user","content":"旧金山天气怎么样?"}]},config=config,)result=agent.invoke({"messages":[{"role":"user","content":"那明天呢?"}]},config=config,)

在这个脚本里,记忆是工作的。但当我再次运行脚本,问"上一轮我问了什么",Agent 说"这是我们的第一次对话"。


二、我写的代码

要让记忆跨脚本运行保持,必须保证thread_id不变,并且使用持久化存储。测试时最简单的办法是固定thread_id

# 不要每次都用新的 uuidconfig={"configurable":{"thread_id":"user-chenzc-001"}}

或者把thread_id存到文件里:

importos THREAD_ID_FILE="thread_id.txt"defget_thread_id():ifos.path.exists(THREAD_ID_FILE):withopen(THREAD_ID_FILE,"r")asf:returnf.read().strip()new_id="user-chenzc-001"withopen(THREAD_ID_FILE,"w")asf:f.write(new_id)returnnew_id config={"configurable":{"thread_id":get_thread_id()}}

真正生产环境应该用 SQLite 或 Postgres:

fromlanggraph.checkpoint.sqliteimportSqliteSaverwithSqliteSaver.from_conn_string("checkpoints.sqlite")ascheckpointer:agent=create_agent(model=llm,tools=[search],checkpointer=checkpointer,)

三、我遇到的坑

坑 1:InMemorySaver只存当前进程

我以为InMemorySaver像 Redis 一样会持久化。实际上它就是内存里的一个字典,脚本结束就清空。

fromlanggraph.checkpoint.memoryimportInMemorySaver checkpointer=InMemorySaver()# 这个对象在进程里存数据# 进程结束,数据消失

如果你要跨运行保持记忆,必须换成SqliteSaverPostgresSaver

坑 2:uuid7()每次运行都变

我的代码里:

config={"configurable":{"thread_id":str(uuid7())}}

每次运行脚本,uuid7()都会生成一个新的thread_id。LangGraph 把每个新thread_id当成一次全新对话,自然没有历史。

这就好比你每次打开微信都用一个新微信号,当然看不到昨天的聊天记录。

坑 3:流式输出也忘了传config

我改成stream_events后,又踩了一次同样的坑:

stream=agent.stream_events({"messages":[{"role":"user","content":"搜索 AI 新闻并总结"}]},version="v3",)

报错:

KeyError: 'thread_id'

因为 Agent 配置了checkpointer,所以stream_events也必须传config

stream=agent.stream_events({"messages":[{"role":"user","content":"搜索 AI 新闻并总结"}]},config=config,version="v3",)

坑 4:一次脚本运行只算一轮对话

我最初的测试方式是:运行脚本 → 输入一个问题 → 得到回答 → 脚本退出 → 再次运行脚本。

这种方式下,即使thread_id固定、用了持久化 checkpointer,第二轮运行时 Agent 看到的"历史"也只有第一轮那一条消息。因为脚本一次运行只发了一次invoke

要测试真正的多轮对话,应该把输入放在while循环里:

config={"configurable":{"thread_id":"user-chenzc-001"}}whileTrue:user_input=input("你:")ifuser_input.lower()in["exit","quit"]:breakresult=agent.invoke({"messages":[{"role":"user","content":user_input}]},config=config,)print(result["messages"][-1].content)

四、搞清楚后的结论

LangChain Agent 的记忆机制有三个层级:

每次 invoke 独立

同一进程内多轮

跨进程持久化

无 checkpointer

无记忆

InMemorySaver + 固定 thread_id

进程内记忆

SqliteSaver/PostgresSaver + 固定 thread_id

长期记忆

关键认知:

  1. checkpointer 负责存thread_id负责定位是哪次对话
  2. InMemorySaver只保当前进程,重启就丢
  3. uuid7()适合一次会话内的临时 ID,跨运行要用固定 ID 或持久化生成
  4. invokestream_events都需要config

五、我的收获

记忆不是默认开启的,它需要你显式地告诉系统"这是谁的对话、存在哪里"。

我以前以为加了checkpointer就万事大吉,结果发现它只是基础设施。真正让记忆生效的是thread_id的一致性和存储介质的持久性。

这件事也让我意识到 LangChain 的设计哲学:它把每个能力都拆成显式配置,而不是隐式魔法。记忆如此,工具如此,Agent 也如此。


下一篇我会写 Middleware——怎么给 Agent 加容错、人工审核和文件操作能力。

http://www.jsqmd.com/news/1142694/

相关文章:

  • Fastjson 1.2.68反序列化漏洞实战:绕过AutoType黑名单的攻防解析
  • Airtest自动化测试第二篇:图像识别脚本编写——核心API详解与实战技巧
  • lsof 命令使用参数
  • 食堂采购系统源码解析:核心功能设计与技术实现
  • 开启 Harness Engineering 探索之旅
  • PW7126+PW80N03*2三串锂电池充放电保护板方案,持续7A,过流保护10A
  • IRISMAN完整指南:PS3自定义固件玩家的终极游戏管理解决方案
  • 2026最新7款AI编程工具平替实测合集
  • [SQL实战] 对账总是对不上?先把主键、时间口径和异常明细拆出来再查
  • (最新安装包)告别繁琐配置!OpenClaw 一键脚本,轻松搞定本地 AI 自动化
  • PW7126+PW4406A*2三串锂电池充放电保护板方案,持续3A,过流保护4A
  • AI不是代替人,而是把人的价值放大
  • 5大核心优势解析:为什么ImageGlass是现代Windows图片浏览的终极解决方案?
  • 2026平价入门蓝牙耳机选购指南:热门机型选购全指南
  • 一次RSS配置错误引发的百万级乱序事故——深入理解DPDK RSS、Toeplitz Hash、Queue Affinity与Flow一致性(下)
  • 2026毕节黄金回收白银回收铂金回收价格高无损耗专业鉴定本地人常去门店联系方式推荐
  • 2026周口黄金回收白银回收铂金回收旧料回收怎么选?五家高实价铂金白银线下门店测评清单 + 联系方式
  • BuildingAI技术解析:开源智能体平台
  • 瘢痕体质总留丑疤?别乱抠,这些坑踩一个悔半年
  • EtherCAT同步机制全解析:从基础模式到DC精准控制
  • 具身智能学习路径:从犹豫到落地的三年能力演进
  • 解锁惠普暗影精灵笔记本性能潜力:OmenSuperHub轻量化控制方案全解析
  • 终极黑苹果配置指南:OpCore Simplify让你30分钟搞定专业级EFI
  • 咳嗽总好不了?别瞎炖梨水!搞反寒热越咳越重
  • python6.5-函数的传参方式
  • 习惯固化+日常强化,崇文高中让优秀成为自然
  • 山东云弈创峰:跨境电商AI智能体的多模态感知与自动化决策架构
  • 清除go-build缓存、关闭占用进程
  • 号总被秒封?个人微信API二次开发如何绕过底层风控与加密探针?
  • Linux 系统管理之云计算概述完整学习笔记(理论 + 私有云实操)