以下是三个 LangGraph 长期存储案例,从内存版到 PostgreSQL 持久化版,逐层递进,展示如何在跨会话、跨线程的维度上记忆用户偏好等信息。
🧠 核心概念:Checkpointer ≠ Store
| 对比维度 | Checkpointer(短期记忆) | Store(长期记忆) |
|---|---|---|
| 记忆范围 | 单个线程(单次会话),线程内状态持久化 | 跨会话、跨线程,同一用户的所有对话都可访问 |
| 存储内容 | 图状态(messages、step 等动态数据) |
JSON 文档,按 namespace + key 组织 |
| 存储机制 | checkpointer.put / checkpointer.get |
store.put / store.get / store.search |
| 典型用途 | 同一会话内多轮对话记忆(上次聊到哪) | 跨会话的用户画像(姓名、偏好、兴趣),不随会话结束而丢失 |
| 后端存储 | SQLite / PostgreSQL / MySQL | InMemory / PostgreSQL / 自定义 BaseStore |
User A (thread_1) ──┐
(对话1) Checkpointer ── 记住本会话内部:上次聊到哪、中间结果
User A (thread_2) ──┘
Store (跨线程) ── 记住跨会话信息:姓名、兴趣、偏好
User B (任何会话) ──────────────────┘
📦 案例一:InMemoryStore 基础版(内存长期存储)
演示跨会话记忆的核心功能:第一次对话告诉 AI 姓名,第二次新会话中 AI 依然能回忆起来。
完整代码
python
import os
import asyncio
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.postgres import PostgresStore # 需要安装 langgraph-store-postgres
load_dotenv()
DB_URI = "postgresql://langgraph_user:your_password@localhost:5432/langgraph_db"
llm = ChatOpenAI(
model="qwen-plus",
temperature=0.7,
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv("DASHSCOPE_BASE_URL"),
model_kwargs={"extra_body": {"enable_thinking": False}}
)
checkpointer = InMemorySaver()
def call_agent(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": [response]}
async def main():
# 正确的用法:直接 async with,不需要 await
async with PostgresStore.from_conn_string(DB_URI) as store:
await store.setup() # 创建表(首次运行)
print("✅ PostgreSQL 长期存储已连接")
builder = StateGraph(MessagesState)
builder.add_node("call_agent", call_agent)
builder.add_edge(START, "call_agent")
graph = builder.compile(checkpointer=checkpointer, store=store)
user_id = "user_001"
config = {"configurable": {"thread_id": f"{user_id}_thread"}}
namespace = ("profiles", user_id)
# 读取已有记忆
existing = await store.aget(namespace, "user_info")
if not existing:
await store.aput(namespace, "user_info", {
"name": "张明",
"hobby": "摄影",
"preferences": {"language": "zh-CN", "style": "detailed"}
})
print("💾 用户画像已写入 PostgreSQL")
else:
print(f"📋 读取用户画像: {existing.value}")
# 对话示例
print("\n用户: 我喜欢拍风景照片")
messages = [("user", "我喜欢拍风景照片")]
if existing:
context = f"用户偏好:{existing.value}。请基于此回答。"
messages.insert(0, ("system", context))
for event in graph.stream(
{"messages": messages},
config=config,
stream_mode="values"
):
event["messages"][-1].pretty_print()
if __name__ == "__main__":
asyncio.run(main())
预期输出
📞【第一次对话(新会话)】
用户: 你好,我叫林小夕,我最喜欢喝咖啡。
================================ Ai Message =================================
你好林小夕!很高兴认识你,咖啡爱好者!☕️
💾 [长期存储写入] 用户信息已保存。
📞【第二次对话(新会话)】
用户: 你还记得我是谁吗?我喜欢什么?
================================ Ai Message =================================
当然记得!你是林小夕,你喜欢喝咖啡。☕️
📋 长期存储最终内容: {'name': '林小夕', 'preferences': ['咖啡'], 'first_seen': '2026-06-10'}
💡 案例一核心知识点
- Store 核心方法 :
store.put(namespace, key, value)写入、store.get(namespace, key)读取。 - Namespace 设计 :
("user_profiles", user_id)------ 第一层表领域,第二层表用户 ID,支持分层组织。 - Store 与 Checkpointer 同时使用:短期会话记忆 + 长期跨会话记忆互补。
🗄️ 案例二:PostgreSQL Store 持久化版(生产级方案)
开发阶段可以用 InMemoryStore 快速验证,生产环境必须使用 PostgreSQL Store 实现数据持久化。官方提供
PostgresStore支持 PostgreSQL 后端存储,数据不随服务重启丢失。
准备工作
bash
# 安装依赖
pip install "langgraph-checkpoint-postgres[store]"
完整代码
python
import os
import asyncio
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.postgres import PostgresStore # 👈 PostgreSQL Store
load_dotenv()
DB_URI = "postgresql://langgraph_user:your_password@localhost:5432/langgraph_db"
llm = ChatOpenAI(
model="qwen-plus",
temperature=0.7,
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv("DASHSCOPE_BASE_URL"),
model_kwargs={"extra_body": {"enable_thinking": False}}
)
checkpointer = InMemorySaver()
def call_agent(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": [response]}
async def main():
async with await PostgresStore.from_conn_string(DB_URI) as store:
await store.setup() # 创建表(首次运行)
print("✅ PostgreSQL 长期存储已连接")
builder = StateGraph(MessagesState)
builder.add_node("call_agent", call_agent)
builder.add_edge(START, "call_agent")
graph = builder.compile(checkpointer=checkpointer, store=store)
user_id = "user_001"
config = {"configurable": {"thread_id": f"{user_id}_thread"}}
namespace = ("profiles", user_id)
# 读取已有记忆
existing = await store.aget(namespace, "user_info")
if not existing:
await store.aput(namespace, "user_info", {
"name": "张明",
"hobby": "摄影",
"preferences": {"language": "zh-CN", "style": "detailed"}
})
print("💾 用户画像已写入 PostgreSQL")
else:
print(f"📋 读取用户画像: {existing.value}")
# 对话示例...
print("\n用户: 我喜欢拍风景照片")
messages = [("user", "我喜欢拍风景照片")]
if existing:
context = f"用户偏好:{existing.value}。请基于此回答。"
messages.insert(0, ("system", context))
for event in graph.stream(
{"messages": messages},
config=config,
stream_mode="values"
):
event["messages"][-1].pretty_print()
if __name__ == "__main__":
asyncio.run(main())
PostgreSQL 长期存储案例:代码流程与数据交互详解
这是一个将 LangGraph 与 PostgreSQL 集成,实现 跨会话长期记忆 的异步版本代码。它利用 PostgresStore 存储用户画像(如姓名、爱好、偏好),并在对话中动态读取该记忆来增强 Agent 的回答。
一、整体架构图
Qwen模型 call_agent() compiled Graph PostgresStore main() Qwen模型 call_agent() compiled Graph PostgresStore main() #mermaid-svg-ETed0WbDtavim6g4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ETed0WbDtavim6g4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ETed0WbDtavim6g4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ETed0WbDtavim6g4 .error-icon{fill:#552222;}#mermaid-svg-ETed0WbDtavim6g4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ETed0WbDtavim6g4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ETed0WbDtavim6g4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ETed0WbDtavim6g4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ETed0WbDtavim6g4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ETed0WbDtavim6g4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ETed0WbDtavim6g4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ETed0WbDtavim6g4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ETed0WbDtavim6g4 .marker.cross{stroke:#333333;}#mermaid-svg-ETed0WbDtavim6g4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ETed0WbDtavim6g4 p{margin:0;}#mermaid-svg-ETed0WbDtavim6g4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ETed0WbDtavim6g4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ETed0WbDtavim6g4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ETed0WbDtavim6g4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ETed0WbDtavim6g4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ETed0WbDtavim6g4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ETed0WbDtavim6g4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ETed0WbDtavim6g4 .sequenceNumber{fill:white;}#mermaid-svg-ETed0WbDtavim6g4 #sequencenumber{fill:#333;}#mermaid-svg-ETed0WbDtavim6g4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ETed0WbDtavim6g4 .messageText{fill:#333;stroke:none;}#mermaid-svg-ETed0WbDtavim6g4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ETed0WbDtavim6g4 .labelText,#mermaid-svg-ETed0WbDtavim6g4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ETed0WbDtavim6g4 .loopText,#mermaid-svg-ETed0WbDtavim6g4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ETed0WbDtavim6g4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ETed0WbDtavim6g4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ETed0WbDtavim6g4 .noteText,#mermaid-svg-ETed0WbDtavim6g4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ETed0WbDtavim6g4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ETed0WbDtavim6g4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ETed0WbDtavim6g4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ETed0WbDtavim6g4 .actorPopupMenu{position:absolute;}#mermaid-svg-ETed0WbDtavim6g4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ETed0WbDtavim6g4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ETed0WbDtavim6g4 .actor-man circle,#mermaid-svg-ETed0WbDtavim6g4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ETed0WbDtavim6g4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt无画像有画像 异步连接 + 建表连接成功读取用户画像 (aget)写入默认画像 (aput)返回 existing.valuestream(messages, config)调用 call_agent(state)invoke(messages + 系统提示)返回回复{"messages": response}流式输出消息
二、代码逐段解析
1. 导入与配置
python
import os
import asyncio
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.postgres import PostgresStore # PostgreSQL Store
- 异步支持 :
PostgresStore使用async/await语法,因此主函数需要asyncio.run()。 - MessagesState :内置状态,包含
messages: Annotated[list, add_messages],自动追加新消息。 InMemorySaver:此处用于短期会话记忆(同一thread_id内状态持久化)。长期记忆由PostgresStore负责。PostgresStore:来自langgraph.store.postgres,用于在 PostgreSQL 中存储 JSON 文档。
2. 数据库连接字符串
python
DB_URI = "postgresql://langgraph_user:your_password@localhost:5432/langgraph_db"
- 格式:
postgresql://用户名:密码@主机:端口/数据库名 - 需提前在 PostgreSQL 中创建数据库和用户(可使用 pgAdmin 或命令行)。
3. 初始化 LLM
python
llm = ChatOpenAI(
model="qwen-plus",
temperature=0.7,
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv("DASHSCOPE_BASE_URL"),
model_kwargs={"extra_body": {"enable_thinking": False}}
)
- 使用阿里云百炼 Qwen 模型,兼容 OpenAI API 格式。
4. 定义 Agent 节点
python
def call_agent(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": [response]}
- 接收当前状态(包含
messages列表)。 - 调用 LLM 生成回复,并返回新消息(LangGraph 会自动追加到
messages中)。
5. 主异步函数 main()
a) 创建并设置 PostgresStore
python
async with await PostgresStore.from_conn_string(DB_URI) as store:
await store.setup()
print("✅ PostgreSQL 长期存储已连接")
PostgresStore.from_conn_string(DB_URI)返回一个异步上下文管理器。await store.setup()会创建必要的表(如果不存在),通常只需运行一次。- 退出
async with块时自动关闭连接。
b) 构建并编译图
python
builder = StateGraph(MessagesState)
builder.add_node("call_agent", call_agent)
builder.add_edge(START, "call_agent")
graph = builder.compile(checkpointer=checkpointer, store=store)
- 同时传入
checkpointer(短期记忆)和store(长期记忆)。 - LangGraph 会自动将
store对象注入到运行时环境中,但本例中并未在节点函数内部直接使用store,而是在外部先读取 store 内容,再构造带上下文的系统消息。这是一种简化的做法。
c) 读写长期存储(数据交互核心)
python
user_id = "user_001"
config = {"configurable": {"thread_id": f"{user_id}_thread"}}
namespace = ("profiles", user_id)
existing = await store.aget(namespace, "user_info")
if not existing:
await store.aput(namespace, "user_info", {
"name": "张明",
"hobby": "摄影",
"preferences": {"language": "zh-CN", "style": "detailed"}
})
print("💾 用户画像已写入 PostgreSQL")
else:
print(f"📋 读取用户画像: {existing.value}")
- Namespace :
("profiles", user_id)用于组织存储的文档。第一层"profiles"表示领域,第二层user_id区分不同用户。 aget:异步读取。如果存在,返回Item对象(包含.value字段);否则返回None。aput:异步写入/覆盖。将用户画像存储为 JSON 文档,键名为"user_info"。
📌 数据交互流程:
- 首次运行,无画像 → 写入默认画像。
- 后续运行(相同
user_id)→ 直接从 PostgreSQL 读取画像,无需重新写入。
d) 构造带长期记忆的对话输入
python
print("\n用户: 我喜欢拍风景照片")
messages = [("user", "我喜欢拍风景照片")]
if existing:
context = f"用户偏好:{existing.value}。请基于此回答。"
messages.insert(0, ("system", context))
- 如果读取到了画像,就将其作为系统消息(
SystemMessage)插入到对话开头,提示 LLM 利用这些信息。 - 这种方式不依赖 Agent 内部自动调用 store,而是由外部代码手动注入记忆。
e) 执行图并流式输出
python
for event in graph.stream(
{"messages": messages},
config=config,
stream_mode="values"
):
event["messages"][-1].pretty_print()
stream_mode="values"会在每个节点执行后返回完整状态。- 由于图中只有一个
call_agent节点,所以会输出一次模型的回复。 - 配置中的
thread_id用于InMemorySaver区分短期会话(本例中只使用了一次会话,未跨多次调用,因此短期记忆作用不明显,但语法上保留)。
三、数据库表结构(PostgresStore 自动创建)
执行 await store.setup() 后,会在 PostgreSQL 中创建以下表:
| 表名 | 用途 |
|---|---|
store |
存储文档的主表,包含字段:namespace (text\[\]), key (text), value (jsonb), created_at, updated_at |
store_embeddings |
可选,如果开启向量索引,存储 embedding |
可通过 pgAdmin 查看数据:
sql
SELECT * FROM store WHERE namespace = '{"profiles","user_001"}';
四、执行流程时序图(详细版)
时间轴
│
├─ 1. 主函数启动 → 加载 .env
│
├─ 2. 异步连接 PostgreSQL
│ └─ PostgresStore.from_conn_string() 建立连接池
│ └─ store.setup() → 创建表(首次)
│
├─ 3. 读取长期记忆
│ └─ store.aget(("profiles","user_001"), "user_info")
│ └─ PostgreSQL 执行 SELECT ... WHERE namespace = '{profiles,user_001}' AND key='user_info'
│ └─ 若不存在:store.aput(...) → INSERT 用户画像
│ └─ 若存在:existing.value 包含 JSON 对象
│
├─ 4. 构建消息列表
│ └─ 如果有画像,插入 SystemMessage:"用户偏好:{...}。请基于此回答。"
│ └─ 追加 UserMessage:"我喜欢拍风景照片"
│
├─ 5. 执行图 graph.stream()
│ ├─ 图内部:call_agent 节点
│ │ └─ llm.invoke(messages)
│ │ ├─ LLM 接收系统提示(含用户画像) + 用户问题
│ │ └─ 生成回复(例如:"张明,你喜欢摄影,拍风景照片是个好主意...")
│ └─ 返回 {"messages": [AIMessage(...)]}
│
├─ 6. 流式输出最终消息(pretty_print)
│
└─ 7. 退出 async with → 关闭数据库连接
五、与短期记忆(Checkpointer)的对比
| 特性 | 本案例中的 InMemorySaver |
PostgresStore |
|---|---|---|
| 存储内容 | 图的状态(messages 列表) |
任意 JSON 文档(用户画像) |
| 作用域 | 同一个 thread_id 内的多次调用 |
跨任意 thread_id,只要 user_id 相同 |
| 生命周期 | 进程结束后丢失(内存) | 持久化到磁盘,服务重启不丢失 |
| 访问方式 | 自动恢复(LangGraph 内部处理) | 需手动 store.get() / store.put() |
六、可能的改进方向
-
在节点内部直接访问 store
LangGraph 的运行时会将
store注入到RunnableConfig中,节点函数可以通过config["store"]获取。这样无需在外部手动读取和拼装系统消息,Agent 可以自主调用长期记忆。 -
使用
store.search进行语义检索如果启用了 embedding 索引,可以用自然语言查询长期记忆,而非依赖精确的 key。
-
结合
create_react_agent与 Tool定义工具
recall_user_memory和remember_user_memory,让 Agent 自主决定何时读写长期存储。
七、运行结果示例(假设第二次运行)
✅ PostgreSQL 长期存储已连接
📋 读取用户画像: {'name': '张明', 'hobby': '摄影', 'preferences': {'language': 'zh-CN', 'style': 'detailed'}}
用户: 我喜欢拍风景照片
================================ Human Message =================================
用户偏好:{'name': '张明', 'hobby': '摄影', 'preferences': {'language': 'zh-CN', 'style': 'detailed'}}。请基于此回答。
================================ Human Message =================================
我喜欢拍风景照片
================================== Ai Message ==================================
张明,你本来就喜欢摄影,拍风景照片正好发挥你的特长!建议试试清晨或黄昏的光线,效果更佳。📸
可以看到,模型成功利用长期记忆中存储的姓名和爱好给出了个性化回复。即使 thread_id 改变或程序重启,只要 user_id 不变,这些记忆依然存在。
八、总结
- PostgresStore 提供了异步 API,用于在 LangGraph 中实现跨会话、跨线程的长期记忆。
- 数据交互通过
store.aget/store.aput实现,数据以 JSON 格式存储在 PostgreSQL 的store表中。 - 本案例演示了外部手动注入记忆 的方式,适合简单场景;复杂场景应让 Agent 通过工具或
config["store"]自主访问长期存储。 - 与
Checkpointer相辅相成:Checkpointer负责单次会话的上下文连续性,Store负责跨会话的用户画像沉淀。
如果你需要进一步了解如何在节点函数内部动态使用 store,或如何集成向量检索,我可以提供进阶示例。
📚 最佳实践总结
| 最佳实践 | 说明 |
|---|---|
| 开发用 InMemory,生产用 PostgreSQL | InMemoryStore 验证逻辑,PostgresStore 部署到生产 |
| Namespace 设计建议 | (领域, 用户ID, 子类型) ------ 命名空间支持分层组织和跨命名空间搜索 |
| Store 核心 API | store.put(namespace, key, value)、store.get()、store.search(filter, query) |
| Store + Checkpointer 结合 | Store 提供跨会话长期记忆(用户画像),Checkpointer 维持线程内短期状态(会话历史) |
| Runtime 中访问 Store | 部署到 LangGraph Platform 时,runtime.store 可自动注入 |
| 语义检索支持 | Store 支持配置 index 参数开启向量检索,实现基于语义而非关键词的记忆召回 |
如果需要进一步了解语义检索(长期记忆 + RAG)的实现,或自定义 Store 集成其他数据库,请随时告诉我。
📋 代码流程分析 + 数据库验证步骤
你提供的代码实现了一个 LangGraph + PostgreSQL 长期记忆系统,核心功能是:在 PostgreSQL 中存储用户画像(姓名、爱好、偏好),并在对话中读取该画像注入系统提示,让 Agent 提供个性化回复。
一、代码执行流程(分阶段解析)
阶段 0:环境初始化
- 加载
.env环境变量(含DASHSCOPE_API_KEY、DASHSCOPE_BASE_URL)。 - 建立数据库连接字符串
postgresql://postgres:root@localhost:5432/langgraph_db。
阶段 1:异步连接 PostgreSQL
python
async with AsyncPostgresStore.from_conn_string(DB_URI) as store:
await store.setup()
- 使用 异步上下文管理器 创建与 PostgreSQL 的连接池。
store.setup()执行 DDL 语句,自动创建store表(如果表不存在)。表结构如下:namespace(text\[\]) ------ 分层命名空间key(text) ------ 文档键名value(jsonb) ------ 实际存储的 JSON 数据created_at,updated_at(timestamptz)
阶段 2:构建并编译 LangGraph 图
python
builder = StateGraph(MessagesState)
builder.add_node("call_agent", call_agent)
builder.add_edge(START, "call_agent")
graph = builder.compile(checkpointer=checkpointer, store=store)
- 图只有一条直线:
START → call_agent → END。 checkpointer = InMemorySaver:短期记忆(仅在此程序运行期间有效,用于同一thread_id的多轮对话)。store = AsyncPostgresStore:长期记忆,跨会话、跨程序运行持久化。
阶段 3:读写长期记忆
python
user_id = "user_001"
namespace = ("profiles", user_id)
existing = await store.aget(namespace, "user_info")
if not existing:
await store.aput(namespace, "user_info", {...})
else:
print(f"读取用户画像: {existing.value}")
- 命名空间 :
("profiles", "user_001")------ 类似文件夹路径,第一层是领域(profiles),第二层是用户ID。 - 键名 :
"user_info"。 - 若首次访问,写入默认画像;否则读取并打印。
阶段 4:构造带记忆的对话消息
python
messages = [("user", "我喜欢拍风景照片")]
if existing:
context = f"用户偏好:{existing.value}。请基于此回答。"
messages.insert(0, ("system", context))
- 如果读取到画像,将其转换为系统消息(SystemMessage),插入对话开头。
- 用户消息紧随其后。
阶段 5:执行图并输出回复
python
for event in graph.stream({"messages": messages}, config=config, stream_mode="values"):
event["messages"][-1].pretty_print()
- 图内的
call_agent节点调用 LLM,传入的消息列表:- SystemMessage(含画像)
- UserMessage("我喜欢拍风景照片")
- LLM 根据画像生成个性化回答,打印输出。
二、数据库验证步骤(使用 pgAdmin 或 psql)
✅ 前提条件
- PostgreSQL 服务已启动。
- 数据库
langgraph_db已创建(如果未创建,需手动执行CREATE DATABASE langgraph_db;)。 - 已运行上述 Python 代码至少一次(以执行
store.setup()建表及store.aput写入数据)。
步骤 1:连接到数据库
-
pgAdmin :在左侧树形菜单中找到
Servers → PostgreSQL → Databases → langgraph_db。 -
psql :在命令行输入:
bashpsql -U postgres -d langgraph_db密码:
root
步骤 2:查看表是否创建成功
sql
\dt
应该看到名为 store 的表。若使用异步版本,表名可能是 store 或 checkpoint_store(取决于版本),但最常见的是 store。
步骤 3:查询 store 表中的长期记忆数据
sql
SELECT * FROM store;
或者按命名空间查询:
sql
SELECT namespace, key, value FROM store WHERE namespace = '{profiles,user_001}';
预期输出示例(JSONB 格式):
namespace | key | value
----------------+-----------+------------------------------------------------------------------
{profiles,user_001} | user_info | {"name": "张明", "hobby": "摄影", "preferences": {"language": "zh-CN", "style": "detailed"}}
步骤 4:验证数据更新(后续运行)
- 修改代码中的画像内容(如将
hobby改为"绘画"),再次运行程序。 - 再次执行上述 SQL 查询,观察
value是否已更新。
步骤 5:检查表结构详细信息(可选)
sql
\d store
可查看列类型、约束等。
三、可能出现的问题及排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
async with ... 报错 TypeError |
使用了同步 PostgresStore 而非 AsyncPostgresStore |
确认导入的是 AsyncPostgresStore |
store.setup() 卡住或报权限错误 |
数据库用户无建表权限 | 使用 PostgreSQL 超级用户 postgres 或授予 CREATE 权限 |
查询 store 表返回空 |
store.aput 未成功执行或 namespace/key 错误 |
检查代码中 namespace 是否为 ("profiles", user_id),且键名为 "user_info" |
| 模型回答未使用画像信息 | 可能 existing 为 None 或消息构造逻辑有误 |
打印 existing 检查是否读取成功;确保 messages 中包含系统消息 |
四、总结:长期记忆的持久化流程
text
[代码首次运行]
└─ store.setup() → 在 PostgreSQL 中创建 store 表
└─ store.aput() → 插入一行 JSON 文档
└─ namespace = ['profiles', 'user_001']
└─ key = 'user_info'
└─ value = {"name": "张明", "hobby": "摄影", ...}
[代码再次运行(相同 user_id)]
└─ store.aget() → 根据 namespace + key 检索
└─ 从 store 表读取 JSON 文档
└─ 将 JSON 内容注入系统提示
└─ LLM 基于历史画像回答
通过以上验证步骤,你可以直观地看到 LangGraph 如何利用 PostgreSQL 实现跨会话的长期记忆存储。如果还有任何疑问,欢迎继续交流!