跳到主要内容

Agent 长期记忆

长期记忆介绍

长期记忆是跨越会话和线程共享的存储系统。与短期记忆局限于 thread_id 不同,长期记忆的数据可以在任何线程中被召回,实现真正的跨会话信息持久化。

在 LangChain 中,长期记忆通过 Store 组件实现。Store 允许将记忆保存为 JSON 文档,通过 namespace(命名空间)key(键) 进行组织管理:

  • namespace:类似文件夹,用于逻辑分组
  • key:命名空间内文档的唯一标识

Store 支持以下四种操作:

操作说明
put写入记忆
get读取记忆
delete删除记忆
search搜索记忆

基本操作示例

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

namespace = ("user1", "preferences")

# 写入记忆
store.put(namespace, "fruit", {"likes": ["苹果", "香蕉"], "dislikes": ["橙子"]})

print("=== 读取 fruit 记忆 ===")
memory = store.get(namespace, "fruit")
print(memory)

store.put(namespace, "chat", {"language": "中文", "emotion": "高兴"})
print("=== 读取 chat 记忆 ===")
memory = store.get(namespace, "chat")
print(memory)

print("=== 搜索所有记忆 ===")
memories = store.search(namespace)
print(memories)

# 更新记忆(相同 key 的 put 会覆盖原值)
store.put(namespace, "fruit", {"likes": ["橘子", "葡萄"], "dislikes": ["草莓"]})
print("=== 读取更新后的 fruit 记忆 ===")
memory = store.get(namespace, "fruit")
print(memory)
要点
  • namespace 为元组格式,建议包含用户 ID 和上下文特征
  • 相同 key 的 put覆盖原值
  • value 是字典结构

Agent 中使用长期记忆方式

长期记忆的核心作用是跨会话数据共享。Agent 通过将 Store 注入到不同会话中实现这一能力。

使用内存存储长期记忆

使用 InMemoryStore + ToolRuntime + context_schema 实现内存级长期记忆:

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langchain.tools import tool, ToolRuntime
from pydantic import BaseModel
from init_llm import deepseek_llm

class UserContext(BaseModel):
user_id: str

store = InMemoryStore()
checkpointer = InMemorySaver()

# 预存用户信息
store.put(("users",), "user_123", {"name": "张三", "age": 28, "city": "北京", "hobby": "编程、阅读"})
store.put(("users",), "user_456", {"name": "李四", "age": 32, "city": "上海", "hobby": "旅游、摄影"})

@tool
def get_user_info(runtime: ToolRuntime[UserContext]) -> str:
"""从长期记忆中获取当前用户的信息"""
store = runtime.store
user_id = runtime.context.user_id
user_data = store.get(("users",), user_id)
if user_data:
info = user_data.value
return f"用户信息:姓名-{info['name']}, 年龄-{info['age']}, 城市-{info['city']}, 爱好-{info['hobby']}"
else:
return "未找到该用户的信息。"

agent = create_agent(
model=deepseek_llm,
tools=[get_user_info],
checkpointer=checkpointer,
store=store,
context_schema=UserContext
)

print("=== 长期记忆的跨线程读取 ===")

print("线程1 - 用户123询问信息:")
result1 = agent.invoke(
{"messages": [{"role": "user", "content": "我的个人信息是什么?"}]},
config={"configurable": {"thread_id": "thread_1"}},
context=UserContext(user_id="user_123")
)
print(f"Agent回复: {result1['messages'][-1].content}\n")

print("线程2 - 相同的用户,不同的线程:")
result2 = agent.invoke(
{"messages": [{"role": "user", "content": "再告诉我一次我的信息"}]},
config={"configurable": {"thread_id": "thread_2"}},
context=UserContext(user_id="user_123")
)
print(f"Agent回复: {result2['messages'][-1].content}\n")

print("线程3 - 用户456询问信息:")
result3 = agent.invoke(
{"messages": [{"role": "user", "content": "我的信息是什么?"}]},
config={"configurable": {"thread_id": "thread_3"}},
context={"user_id": "user_456"}
)
print(f"Agent回复: {result3['messages'][-1].content}")
要点

create_agent 通过 store 参数指定长期记忆,context_schema 指定上下文类型。

使用数据库存储长期记忆

使用 MySQL(PyMySQLStore)实现持久化长期记忆。

前置准备

create database langchain_db;

安装依赖

pip install langgraph-checkpoint-mysql==3.0.0 pymysql==1.1.2 cryptography==46.0.3
pip install aiomysql==0.3.2 asyncmy==0.2.11

代码示例

from langchain.agents import create_agent
from langgraph.checkpoint.mysql.pymysql import PyMySQLSaver
from langchain.tools import tool, ToolRuntime
from langgraph.store.mysql import PyMySQLStore
from pydantic import BaseModel
from init_llm import deepseek_llm

class UserContext(BaseModel):
user_id: str

@tool
def get_user_info(runtime: ToolRuntime[UserContext]) -> str:
"""从长期记忆中获取当前用户的信息"""
store = runtime.store
user_id = runtime.context.user_id
user_data = store.get(("users",), user_id)
if user_data:
info = user_data.value
return f"用户信息:姓名-{info['name']}, 年龄-{info['age']}, 城市-{info['city']}, 爱好-{info['hobby']}"
else:
return "未找到该用户的信息。"

DB_URI = "mysql+pymysql://root:123456@localhost:3306/langchain_db?charset=utf8mb4"

with (
PyMySQLSaver.from_conn_string(DB_URI) as checkpointer,
PyMySQLStore.from_conn_string(DB_URI) as store
):
checkpointer.setup()
store.setup()

store.put(("users",), "user_123", {"name": "张三", "age": 28, "city": "北京", "hobby": "编程、阅读"})
store.put(("users",), "user_456", {"name": "李四", "age": 32, "city": "上海", "hobby": "旅游、摄影"})

agent = create_agent(
model=deepseek_llm,
tools=[get_user_info],
checkpointer=checkpointer,
store=store,
context_schema=UserContext
)

print("=== 长期记忆的跨线程读取 ===")
print("线程1 - 用户123询问信息:")
result1 = agent.invoke(
{"messages": [{"role": "user", "content": "我的个人信息是什么?"}]},
config={"configurable": {"thread_id": "thread_1"}},
context=UserContext(user_id="user_123")
)
print(f"Agent回复: {result1['messages'][-1].content}\n")

print("线程2 - 相同的用户,不同的线程:")
result2 = agent.invoke(
{"messages": [{"role": "user", "content": "再告诉我一次我的信息"}]},
config={"configurable": {"thread_id": "thread_2"}},
context=UserContext(user_id="user_123")
)
print(f"Agent回复: {result2['messages'][-1].content}\n")

print("线程3 - 用户456询问信息:")
result3 = agent.invoke(
{"messages": [{"role": "user", "content": "我的信息是什么?"}]},
config={"configurable": {"thread_id": "thread_3"}},
context=UserContext(user_id="user_456")
)
print(f"Agent回复: {result3['messages'][-1].content}")
要点

PyMySQLStore.from_conn_string 创建持久化连接;store.setup() 自动建表。


工具中实现长期记忆读写

通过 ToolRuntimestorecontext 注入工具,实现长期记忆的读写操作。

使用 args_schema 定义工具输入,save_user_preference 负责写入偏好,get_user_preferences 负责读取所有偏好:

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langchain.tools import tool, ToolRuntime
from typing import Literal
from pydantic import BaseModel, Field
from init_llm import deepseek_llm
import uuid

class UserContext(BaseModel):
user_id: str

class UserPreference(BaseModel):
category: Literal["color", "food", "music"] = Field(description="用户偏好类别,必须是 'color', 'food', 'music' 中的一个")
preference: str = Field(description="具体偏好内容,如'红色'、'中国美食'等")

store = InMemoryStore()
checkpointer = InMemorySaver()

@tool(args_schema=UserPreference)
def save_user_preference(category: str, preference: str, runtime: ToolRuntime) -> str:
"""将用户偏好保存到长期记忆中"""
user_id = runtime.context.user_id
namespace = (user_id, "preferences")
memory_id = str(uuid.uuid4())
memory_value = {"category": category, "preference": preference}
runtime.store.put(namespace, memory_id, memory_value)
return f"已成功保存你的{category}偏好:{preference}"

@tool
def get_user_preferences(runtime: ToolRuntime) -> str:
"""从长期记忆中获取用户所有偏好"""
user_id = runtime.context.user_id
namespace = (user_id, "preferences")
memories = runtime.store.search(namespace)
if not memories:
return f"您还没有保存过偏好"
print("memories:", memories)
preferences_list = []
for mem in memories:
pref = mem.value
preferences_list.append(f"- 种类:{pref['category']},偏好:{pref['preference']}")
return f"你的偏好有:\n" + "\n".join(preferences_list)

memory_agent = create_agent(
model=deepseek_llm,
tools=[save_user_preference, get_user_preferences],
checkpointer=checkpointer,
store=store,
context_schema=UserContext
)

print("=== 完整演示:长期记忆的写入与跨线程读取 ===")

print("第一轮(线程1):用户保存颜色偏好")
result1 = memory_agent.invoke(
{"messages": [{"role": "user", "content": "请记住我喜欢的颜色是蓝色"}]},
config={"configurable": {"thread_id": "thread1"}},
context=UserContext(user_id="current_user")
)
print(f"Agent回复: {result1['messages'][-1].content}")

print("第二轮(同一线程):用户保存食物偏好")
result2 = memory_agent.invoke(
{"messages": [{"role": "user", "content": "我还喜欢的食物是意大利面"}]},
config={"configurable": {"thread_id": "thread1"}},
context=UserContext(user_id="current_user")
)
print(f"Agent回复: {result2['messages'][-1].content}")

print("第三轮(新线程):查询我的所有偏好")
result3 = memory_agent.invoke(
{"messages": [{"role": "user", "content": "告诉我我都喜欢什么颜色和食物"}]},
config={"configurable": {"thread_id": "thread2"}},
context=UserContext(user_id="current_user")
)
print(f"Agent回复: {result3['messages'][-1].content}")

print("=== 直接验证:从长期记忆存储中读取数据 ===")
color_memories = store.search(("current_user", "preferences"))
print(f"长期记忆中存储的颜色偏好: {[m.value for m in color_memories if m.value['category'] == 'color']}")
food_memories = store.search(("current_user", "preferences"))
print(f"长期记忆中存储的食物偏好: {[m.value for m in food_memories if m.value['category'] == 'food']}")

短期记忆和长期记忆区别总结

对比维度短期记忆长期记忆
作用域线程/会话范围(thread_id 绑定)跨线程/会话(自定义 namespace
核心目的保证单次对话的连贯性和上下文感知实现跨对话的个性化、知识积累和持续学习
主要存储内容对话的原始历史和当前会话状态数据从交互中提炼的结构化知识
管理组件Checkpointer(InMemorySaverPyMySQLSaverStore(InMemoryStorePyMySQLStore
生命周期随线程创建开始,随线程销毁结束独立于线程,除非显式删除否则永久存在
访问方式自动管理,每步自动持久化并恢复手动控制,需在工具或逻辑中显式调用 store.put()/get()
典型应用场景维持聊天上下文记住用户身份、偏好、历史行为

记忆综合案例 - 电商客服助手

本案例整合短期记忆与长期记忆,构建一个完整的电商客服助手,具备以下能力:

  1. 记住当前会话状态(短期记忆):当前查询的订单号
  2. 了解用户长期偏好(长期记忆):偏好的商品类型和名称
  3. 处理多轮对话:通过消息摘要管理长对话上下文
  4. 工具调用:查询用户信息、订单信息、更新偏好、推荐商品
  5. 错误捕获@wrap_tool_call 中间件

前置准备:MySQL 建库和安装依赖(与上文相同)。

完整代码

"""
智能电商客服助手
功能:结合短期记忆、长期记忆、消息摘要,提供个性化客服服务。
"""
import uuid
import warnings
from typing import List, Optional
from pydantic import BaseModel, Field
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import SummarizationMiddleware, wrap_tool_call
from langchain_core.tools import tool
from langchain_core.messages import ToolMessage
from langgraph.checkpoint.mysql.pymysql import PyMySQLSaver
from langgraph.store.mysql.pymysql import PyMySQLStore
from langgraph.prebuilt import ToolRuntime
from langgraph.types import Command
from init_llm import deepseek_llm

warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.main")

# 1. 定义 Context Schema
class UserContext(BaseModel):
user_id: str = Field(description="用户的唯一标识符")
channel: str = Field(description="用户咨询渠道,如: APP, Web, 小程序")

# 2. 自定义短期记忆状态
class CustomerSessionState(AgentState):
current_order_id: str

# 3. 模拟订单数据
MOCK_DATABASE = {
"orders": {
"order001": {"order_id": "order001", "status": "已发货", "product": "智能手机", "preference_context": "华为手机P70"},
"order002": {"order_id": "order002", "status": "待支付", "product": "智能手表", "preference_context": "Apple Watch Series 8"},
}
}

# 4. 定义工具
@tool
def get_user_info(runtime: ToolRuntime) -> str:
"""获取用户当前用户信息"""
current_user_id = runtime.context.user_id
user_channel = runtime.context.channel
state = runtime.state
current_order_id = state.get("current_order_id", "无")
return f"用户ID: {current_user_id}, 咨询渠道: {user_channel}, 当前查询订单号: {current_order_id}"

@tool
def query_order_status(order_id: str, runtime: ToolRuntime) -> Command:
"""查询用户订单状态"""
order_info = MOCK_DATABASE["orders"].get(order_id)
if not order_info:
return Command(update={
"messages": [ToolMessage(content=f"错误:订单 [{order_id}] 不存在", tool_call_id=runtime.tool_call_id)]
})
updates = {
"current_order_id": order_id,
"messages": [ToolMessage(
content=f"订单 [{order_id}] 状态: {order_info['status']}, 商品: {order_info['product']}。需要进行用户偏好更新,用户偏好: {order_info['preference_context']}",
tool_call_id=runtime.tool_call_id
)]
}
return Command(update=updates)

@tool
def update_user_preference(preference: str, runtime: ToolRuntime) -> str:
"""更新用户的商品偏好到长期记忆"""
user_id = runtime.context.user_id
namespace = (user_id, "preferences")
memory_id = str(uuid.uuid4())
runtime.store.put(namespace, memory_id, {"preference": preference, "source": "order_interaction"})
return f"已更新用户偏好: {preference}"

@tool
def recommend_products(runtime: ToolRuntime) -> str:
"""基于用户偏好推荐商品"""
user_id = runtime.context.user_id
namespace = (user_id, "preferences")
memories = runtime.store.search(namespace, limit=10)
if not memories:
return "暂无用户偏好记录,推荐热门商品:iPhone 16、小米手环、AirPods Pro"
prefs = [m.value.get("preference", "") for m in memories]
return f"基于您的偏好 {prefs},为您推荐相关商品"

# 5. 工具错误捕获中间件
@wrap_tool_call
def handle_tool_error(tool_call, tool_result, state, runtime):
"""捕获工具调用错误,返回友好提示"""
if isinstance(tool_result, Exception):
return ToolMessage(
content=f"工具调用出错:{str(tool_result)},请稍后重试",
tool_call_id=tool_call["id"]
)
return tool_result

DB_URI = "mysql+pymysql://root:123456@localhost:3306/langchain_db?charset=utf8mb4"

def run_ecommerce_agent():
with (
PyMySQLSaver.from_conn_string(DB_URI) as checkpointer,
PyMySQLStore.from_conn_string(DB_URI) as store
):
checkpointer.setup()
store.setup()

agent = create_agent(
model=deepseek_llm,
tools=[get_user_info, query_order_status, update_user_preference, recommend_products],
state_schema=CustomerSessionState,
system_prompt="你是电商客服助手。查询订单后自动更新用户偏好到长期记忆,推荐商品前先查询用户偏好。",
middleware=[
handle_tool_error,
SummarizationMiddleware(model=deepseek_llm, trigger=('messages', 10), keep=('messages', 4)),
],
checkpointer=checkpointer,
store=store,
context_schema=UserContext
)

# 场景1:用户查询订单
print("=" * 60)
print("场景1:用户查询订单")
print("=" * 60)
config1 = {"configurable": {"thread_id": "session_001"}}
response = agent.invoke(
{"messages": [{"role": "user", "content": "帮我查一下订单order001的状态"}], "current_order_id": ""},
config=config1,
context=UserContext(user_id="user_wang", channel="App")
)
print(f"客服: {response['messages'][-1].content}")

# 场景2:用户请求推荐
print("\n" + "=" * 60)
print("场景2:用户请求推荐商品")
print("=" * 60)
response = agent.invoke(
{"messages": [{"role": "user", "content": "有什么商品推荐给我吗?"}]},
config=config1,
context=UserContext(user_id="user_wang", channel="App")
)
print(f"客服: {response['messages'][-1].content}")

# 场景3:新会话,跨会话记忆
print("\n" + "=" * 60)
print("场景3:新会话,测试跨会话记忆")
print("=" * 60)
config2 = {"configurable": {"thread_id": "session_002"}}
response = agent.invoke(
{"messages": [{"role": "user", "content": "我之前买过什么类型的商品?推荐类似的"}], "current_order_id": ""},
config=config2,
context=UserContext(user_id="user_wang", channel="Web")
)
print(f"客服: {response['messages'][-1].content}")

if __name__ == "__main__":
run_ecommerce_agent()

案例总结

能力实现组件作用
当前对话连贯Checkpointer(短期记忆)维持多轮对话上下文
跨会话记忆Store(长期记忆)记住偏好、反馈
长对话管理SummarizationMiddleware消息摘要控制 token
错误处理@wrap_tool_call工具错误友好提示
用户隔离namespace + context_schema不同用户数据互不干扰