让 Agent 不再失忆:LangChain 短期记忆实战

让 Agent 不再失忆:LangChain 短期记忆实战

在多轮对话里,Agent 默认每轮都是「新开始」------你刚说过名字,下一轮它就忘了。LangChain 的短期记忆解决这个问题:在同一会话内自动带上历史上下文,包括工具调用记录,无需手动拼消息。

本文基于前文 工具调用 Agent(硅基 DeepSeek + @tool),只加三处改动即可开启记忆;并覆盖窗口截断Redis 持久化。配套可运行脚本。


一、一句话理解:短期记忆是什么?

短期记忆 = 同一会话内的多轮对话上下文缓存。

特点 说明
会话隔离 只在同一个 thread_id 内生效,不同 ID 互不干扰
存什么 完整对话状态(用户消息、AI 回复、工具调用与结果)
默认行为 调用前自动读历史,调用后自动存档
持久化 可选:内存重启即丢;Redis / Postgres 重启不丢

注意区分两个层次:概念上 叫「短期记忆」(相对长期记忆 / 用户画像);实现上可以持久化到 Redis,重启后仍能续聊。


二、三个核心概念

1. thread_id(会话 ID)

对话的唯一标识,类似微信里每个聊天窗口。

  • 相同 thread_id → 同一场对话,Agent 有记忆
  • 不同 thread_id → 完全隔离(相当于新开聊天)

易混淆点 :此处的 thread_id 不是操作系统里的「线程 ID」。LangGraph 把每段独立对话的执行链路抽象成一条 thread,因此沿用这个命名。

2. checkpointer(检查点存储器)

Agent 每执行一步,就把当前完整状态 拍快照存下来;下次同 thread_id 调用时从最新快照恢复。

场景 实现 特点
开发调试 InMemorySaver 零依赖,进程退出即丢失
生产上线 RedisSaver / Postgres 持久化,重启可续聊

类比:游戏自动存档 + 读档。没有 checkpointer,Agent 就没有记忆。

3. 上下文自动管理

开启记忆后无需手动维护 messages 列表:

复制代码
用户提问 → checkpointer 读出历史 → 拼上本轮消息 → 调模型/工具 → 写回 checkpoint

三、最简示例:内存记忆(5 行改动)

前置 :沿用项目里的 llm.pytools.py(见 工具调用),无需修改。

相对无记忆 Agent,只需三步:

  1. 创建 checkpointer = InMemorySaver()
  2. create_agent(..., checkpointer=checkpointer) ------ 必须关键字传参
  3. 每次 invoke 传入固定 config
python 复制代码
from llm import llm
from tools import tools
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()

agent = create_agent(
    llm,
    tools,
    checkpointer=checkpointer,
    system_prompt="精准使用工具回答用户问题",
)

config = {"configurable": {"thread_id": "user_001"}}

res1 = agent.invoke({"messages": "我叫小明,帮我算 5+3"}, config)
print("第一轮:", res1["messages"][-1].content)

res2 = agent.invoke({"messages": "我叫什么?刚才的计算结果再乘以2"}, config)
print("第二轮:", res2["messages"][-1].content)

预期效果

  • 第一轮:算出 5+3=8
  • 第二轮:回答「小明」,并调用计算器把 8*2=16 ------ 无需重复自我介绍

多用户隔离 :把 thread_id 换成 "user_002" 即开启全新对话,互不影响。


四、上下文截断:对话太长怎么办?

对话累积后会超出模型上下文窗口。入门阶段用窗口截断即可:发给模型前只保留最近 N 轮用户提问(及对应的 AI / 工具消息)。

关键机制(易踩坑)

组件 作用
checkpointer 完整历史,不受截断影响
trim_window 中间件 只限制发给模型的消息长度

因此:截断后 Agent 可能「想不起来」很早以前的信息------不是没存,是模型本轮看不到。

python 复制代码
KEEP_ROUNDS = 3  # 保留最近 3 轮 user 提问(含当前这轮)

@wrap_model_call
def trim_window(request, handler):
    messages = request.messages
    human_idxs = [i for i, m in enumerate(messages) if m.type == "human"]
    if len(human_idxs) > KEEP_ROUNDS:
        start = human_idxs[-KEEP_ROUNDS]
        messages = messages[start:]
    return handler(request.override(messages=messages))

agent = create_agent(
    llm, tools,
    checkpointer=checkpointer,
    middleware=[trim_window],
    system_prompt="精准使用工具回答用户问题;记住用户提到的姓名、喜好等信息",
)

验证截断效果KEEP_ROUNDS = 3):

轮次 用户说 模型能否记住
1 我叫小明,职业是数学老师 ✓(在窗口内)
2 我喜欢打乒乓球
3 我喜欢钓鱼
4 我喜欢什么?职业是什么? 爱好 ✓;职业 ✗(第 1 轮已被截出窗口)

进阶方案(消息摘要、智能清理)见 官方文档


五、持久化:Redis Stack

本地调试用 InMemorySaver 足够;上线必须换持久化存储,否则重启后记忆全部丢失。

RedisSaver 依赖 RediSearch 模块,macOS 上 brew install redis 的普通版不支持,需用 Redis Stack。

1. 安装依赖

shell 复制代码
pip install langgraph-checkpoint-redis

2. 启动 Redis Stack(Docker)

shell 复制代码
docker pull redis/redis-stack-server:latest
docker run -d --name redis-stack -p 6379:6379 redis/redis-stack-server:latest

# 验证:必须能看到 search 模块
redis-cli MODULE LIST
redis-cli ping   # PONG

.env 可选(默认 redis://localhost:6379):

shell 复制代码
REDIS_URI=redis://localhost:6379

常用管理

shell 复制代码
docker ps
docker stop redis-stack && docker start redis-stack
docker rm -f redis-stack   # 删除容器(数据一并清空)

3. Python 代码

只需把 InMemorySaver 换成 RedisSaverthread_id 与测试逻辑不变:

python 复制代码
import os
from llm import llm
from tools import tools
from langchain.agents import create_agent
from langgraph.checkpoint.redis import RedisSaver

REDIS_URI = os.getenv("REDIS_URI", "redis://localhost:6379")

with RedisSaver.from_conn_string(REDIS_URI) as checkpointer:
    checkpointer.setup()  # 首次初始化 RediSearch 索引,重复调用安全

    agent = create_agent(llm, tools, checkpointer=checkpointer, ...)
    config = {"configurable": {"thread_id": "user_001"}}

    res1 = agent.invoke({"messages": "我叫小明,帮我算 5+3"}, config)
    res2 = agent.invoke({"messages": "我叫什么?刚才的计算结果再乘以2"}, config)

4. 验证持久化

  1. 第一次运行:完成上述两轮对话
  2. 关闭 Python 进程,再次运行
  3. 注释掉第一轮,只执行:agent.invoke({"messages": "我叫什么?"}, config)
  4. 仍应回答「小明」

5. 查看 Redis 中的数据

shell 复制代码
redis-cli KEYS 'checkpoint*user_001*'
redis-cli GET checkpoint_latest:user_001:__empty__
redis-cli JSON.GET "checkpoint:user_001:__empty__:xxxx"   # 替换为实际 key

若使用带 UI 的镜像 redis/redis-stack(端口 8001),可在 http://localhost:8001 的 Browse 中搜索 user_001

6. 常见报错

报错 原因 处理
unknown command 'FT._LIST' 连的是普通 Redis,非 Redis Stack 停掉 brew redis,改用 Docker 镜像
Connection refused Redis 未启动 docker start redis-stack,检查 6379 端口
Docker 拉镜像超时 网络问题 配置镜像加速

六、避坑清单

  1. 没配 checkpointer = 没记忆 ------ 这是唯一开关,漏写即每轮失忆
  2. 记忆跟 thread_id ------ 续聊必须用同一个 ID;生产环境用用户 ID / 会话 ID 生成
  3. InMemorySaver 不能上线 ------ 仅调试用,生产用 Redis Stack 或 Postgres
  4. Redis 必须是 Stack 版 ------ 普通 Redis 缺 RediSearch,setup() 会报 FT._LIST
  5. 截断 ≠ 删除 ------ checkpoint 仍存全量历史,只是模型本轮看不到被截掉的部分

相关推荐
吴佳浩1 小时前
Hermes vs OpenClaw:基于源码的 Agent Loop 全面分析
人工智能·llm·agent
江夏尧2 小时前
Peri Code 的工具分层——LLM 面对 50 个工具时会停止调用工具
agent
装不满的克莱因瓶3 小时前
了解 LangChain 中的 LLM 与 ChatModel 的差异
人工智能·python·ai·langchain·llm·agent·chatmodel
颜酱4 小时前
LangChain 工具调用:从原理、入门到落地
langchain·llm
黑马师兄4 小时前
RAG混合检索深度解析:让AI真正找到你要的内容
java·人工智能·ai·agent·rag·ai-native
阿里云云原生4 小时前
Agentic AICon【智能体基础设施与 AgentOps 专场】精彩回顾 & PPT 下载
agent
swipe4 小时前
别再把关系库和向量库拆开了:PostgreSQL 搭建 AI 长期记忆层实战
面试·langchain·llm
货拉拉技术5 小时前
面向 Agent Skill 的 CLI/SSO 鉴权体系:安全、无感、可追溯
前端·agent
小马哥讲AI5 小时前
给 Agent 选记忆引擎:我为什么选了 Hindsight
agent