记忆分为短期记忆和长期记忆,对应不同的使用场景:
在langchain 1.x中我们如何使用记忆呢
在LangChain v0.x版本中,通过专用的xxxMemory类管理记忆。 在LangChain v1.x版本中,Agent是构建在LangGraph图结构之上的,通过上文提到的state和store构建记忆系统。使用更简单、功能更统一。
LangChain1.x 的短期记忆是三者的组合:
State(会话内部状态) + Checkpointer(持久化机制) + Thread ID(会话作用域)
pythonfrom langchain_core.messages import HumanMessage
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
# 1. 创建 Agent 时添加 checkpointer
agent = create_agent(
model=model,
checkpointer=checkpointer # 添加内存管理
)
# 2. 调用时指定 thread_id
config = {
"configurable": {
"thread_id": "1"
}
}
print("\n第一轮对话:")
response1 = agent.invoke({
"messages": [HumanMessage("我叫张三")]},
config=config # 传入 config
)
print(f"Agent: {response1['messages'][-1].content}")
print("\n第二轮对话:")
response2 = agent.invoke({
"messages": [HumanMessage("我叫什么?")]},
config=config # 使用相同的 thread_id
)
print(f"Agent: {response2['messages'][-1].content}")
只需传入 checkpointer 和 config,Agent 就能自然具备连续对话能力。 如果更新线程ID,则会重新开启对话。thread_id 隔离不同会话空间。
注意: InMemorySaver会存在以下问题
InMemorySaver 只保存在内存中
✅ 同一进程内有效(不支持跨进程共享) ❌ 程序重启后丢失(或进程重启后丢失) ❌ 不同进程无法共享 解决方案:持久化(SQLite、PostgreSQL)
InMemorySaver 会保存所有消息
消息越来越多(无限增长,需要管理上下文) token消耗增加,甚至会超过模型的 token 限制 响应速度变慢、成本增加 解决方案:上下文管理(修剪、摘要)
如果将 状态检查点(checkpointer) 保存在内存, 进程结束 则状态丢失,生产环境不可接受。因此,生产环境要用持久化的外部存储介质,如PostgreSQL。LangGraph提供的checkpointer后端列表如下 https://docs.langchain.com/oss/python/langgraph/persistence#checkpointer-libraries 此处选择PostgreSQL作为持久化器。
pythonfrom langgraph.checkpoint.postgres import PostgresSaver
DB_URL = "postgresql://postgres:123456@127.0.0.1:5432/langchain_db?sslmode=disable"
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
# 初始化PostgreSQL数据库
checkpointer.setup()
agent = create_agent(
model=model,
checkpointer=checkpointer
)
config = {"configurable": {"thread_id": "1"}}
response1 = agent.invoke(
{"messages": [HumanMessage("你好,我是老王")]},
config=config
)
print("=" * 30, "-> 第一次调用 <-", "=" * 30)
for msg in response1["messages"]:
msg.pretty_print()
response2 = agent.invoke(
{"messages": [HumanMessage("你好,我是谁?")]},
config=config
)
print("=" * 30, "-> 第二次调用 <-", "=" * 30)
for msg in response2["messages"]:
msg.pretty_print()
setup() 用于初始化PostgreSQL数据库,首次运行会创建必要的表,重复执行不会重新建表,底层逻辑是 Create IF Not Exists
langchain会初始化4张表:
关于这两种策略:
随着对话的进行,历史消息不断累积, state会持续增长 ,为模型带来挑战:
调用模型前裁剪上下文。 目标是控制token用量,通常 保留系统初始消息和最近若干消息 ,或 按token数保留末尾内容 。 适合成本敏感、对旧上下文依赖不强的场景。
pythonfrom langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
from typing import Any
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
"""在调用模型之前,判断是否需要对消息进行裁剪"""
messages = state["messages"]
if len(messages) <= 3:
return None
# 保留起始消息
first_msg = messages[0]
# 如果有偶数条消息,则取最近的3条消息;如果有奇数条消息,则取最近4条消息
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),
# 再添加要保留的消息
*new_messages
]
}
agent = create_agent(
model=model,
middleware=[trim_messages],
checkpointer=InMemorySaver(),
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [HumanMessage("你好,我是老王")]}, config)
agent.invoke({"messages": [HumanMessage("从现在起,你叫小王")]}, config)
agent.invoke({"messages": [HumanMessage("今天天气不错")]}, config)
final_response = agent.invoke({"messages": [HumanMessage("告诉我,你是谁?我是谁?")]}, config)
for msg in final_response["messages"]:
msg.pretty_print()
消息裁剪强调“在 模型调用前裁剪 消息列表,控制模型可以看到的上下文范围”,而消息删除强调 模型调用完成后将某些消息从消息列表中移除 ,永久更改状态。 适合明确要遗忘、清理、重置某些历史。
pythonfrom langchain.agents.middleware import after_model
@after_model
def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:
messages = state["messages"]
# 保持最近的 5 条消息
if len(messages) > 5:
# 框架中通常使用 RemoveMessage 来标记删除,并返回更新状态。
to_delete = len(messages) - 5
return {"messages": [RemoveMessage(id=m.id) for m in messages[:to_delete]]}
return None
agent = create_agent(
model=model,
middleware=[delete_old_messages],
checkpointer=InMemorySaver()
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": "你好,我是老王"}, config)
agent.invoke({"messages": "从现在起,你叫小王"}, config)
agent.invoke({"messages": "今天天气不错"}, config)
final_response = agent.invoke({"messages": "告诉我,你是谁?我是谁?"}, config)
for msg in final_response["messages"]:
msg.pretty_print()
RemoveMessage到底干了什么? 当你在中间件里返回 [RemoveMessage(id=m.id)] 时,你实际上是向框架发送了一个 删除指令 。 框架的底层处理逻辑如下:
palaintext[历史消息池 (内存中持续存在)] ├── Message(id="1", content="你好,我是老王") ├── Message(id="2", content="...") └── RemoveMessage(id="1") <-- 这是一个新追加进去的“墓碑”标记
把早期历史压缩成摘要,再替换原始消息。 消息裁剪和删除都会导致上下文缺失,影响回答质量和用户体验。和它们相比,摘要是更适合长会话的 折中方案:保语义,不保原文。官方推荐内置 SummarizationMiddleware 。
pythonagent = create_agent(
model=model_out,
tools=[],
checkpointer=InMemorySaver(),
middleware=[
SummarizationMiddleware(
model=model_in,
trigger=[
("tokens", 100), # 超过 100 tokens 就摘要
],
keep=("messages", 2),
summary_prompt="对历史消息摘要,消息列表如下\n{messages}",
)
]
)
具体使用参考上一篇
设置最大token数触发摘要的标准是啥?
建议如下:
trigger_token = 模型总上下文窗口 × 0.70 ~ 0.80
长期记忆的存储是 store -> namespace -> key -> value 的四层架构。
每个namespace存储的都是key-value键值对,通过key可以唯一标识一条value。
pythonnamespace = ("users", "user_123", "preferences") # 元组类型
key = "profile" # 字符串类型
value = { # 字典类型
"language": "zh-CN",
"style": "short_direct",
"likes": ["python", "rag"]
}
store.put(namespace, key, value)
LangChain 1.2.x 的长期记忆基于 store 持久化数据,相关的API有:
pythonfrom langgraph.store.memory import InMemoryStore
store = InMemoryStore()
namespace = ("users",)
user_id = 'user-1'
username = "小蓝"
store.put(namespace, user_id, {"name": username})
print(store.get(namespace, user_id))
# Item(namespace=['users'], key='user-1', value={'name': '小蓝'}, created_at='2026-08-12T09:05:13.505569+00:00', updated_at='2026-08-12T09:05:13.505573+00:00')
注意到,Item对象新增了 created_at 和 updated_at 字段,分别为数据新增和更改时间。 注意:对于当前版本,InMemoryStore每次put都会创建一个新的Item对象,无论namespace和key是否相同,所以 created_at 和 updated_at 始终是一致的。
更新 对同一条数据进行更改后查询。
pythonstore.put(namespace, user_id, {"name": '小红'})
print(store.get(namespace, user_id))
# Item(namespace=['users'], key='user-1', value={'name': '小红'}, created_at='2026-08-12T09:07:25.450238+00:00', updated_at='2026-08-12T09:07:25.450242+00:00')
我们发现当namespace,key完全相同时,value被完全覆盖了,生成一条新的数据。
pythonfrom langgraph.store.postgres import PostgresStore
namespace = ("users",)
user_id = "user-11"
username = "小蓝"
DB_URL = "postgresql://postgres:123456@127.0.0.1:5432/langchain_db?sslmode=disable"
with PostgresStore.from_conn_string(DB_URL) as store:
store.setup()
store.put(namespace, user_id, {"name": username})
print(store.get(namespace, user_id))
# Item(namespace=['users'], key='user-11', value={'name': '小蓝'}, created_at='2026-08-12T09:12:12.388146+00:00', updated_at='2026-08-12T09:12:12.388146+00:00')
当对一条数据更新时
pythonwith PostgresStore.from_conn_string(DB_URI) as store:
store.setup()
store.put(namespace, user_id, {"name": "小红"})
print(store.get(namespace, user_id))
created_at 不会变,只会更新updated_at的值。
参数说明:
它支持两种检索方式(对应上面的参数2、3):
返回值: 返回匹配的 SearchItem 列表,并额外携带匹配分数等检索元信息。
举例1:按照namespace前缀搜索
pythonfrom langgraph.store.memory import InMemoryStore
store = InMemoryStore()
namespace1 = ("users", "Alice", "memories")
key1 = 'preferences'
value1 = {
"course": "计算机组成原理",
"sports": "跑步",
"food": "紫光园奶皮子酸奶"
}
namespace2 = ("users", "Bob", "memories")
key2 = 'preferences'
value2 = {
"course": "数字电路与模拟电路",
"sports": "跑步",
"food": "奶皮子糖葫芦"
}
namespace3 = ("users", "Black", "memories")
key3 = 'preferences'
value3 = {
"course": "数字电路与模拟电路",
"sports": "羽毛球",
"food": "紫光园奶皮子酸奶"
}
store.put(namespace1, key1, value1)
store.put(namespace2, key2, value2)
store.put(namespace3, key3, value3)
print('=' * 30, '-> (users, Alice) <-', "=" * 30)
for item in store.search(("users", "Alice")):
print(item)
检索结果如下:
plaintext============================== -> (users, Alice) <- ============================== Item(namespace=['users', 'Alice', 'memories'], key='preferences', value={'course': '计算机组成原理', 'sports': '跑步', 'food': '紫光园奶皮子酸奶'}, created_at='2026-08-12T09:18:51.595739+00:00', updated_at='2026-08-12T09:18:51.595741+00:00', score=None)
举例2:按照filter过滤
pythonprint("=" * 30, '-> (users, ), filter=food <-", "=" * 30)')
for item in store.search(("users", ), filter={"food": "紫光园奶皮子酸奶"}):
print(item)
检索结果如下:
plaintext============================== -> (users, ), filter=food <-", "=" * 30) Item(namespace=['users', 'Alice', 'memories'], key='preferences', value={'course': '计算机组成原理', 'sports': '跑步', 'food': '紫光园奶皮子酸奶'}, created_at='2026-08-12T09:18:51.595739+00:00', updated_at='2026-08-12T09:18:51.595741+00:00', score=None) Item(namespace=['users', 'Black', 'memories'], key='preferences', value={'course': '数字电路与模拟电路', 'sports': '羽毛球', 'food': '紫光园奶皮子酸奶'}, created_at='2026-08-12T09:18:51.595784+00:00', updated_at='2026-08-12T09:18:51.595785+00:00', score=None)
举例3:按照语义搜索 自定义嵌入函数,目的是查看嵌入向量
python# 自定义嵌入函数
def embed(text: list[str]) -> list[list[float]]:
return [[1.0] * 6 for _ in range(len(text))]
index_config = {
"embed": embed,
"dims": 6,
"fields": ["$", "course"]
}
store = InMemoryStore(
index = index_config
)
初始化Store时通过index指定索引方式
查看嵌入向量
pythonfrom pprint import pprint
pprint(store._vectors)
还可以通过指定namespace、key、index_config的fields中指定的字段名查看特定向量。
pythonfrom pprint import pprint
pprint(store._vectors[('users', 'Alice', 'memories')]['preferences']['$'])
使用嵌入模型 本例要通过CloseAI平台调用OpenAI的嵌入模型 openai
其嵌入维度为3072,所以此处的 dims 应设置为3072pythonfrom langgraph.store.memory import InMemoryStore
from langchain.embeddings import init_embeddings
embedding_model = init_embeddings(
model="openai:text-embedding-3-large",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL"),
)
index_config = {
"embed": embedding_model,
"dims": 3072,
"fields": ["$"]
}
store = InMemoryStore(
index=index_config
)
for item in store.search(("users", ), query="数电模电"):
print(item)
如果只是指定了 query ,返回的是所有 namespace 前缀满足要求的 item 。底层会按照向量相 似度计算查询和候选的 score ,输出结果按照score 降序排列 。可以结合 limit 或 filter 限制数据条数。
我们可以在工具或中间件中访问长期记忆。
基于内存模式
pythonfrom langchain_core.messages import HumanMessage
from typing import NotRequired
from langchain.agents import AgentState, create_agent
from langchain.tools import tool, ToolRuntime
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
class CustomState(AgentState):
user_id: NotRequired[str]
@tool(parse_docstring=True)
def save_user_info(name: str, runtime: ToolRuntime) -> str:
"""
将用户信息保存在长期记忆中
Args:
name: 用户名
Returns:
str: 保存状态
"""
runtime.store.put(("users",), runtime.state["user_id"], {"name": name})
return "saved"
@tool(parse_docstring=True)
def get_user_info(runtime: ToolRuntime) -> str:
"""
从长期记忆中读取用户信息
Returns:
str: 用户信息
"""
item = runtime.store.get(("users",), runtime.state["user_id"])
return str(item.value) if item else "unknown"
agent = create_agent(
model=model,
tools=[save_user_info, get_user_info],
store=store,
system_prompt="用户提及个人信息时及时记录,用户询问个人信息时尝试用工具检索",
state_schema=CustomState,
)
print("=" * 30, '-> 第一个会话(线程) <-', "=" * 30)
response1 = agent.invoke({
"messages": [HumanMessage("你好,很高兴认识你,我是小花")],
"user_id": "user-1"
})
for msg in response1["messages"]:
msg.pretty_print()
print("=" * 30, '-> 第二个会话(线程) <-', "=" * 30)
response2 = agent.invoke({
"messages": [HumanMessage("我是谁")],
"user_id": "user-1"
})
for msg in response2["messages"]:
msg.pretty_print()
其中,CustomState扩展了 Agent 的标准状态。除了默认的 messages (历史消息列表)之外,还额外增加了一个 user_id 字段。这样,Agent 在运行过程中随时随地都能知道当前和它说话的用户 ID 是什么。
基于PostgresStore
pythonfrom langgraph.store.postgres import PostgresStore
DB_URI = "postgresql://postgres:123456@127.0.0.1:5432/langchain_db?sslmode=disable"
class CustomState(AgentState):
user_id: NotRequired[str]
@tool(parse_docstring=True)
def save_user_info(name: str, runtime: ToolRuntime) -> str:
"""
将用户信息保存在长期记忆中
Args:
name: 用户名
Returns:
str: 保存状态
"""
runtime.store.put(("users",), runtime.state["user_id"], {"name": name})
return "saved"
@tool(parse_docstring=True)
def get_user_info(runtime: ToolRuntime) -> str:
"""
从长期记忆中读取用户信息
Returns:
str: 用户信息
"""
item = runtime.store.get(("users",), runtime.state["user_id"])
return str(item.value) if item else "unknown"
with PostgresStore.from_conn_string(DB_URI) as store:
store.setup()
agent = create_agent(
model=model,
tools=[save_user_info, get_user_info],
store=store,
system_prompt="用户提及个人信息时及时记录,用户询问个人信息时尝试用工具检索",
state_schema=CustomState,
)
print("=" * 30, '-> 第一个会话(线程) <-', "=" * 30)
response1 = agent.invoke({
"messages": "你好,很高兴认识你,我是小花",
"user_id": "user-1"
})
for msg in response1["messages"]:
msg.pretty_print()
print("=" * 30, '-> 第二个会话(线程) <-', "=" * 30)
response2 = agent.invoke({
"messages": "我是谁",
"user_id": "user-1"
})
for msg in response2["messages"]:
msg.pretty_print()
① Node-style hooks中访问 以 before_model 为例
python@before_model
def before_model_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
item = runtime.store.get(("users",), runtime.state["user_id"])
return None
② Wrap-style hooks中访问
python@wrap_tool_call
def wrap_tool_call_middleware(
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command],
) -> ToolMessage | Command:
request.runtime.store.get(("users",), runtime.state["user_id"])
return None


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!