告别 Token 焦虑:LangGraph 工业级记忆模块实现深度指南
在构建长对话 AI Agent 时,开发者通常会面临一个“不可能三角”:对话长度、推理成本(Token 消耗)以及记忆准确度。
传统的滑动窗口(Sliding Window)直接丢弃旧消息,会导致 AI 忘记半小时前聊过的核心需求;而全量存储则会让上下文迅速膨胀,最终触及模型限制并让你的账单爆炸。
今天,我们分享一种在 LangGraph 中实现的工业级方案:滚动摘要(Rolling Summary)+ 状态裁剪(State Trimming)+ 异构存储(Heterogeneous Storage)。
1. 核心架构设计
我们的目标是建立一套“像人类一样记忆”的系统:短期细节清晰,长期重点明确,完整历史可查。
三位一体的存储策略
- Checkpointer (热存储):存储在数据库(如 SQLite/Postgres)中的精简 State。只包含最新的 $N$ 条消息和一份不断更新的全局摘要。
- State (运行时内存):利用 LangGraph 的
add_messages机制,通过RemoveMessage指令动态裁剪对话列表。 - MySQL (冷存储):异步记录每一条原始对话,仅供前端 UI 展示历史记录,不参与模型推理。
2. 关键技术实现
A. 定义智能状态 (State)
在 LangGraph 中,State 是唯一的真理。我们必须通过 Annotated 告诉系统如何处理消息的增量更新。
from typing import Annotated, List, Union
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage, RemoveMessage
from langgraph.graph.message import add_messages
class RAGState(TypedDict):
# add_messages 会处理逻辑:如果是新消息就追加,如果是 RemoveMessage 就删除
messages: Annotated[List[AnyMessage], add_messages]
# 全局滚动摘要
summary: str
# 业务 ID
conversation_id: str
B. 滚动压缩节点 (The Compactor Node)
这是记忆模块的心脏。当消息超过阈值时,它负责将“旧闻”提炼成“摘要”。
async def memory_compact_node(state: RAGState):
messages = state["messages"]
# 触发检查:如果消息没满,直接跳过
if len(messages) <= MAX_MESSAGES:
return {}
# 1. 划分归档区和保留区
to_archive = messages[:-KEEP_RECENT]
# 2. 迭代摘要:旧摘要 + 旧消息片段 = 新摘要
new_summary = await llm.ainvoke(f"现有摘要: {state['summary']}\n追加内容: {to_archive}")
# 3. 构造删除指令
# 关键:必须传入消息的 ID,LangGraph 才会执行逻辑删除
delete_actions = [RemoveMessage(id=m.id) for m in to_archive]
return {
"messages": delete_actions,
"summary": new_summary.content,
"_archived_data": to_archive # 传递给下游异步节点
}
3. 为什么这是“最佳实践”?
1. 彻底解决 Token 膨胀
无论用户与 Agent 聊了 100 轮还是 1000 轮,由于 memory_compact_node 的存在,输入给 LLM 的 messages 永远被控制在 $N$ 条以内。
2. 完美的断点续传
依靠 LangGraph 内置的 Checkpointer,你只需要一个 thread_id 就能恢复现场:
# 自动加载该用户最后的 summary 和 裁剪后的 messages
config = {"configurable": {"thread_id": "user_888"}}
app.invoke(input_data, config)
3. 用户体验与模型性能的平衡
- 模型侧:只读精简后的
State,推理速度快,事实聚焦。 - 用户侧:前端通过接口读取 MySQL,看到的是完整的对话流(包括那些已经被模型“裁剪”掉的消息),体验连贯。
4. 实施建议与避坑指南
- ID 陷阱:在使用
RemoveMessage时,务必确保消息对象拥有唯一的id。如果是手动构造的消息,记得手动指定 ID。 - 摘要策略:在重写摘要的 Prompt 中,务必加入“保留关键事实(如姓名、地址、特定偏好)”的强约束,防止摘要在多次迭代后变得模糊。
- 异步落库:将 MySQL 的写入逻辑放在一个
archive_node中,并使用asyncio.create_task处理,避免数据库 IO 阻塞 Agent 的响应速度。
结语
在 AI Agent 的长跑中,记忆力决定了它能跑多远。通过 LangGraph 的 State 机制配合滚动摘要方案,你不仅在技术上实现了真正的“长效记忆”,更在工程架构上实现了极高的性价比。
你的 Agent,现在准备好应对无限的对话了吗?
作者注:本方案已在生产环境下验证,能有效支撑 50 轮以上的深度对话场景。