LangGraph记忆避坑指南:自动管理为什么还不够?

使用LangGraph开发多轮对话应用时,很多人都会疑惑:

LangGraph已经提供了Checkpointer,为什么真实项目还要自己保存聊天记录、截取最近几轮,再手动传给大模型?

答案是:保存状态、管理上下文、让模型正确理解历史,是三件不同的事。

LangGraph能帮助我们保存和恢复状态,但它不知道哪些历史对当前任务有用,也不会自动完成问题改写、长期存储和业务数据管理。

一、大模型本身没有记忆

调用大模型API时,每次请求都是独立的。

第一轮发送:

[{"role": "user", "content": "我叫小明"}]

第二轮如果只发送:

[{"role": "user", "content": "我叫什么名字?"}]

模型无法知道答案。要让它回答“小明”,应用必须在第二次调用时重新提供第一轮对话。

所以,聊天记忆的本质是:

应用保存过去的信息,并在后续调用时重新提供给模型。

模型不是自然地“回忆”起来,而是重新阅读了上下文。

二、LangGraph自动记忆做了什么

典型写法如下:

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import MessagesState, StateGraph

builder = StateGraph(MessagesState)
graph = builder.compile(checkpointer=InMemorySaver())

调用时提供固定的会话标识:

config = {
    "configurable": {
        "thread_id": "session-001"
    }
}

它主要完成三件事:

  1. 根据thread_id找到对应会话;
  2. 在图运行前恢复上一次保存的状态;
  3. 将本轮消息合并进去,并保存新状态。

MessagesState已经为messages配置了消息合并规则,因此标准聊天场景不需要反复手写:

history.append(user_message)
history.append(assistant_message)

但要注意:

Checkpointer自动保存的是“图状态”,不是业务意义上的完整记忆。

即使状态中保存了100条消息,如果节点调用模型时只传当前问题:

llm.invoke([current_question])

模型仍然看不到历史。历史必须真正进入模型输入才会生效。

三、为什么真实项目仍要手动管理

普通聊天机器人可以直接把消息列表交给模型,但真实应用通常还有意图识别、知识库检索、SQL生成、工具调用和多智能体协作。

1. 不同节点需要不同的上下文

假设用户连续提问:

用户:介绍一下A型号打印机。
用户:它支持双面打印吗?

理解节点需要参考历史,把第二个问题改写为:

A型号打印机支持双面打印吗?

后续知识库检索更适合使用这个独立问题,而不是整段聊天记录:

历史消息 + 当前问题
问题改写
独立、明确的问题
检索、SQL或知识图谱查询

如果把全部历史直接交给每个节点,旧话题中的地点、商品和条件可能干扰实体抽取与查询生成。

2. 自动保存不会自动理解历史

真实用户常使用省略和指代:

第一轮:介绍一下A产品。
第二轮:多少钱?
第三轮:它和B产品有什么区别?

系统需要将后两轮改写为:

A产品多少钱?
A产品和B产品有什么区别?

Checkpointer不会自动完成指代消解、问题改写、实体继承和业务参数提取。这些仍需要专门节点或业务代码。

3. 长期记录不等于模型上下文

业务系统可能需要永久保存聊天记录,用于历史查询、客服审计和质量分析。这些数据适合存入MySQL、PostgreSQL或MongoDB。

但数据库里保存了全部记录,不代表每次都应全部传给模型:

数据库聊天记录 = 档案库
模型当前上下文 = 工作台

档案库可以保存几年,工作台只应摆放解决当前问题需要的材料。

4. 业务记忆不只是聊天文本

系统还可能需要记住:

{
    "selected_product": "A型号打印机",
    "destination": "上海",
    "travel_date": "下周一",
    "uploaded_files": ["合同.pdf"],
    "confirmed_parameters": {...}
}

这些是结构化业务状态,不应该全部伪装成聊天消息。用户和助手的对话适合放在messages中,商品、订单、文件和确认参数则更适合独立字段或数据库。

四、全部改成自动管理会有什么问题

统一使用MessagesState + Checkpointer可以减少代码、统一消息格式,也更方便恢复Agent和工具调用过程,但直接把完整历史自动传给模型会产生新问题。

历史无限增长

随着轮数增加,Token成本和响应延迟不断上升,最终还可能超过模型上下文窗口。

无关历史干扰当前任务

旧话题中的实体和条件可能影响意图分类、检索关键词、SQL或知识图谱查询。

不能替代问题改写

让模型看到历史,不等于它能稳定地产生适合检索的独立问题。

不能替代业务数据库

InMemorySaver在服务重启后会丢失;多进程部署时,各进程内存也不能天然共享。持久化Checkpointer虽然能保存图状态,却不一定方便业务查询、统计和审计。

旧信息和隐私信息可能被反复发送

用户可能先说去北京,随后改成上海。完整历史同时包含新旧条件,可能造成冲突。历史里如果还有地址、手机号等敏感信息,全量自动传递也会扩大数据使用范围。

全部历史自动传入与按任务选择上下文的对比

五、推荐方案:分层管理记忆

自动记忆和手动管理不是二选一。生产系统更适合分成三层。

AI应用的三层记忆架构

第一层:短期上下文

使用LangGraph的MessagesState + Checkpointer管理最近几轮用户消息、助手回复和必要的工具结果,保证当前对话连续。

第二层:结构化业务状态

使用独立字段保存已确认的信息:

class AgentState(MessagesState):
    standalone_query: str
    selected_product: str
    destination: str
    retrieved_documents: list

它比让模型反复阅读长对话并自行判断更稳定。

第三层:长期记录

使用数据库保存完整会话、业务实体、时间和用户反馈,用于跨重启恢复、查询和审计。

在调用模型前,再按任务组合真正需要的内容:

最近几轮消息
    +
较早历史的摘要
    +
与当前问题相关的长期记忆
    +
已确认的结构化状态

六、什么时候可以只用自动记忆

以下场景通常可以直接使用LangGraph自动消息管理:

  • 简单聊天机器人;
  • 对话轮数较少;
  • 不需要跨重启保存;
  • 没有复杂业务字段;
  • 所有节点都适合读取相同历史。

而RAG、Text-to-SQL、知识图谱、多Agent、客服和订单系统,通常仍需要手动控制上下文,因为它们要解决问题改写、数据持久化、业务状态和隐私治理。

总结

LangGraph记忆主要解决:

根据thread_id保存和恢复图状态,并按照规则合并消息。

真实项目还要决定:

  • 哪些历史与当前问题相关;
  • 哪些节点应该读取历史;
  • 如何处理省略和指代;
  • 如何限制Token和成本;
  • 如何保存结构化业务信息;
  • 如何实现跨重启、查询和审计。

因此,更实用的方案是:

用LangGraph管理短期消息,用结构化状态管理当前业务,用数据库保存长期记录,再由业务逻辑选择真正需要交给模型的上下文。

自动记忆解决“如何保存”,手动管理解决“如何正确使用”。