LangChain 实战:玩转短期记忆

0 阅读6分钟

大模型天生就是个“金鱼脑”,聊完就忘。

上下文工程,就是让它在干当前这事的时候,能把前面聊的、工具查到的东西“摊在桌面上”。不然它干到第三步就忘了第一步说了啥,直接瞎指挥。

记忆,就是让它下次还能认出你——你上周跟它说“我过敏不吃香菜”,这周它还能想起来。

一句话总结:上下文工程管“眼前这摊事别断片”,记忆管“别每次都像第一次见面”。

一、概述

1.1 什么是短期记忆?

在 AI Agent 的世界里,**记忆(Memory)**是一套用于留存历史交互信息的机制。

对于 Agent 而言,记忆至关重要:它使 Agent 能够记住过往对话、从反馈中学习、适配用户偏好

短期记忆:让应用在单条会话线程内记住历史交互。

1.2 langchain 三类上下文

官方文档划分:静态运行上下文、动态运行上下文(短期记忆)、跨会话动态上下文(长期记忆)

img

1.3 使用方法

要为 Agent 开启短期记忆(线程级持久化),创建 Agent 的时候必须指定检查点器 (checkpointer)

LangChain Agent 将短期记忆作为 Agent 状态的一部分进行管理。

把对话上下文保存在图状态中,Agent 既可以拿到单条对话的完整上下文,又可以做到不同线程之间数据相互隔离

状态通过 checkpointer 持久化到数据库(或内存),因此任意时刻都可以恢复这条线程继续执行。

Agent 被调用、或是完成一步(例如工具调用)时,短期记忆就会更新;每一步执行开始时都会读取状态

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver  


def get_user_info() -> str:
    """查看用户信息"""
    return "现在还没有用户"


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[get_user_info],
    checkpointer=InMemorySaver(),
)

1.4 存储

不管是长期记忆还是短期记忆,都有两种存储记忆的方式:

  • 基于内存
  • 基于外部存储

二、使用方式

2.1 通过内存

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver


def get_user_info() -> str:
    """查看用户信息."""
    return "空空如也."


agent = create_agent(
    model=model,
    tools=[get_user_info],
    checkpointer=InMemorySaver(),
)

thread_config = {"configurable": {"thread_id": "1"}}
response = agent.invoke(
    {"messages": [{"role": "user", "content": "哈喽,我是张三."}]},
    thread_config,
)["messages"][-1].content

rprint(response)

response = agent.invoke(
    {"messages": [{"role": "user", "content": "我是谁?"}]},
    thread_config,
)["messages"][-1].content

rprint(response)

img

2.2 使用pgsql

在生产环境中,使用由数据库支持的checkpointer

安装pgsql包

pip install -U langgraph-checkpoint-postgres "psycopg[binary]"

默认情况下,langgraph-checkpoint-postgres 会安装 psycopg(Psycopg 3),但不包含额外组件。

上述安装方式会添加 psycopg[binary],这是大多数用户的推荐选择。

from langchain.agents import create_agent
from langgraph.checkpoint.postgres import PostgresSaver

def get_user_info() -> str:
    """查看用户信息."""
    return "空空如也."


DB_URI = "postgresql://admin:Pw%40123456@133.133.133.133:5432/mydatabase?sslmode=disable"

with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    checkpointer.setup() # 会在pgsql中自动创建表
    agent = create_agent(
        model=model,
        tools=[get_user_info],
        checkpointer=checkpointer,
    )

看一下,自动创建的表结构

img

这四张表都是 setup() 函数初始化时创建的。

  • checkpoints :这是主表,存每个 thread 在某个时刻的 checkpoint 快照。
  • checkpoint_blobs :这张表专门存不适合直接内联进 checkpoints.checkpoint 的较复杂 channel 值。
  • checkpoint_writes :这张表存的是中间写入 / pending writes,不是最终完整 checkpoint。
  • checkpoint_migrations :这张表不是业务数据表,而是迁移版本表。

2.3 自定义 AgentState

默认情况下,agent 使用 AgentState 来管理短期内存

可以扩展 AgentState 以添加额外的字段。自定义状态 schema 通过 state_schema 参数传递给 create_agent。

class CustomAgentState(AgentState):
    user_id: str
    preferences: dict


agent = create_agent(
    model = model,
    tools = [get_user_info],
    state_schema = CustomAgentState,
    checkpointer = InMemorySaver(),
)

thread_config = {"configurable": {"thread_id": "1"}}
response = agent.invoke(
    {
     "messages": [{"role": "user", "content": "哈喽,我是张三."}],
     "user_id": "user_123",
     "preferences": {"theme": "dark"}
     },
    thread_config,
)["messages"][-1].content

rprint(response)


state = agent.get_state(thread_config)
for key, value in state.values.items():
    print(f"{key}: {value}")

img

三、常用模式

3.1 裁剪消息

绝大多数大模型都存在最大支持上下文窗口(以 token 为计量单位)

一种消息截断策略:统计消息历史的 token 数量,当总量接近上限时执行截断

调用修剪消息工具函数,指定需要保留的 token 数量,同时配置边界处理策略(例如:保留最后的 max_tokens 条 token)

想要在 Agent 内部对消息历史做修剪,可以使用 before_model 中间件装饰器。

@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """只保留最近5条消息到上下文中"""
    messages = state["messages"]

    # 消息<=3什么也不干
    if len(messages) <= 3:
        return None

    first_msg = messages[0]
    recent_messages = messages[-3:] if len(messages) % 2 == 0 else messages[-4:]
    new_messages = [first_msg] + recent_messages

    return {
        "messages": [
            RemoveMessage(id=REMOVE_ALL_MESSAGES),
            # 解包后放入messages里
            *new_messages
        ]
    }

agent = create_agent(
    model=model,
    middleware=[trim_messages],
    checkpointer=InMemorySaver(),
)

config: RunnableConfig = {"configurable": {"thread_id": "1"}}

agent.invoke({"messages": "哈喽,我是 bob"}, config)
agent.invoke({"messages": "你家是不是有个加菲猫"}, config)
agent.invoke({"messages": "不是,我家现在是一个 dog 吴"}, config)
final_response = agent.invoke({"messages": "我叫什么?"}, config)

for e in final_response["messages"]:
    e.pretty_print()

打印结果如下:

img

提示:@before_model

3.2 截断 (truncate) vs 摘要 (summarize)

区别

  1. truncate /trim(修剪截断):直接删除旧消息,原始内容丢失;速度快,无额外模型调用开销。
  2. summarization(摘要):调用模型把旧消息浓缩成一段摘要文本,保留信息;会消耗额外 token,有调用成本。

before_model 是通用钩子:除了 trim 修剪,你也可以在这里自己实现摘要、过滤、增删消息。而 SummarizationMiddleware 是封装好的成品中间件;before_model 是底层自定义扩展点。

执行时机

before_model组装完消息,还没发给大模型那一刻执行。 只修改即将送入模型的消息,不会自动改写持久化在 checkpointer 里的 state 历史消息

3.3 删除消息

从图状态(graph state)中删除消息,以此管理消息历史。适合场景:移除指定消息,或者清空全部对话历史。

在图状态里删除消息,使用 RemoveMessage 对象。想要 RemoveMessage 生效,状态字段必须使用 add_messages 归约器(reducer)

LangGraph 默认的 AgentState 已经内置该归约器,开箱可用

  • 删除所有消息
from langgraph.graph.message import REMOVE_ALL_MESSAGES  

def delete_messages(state):
    return {"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]}
  • 删除指定的消息
from langchain.messages import RemoveMessage  

def delete_messages(state):
    messages = state["messages"]
    if len(messages) > 2:
        # 删除开头的两条数据
        return {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]}

输出详情:

img

Agent 删除消息处理时序图

img

3.4 摘要

消息截断或删除消息存在一个问题:清理消息队列时会直接丢失信息

如此,部分应用会采用更完善的方案:借助对话大模型对消息历史做摘要处理

img

详见:《Langchain实战:常用中间件Summarization》

四、总结

  • 短期记忆:解决大模型“金鱼脑”问题,保证单次会话(Thread)不“断片”。
  • 核心机制:靠 Checkpointer(检查点器) 持久化状态,用 thread_id 隔离会话。
  • 存储选型:开发调试用 内存,生产环境上 PostgreSQL
  • 防爆窗三招:消息太多时,要么 裁剪(Trim) 丢旧消息,要么 删除(Remove) 清缓存,要么 摘要(Summarize) 保核心。

上下文工程管眼前,记忆管长远。短期记忆只是上半场,搞定它,你的 Agent 才算真正“在线”。

觉得有用的话,点个赞或在看支持一下吧~