3.3 在Agent运行图中访问长期记忆
我们可以在工具或中间件中访问长期记忆。
1 在工具中访问长期记忆
基于InMemoryStore
from typing import NotRequired
from langchain.agents import AgentState, create_agent
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langgraph.prebuilt import ToolRuntime
from langgraph.store.memory import InMemoryStore
from dotenv import load_dotenv
from langgraph.store.postgres import PostgresStore
load_dotenv(override=True)
# ============================================================
# 1. 自定义 Agent 状态
# ============================================================
class CustomState(AgentState):
"""
自定义 Agent 状态。
在默认 AgentState 的基础上增加 user_id,
用于标识当前用户,从而实现不同用户之间的长期记忆隔离。
"""
user_id: NotRequired[str]
# ============================================================
# 2. 创建长期记忆存储
# ============================================================
store = InMemoryStore()
# ============================================================
# 3. 保存用户信息
# ============================================================
@tool(parse_docstring=True)
def save_user_info(
name: str,
runtime: ToolRuntime,
) -> str:
"""
将用户信息保存到长期记忆中。
Args:
name: 用户名。
runtime: 工具运行时,可以通过它访问当前 Agent 的状态和 Store。
Returns:
str: 保存结果。
"""
# 长期记忆的命名空间
namespace = ("users",)
# 使用 user_id 作为长期记忆的 key,
# 从而实现不同用户之间的数据隔离。
key = runtime.state["user_id"]
# 要保存的用户信息
value = {
"name": name,
}
# 写入长期记忆
runtime.store.put(
namespace,
key,
value,
)
return "saved"
# ============================================================
# 4. 获取用户信息
# ============================================================
@tool(parse_docstring=True)
def get_user_info(
runtime: ToolRuntime,
) -> str:
"""
从长期记忆中读取当前用户的信息。
Args:
runtime: 工具运行时,可以通过它访问当前 Agent 的状态和 Store。
Returns:
str: 当前用户的信息,如果不存在则返回 unknown。
"""
# 与保存用户信息时使用相同的命名空间
namespace = ("users",)
# 获取当前用户 ID
key = runtime.state["user_id"]
# 从长期记忆中读取数据
item = runtime.store.get(
namespace,
key,
)
# 如果存在用户信息,则返回保存的数据
if item:
return str(item.value)
# 用户信息不存在
return "unknown"
# ============================================================
# 5. 创建 Agent
# ============================================================
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[
save_user_info,
get_user_info,
],
store=store,
state_schema=CustomState,
system_prompt=(
"用户提及个人信息时及时记录,"
"用户询问个人信息时尝试使用工具检索。"
),
)
# ============================================================
# 6. 第一个会话(线程)
# ============================================================
print("=" * 30, "-> 第一个会话(线程) <-", "=" * 30)
response1 = agent.invoke(
{
"messages": [
HumanMessage("你好,很高兴认识你,我是小花")
],
"user_id": "user-1",
}
)
for msg in response1["messages"]:
msg.pretty_print()
# ============================================================
# 7. 第二个会话(线程)
# ============================================================
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 是 什么
============================== -> 第一个会话(线程) <- ==============================
================================ Human Message =================================
你好,很高兴认识你,我是小花
================================== Ai Message ==================================
Tool Calls:
save_user_info (call_00_Svus19LFM2OgOZtELi6G0298)
Call ID: call_00_Svus19LFM2OgOZtELi6G0298
Args:
name: 小花
================================= Tool Message =================================
Name: save_user_info
saved
================================== Ai Message ==================================
你好呀,小花!很高兴认识你~我已经记住你的名字啦。有什么我可以帮你的吗?😊
============================== -> 第二个会话(线程) <- ==============================
================================ Human Message =================================
我是谁
================================== Ai Message ==================================
Tool Calls:
get_user_info (call_00_4SzCqDqy4HBmwXO9suQf5842)
Call ID: call_00_4SzCqDqy4HBmwXO9suQf5842
Args:
================================= Tool Message =================================
Name: get_user_info
{'name': '小花'}
================================== Ai Message ==================================
你叫小花。😊 有什么我可以帮你的吗?
两次invoke没有通过config串联为起来,是两个独立的会话,但第二个会话可以访问第一个会话写入长 期记忆的内容。
② 基于PostgresStore
import os
from typing import NotRequired
from dotenv import load_dotenv
from langchain.agents import AgentState, create_agent
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langgraph.prebuilt import ToolRuntime
from langgraph.store.postgres import PostgresStore
# ============================================================
# 1. 加载环境变量
# ============================================================
load_dotenv(override=True)
DB_URL = os.getenv("DB_URL")
if not DB_URL:
raise ValueError("DB_URL 未配置,请检查 .env 文件")
# ============================================================
# 2. 自定义 Agent 状态
# ============================================================
class CustomState(AgentState):
"""
自定义 Agent 状态。
在默认 AgentState 的基础上增加 user_id,
用于标识当前用户,从而实现不同用户之间的长期记忆隔离。
"""
user_id: NotRequired[str]
# ============================================================
# 3. 保存用户信息
# ============================================================
@tool(parse_docstring=True)
def save_user_info(
name: str,
runtime: ToolRuntime,
) -> str:
"""
将用户信息保存到长期记忆中。
Args:
name: 用户名。
runtime: 工具运行时,可以访问当前 Agent 的状态和 Store。
Returns:
str: 保存结果。
"""
# 长期记忆的命名空间
namespace = ("users",)
# 获取当前用户 ID
key = runtime.state["user_id"]
# 要保存的用户信息
value = {
"name": name,
}
# 保存到 PostgreSQL Store
runtime.store.put(
namespace,
key,
value,
)
return "saved"
# ============================================================
# 4. 获取用户信息
# ============================================================
@tool(parse_docstring=True)
def get_user_info(
runtime: ToolRuntime,
) -> str:
"""
从长期记忆中读取当前用户的信息。
Args:
runtime: 工具运行时,可以访问当前 Agent 的状态和 Store。
Returns:
str: 用户信息。如果不存在则返回 unknown。
"""
# 与保存时保持相同的命名空间
namespace = ("users",)
# 获取当前用户 ID
key = runtime.state["user_id"]
# 从 PostgreSQL Store 查询
item = runtime.store.get(
namespace,
key,
)
if item:
return str(item.value)
return "unknown"
# ============================================================
# 5. 创建 PostgreSQL Store
# ============================================================
with PostgresStore.from_conn_string(DB_URL) as store:
# 第一次使用时执行数据库初始化。
# 会创建 Store 所需要的数据表和迁移信息。
store.setup()
# ========================================================
# 6. 创建 Agent
# ========================================================
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[
save_user_info,
get_user_info,
],
store=store,
state_schema=CustomState,
system_prompt=(
"用户提及个人信息时及时记录,"
"用户询问个人信息时尝试使用工具检索。"
),
)
# ========================================================
# 7. 第一个会话(线程)
# ========================================================
print(
"=" * 30,
"-> 第一个会话(线程) <-",
"=" * 30,
)
response1 = agent.invoke(
{
"messages": [
HumanMessage(
"你好,很高兴认识你,我是小花"
)
],
"user_id": "user-1",
}
)
for msg in response1["messages"]:
msg.pretty_print()
# ========================================================
# 8. 第二个会话(线程)
# ========================================================
print(
"=" * 30,
"-> 第二个会话(线程) <-",
"=" * 30,
)
response2 = agent.invoke(
{
"messages": [
HumanMessage("我是谁")
],
"user_id": "user-1",
}
)
for msg in response2["messages"]:
msg.pretty_print()
查看PostgresSQL数据表
打开Xshell客户端,查看表

新增两张Store相关的表 store 和 store_migrations 。
2 在中间件中访问长期记忆
① Node-style hooks中访问
以 before_model 为例,其钩子函数签名如下
def before_model(
self,
state: StateT,
runtime: Runtime[ContextT],
) -> dict[str, Any] | None:
"""
在模型调用之前执行。
Args:
state: 当前 Agent 的状态数据。
runtime: 当前运行时上下文,可用于获取 Context 等运行时信息。
Returns:
dict[str, Any] | None:
- 返回字典:用于更新当前 State。
- 返回 None:表示不修改当前 State。
"""
pass
Runtime 定义如下
@dataclass(**_DC_KWARGS)
class Runtime(Generic[ContextT]):
# 当前这次 Agent / Graph 运行的静态上下文
# 例如:user_id、数据库连接、租户信息等
context: ContextT = field(default=None) # type: ignore[assignment]
"""
Static context for the graph run, like `user_id`, `db_conn`, etc.
可以理解为:
当前这次 Graph 运行过程中一直存在的"依赖信息"。
例如:
context = {
"user_id": 1001,
"db_conn": db
}
这些数据一般不会随着模型调用不断变化。
"""
# Graph 运行过程中使用的 Store
# 可以用于持久化数据、长期记忆等
store: BaseStore | None = field(default=None)
"""
Store for the graph run, enabling persistence and memory.
可以理解为:
Agent 的"长期存储"。
例如可以保存:
- 用户偏好
- 用户历史信息
- 长期记忆
- 向量检索数据
与 state 的区别:
state -> 当前这一次运行的数据
store -> 可以跨运行保存的数据
"""
# 自定义流式输出函数
# Agent 运行过程中,可以通过它向外部发送数据
stream_writer: StreamWriter = field(
default=_no_op_stream_writer
)
"""
Function that writes to the custom stream.
可以理解为:
一个"消息输出器"。
在流式响应(stream)场景中,
可以通过这个函数把中间结果发送给前端。
如果没有提供,则使用 _no_op_stream_writer,
即默认什么都不做。
"""
# 当前 Thread 上一次运行返回的结果
# 主要用于 Functional API + Checkpointer
previous: Any = field(default=None)
"""
The previous return value for the given thread.
表示:
当前 thread 上一次执行的返回值。
注意:
这个字段只有在:
Functional API
+
Checkpointer
的情况下才可用。
"""
...
所以,我们可以通过 runtime.store 在中间件中访问长期记忆
② Wrap-style hooks中访问
- wrap_model_call
钩子函数签名如下
def wrap_model_call(
self,
request: ModelRequest[ContextT],
handler: Callable[
[ModelRequest[ContextT]],
ModelResponse[ResponseT],
],
) -> ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]:
# request:当前模型请求,包含消息、模型、工具等信息
# handler:继续执行模型调用的处理函数
# 返回值:模型响应、AI 消息或扩展模型响应
ModelRequest 定义如下
@dataclass(init=False)
class ModelRequest(Generic[ContextT]):
"""
Agent 的模型请求信息。
Type Parameters:
ContextT:
Runtime 上下文的类型。
如果没有指定,则默认为 None。
"""
# 当前使用的聊天模型
model: BaseChatModel
# 当前消息列表,不包含 system message
messages: list[AnyMessage]
# 系统消息
system_message: SystemMessage | None
# 工具调用选择配置
tool_choice: Any | None
# 当前可用的工具列表
tools: list[BaseTool | dict[str, Any]]
# 结构化响应格式
response_format: ResponseFormat[Any] | None
# Agent 当前状态
state: AgentState[Any]
# 当前运行时上下文
runtime: Runtime[ContextT]
# 模型配置参数
model_settings: dict[str, Any] = field(
default_factory=dict
)
所以,可以通过 request.runtime.store 访问长期记忆。
- wrap_tool_call
钩子函数签名如下
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[
[ToolCallRequest],
ToolMessage | Command[Any],
],
) -> ToolMessage | Command[Any]:
# request:当前工具调用请求
# handler:继续执行工具调用的处理函数
# 返回值:工具消息或 Command 指令
ToolCallRequest 定义如下
@dataclass
class ToolCallRequest:
"""
传递给工具调用拦截器的工具执行请求。
Attributes:
tool_call:
模型输出的工具调用信息。
包含工具名称、参数和调用 ID。
tool:
要执行的 BaseTool 实例。
如果工具没有注册到 ToolNode,则为 None。
当 tool 为 None 时,拦截器可以直接处理请求,
此时不会进行工具验证。
如果拦截器调用 execute(),
则会进行工具验证。
如果工具未注册,则会抛出异常。
state:
Agent 当前状态。
可以是 dict、list 或 BaseModel。
runtime:
LangGraph 的运行时上下文。
如果在 Graph 外部运行,则为 None。
"""
# 模型生成的工具调用信息
# 包含工具名称、参数和调用 ID
tool_call: ToolCall
# 当前需要执行的工具
# 如果工具未注册到 ToolNode,则为 None
tool: BaseTool | None
# Agent 当前状态
state: Any
# LangGraph 运行时上下文
# 可以通过 request.runtime.store 访问长期记忆
runtime: ToolRuntime
ToolRuntime 定义如下
@dataclass
class ToolRuntime(
_DirectlyInjectedToolArg,
Generic[ContextT, StateT],
):
# Agent 当前的状态
state: StateT
# 当前运行的上下文
# 例如:user_id、数据库连接等
context: ContextT
# LangChain Runnable 的运行配置
config: RunnableConfig
# 流式输出函数
# 可以向外部发送自定义流式数据
stream_writer: StreamWriter
# 当前工具调用的 ID
# 如果没有工具调用 ID,则为 None
tool_call_id: str | None
# 持久化存储
# 可以用于保存和读取长期记忆
store: BaseStore | None
所以,可以通过 request.runtime.store 访问长期记忆。
3.4 何时写入记忆
官方介绍了两种方式。
- 在主流程里写(hot path)
也就是:用户发消息,AI 一边回答,一边决定要不要记下来。
- 优点: 立即生效 下一轮马上能用 用户可感知,透明
- 缺点: 增加延迟 逻辑变复杂
- 在后台写(background)
就是先回答用户,记忆整理放到后台 异步 做
- 优点: 主流程更快 记忆逻辑更独立 更适合批量整理
- 缺点: 不能立刻生效 要决定多久整理一次 触发时机不好选
工程上通常这么选:
- 用户偏好、账号资料 :可热路径写
- 对话摘要、经验沉淀、行为分析 :更适合后台写