高级功能

记忆管理

构建具备短期对话记忆和长期用户偏好记忆的智能应用

📚 学习目标

学完这篇文章后,你将能够:

  • 区分短期记忆(Short-term)和长期记忆(Long-term)的应用场景
  • 实现自动管理对话历史(History Management)
  • 使用 Store API 存储跨会话的用户偏好

前置知识

在开始学习之前,建议先阅读:


1 短期记忆 vs 长期记忆

  • 短期记忆 (Short-term)

    • 范围:单个 Thread。
    • 生命周期:随会话结束(或 TTL)而失效。
    • 实现:Checkpointer (State 中的 messages 列表)。
    • 用途:多轮对话上下文。
  • 长期记忆 (Long-term)

    • 范围:跨 Thread,通常绑定到 User ID。
    • 生命周期:永久。
    • 实现:Store (Key-Value 数据库)。
    • 用途:用户偏好、个性化设置、Facts。

2 管理短期记忆 (Window Buffer)

State 中的 messages 列表不能无限增长。我们需要定期修剪它。

方式 1:Message Window(窗口裁剪)

在 Checkpointer 保存前或 Model 调用前裁剪。

// 在节点内部处理
const modelNode = async (state) => {
  const allMessages = state.messages;
  // 只保留最近 10 条
  const context = allMessages.slice(-10); 
  const response = await model.invoke(context);
  // ...
};

更工程化的做法是把“裁剪策略”做成可配置:

import { RunnableConfig } from '@langchain/core/runnables';

const modelNode = async (state, config?: RunnableConfig) => {
  const keep = (config?.configurable?.keepMessages as number) ?? 10;
  const context = state.messages.slice(-keep);
  const response = await model.invoke(context);
  return { messages: [response] };
};

方式 2:Summarization(摘要)

让 LLM 定期把历史对话压缩成一段 Summary。

const summarizeNode = async (state) => {
  const summary = await summarizationModel.invoke(state.messages);
  // 返回 newMessages,其中包含 Summary 和少量最近消息
  return { messages: [new SystemMessage(`Summary: ${summary}`), ...recent] };
};

[!NOTE] 摘要节点通常要配合持久化使用:不然服务重启后 summary 丢失,消息又会重新膨胀。


3 实现长期记忆 (Store)

LangGraph 提供了 Store 接口来处理跨线程数据。

// 不同版本/运行环境的 Store 名称可能不同,这里用 InMemoryStore 作为示例。
// 如果你的版本没有 InMemoryStore,可以用同语义的 KV Store 替代。
import { InMemoryStore } from "@langchain/langgraph";

// 1. 初始化 Store
const store = new InMemoryStore();

// 2. 编译时传入
const app = graph.compile({ store });

// 3. 在节点中使用
const agentNode = async (state, config) => {
  const { store } = config;
  // 命名空间通常是 [scope, identifier, key_group]
  const userId = config.configurable.user_id;
  const namespace = ["users", userId, "prefs"];
  
  // 读取记忆
  const prefs = await store.get(namespace, "language");
  const lang = prefs?.value?.data || "English";
  
  // ... 生成回答 ...

  // 写入记忆 (例如用户说 "以后叫我 Lord")
  await store.put(namespace, "nickname", { data: "Lord" });
};

Namespace 设计建议

把 namespace 当成“分区 + 层级路径”:

  • 第一段:scope(例如 users / orgs
  • 第二段:主体 id(userId/orgId)
  • 第三段:类别(prefs/memories/flags)

这样你的长期记忆不会变成一团散乱的 key。


4 一个实用组合:短期 messages + 长期偏好

在真实聊天产品里,最常见的是:

  1. 短期:靠 messages 维持上下文(记住当前会话)
  2. 长期:靠 Store 记住偏好(语言、风格、昵称、常用工具)

你可以在 Agent 节点里先读偏好,再拼进 system prompt:

import { SystemMessage } from '@langchain/core/messages';

const agentNode = async (state, config) => {
  const userId = config.configurable.user_id;
  const ns = ['users', userId, 'prefs'];
  const nickname = (await config.store?.get(ns, 'nickname'))?.value?.data;

  const system = new SystemMessage(
    nickname ? `用户昵称是 ${nickname},请用这个称呼。` : '按默认方式称呼用户。'
  );

  const response = await model.invoke([system, ...state.messages]);
  return { messages: [response] };
};

💡 练习题

  1. 设计题:设计一个“个性化新闻助手”。

    • 短期记忆:记住刚才讨论的时事新闻。
    • 长期记忆:记住用户关心的领域(如“科技”、“体育”)和语言偏好。
    • 请写出 Store 的 Namespace 设计和 Key 结构。
    点击查看答案

    可按三层命名:news-assistant/{userId}/short-term/{threadId}news-assistant/{userId}/long-term/profileshort-term 里存最近话题和摘要;long-term/profileinterests[]languageupdateAt,按 userId 统一聚合方便跨会话复用。


✅ 总结

本章要点

  • 短期记忆靠 State + Checkpointer,重点是修剪 (Pruning)。
  • 长期记忆靠 Store,重点是 Namespace 设计。
  • Store 本质上是一个简单的 KV 数据库,支持 JSON 存储。

下一步:搞砸了怎么办?如何回到过去?学习时间旅行

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page