LangChain 学习笔记(九):上下文与记忆

LangChain 学习笔记(九):上下文与记忆

本文基于《尚硅谷 LangChain 1.2》第9章整理,并结合实际开发经验进行补充。

记忆(Memory)是 Agent 实现复杂多轮交互的核心基础。

本章深入讲解 LangChain v1.x 中基于 LangGraph 的短期记忆与长期记忆体系。


一、本章学习目标

学习完本章,你应该能够:

  • 理解大模型为什么是"无状态"的,以及记忆系统如何解决这个问题
  • 掌握 LangGraph 中 State + Checkpointer + Thread ID 的短期记忆机制
  • 会使用 InMemorySaver 和 PostgresSaver 实现会话持久化
  • 掌握消息裁剪、消息删除、摘要三种上下文管理策略
  • 理解 LangGraph Store 的四层存储架构(Store -> Namespace -> Key -> Value)
  • 会使用 InMemoryStore 和 PostgresStore 实现长期记忆
  • 能够在工具和中间件中读写长期记忆
  • 掌握 Static Runtime Context 的使用方式
  • 理解热路径写入 vs 后台写入的选择策略

二、为什么需要记忆(Memory)

1、大模型本身是"无状态"的

大多数大模型应用程序都会有一个会话接口,允许我们进行多轮对话。

但实际上,大模型本身不会记忆任何上下文。

每次调用 agent.invoke() 都是全新的开始,不记得之前的对话。

举例说明:

python 复制代码
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage

agent = create_agent(model=model, tools=[])

# 第一轮:自我介绍
messages1 = [
    HumanMessage("你好,我叫小明"),
    AIMessage("很高兴认识你,小明"),
    HumanMessage("你用一句话介绍下你自己"),
]
response1 = agent.invoke({"messages": messages1})
# 模型可以正常回答

# 第二轮:追问名字(新对话,没有历史)
messages2 = [HumanMessage("我叫什么名字?")]
response2 = agent.invoke({"messages": messages2})
# 模型回答:"我不知道你的名字,除非你告诉我。"

可以看到:

同样是问"我叫什么名字",有历史上下文时模型能答出来,没有历史上下文时模型完全不知道。

这就是大模型的"无状态"特性:

复制代码
每次 invoke()
    ↓
全新的开始
    ↓
不记得之前的任何对话

2、我们期望的行为

我们希望 Agent 能够:

复制代码
用户:你好,我叫小明
    ↓
Agent:你好,小明!
    ↓
用户:我叫什么名字?
    ↓
Agent:你叫小明。    ← 能记住之前的信息!

这是所有聊天应用的基本需求。

3、记忆问题的解决思路

实现记忆功能,需要额外的模块去保存上下文信息,然后在下一次请求时,把历史信息都输入给模型。

复制代码
第N轮调用
    ↓
收集历史消息 [msg1, msg2, ..., msgN-1]
    ↓
加上当前消息 msgN
    ↓
发送完整历史给模型
    ↓
模型"看到"了所有历史
    ↓
返回基于上下文的回答

在 LangChain 中,记忆(Memory)就是专门负责"存储历史交互信息"的组件。

核心作用:

  • 保存上下文
  • 提供上下文

上下文工程(Context Engineering)负责"合理组织"这些记忆和任务信息,让 LLM 的响应更连贯、更贴合需求。


三、上下文类型及相关的 API

LangChain 的上下文工程构建在 LangGraph 之上。

LangGraph 提供了三种管理上下文的方法:

复制代码
                    LangGraph 上下文体系
                           │
          ┌────────────────┼────────────────┐
          │                │                │
   动态运行时上下文    动态跨会话上下文    静态运行时上下文
   (State 对象)       (Store 对象)       (Context 对象)
          │                │                │
   单次运行内可变     跨会话持久化       启动时传入,不可变
   生命周期:单次运行  生命周期:跨会话    生命周期:单次运行
上下文类型 描述 可变性 生命周期 访问方法
动态运行时上下文 在单次运行中会演变的可变数据 动态 单次运行 LangGraph state 对象
动态跨会话上下文 在对话间共享的持久数据 动态 跨会话 LangGraph store 对象
静态运行时上下文 启动时传入的用户元数据、工具、数据库连接 静态 单次运行 LangGraph context 对象

举例:

复制代码
AI应用
├─ thread_id = t1 -> state_1
│  ├─ messages = [...]
│  ├─ current_intent = "travel_planning"
│  └─ collected_slots = {"destination": "北京"}
│
├─ thread_id = t2 -> state_2
│  ├─ messages = [...]
│  └─ current_intent = "write_report"
│
└─ shared store
   ├─ namespace = (user_1, "memories")
   │  ├─ key = "profile"
   │  │  value = {"name": "张三", "city": "上海", "preferences": ["简洁风格"]}
   │  └─ key = "travel_preference"
   │     value = {"favorite_cities": ["北京", "杭州"]}
   │
   └─ namespace = (user_2, "memories")
      └─ key = "profile"
         value = {"name": "李四", "city": "深圳"}

四、记忆的分类

1、短期记忆 vs 长期记忆

维度 短期记忆 长期记忆
作用范围 单个对话线程(Thread)内 跨会话、跨线程
生命周期 更换 thread_id 即消失 持久化存储,不随会话结束而消失
存储内容 当前会话的消息记录、临时状态 用户偏好、历史洞察、知识条目
底层实现 State + Checkpointer + Thread ID Store + Namespace + Key + Value
复制代码
短期记忆(会话级):
  Thread-1: [msg1, msg2, msg3, ...]
  Thread-2: [msg1, msg2, ...]
  互不干扰,各自独立

长期记忆(跨会话级):
  用户A: {偏好、历史、知识}
  用户B: {偏好、历史、知识}
  任何会话都可以随时访问

2、LangChain v1.x 的记忆管理方式

在 LangChain v0.x 版本中,通过专用的 xxxMemory 类管理记忆。

在 LangChain v1.x 版本中,Agent 构建在 LangGraph 图结构之上,通过 state 和 store 构建记忆系统。

  • state:短期记忆对象,以会话为单位组织,包含当前会话的所有消息记录以及自定义信息
  • store:长期记忆对象,跨会话持久化的数据,通常需要结合向量数据库或外部存储实现

五、短期记忆

LangChain 1.x 的短期记忆是三者的组合:

复制代码
State(会话内部状态)
    +
Checkpointer(持久化机制)
    +
Thread ID(会话作用域)
  • State :默认存储历史消息列表 messages,通过 State 管理历史消息
  • Checkpointer:负责将 State 作为检查点持久化保存,检查点是某个时刻的 State 快照
  • Thread ID:用于唯一标识 State,LangChain 运行时会按照 thread_id 读写 State 快照

类比:就像玩 RPG 游戏时的"自动存档"。

你不需要手动保存,系统在关键节点自动记录,下次进入游戏随时可以从上次的存档点继续。


5.1 基于内存的持久化器(InMemorySaver)

这是最便捷的使用方式,适合快速测试或调试。

没有记忆的情况

python 复制代码
from langchain.agents import create_agent
from langchain.messages import HumanMessage

agent = create_agent(model=model, tools=[])

# 第一轮
response1 = agent.invoke({
    "messages": [HumanMessage("我叫张三")]
})
print(f"Agent: {response1['messages'][-1].content}")
# 输出:Agent: 你好,张三!很高兴认识你。

# 第二轮
response2 = agent.invoke({
    "messages": [HumanMessage("我叫什么?")]
})
print(f"Agent: {response2['messages'][-1].content}")
# 输出:Agent: 我不知道你的名字,除非你告诉我。

第二轮无法记住第一轮的信息。

拥有记忆的情况(★★★★★)

python 复制代码
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"
    }
}

# 第一轮
response1 = agent.invoke(
    {"messages": [HumanMessage("我叫张三")]},
    config=config  # 传入 config
)
print(f"Agent: {response1['messages'][-1].content}")
# 输出:Agent: 你好,张三!很高兴认识你。

# 第二轮(使用相同的 thread_id)
response2 = agent.invoke(
    {"messages": [HumanMessage("我叫什么?")]},
    config=config  # 使用相同的 thread_id
)
print(f"Agent: {response2['messages'][-1].content}")
# 输出:Agent: 你叫张三。

你只需传入 checkpointerconfig,Agent 就能自然具备连续对话能力。

查看 State 快照

python 复制代码
from rich import print as rprint

latest_state = agent.get_state(config)
rprint(latest_state)
# 可以看到完整的 messages 列表和元数据

关键步骤说明

第1步:初始化记忆引擎

python 复制代码
checkpointer = InMemorySaver()

创建一个内存级的记忆存储。

注意:InMemorySaver 只在内存中保存,进程结束就丢失数据,适合测试。

第2步:绑定 Agent

create_agent 时传入 checkpointer,让 Agent 具备状态存储能力。

第3步:设定会话 ID

通过 config = {"configurable": {"thread_id": "1"}} 为每次调用指定线程标识。

同一个 thread_id 共享记忆,不同 thread_id 完全隔离。

thread_id 隔离机制

thread_id 是记忆管理的核心开关。

在会话2里询问会话1的信息,Agent 会表示不知道------因为双方记忆空间完全隔离。

复制代码
会话1 (thread_id="1"):          会话2 (thread_id="2"):
  [msg1, msg2, msg3, ...]         [msg1, msg2, ...]
        ↓                               ↓
   完全独立                          完全独立

生产环境的线程隔离场景

场景1:多用户聊天

python 复制代码
# 用户 Alice
config_alice = {"configurable": {"thread_id": "user_alice"}}
agent.invoke({"messages": [...]}, config_alice)

# 用户 Bob
config_bob = {"configurable": {"thread_id": "user_bob"}}
agent.invoke({"messages": [...]}, config_bob)

# 两个会话完全独立

场景2:同一用户的不同任务

python 复制代码
# 任务1:写代码
config_task1 = {"configurable": {"thread_id": "task_coding"}}
agent.invoke({"messages": [...]}, config_task1)

# 任务2:写文档
config_task2 = {"configurable": {"thread_id": "task_docs"}}
agent.invoke({"messages": [...]}, config_task2)

工作原理

checkpointer 自动完成以下步骤:

复制代码
① 读取之前的历史
    ↓
② 追加新消息
    ↓
③ 调用模型(传入完整历史)
    ↓
④ 保存新的历史

你只需要传新消息,checkpointer 自动管理历史:

python 复制代码
# 你只需要传新消息
agent.invoke(
    {"messages": [{"role": "user", "content": "新问题"}]},
    config
)

常见问题

1、为什么 Agent 不记得?

检查清单:

检查项 正确做法
是否添加了 checkpointer checkpointer=InMemorySaver()
是否传入了 config 参数 config={"configurable": {"thread_id": "1"}}
两次调用的 thread_id 是否相同 相同 thread_id 才共享记忆
python 复制代码
# ❌ 错误:没有 checkpointer
agent = create_agent(model=model, tools=[])
agent.invoke({...})  # 不会记住

# ❌ 错误:没有 config
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())
agent.invoke({...})  # 不会记住

# ❌ 错误:thread_id 不同
agent.invoke({...}, config={"configurable": {"thread_id": "1"}})
agent.invoke({...}, config={"configurable": {"thread_id": "2"}})  # 不同会话

# ✅ 正确
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "1"}}
agent.invoke({...}, config)
agent.invoke({...}, config)  # 记得!

2、InMemorySaver 会丢失数据吗?

会!InMemorySaver 只保存在内存中:

  • ✅ 同一进程内有效
  • ❌ 程序重启后丢失
  • ❌ 不同进程无法共享

解决方案:持久化(SQLite、PostgreSQL)。

3、内存会无限增长吗?

会!默认情况下,InMemorySaver 会保存所有消息。

问题:

  • 消息越来越多(无限增长,需要管理上下文)
  • Token 消耗增加,甚至会超过模型的 token 限制
  • 响应速度变慢、成本增加

解决方案:上下文管理(裁剪、摘要)。

4、如何清空某个会话的历史?

InMemorySaver 没有提供删除 API。

临时方案:

  • 使用新的 thread_id
  • 或重新创建 Agent

5.2 基于外部存储介质的持久化器(PostgresSaver)

如果将状态检查点保存在内存,进程结束则状态丢失,生产环境不可接受。

因此,生产环境要用持久化的外部存储介质,如 PostgreSQL。

数据库环境准备

连接 URL 格式:

复制代码
postgresql://用户名:密码@IP:端口/数据库名?sslmode=disable

依赖安装:

bash 复制代码
pip install langgraph-checkpoint-postgres

代码实现

python 复制代码
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from langgraph.checkpoint.postgres import PostgresSaver

DB_URL = "postgresql://langchain_user:abcd1234@118.195.128.47: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
    )

    # 第二次调用(能记住)
    response2 = agent.invoke(
        {"messages": [HumanMessage("你好,我是谁?")]},
        config=config
    )
    # 输出:你是老王。

setup() 用于初始化 PostgreSQL 数据库,首次运行会创建必要的表,底层逻辑是 CREATE IF NOT EXISTS

PostgreSQL 存储结构

复制代码
Database: langgraph_db
  └─ Schema: public
       ├─ checkpoints          -- 主表,存每个 thread 的 checkpoint 快照
       ├─ checkpoint_blobs     -- 存较复杂的 channel 值
       ├─ checkpoint_writes    -- 存中间写入 / pending writes
       └─ checkpoint_migrations -- 迁移版本表

5.3 两种持久化方式对比

InMemorySaver 测试

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

# 每次运行都创建新的 InMemorySaver()
agent = create_agent(model=model, checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "1"}}

agent.invoke({"messages": [HumanMessage("我是老王")]}, config)
# ... 程序结束,重新运行 ...

# 再次运行:即使 thread_id 相同,也看不到历史状态
# 因为新的 InMemorySaver() 是空的

PostgresSaver 测试

python 复制代码
from langgraph.checkpoint.postgres import PostgresSaver

with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
    checkpointer.setup()
    agent = create_agent(model=model, checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "3"}}

    agent.invoke({"messages": [HumanMessage("我是老王")]}, config)
    # ... 程序结束,重新运行 ...

    # 再次运行:PostgreSQL 中数据还在
    # 只要 thread_id 一致,就能加载历史状态

总结

特性 InMemorySaver PostgresSaver
存储位置 内存 PostgreSQL 数据库
进程重启 ❌ 数据丢失 ✅ 数据持久
重建 Saver ❌ 历史状态丢失 ✅ 可加载历史
跨进程共享 ❌ 不支持 ✅ 支持
适用场景 开发测试 生产环境
配置复杂度 ★☆☆☆☆ ★★★☆☆

5.4 记忆治理策略(上下文管理)

随着对话的进行,历史消息不断累积,state 会持续增长,带来挑战:

  1. LLM 的上下文窗口是有限的,完整历史可能无法装入
  2. 即便窗口够大,多数 LLM 在长上下文场景仍然表现不佳(被陈旧内容"分散注意力")
  3. 高昂的 token 花费

此时需要对上下文进行管理:压缩、清理、重组。


5.4.1 消息裁剪(Trim Messages) ★★★★★

调用模型前裁剪上下文。

目标是控制 token 用量,通常保留系统初始消息和最近若干消息,或按 token 数保留末尾内容。

适合成本敏感、对旧上下文依赖不强的场景。

python 复制代码
from langchain_core.messages import HumanMessage
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
from langchain_core.runnables import RunnableConfig
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]
    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
)
# 模型仍然可以回答:"我是你的AI助手。你是老王。"

裁剪流程分析:

复制代码
invoke("你好,我是老王") → 1条消息,不触发裁剪
    ↓
invoke("从现在起,你叫小王") → 3条消息,不触发裁剪
    ↓
invoke("今天天气不错") → 5条消息,触发裁剪!
  保留第一条 + 后4条
    ↓
invoke("告诉我,你是谁?我是谁?") → 7条消息,触发裁剪!
  保留第一条 + 后4条

5.4.2 消息删除(Delete Messages)

消息裁剪强调"在模型调用前裁剪消息列表",而消息删除强调"模型调用完成后将某些消息从消息列表中移除",永久更改状态。

适合明确要遗忘、清理、重置某些历史的场景。

python 复制代码
from langchain.messages import RemoveMessage
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import after_model
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.runtime import Runtime
from langchain_core.runnables import RunnableConfig

@after_model
def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:
    messages = state["messages"]
    # 保持最近的 5 条消息
    if len(messages) > 5:
        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)
# 输出:我是小王,你是老王。

RemoveMessage 的底层机制:

复制代码
[历史消息池(内存中持续存在)]
 ├── Message(id="1", content="你好,我是老王")
 ├── Message(id="2", content="...")
 └── RemoveMessage(id="1")  ← 这是一个"墓碑"标记

当下一次读取上下文时:
  原始消息 + 墓碑标记 → Reducer 合并计算 → 过滤掉被标记的消息

RemoveMessage 并非真的删除,而是追加一个"墓碑"标记。

框架的内置合并器(Reducer)在丢给大模型之前,自动把被标记删除的消息过滤掉。


5.4.3 摘要(Summarization) ★★★★★

把早期历史压缩成摘要,再替换原始消息。

消息裁剪和删除都会导致上下文缺失,影响回答质量和用户体验。

摘要是更适合长会话的折中方案:保语义,不保原文

官方推荐内置 SummarizationMiddleware

python 复制代码
from langchain.agents.middleware import SummarizationMiddleware
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

# 创建带摘要中间件的 Agent
agent = create_agent(
    model=model_out,
    tools=[],
    checkpointer=InMemorySaver(),
    middleware=[
        SummarizationMiddleware(
            model=model_in,           # 用于生成摘要的模型(可用便宜的)
            trigger=[
                ("tokens", 100),      # 超过 100 tokens 就触发摘要
            ],
            keep=("messages", 2),     # 保留最近的 2 条消息
            summary_prompt="对历史消息摘要,消息列表如下\n{messages}",
        )
    ]
)

config = {"configurable": {"thread_id": "1"}}

conversations = [
    "我叫张三,是工程师。这里是一段非常长非常长的废话..." * 20,  # 撑爆 100 tokens
    "请总结一下我的信息"
]

for msg in conversations:
    response = agent.invoke(
        {"messages": [{"role": "user", "content": msg}]},
        config=config
    )
# 第二轮的 messages 中自动包含了摘要信息

摘要工作原理:

复制代码
对话历史: [消息1, 消息2, ..., 消息20] (超过 100 tokens)
    ↓
SummarizationMiddleware 自动触发
    ↓
摘要旧消息: "用户是张三,在北京工作,喜欢编程..."
    ↓
新历史: [摘要, 最近消息] (token 数大幅减少)

常见问题

  1. 摘要会丢失信息吗?

    • 会有一些细节丢失
    • 但重要信息会保留(姓名、关键事实)
    • 最近的消息完整保留
    • 对于大部分场景足够
  2. 摘要成本高吗?

    • 摘要只在超过阈值时触发
    • 可以使用便宜的模型(如 gpt-4o-mini)
    • 相比传输全部历史,通常更便宜
  3. 设置最大 token 数的标准?

    复制代码
    模型上下文窗口 4k  → 设置 3000
    模型上下文窗口 8k  → 设置 6000
    模型上下文窗口 16k → 设置 12000
    留一些余量给工具调用和系统提示
  4. 摘要触发频率如何调整?

    • 如果频繁触发 → 提高阈值
    • 如果从不触发 → 降低阈值
    • 根据监控调整

5.4.4 三种策略对比

策略 时机 原理 优点 缺点 适用场景
消息裁剪 模型调用前 只保留最近N条 简单直接 丢失旧上下文 成本敏感、短会话
消息删除 模型调用后 永久移除旧消息 状态可控 不可恢复 明确要遗忘的场景
摘要 超过阈值时 旧消息压缩为摘要 保语义 细节丢失 长会话最佳方案

5.5 State 的理解

state 是 agent 底层有状态运行图的状态信息,是 AgentState 类型的实例。

python 复制代码
class AgentState(TypedDict, Generic[ResponseT]):
    """State schema for the agent."""
    messages: Required[Annotated[list[AnyMessage], add_messages]]
    jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
    structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]
字段 是否必须 说明
messages Required 截止到当前节点的历史会话消息记录
jump_to NotRequired 跳转至运行图的指定节点
structured_response NotRequired 结构化输出内容

AgentState 是 TypedDict 的子类,可以按照字典的读写方式访问。


六、长期记忆

6.1 基本理解

什么是长期记忆

短期记忆记录的是会话级别(Thread)的数据,会话间不共享。

长期记忆记录的是用户特定或应用级别的数据,任何会话都可以随时访问。

比如:

复制代码
你喜欢简短回答
你偏好 Python
某个用户是 VIP
某个流程过去怎么做效果更好

这类信息不属于某一条聊天,而属于"用户 / 组织 / 应用本身"。

类型划分(参考 CoALA paper)

记忆类型 存储内容 举例
Semantic(语义记忆) 事实 用户喜欢简洁回答、用户常用中文
Episodic(情景记忆) 经验 过去某个任务怎么成功的、Few-shot examples
Procedural(程序性记忆) 规则/做事方法 Agent 系统提示词、工作流程、工具调用规则

存储架构(四层结构)

长期记忆的存储是 Store -> Namespace -> Key -> Value 的四层架构。

复制代码
Store(记忆仓库)
  └─ Namespace(命名空间)
       └─ Key(键)
            └─ Value(值,字典类型)

第1层:Store(记忆仓库)

langgraph.store.base.BaseStore 的子类。

常用实现类:

实现类 适用场景
InMemoryStore 开发测试
PostgresStore 生产环境

第2层:Namespace(命名空间)

数据类型是 tuple[str, ...],用于给长期记忆分组和隔离。

作用很像"文件路径 / 文件夹层级"。

复制代码
("users", "user_123", "preferences")
("users", "user_123", "memories")
("org", "company_a", "settings")

第3层:Key(键)

该 namespace 下的唯一标识,字符串类型。

第4层:Value(值)

存储的值,字典类型 dict[str, Any]

完整示例:

python 复制代码
namespace = ("users", "user_123", "preferences")  # 元组
key = "profile"                                    # 字符串
value = {                                          # 字典
    "language": "zh-CN",
    "style": "short_direct",
    "likes": ["python", "rag"]
}
store.put(namespace, key, value)

6.2 基础 API 的使用

LangChain 1.2.x 的长期记忆基于 store 持久化数据,相关 API 有:

API 作用 说明
put() 写入 支持 TTL 过期、语义索引配置
get() 读取 按 namespace + key 精确查询,返回 Item 对象
search() 检索 支持按前缀搜索、按 filter 过滤、按 query 语义检索

6.2.1 put() / get():写入与读取

put() 写入
python 复制代码
def put(
    self,
    namespace: tuple[str, ...],
    key: str,
    value: dict[str, Any],
    index: Literal[False] | list[str] | None = None,
    *,
    ttl: float | None | NotProvided = NOT_PROVIDED,
) -> None:

参数说明:

参数 说明
namespace 文档所在的层级路径
key 该路径下的唯一键
value 要保存的 JSON-like 字典
index None=使用默认索引配置;False=不建语义索引;liststr=指定字段建索引
ttl 可选,过期时间
python 复制代码
store.put(
    ("users", "alice", "memories"),  # namespace
    "pref_food",                      # key
    {"category": "food", "text": "Alice likes sushi"}  # value
)
get() 读取
python 复制代码
def get(
    self,
    namespace: tuple[str, ...],
    key: str,
    *,
    refresh_ttl: bool | None = None,
) -> Item | None:

返回的不止是 value,而是完整的 Item 对象。

python 复制代码
item = my_store.get(("users", "alice", "memories"), "pref_food")
if item is not None:
    print(item.value)
    # {'category': 'food', 'text': 'Alice likes sushi'}

Item 对象包含:

复制代码
Item(
    namespace=['users', ...],
    key='...',
    value={...},
    created_at='2026-06-11T...',
    updated_at='2026-06-11T...'
)
基于 InMemoryStore 的示例
python 复制代码
from 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': '小蓝'}, ...)

# 更新(覆盖写入)
store.put(namespace, user_id, {"name": '小红'})
print(store.get(namespace, user_id))
# Item(namespace=['users'], key='user-1', value={'name': '小红'}, ...)

注意:InMemoryStore 每次 put 都会创建新的 Item 对象,created_at 和 updated_at 始终一致。

基于 PostgresStore 的示例
python 复制代码
from langgraph.store.postgres import PostgresStore

namespace = ("users",)
user_id = "user-11"
username = "小蓝"

DB_URL = "postgresql://langchain_user:abcd1234@118.195.128.47: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))
    # created_at='...', updated_at='...'

# 更新(update 而非覆盖)
with PostgresStore.from_conn_string(DB_URL) as store:
    store.setup()
    store.put(namespace, user_id, {"name": "小红"})
    print(store.get(namespace, user_id))
    # created_at 没变,但 updated_at 更改了

PostgresStore 更改数据的逻辑是 update 而非覆盖,created_at 固定为创建时间,updated_at 为更新时间。


6.2.2 search():检索 API

python 复制代码
def search(
    self,
    namespace_prefix: tuple[str, ...],
    /,
    *,
    query: str | None = None,
    filter: dict[str, Any] | None = None,
    limit: int = 10,
    offset: int = 0,
    refresh_ttl: bool | None = None,
) -> list[SearchItem]:

参数说明:

参数 说明
namespace_prefix 命名空间前缀,在该前缀下搜索
query 语义检索时用于查询的自然语言
filter 过滤条件,value 中的键值对组合
limit 返回 item 的最大条数
offset 跳过前 N 条

支持两种检索方式:

  • filter 做结构化过滤:用 value 中的键值筛选
  • query 做语义相似度检索:需要将输入转换为向量
按 namespace 前缀搜索
python 复制代码
# 搜索所有 users 下的记忆
for item in store.search(("users",)):
    print(item)

# 只搜索 Alice 的记忆
for item in store.search(("users", "Alice")):
    print(item)
按 filter 过滤
python 复制代码
# 过滤特定食物偏好
for item in store.search(("users",), filter={"food": "紫光园奶皮子酸奶"}):
    print(item)

# 过滤特定运动偏好
for item in store.search(("users",), filter={"sports": "跑步"}):
    print(item)

# 过滤特定课程偏好
for item in store.search(("users",), filter={"course": "数字电路与模拟电路"}):
    print(item)
按语义搜索

需要先配置索引(IndexConfig):

python 复制代码
from langgraph.store.memory import InMemoryStore

# 方式1:使用嵌入模型
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,          # text-embedding-3-large 的维度
    "fields": ["$"]         # 对整个 value 做嵌入
}

store = InMemoryStore(index=index_config)

# 写入数据后,按语义搜索
for item in store.search(("users",), query="数电模电"):
    print(item)
    # 按相似度降序返回,附带 score 字段

IndexConfig 参数:

参数 说明
embed 嵌入函数(自定义函数或嵌入模型对象)
dims 输出向量维度
fields 用于计算向量的属性列表

fields 可取值:

  • ["$"]:将 value 作为整体嵌入
  • ["field1", "field2"]:单独指定某个一级字段
  • ["parent.child"]:从嵌套 JSON 对象中获取子字段
  • ["array[*].field"]:从 JSON 数组的每个对象中获取子字段

6.3 在 Agent 运行图中访问长期记忆

可以在工具或中间件中访问长期记忆。

6.3.1 在工具中访问长期记忆(★★★★★)

工具函数可以通过 ToolRuntime 参数访问 store。

python 复制代码
from typing import NotRequired
from langchain.agents import create_agent, AgentState
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,
)

# 第一个会话
response1 = agent.invoke({
    "messages": [HumanMessage("你好,很高兴认识你,我是小花")],
    "user_id": "user-1"
})

# 第二个会话(没有通过 config 串联,是独立会话)
response2 = agent.invoke({
    "messages": [HumanMessage("我是谁")],
    "user_id": "user-1"
})
# Agent 仍能回答:"你是小花。"
# 因为长期记忆跨会话共享!

关键点:

两次 invoke 没有通过 config 串联,是两个独立的会话,但第二个会话可以访问第一个会话写入长期记忆的内容。

这就是长期记忆的核心价值:跨会话数据共享

基于 PostgresStore 的实现类似,只需替换 store 为 PostgresStore。

PostgreSQL 数据库中会新增两张表:

复制代码
store               -- 长期记忆数据表
store_migrations    -- 迁移版本表

6.3.2 在中间件中访问长期记忆

中间件可以通过 runtime.store 访问长期记忆。

Node-style hooks(before_model, after_model 等)
python 复制代码
@before_model
def my_before_model(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    # 通过 runtime.store 访问长期记忆
    store = runtime.store
    item = store.get(("users", "memories"), "some_key")
    ...

Runtime 定义:

python 复制代码
@dataclass
class Runtime(Generic[ContextT]):
    context: ContextT          # 静态上下文
    store: BaseStore | None    # 长期记忆存储
    stream_writer: StreamWriter
    previous: Any
Wrap-style hooks(wrap_model_call, wrap_tool_call)
python 复制代码
class MyMiddleware(AgentMiddleware):
    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        # 通过 request.runtime.store 访问长期记忆
        store = request.runtime.store
        ...
        return handler(request)

    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
        # 通过 request.runtime.store 访问长期记忆
        store = request.runtime.store
        ...
        return handler(request)

6.4 何时写入记忆

官方介绍了两种方式:

方式1:在主流程里写(Hot Path)

也就是:用户发消息,AI 一边回答,一边决定要不要记下来。

复制代码
用户消息 → Agent 处理 → 同时写入记忆 → 返回响应
优点 缺点
立即生效 增加延迟
下一轮马上能用 逻辑变复杂
用户可感知,透明 ------

方式2:在后台写(Background)

先回答用户,记忆整理放到后台异步做。

复制代码
用户消息 → Agent 处理 → 返回响应
                ↓
         后台异步 → 写入记忆
优点 缺点
主流程更快 不能立刻生效
记忆逻辑更独立 要决定多久整理一次
更适合批量整理 触发时机不好选

工程上的选择建议

数据类型 推荐方式
用户偏好、账号资料 热路径写入
对话摘要、经验沉淀、行为分析 后台写入

七、静态运行时上下文(Static Runtime Context)

静态运行时上下文表示不可变的数据,如用户元数据、工具和传递给应用程序的数据库连接对象。

通常在运行开始时通过 invoke / streamcontext 参数传递,此类数据在运行期间不会更改。

7.1 中间件中访问

通过 runtime.context 访问上下文对象。

用户额度校验示例

python 复制代码
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore

store = InMemoryStore()
namespace = ("users", "credits")

# 准备测试数据
store.put(namespace, 'Ada Lovelace', {"tokens_credit_left": 5000, "user_level": 5})
store.put(namespace, 'Blackwell', {"tokens_credit_left": 2999, "user_level": 5})
store.put(namespace, 'Ampere', {"tokens_credit_left": 1000, "user_level": 5})

@dataclass
class UserContext:
    username: str

class CheckCredit(AgentMiddleware):
    @hook_config(can_jump_to=["end"])
    def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        store = runtime.store
        context = runtime.context
        username = context.username
        credit = store.get(namespace, username)

        if not credit:
            return {
                "jump_to": "end",
                "messages": AIMessage("您尚未注册~")
            }

        if credit.value["tokens_credit_left"] < 3000:
            return {
                "jump_to": "end",
                "messages": AIMessage(f"{username}额度不足,请充值")
            }

        return None

    def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        # 扣减 token 额度
        store = runtime.store
        context = runtime.context
        username = context.username
        credit = store.get(namespace, username)
        usage_metadata = state["messages"][-1].usage_metadata
        token_usage = usage_metadata["input_tokens"] + usage_metadata["output_tokens"] * 6
        credit.value["tokens_credit_left"] -= token_usage
        store.put(namespace, username, credit.value)
        return None

    def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        store = runtime.store
        context = runtime.context
        username = context.username
        item = store.get(namespace, username)
        credit = item.value["tokens_credit_left"]
        logger.info(f"{username} 当前剩余额度:{credit}")
        return None

agent = create_agent(
    model="deepseek-chat",
    middleware=[CheckCredit()],
    store=store,
    context_schema=UserContext
)

# 调用时传入 context
response1 = agent.invoke(
    {"messages": ["你好啊,你知道 Ada Lovelace 的贡献吗?"]},
    context=UserContext(username="Ada Lovelace")
)
# Ada Lovelace 额度充足,正常回答

response2 = agent.invoke(
    {"messages": ["你好啊,你知道 Blackwell 的贡献吗?"]},
    context=UserContext(username="Blackwell")
)
# 输出:Blackwell额度不足,请充值

动态调整工具集示例

根据用户权限动态限制可用的工具:

python 复制代码
class CheckCredit(AgentMiddleware):
    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        context = request.runtime.context
        store = request.runtime.store
        value = store.get(namespace, context.username).value

        tools = []
        for tool in request.tools:
            tool_name = tool.name
            if value.get(tool_name) == "yes":
                tools.append(tool)
            else:
                logger.warning(f"{context.username} 无权调用 {tool_name}")

        # 更改调用请求携带的工具集,仅本次生效
        request = request.override(tools=tools)
        return handler(request)

动态系统提示词示例(dynamic_prompt)

python 复制代码
from langchain.agents.middleware import dynamic_prompt, ModelRequest

@dynamic_prompt
def personalized_prompt(request: ModelRequest) -> str:
    username = request.runtime.context.username
    store = request.runtime.store
    preferences = store.get(namespace, username).value["chat_preferences"]
    custom_prompt = f"# 用户偏好\n{'\n'.join(preferences)}"
    logger.info(f"{username} 自定义系统提示词\n{custom_prompt}")
    return custom_prompt

agent = create_agent(
    model="deepseek-chat",
    middleware=[personalized_prompt],
    store=store,
    context_schema=UserContext
)

# 不同用户获得不同的系统提示词
# Ada Lovelace: "不喜欢啰嗦,尽可能用最精简的文字解释清楚"
# Blackwell: "喜欢长篇大论、引经据典,不知道的东西要明确说'不知道'"

7.2 工具中访问 Context

工具函数可以通过 ToolRuntime 的泛型参数指定 Context 类型。

python 复制代码
from pydantic import BaseModel, Field
from typing import List, Any
from dataclasses import dataclass

@dataclass
class UserContext:
    user_id: str

class UserInfo(BaseModel):
    username: str = Field(description="用户名", default="unknown")
    age: int = Field(description="年龄", default=0)
    hobbies: List[str] = Field(description="兴趣爱好", default_factory=list)

@tool(parse_docstring=True)
def read_user_info(runtime: ToolRuntime[UserContext, Any]) -> UserInfo | str:
    """
    读取用户信息
    """
    user_id = runtime.context.user_id
    store = runtime.store
    item = store.get(("users", "user_info"), user_id)
    if item:
        return UserInfo(**item.value)
    return ''

@tool(parse_docstring=True)
def write_user_info(user_info: UserInfo, runtime: ToolRuntime[UserContext, Any]) -> bool:
    """
    将用户信息写入长期记忆
    """
    user_id = runtime.context.user_id
    store = runtime.store
    try:
        logger.info(f"写入记忆, user_id: {user_id}, user_info: {user_info.model_dump()}")
        store.put(("users", "user_info"), user_id, user_info.model_dump())
    except Exception as e:
        logger.error(e)
        return False
    return True

# 使用示例
agent = create_agent(
    model="deepseek-chat",
    tools=[read_user_info, write_user_info],
    store=store,
    checkpointer=InMemorySaver(),
    context_schema=UserContext
)

config1 = {"configurable": {"thread_id": "thread_1"}}
config2 = {"configurable": {"thread_id": "thread_2"}}

# 线程1:首次写入用户信息
agent.invoke(
    {"messages": [
        SystemMessage("如果输入包含用户信息,抽取并调用工具记录..."),
        HumanMessage("你好,我是韩立,我喜欢修仙")
    ]},
    context=UserContext(user_id='user_1'),
    config=config1
)

# 线程1:追加信息(共享短期记忆)
thread_1_response = agent.invoke(
    {"messages": ["我今年两百岁了,是你们口中的'元婴老怪',我喜欢跑步"]},
    context=UserContext(user_id='user_1'),
    config=config1
)

# 线程2:不同会话,但同一用户,可以通过长期记忆获取信息
thread_2_response = agent.invoke(
    {"messages": ["你还记得我吗?"]},
    context=UserContext(user_id='user_1'),
    config=config2  # 不同的 thread_id
)
# Agent 能回答:你是韩立,200岁,喜欢修仙和跑步。

分析:

  1. 前两次 invoke 通过相同的 thread_id 串联为一个会话,共享短期记忆
  2. context 是运行时静态配置,每次 invoke 互相独立
  3. 长期记忆可以在任意位置访问,全局共享同一份信息
  4. tool 参数列表中的 ToolRuntime 是 LangChain/LangGraph 运行时注入的
  5. ToolRuntime 的泛型需要正确指定 Context 类型,否则可能抛出警告

八、LangChain v0.x 的传统 Memory 类(了解)

在 LangChain v0.x 中的传统 Memory 类已被 v1.x 的 LangGraph 体系取代,但了解它们有助于理解设计演进。

Memory 类 对应 v1.x 实现 特点
ConversationBufferMemory State + Checkpointer 保存所有消息
ConversationBufferWindowMemory Trim Messages 中间件 只保留最近K轮对话
ConversationTokenBufferMemory Token-based Trim 按 token 数保留
ConversationSummaryMemory SummarizationMiddleware 旧消息压缩为摘要

v1.x 不再直接使用这些类,而是通过 LangGraph 的 State、Checkpointer、Store 和 Middleware 统一实现。


九、本章完整的记忆体系总览

复制代码
                        LangChain Memory System
                               │
              ┌────────────────┼────────────────┐
              │                │                │
         短期记忆           长期记忆         静态上下文
    (会话级/Thread级)    (跨会话级)       (单次运行)
              │                │                │
    State + Checkpointer    Store + NS      Context Schema
         + Thread ID        + Key + Value       │
              │                │                │
    ┌────────┼────────┐  ┌────┼────┐    ┌──────┼──────┐
    │        │        │  │    │    │    │      │      │
InMemory  SQLite  Postgres InMemory Postgres  用户元数据
  Saver   Saver   Saver   Store   Store   工具/DB连接
              │                │                │
    ┌────────┼────────┐       │                │
    │        │        │       │                │
  裁剪    删除     摘要      put/get        runtime.context
(Middleware体系)          /search

十、最佳实践总结

短期记忆

  1. 开发测试用 InMemorySaver,生产用 PostgresSaver

    • InMemorySaver 进程重启即丢失
    • PostgresSaver 支持持久化和跨进程共享
  2. thread_id 是记忆隔离的核心开关

    • 同一用户不同任务用不同 thread_id
    • 不同用户必须用不同 thread_id
  3. 必须进行上下文管理

    • 不做管理会导致 token 无限增长
    • 推荐使用 SummarizationMiddleware 做摘要
  4. 摘要触发阈值建议

    复制代码
    模型上下文窗口 4k  → 阈值 3000
    模型上下文窗口 8k  → 阈值 6000
    模型上下文窗口 16k → 阈值 12000

长期记忆

  1. 使用命名空间隔离不同租户的数据

    复制代码
    ("users", user_id, "memories")   -- 用户记忆
    ("users", user_id, "preferences") -- 用户偏好
    ("org", org_id, "settings")      -- 组织设置
  2. 开发测试用 InMemoryStore,生产用 PostgresStore

  3. 摘要模型选择便宜的模型

    • 用 gpt-4o-mini 做摘要
    • 用 gpt-5.4-mini 做主对话

写入策略

  1. 按数据类型选择写入时机

    • 用户偏好、账号资料:热路径写入
    • 对话摘要、经验沉淀:后台写入
  2. 通过中间件统一管理记忆逻辑

    • 额度校验、工具权限控制等放在中间件中
    • 避免在业务逻辑中散落记忆操作

安全

  1. 静态 Context 中不要存放敏感数据
    • Context 会在日志中打印
    • 敏感数据用加密存储

十一、面试常见问题

Q1:为什么大模型需要记忆系统?

大模型本身是"无状态"的,每次调用 invoke() 都是全新的开始,不记得之前的对话。

记忆系统通过在每次请求时自动收集历史消息并一起发送给模型,让模型"看到"之前的对话内容,从而实现多轮对话。

Q2:LangChain v1.x 如何实现短期记忆?

短期记忆 = State + Checkpointer + Thread ID:

  • State 存储历史消息列表
  • Checkpointer 负责持久化 State 快照
  • Thread ID 隔离不同会话空间

使用时只需传入 checkpointerconfig(含 thread_id),Agent 自动管理历史消息。

Q3:InMemorySaver 和 PostgresSaver 怎么选?

场景 选择
开发测试 InMemorySaver
生产环境 PostgresSaver
需要跨进程共享 PostgresSaver
简单脚本 InMemorySaver

Q4:长期记忆的存储架构是怎样的?

四层结构:Store -> Namespace -> Key -> Value

  • Store:记忆仓库(InMemoryStore / PostgresStore)
  • Namespace:命名空间,用于分组隔离,如 ("users", "user_123", "preferences")
  • Key:唯一键
  • Value:字典类型的值

Q5:消息裁剪、删除、摘要有什么区别?

  • 裁剪:模型调用前临时过滤,控制模型看到的上下文范围
  • 删除:模型调用后永久移除,更改 State 状态
  • 摘要:旧消息压缩为摘要保留语义,是长会话的最佳方案

Q6:什么时候用热路径写入,什么时候用后台写入?

  • 热路径:立即需要生效的数据(用户偏好、账号资料)
  • 后台写入:不需要立即生效的数据(对话摘要、经验沉淀、行为分析)

Q7:Static Runtime Context 和 Store 的区别是什么?

维度 Context Store
可变性 静态,运行期间不变 动态,可读写
生命周期 单次运行 跨会话持久
传递方式 invoke(context=...) Agent 创建时绑定
典型用途 用户身份、DB连接 用户偏好、知识记忆

Q8:如何处理多个用户之间的记忆隔离?

短期记忆通过不同的 thread_id 隔离。

长期记忆通过不同的 namespace 隔离,例如:

python 复制代码
("users", "user_alice", "memories")
("users", "user_bob", "memories")

Q9:为什么要用便宜的模型做摘要?

摘要需要额外的模型调用,使用便宜模型可以控制成本。

摘要只在超过 token 阈值时触发,使用 gpt-4o-mini 这样的小模型即可完成质量不错的摘要。

Q10:RemoveMessage 底层是怎么工作的?

RemoveMessage 不是真的删除消息,而是追加一个"墓碑"标记。

框架的 Reducer 在下次读取上下文时,会把原始消息和墓碑标记合并计算,自动过滤掉被标记删除的消息。


十二、本章总结

主题 核心内容 关键 API
无状态问题 LLM 每次调用都是全新开始 agent.invoke()
短期记忆 State + Checkpointer + Thread ID InMemorySaver, PostgresSaver
线程隔离 不同 thread_id 完全隔离 config={"configurable": {"thread_id": "1"}}
消息裁剪 调用前临时过滤旧消息 before_model, RemoveMessage
消息删除 调用后永久移除旧消息 after_model, RemoveMessage
摘要 旧消息压缩为摘要 SummarizationMiddleware
长期记忆 Store + Namespace + Key + Value InMemoryStore, PostgresStore
写入/读取 精确查询 store.put(), store.get()
检索 前缀搜索、过滤搜索、语义搜索 store.search()
工具中访问 通过 ToolRuntime runtime.store, runtime.state
中间件中访问 通过 Runtime runtime.store, runtime.context
静态上下文 运行时不改变的数据 context_schema, context=
热路径写入 立即生效,适合偏好/资料 store.put() in tool
后台写入 不阻塞主流程,适合摘要/经验 异步任务

开发建议: 在 LangChain v1.x 项目中,记忆系统的核心是 LangGraph 的 State + Checkpointer(短期记忆)和 Store(长期记忆)。不要使用旧的 v0.x Memory 类。始终进行上下文管理,推荐使用 SummarizationMiddleware 做摘要,避免 token 无限增长。生产环境使用 PostgreSQL 作为持久化后端。

相关推荐
KaKa_大王2 小时前
关于秒杀项目的一些理解
java·学习
Z5998178412 小时前
c#软件开发学习笔记--Modbus-TCP/UDP网口通讯
笔记·学习·c#
zyf1044163 小时前
暑期实践日志 Day29:根据修改要求,进行视频字幕添加
学习·计算机网络·剪辑·暑期实践·课题任务
阿里巴巴首席技术官4 小时前
目标检测基础
笔记·yolo
xqqxqxxq4 小时前
AI Agent学习:打通真实世界:Agent 工具分类与设计原则(李博杰《深入理解 AI Agent》4.1 4.2观后总结)
学习·ai
TJHHH.4 小时前
SQL注入学习总结
数据库·笔记·sql·学习·注入
m4Rk_4 小时前
【论文阅读】Agent 记忆机制(43):Mem²Evolve——让经验与能力在双记忆中共同进化
论文阅读·人工智能·学习·开源·github
自小吃多4 小时前
Capture软件原理图添加元器件笔记
笔记·嵌入式硬件
TJHHH.5 小时前
文件包含漏洞
笔记·安全·文件包含