记忆与上下文管理:不是记得越多越好,而是取用得刚好
Agent 记忆的难点从来不是“存下来”,而是如何在正确时机取回正确的信息,同时不让上下文膨胀到拖垮效果和成本。
01.记忆系统真正难的,不是“让模型记住”
做 Agent 时,很多人会自然地把“记忆”理解成一件很直观的事:把过去对话存起来,下次继续用。
但一旦系统开始变复杂,问题会立刻出现:
- •历史消息越来越长,模型开始分心
- •旧信息已经过时,却还在污染当前上下文
- •用户偏好、业务事实、任务过程混在一起
- •该在什么时候写入长期记忆,根本没有边界
所以从工程角度看,记忆系统的核心从来不是“存储更多”,而是:
在合适的作用域里保存信息,并在合适的时机把最相关的部分带回上下文。
这个视角会比“短期记忆 vs 长期记忆”的教科书分类更有用。
02.先把记忆分成两类作用域
LangGraph 当前官方 memory 文档把记忆按 recall scope 分成两类,这个划分非常实用:
- •short-term memory:线程级、会话内
- •long-term memory:跨线程、跨会话
短期记忆是什么
短期记忆就是当前 thread 内的状态。它通常包括:
- •历史消息
- •本轮检索结果
- •临时生成草稿
- •上传文件或中间产物
这些内容的共同点是:它们只在这条任务链里有意义。
长期记忆是什么
长期记忆则更像跨会话知识,例如:
- •用户偏好
- •账号资料
- •已确认的业务事实
- •经常复用的操作规则
官方文档里还进一步把长期记忆分成 semantic、episodic、procedural 等类型。对工程实现来说,不必先背术语,更重要的是先问:
- •这条信息会不会跨会话复用
- •它应该按用户、组织还是业务对象分组
- •它值得持久化多久
03.短期记忆的关键,不是全量保留,而是 thread + checkpointer
旧的 LangChain 记忆文章经常从 ConversationBufferMemory 一类 API 开始讲,但按当前官方口径,更值得优先理解的是:
- •thread 是什么
- •checkpointer 如何保存状态
- •状态在哪些步骤被读取和更新
官方 short-term memory 文档强调,短期记忆是 agent state 的一部分,会通过 checkpointer 持久化,从而让线程可以被恢复。
一个最小示意如下:
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
def get_weather(city: str) -> str:
"""查询天气。"""
return f"{city} 当前天气晴。"
agent = create_agent(
model="gpt-5-nano",
tools=[get_weather],
checkpointer=InMemorySaver(),
)
agent.invoke(
{"messages": [{"role": "user", "content": "我叫小明"}]},
{"configurable": {"thread_id": "user-1"}},
)
agent.invoke(
{"messages": [{"role": "user", "content": "我刚才说我叫什么?"}]},
{"configurable": {"thread_id": "user-1"}},
)这里真正重要的是 thread_id,不是“内存对象”本身。只要线程一致,状态就会沿着同一条会话继续累积。
04.上下文膨胀时,真正该管理的是消息,不是“记忆概念”
官方文档对长对话的提醒非常明确:上下文窗口有限,历史消息越长,成本越高,模型也越容易被旧信息干扰。
所以短期记忆一旦变长,你真正需要管理的是消息历史,而不是继续讨论抽象概念。
常见做法包括:
- •删除陈旧消息
- •只保留最近若干轮
- •把旧消息摘要化
- •只把需要的 state 字段放进 prompt
一个简单的消息裁剪逻辑示意如下:
from langchain.messages import RemoveMessage
def trim_messages(state):
messages = state["messages"]
if len(messages) <= 8:
return {}
# 只保留最近 8 条
removed = [RemoveMessage(id=m.id) for m in messages[:-8]]
return {"messages": removed}注意这类操作的目标不是“省 token”这么单一,而是让当前步骤只看到仍然有决策价值的信息。
05.长期记忆更像“可检索的持久化事实”
在当前 LangChain / LangGraph 体系里,长期记忆不是一个魔法记忆盒,而是建立在 store 之上的持久化数据。
官方 long-term memory 文档把它描述成 JSON documents organized by namespace and key。这个设计非常重要,因为它提醒我们:
- •长期记忆不一定非要向量化
- •它可以先是结构化数据
- •namespace 才是组织和检索的关键
一个最小例子:
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langgraph.store.memory import InMemoryStore
@dataclass
class Context:
user_id: str
store = InMemoryStore()
store.put(("users",), "user_123", {"preferences": "回答尽量简洁"})
@tool
def get_user_preferences(runtime: ToolRuntime[Context]) -> str:
"""读取用户偏好。"""
memory = runtime.store.get(("users",), runtime.context.user_id)
return memory.value["preferences"] if memory else "无偏好"
agent = create_agent(
model="gpt-5-nano",
tools=[get_user_preferences],
store=store,
context_schema=Context,
)这里的重点是:
- •长期记忆通过
store管理 - •具体取哪条记忆,由 namespace + key 决定
- •工具通过
runtime访问它
这比“把所有历史都塞进向量数据库”更接近真实工程设计。
06.什么信息适合写入长期记忆
不是每一段对话都值得持久化。更稳妥的判断方式是:
适合写入
- •用户明确声明且后续可能复用的偏好
- •稳定身份信息
- •已确认的业务事实
- •明确可复用的操作规则
不适合直接写入
- •临时情绪
- •未确认猜测
- •只在当前任务里有效的中间过程
- •明显会过期的短时状态
官方 memory 文档也提到一个重要区分:记忆可以在 hot path 写入,也可以在后台写入。这个区分很有价值。
07.什么时候在 hot path 写,什么时候后台写
hot path 写入
适合以下信息:
- •用户刚明确声明的偏好
- •当前回合必须立刻影响回答的事实
优点是即时生效,缺点是会增加主链路复杂度。
后台写入
适合以下信息:
- •需要归纳、清洗、去重的记忆
- •可能要合并多轮结果后才有意义的知识
优点是主流程更轻,缺点是不会立刻生效。
经验上,不要让主执行链路承担过多“记忆整理”工作。主链路应该优先完成当前任务,重加工可以异步做。
08.记忆系统最容易踩的三个坑
1. 把所有历史都当记忆
这会让上下文越来越脏,也会让长期存储越来越难维护。
2. 把长期记忆等同于 RAG
RAG 是一种检索方式,不是长期记忆的全部。很多长期记忆更适合先用结构化 store 表达,再决定是否需要语义检索。
3. 只设计“怎么存”,不设计“怎么取”
一个记忆系统如果没有明确的召回时机、作用域和过滤规则,存得再多也很难稳定产生价值。
09.一个更稳妥的设计顺序
如果你要给 Agent 加记忆,我更建议按这个顺序做:
- •先区分 thread 内状态和跨线程事实
- •先设计消息裁剪策略
- •再设计长期记忆的 namespace 和 key
- •最后才决定是否要引入语义检索
这样做能避免一上来就把问题过早复杂化。
10.总结
记忆系统真正的核心,不是“让模型一直记得”,而是把信息放进正确作用域,并在正确时机取回:
- •thread 内状态解决当前任务连续性
- •store 解决跨会话持久化
- •消息裁剪解决上下文膨胀
- •hot path / background 写入解决主流程和记忆整理之间的平衡
当这些边界清楚以后,记忆系统才会变成 Agent 的增益项,而不是新的成本黑洞。