使用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"
}
}
它主要完成三件事:
- 根据
thread_id找到对应会话; - 在图运行前恢复上一次保存的状态;
- 将本轮消息合并进去,并保存新状态。
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虽然能保存图状态,却不一定方便业务查询、统计和审计。
旧信息和隐私信息可能被反复发送
用户可能先说去北京,随后改成上海。完整历史同时包含新旧条件,可能造成冲突。历史里如果还有地址、手机号等敏感信息,全量自动传递也会扩大数据使用范围。

五、推荐方案:分层管理记忆
自动记忆和手动管理不是二选一。生产系统更适合分成三层。

第一层:短期上下文
使用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管理短期消息,用结构化状态管理当前业务,用数据库保存长期记录,再由业务逻辑选择真正需要交给模型的上下文。
自动记忆解决“如何保存”,手动管理解决“如何正确使用”。