以编程方式使用文档

AI 应用需要 记忆 在多次交互中共享上下文。在 LangGraph 中,你可以添加两种类型的记忆:

添加短期记忆

Short-term 记忆(线程级 持久化)使智能体能够跟踪多轮对话。要添加短期记忆:

const checkpointer = new MemorySaver();

const builder = new StateGraph(...);
const graph = builder.compile({ checkpointer });

await graph.invoke(
  { messages: [{ role: "user", content: "hi! i am Bob" }] },
  { configurable: { thread_id: "1" } }
);

生产环境使用

在生产环境中,使用由数据库支持的检查点器:

Postgres

    const DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable";
    const checkpointer = PostgresSaver.fromConnString(DB_URI);

    const builder = new StateGraph(...);
    const graph = builder.compile({ checkpointer });
    

MongoDB

    const client = new MongoClient("mongodb://user:password@localhost:27017");
    const checkpointer = new MongoDBSaver({ client });

    const builder = new StateGraph(...);
    const graph = builder.compile({ checkpointer });
    

Example: using Postgres checkpointer

  npm install @langchain/langgraph-checkpoint-postgres
  

  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });

  const DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable";
  const checkpointer = PostgresSaver.fromConnString(DB_URI);
  // await checkpointer.setup();

  const callModel: GraphNode<typeof State> = async (state) => {
    const response = await model.invoke(state.messages);
    return { messages: [response] };
  };

  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addEdge(START, "call_model");

  const graph = builder.compile({ checkpointer });

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

  const stream1 = await graph.streamEvents(
    { messages: [{ role: "user", content: "hi! I'm bob" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream1.values) {
    console.log(snapshot);
  }

  const stream2 = await graph.streamEvents(
    { messages: [{ role: "user", content: "what's my name?" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream2.values) {
    console.log(snapshot);
  }
  

Example: using MongoDB checkpointer

  npm install @langchain/langgraph-checkpoint-mongodb
  

  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });

  const client = new MongoClient("mongodb://user:password@localhost:27017");
  const checkpointer = new MongoDBSaver({ client, dbName: "langgraph" });

  const callModel: GraphNode<typeof State> = async (state) => {
    const response = await model.invoke(state.messages);
    return { messages: [response] };
  };

  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addEdge(START, "call_model");

  const graph = builder.compile({ checkpointer });

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

  const stream1 = await graph.streamEvents(
    { messages: [{ role: "user", content: "hi! I'm bob" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream1.values) {
    console.log(snapshot);
  }

  const stream2 = await graph.streamEvents(
    { messages: [{ role: "user", content: "what's my name?" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream2.values) {
    console.log(snapshot);
  }
  

在子图中的使用

如果你的图包含 子图,你只需要在编译父图时提供检查点器。LangGraph 会自动将检查点器传播到子图。

const State = new StateSchema({ foo: z.string() });

const subgraphBuilder = new StateGraph(State)
  .addNode("subgraph_node_1", (state) => {
    return { foo: state.foo + "bar" };
  })
  .addEdge(START, "subgraph_node_1");
const subgraph = subgraphBuilder.compile();

const builder = new StateGraph(State)
  .addNode("node_1", subgraph)
  .addEdge(START, "node_1");

const checkpointer = new MemorySaver();
const graph = builder.compile({ checkpointer });

你可以配置子图特定的检查点行为。参见 子图持久化 了解更多关于持久化级别的信息,包括中断支持和有状态延续。

const subgraphBuilder = new StateGraph(...);
const subgraph = subgraphBuilder.compile({ checkpointer: true });  // [!code highlight]

添加长期记忆

使用长期记忆来在多个对话之间存储用户特定或应用特定的数据。

const store = new InMemoryStore();

const builder = new StateGraph(...);
const graph = builder.compile({ store });

在节点内访问 store

一旦你使用 store 编译了图,LangGraph 会自动将 store 注入到你的节点函数中。访问 store 的推荐方式是通过 Runtime object.

const State = new StateSchema({
  messages: MessagesValue,
});

const callModel: GraphNode<typeof State> = async (state, runtime) => {
  const userId = runtime.context?.userId;
  const namespace = [userId, "memories"];

  // Search for relevant memories
  const memories = await runtime.store?.search(namespace, {
    query: state.messages.at(-1)?.content,
    limit: 3,
  });
  const info = memories?.map((d) => d.value.data).join("\n") || "";

  // ... Use memories in model call

  // Store a new memory
  await runtime.store?.put(namespace, crypto.randomUUID(), { data: "User prefers dark mode" });
};

const builder = new StateGraph(State)
  .addNode("call_model", callModel)
  .addEdge(START, "call_model");
const graph = builder.compile({ store });

// Pass context at invocation time
await graph.invoke(
  { messages: [{ role: "user", content: "hi" }] },
  { configurable: { thread_id: "1" }, context: { userId: "1" } }
);

生产环境使用

在生产环境中,使用由数据库支持的存储:

Postgres

    const DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable";
    const store = PostgresStore.fromConnString(DB_URI);

    const builder = new StateGraph(...);
    const graph = builder.compile({ store });
    

MongoDB

    const MONGODB_URI = "mongodb://user:password@localhost:27017";
    const store = await MongoDBStore.fromConnString(MONGODB_URI, {
      dbName: "langgraph",
      collectionName: "store",
    });

    const builder = new StateGraph(...);
    const graph = builder.compile({ store });
    

Example: using Postgres store

  npm install @langchain/langgraph-checkpoint-postgres
  

  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });

  const callModel: GraphNode<typeof State> = async (state, runtime) => {
    const userId = runtime.context?.userId;
    const namespace = ["memories", userId];
    const memories = await runtime.store?.search(namespace, { query: state.messages.at(-1)?.content });
    const info = memories?.map(d => d.value.data).join("\n") || "";
    const systemMsg = `You are a helpful assistant talking to the user. User info: ${info}`;

    // Store new memories if the user asks the model to remember
    const lastMessage = state.messages.at(-1);
    if (lastMessage?.content?.toLowerCase().includes("remember")) {
      const memory = "User name is Bob";
      await runtime.store?.put(namespace, crypto.randomUUID(), { data: memory });
    }

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

  const DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable";

  const store = PostgresStore.fromConnString(DB_URI);
  const checkpointer = PostgresSaver.fromConnString(DB_URI);
  // await store.setup();
  // await checkpointer.setup();

  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addEdge(START, "call_model");

  const graph = builder.compile({
    checkpointer,
    store,
  });

  const stream1 = await graph.streamEvents(
    { messages: [{ role: "user", content: "Hi! Remember: my name is Bob" }] },
    { configurable: { thread_id: "1" }, context: { userId: "1" }, version: "v3" }
  );
  for await (const snapshot of stream1.values) {
    console.log(snapshot);
  }

  const stream2 = await graph.streamEvents(
    { messages: [{ role: "user", content: "what is my name?" }] },
    { configurable: { thread_id: "2" }, context: { userId: "1" }, version: "v3" }
  );
  for await (const snapshot of stream2.values) {
    console.log(snapshot);
  }
  

Example: using MongoDB store

  npm install @langchain/langgraph-checkpoint-mongodb
  
  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });

  const callModel: GraphNode<typeof State> = async (state, runtime) => {
    const userId = runtime.context?.userId;
    const namespace = ["memories", userId];
    const memories = await runtime.store?.search(namespace);
    const info = memories?.map(d => d.value.data).join("\n") || "n/a";
    const systemMsg = `You are a helpful assistant talking to the user. User info: ${info}`;

    // Store new memories if the user asks the model to remember
    const lastMessage = state.messages.at(-1);
    if (lastMessage?.content?.toLowerCase().includes("remember")) {
      const memory = "User name is Bob";
      await runtime.store?.put(namespace, crypto.randomUUID(), { data: memory });
    }

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

  const MONGODB_URI = "mongodb://user:password@localhost:27017";

  const store = await MongoDBStore.fromConnString(MONGODB_URI, {
    dbName: "langgraph",
    collectionName: "store",
  });

  const checkpointer = new MemorySaver();

  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addEdge(START, "call_model");

  const graph = builder.compile({ checkpointer, store });

  const stream1 = await graph.streamEvents(
    { messages: [{ role: "user", content: "Hi! Remember: my name is Bob" }] },
    { configurable: { thread_id: "1" }, context: { userId: "1" }, version: "v3" }
  );
  for await (const snapshot of stream1.values) {
    console.log(snapshot);
  }

  const stream2 = await graph.streamEvents(
    { messages: [{ role: "user", content: "what is my name?" }] },
    { configurable: { thread_id: "2" }, context: { userId: "1" }, version: "v3" }
  );
  for await (const snapshot of stream2.values) {
    console.log(snapshot);
  }
  

使用语义搜索

在图形的内存存储中启用语义搜索,让图形代理按语义相似度搜索存储中的项目。

// Create store with semantic search enabled
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
const store = new InMemoryStore({
  index: {
    embeddings,
    dims: 1536,
  },
});

await store.put(["user_123", "memories"], "1", { text: "I love pizza" });
await store.put(["user_123", "memories"], "2", { text: "I am a plumber" });

const items = await store.search(["user_123", "memories"], {
  query: "I'm hungry",
  limit: 1,
});

Long-term memory with semantic search

InMemoryStore

        const State = new StateSchema({
          messages: MessagesValue,
        });

        const model = new ChatOpenAI({ model: "gpt-5.4-mini" });

        // Create store with semantic search enabled
        const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
        const store = new InMemoryStore({
          index: {
            embeddings,
            dims: 1536,
          }
        });

        await store.put(["user_123", "memories"], "1", { text: "I love pizza" });
        await store.put(["user_123", "memories"], "2", { text: "I am a plumber" });

        const chat: GraphNode<typeof State> = async (state, runtime) => {
          // Search based on user's last message
          const items = await runtime.store.search(
            ["user_123", "memories"],
            { query: state.messages.at(-1)?.content, limit: 2 }
          );
          const memories = items.map(item => item.value.text).join("\n");
          const memoriesText = memories ? `## Memories of user\n${memories}` : "";

          const response = await model.invoke([
            { role: "system", content: `You are a helpful assistant.\n${memoriesText}` },
            ...state.messages,
          ]);

          return { messages: [response] };
        };

        const builder = new StateGraph(State)
          .addNode("chat", chat)
          .addEdge(START, "chat");
        const graph = builder.compile({ store });

        const stream = await graph.streamEvents(
          { messages: [{ role: "user", content: "I'm hungry" }] },
          { version: "v3" }
        );
        for await (const message of stream.messages) {
          for await (const token of message.text) {
            process.stdout.write(token);
          }
        }
        

MongoDB (manual embedding)

        const State = new StateSchema({
          messages: MessagesValue,
        });

        const model = new ChatOpenAI({ model: "gpt-5.4-mini" });

        // Create store with semantic search enabled
        const MONGODB_URI = "mongodb://user:password@localhost:27017";
        const store = await MongoDBStore.fromConnString(MONGODB_URI, {
          dbName: "langgraph",
          collectionName: "store",
          embeddings: new OpenAIEmbeddings({ model: "text-embedding-3-small" }),
          indexConfig: {
            name: "store_vector_index",
            dims: 1536,
            embeddingKey: "text",
          },
        });

        await store.put(["user_123", "memories"], "1", { text: "I love pizza" });
        await store.put(["user_123", "memories"], "2", { text: "I am a plumber" });

        const chat: GraphNode<typeof State> = async (state, runtime) => {
          // Search based on user's last message
          const items = await runtime.store.search(
            ["user_123", "memories"],
            { query: state.messages.at(-1)?.content, limit: 2 }
          );
          const memories = items.map(item => item.value.text).join("\n");
          const memoriesText = memories ? `## Memories of user\n${memories}` : "";

          const response = await model.invoke([
            { role: "system", content: `You are a helpful assistant.\n${memoriesText}` },
            ...state.messages,
          ]);

          return { messages: [response] };
        };

        const builder = new StateGraph(State)
          .addNode("chat", chat)
          .addEdge(START, "chat");
        const graph = builder.compile({ store });

        const stream = await graph.streamEvents(
          { messages: [{ role: "user", content: "I'm hungry" }] },
          { version: "v3" }
        );
        for await (const message of stream.messages) {
          for await (const token of message.text) {
            process.stdout.write(token);
          }
        }
        

MongoDB (auto embedding)

        const State = new StateSchema({
          messages: MessagesValue,
        });

        const model = new ChatOpenAI({ model: "gpt-5.4-mini" });

        // Auto embedding: no embeddings instance needed.
        // Configure the Voyage AI model and the field path MongoDB will read server-side.
        const MONGODB_URI = "mongodb://user:password@localhost:27017";
        const store = await MongoDBStore.fromConnString(MONGODB_URI, {
          dbName: "langgraph",
          collectionName: "store",
          indexConfig: {
            name: "store_vector_index",
            path: "value.content",  // MongoDB reads this field and embeds it server-side
            model: "voyage-4",      // Voyage AI model used by MongoDB Atlas
          },
        });

        // Values must have the content field matching the configured path (value.content)
        await store.put(["user_123", "memories"], "1", { content: "I love pizza" });
        await store.put(["user_123", "memories"], "2", { content: "I am a plumber" });

        const chat: GraphNode<typeof State> = async (state, runtime) => {
          // MongoDB generates the query embedding server-side
          const items = await runtime.store.search(
            ["user_123", "memories"],
            { query: state.messages.at(-1)?.content, limit: 2 }
          );
          const memories = items.map(item => item.value.content).join("\n");
          const memoriesText = memories ? `## Memories of user\n${memories}` : "";

          const response = await model.invoke([
            { role: "system", content: `You are a helpful assistant.\n${memoriesText}` },
            ...state.messages,
          ]);

          return { messages: [response] };
        };

        const builder = new StateGraph(State)
          .addNode("chat", chat)
          .addEdge(START, "chat");
        const graph = builder.compile({ store });

        const stream = await graph.streamEvents(
          { messages: [{ role: "user", content: "I'm hungry" }] },
          { version: "v3" }
        );
        for await (const message of stream.messages) {
          for await (const token of message.text) {
            process.stdout.write(token);
          }
        }
        

管理短期记忆

使用 短期记忆 启用后,长对话可能会超出 LLM 的上下文窗口。常见解决方案有:

  • * 修剪消息:移除前 N 条或后 N 条消息(在调用 LLM 之前)
  • * 删除消息 从 LangGraph 状态中永久删除
  • * 总结消息:总结历史中较早的消息并用摘要替换
  • * 管理检查点 存储和检索消息历史
  • * 自定义策略(例如消息过滤等)

这允许代理在不超过 LLM 上下文窗口的情况下跟踪对话。

修剪消息

大多数 LLM 都有最大支持的上下文窗口(以 token 为单位)。决定何时截断消息的一种方法是计算消息历史中的 token 数量,并在接近该限制时截断。如果你使用 LangChain,可以使用修剪消息工具并指定从列表中保留的 token 数量,以及用于处理边界的 strategy (例如,保留最后 maxTokens)来处理边界。

要修剪消息历史,请使用 trimMessages function:

const State = new StateSchema({
  messages: MessagesValue,
});

const callModel: GraphNode<typeof State> = async (state) => {
  const messages = trimMessages(state.messages, {
    strategy: "last",
    maxTokens: 128,
    startOn: "human",
    endOn: ["human", "tool"],
  });
  const response = await model.invoke(messages);
  return { messages: [response] };
};

const builder = new StateGraph(State)
  .addNode("call_model", callModel);
  // ...

Full example: trim messages

  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });

  const callModel: GraphNode<typeof State> = async (state) => {
    const messages = trimMessages(state.messages, {
      strategy: "last",
      maxTokens: 128,
      startOn: "human",
      endOn: ["human", "tool"],
      tokenCounter: model,
    });
    const response = await model.invoke(messages);
    return { messages: [response] };
  };

  const checkpointer = new MemorySaver();
  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addEdge(START, "call_model");
  const graph = builder.compile({ checkpointer });

  const config = { configurable: { thread_id: "1" } };
  await graph.invoke({ messages: [{ role: "user", content: "hi, my name is bob" }] }, config);
  await graph.invoke({ messages: [{ role: "user", content: "write a short poem about cats" }] }, config);
  await graph.invoke({ messages: [{ role: "user", content: "now do the same but for dogs" }] }, config);
  const finalResponse = await graph.invoke({ messages: [{ role: "user", content: "what's my name?" }] }, config);

  console.log(finalResponse.messages.at(-1)?.content);
  
  Your name is Bob, as you mentioned when you first introduced yourself.
  

删除消息

您可以从图状态中删除消息来管理消息历史。这在您想要删除特定消息或清除整个消息历史时非常有用。

要从图状态中删除消息,您可以使用 RemoveMessage。要使 RemoveMessage 正常工作,您需要使用带有 messagesStateReducer reducer,如 MessagesValue.

要删除特定消息:

const deleteMessages = (state) => {
  const messages = state.messages;
  if (messages.length > 2) {
    // remove the earliest two messages
    return {
      messages: messages
        .slice(0, 2)
        .map((m) => new RemoveMessage({ id: m.id })),
    };
  }
};

Full example: delete messages

  const State = new StateSchema({
    messages: MessagesValue,
  });

  const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });

  const deleteMessages: GraphNode<typeof State> = (state) => {
    const messages = state.messages;
    if (messages.length > 2) {
      // remove the earliest two messages
      return { messages: messages.slice(0, 2).map(m => new RemoveMessage({ id: m.id })) };
    }
    return {};
  };

  const callModel: GraphNode<typeof State> = async (state) => {
    const response = await model.invoke(state.messages);
    return { messages: [response] };
  };

  const builder = new StateGraph(State)
    .addNode("call_model", callModel)
    .addNode("delete_messages", deleteMessages)
    .addEdge(START, "call_model")
    .addEdge("call_model", "delete_messages");

  const checkpointer = new MemorySaver();
  const app = builder.compile({ checkpointer });

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

  const stream1 = await app.streamEvents(
    { messages: [{ role: "user", content: "hi! I'm bob" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream1.values) {
    console.log(snapshot.messages.map(message => [message.getType(), message.content]));
  }

  const stream2 = await app.streamEvents(
    { messages: [{ role: "user", content: "what's my name?" }] },
    { ...config, version: "v3" }
  );
  for await (const snapshot of stream2.values) {
    console.log(snapshot.messages.map(message => [message.getType(), message.content]));
  }
  
  [['human', "hi! I'm bob"]]
  [['human', "hi! I'm bob"], ['ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?']]
  [['human', "hi! I'm bob"], ['ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'], ['human', "what's my name?"]]
  [['human', "hi! I'm bob"], ['ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'], ['human', "what's my name?"], ['ai', 'Your name is Bob.']]
  [['human', "what's my name?"], ['ai', 'Your name is Bob.']]
  

摘要消息

如上所示,修剪或删除消息的问题在于您可能会从消息队列的清理中丢失信息。因此,一些应用程序受益于使用聊天模型对消息历史进行摘要的更复杂方法。

!摘要

提示和编排逻辑可用于对消息历史进行摘要。例如,在 LangGraph 中,您可以在状态中包含一个 summary 键以及 messages key:

const State = new StateSchema({
  messages: MessagesValue,
  summary: z.string().optional(),
});

然后,您可以使用任何现有摘要作为下一个摘要的上下文来生成聊天历史的摘要。此 summarizeConversation 节点可以在一定数量的消息累积在 messages 状态键中后调用。

const summarizeConversation: GraphNode<typeof State> = async (state) => {
  // First, we get any existing summary
  const summary = state.summary || "";

  // Create our summarization prompt
  let summaryMessage: string;
  if (summary) {
    // A summary already exists
    summaryMessage =
      `This is a summary of the conversation to date: ${summary}\n\n` +
      "Extend the summary by taking into account the new messages above:";
  } else {
    summaryMessage = "Create a summary of the conversation above:";
  }

  // Add prompt to our history
  const messages = [
    ...state.messages,
    new HumanMessage({ content: summaryMessage })
  ];
  const response = await model.invoke(messages);

  // Delete all but the 2 most recent messages
  const deleteMessages = state.messages
    .slice(0, -2)
    .map(m => new RemoveMessage({ id: m.id }));

  return {
    summary: response.content,
    messages: deleteMessages
  };
};

Full example: summarize messages

    SystemMessage,
    HumanMessage,
    RemoveMessage,
  } from "@langchain/core/messages";

    StateGraph,
    StateSchema,
    MessagesValue,
    GraphNode,
    ConditionalEdgeRouter,
    START,
    END,
    MemorySaver,
  } from "@langchain/langgraph";


  const memory = new MemorySaver();

  // We will add a `summary` attribute (in addition to `messages` key)
  const GraphState = new StateSchema({
    messages: MessagesValue,
    summary: z.string().default(""),
  });

  // We will use this model for both the conversation and the summarization
  const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });

  // Define the logic to call the model
  const callModel: GraphNode<typeof GraphState> = async (state) => {
    // If a summary exists, we add this in as a system message
    const { summary } = state;
    let { messages } = state;
    if (summary) {
      const systemMessage = new SystemMessage({
        id: crypto.randomUUID(),
        content: `Summary of conversation earlier: ${summary}`,
      });
      messages = [systemMessage, ...messages];
    }
    const response = await model.invoke(messages);
    // We return an object, because this will get added to the existing state
    return { messages: [response] };
  };

  // We now define the logic for determining whether to end or summarize the conversation
  const shouldContinue: ConditionalEdgeRouter<typeof GraphState, "summarize_conversation"> = (state) => {
    const messages = state.messages;
    // If there are more than six messages, then we summarize the conversation
    if (messages.length > 6) {
      return "summarize_conversation";
    }
    // Otherwise we can just end
    return END;
  };

  const summarizeConversation: GraphNode<typeof GraphState> = async (state) => {
    // First, we summarize the conversation
    const { summary, messages } = state;
    let summaryMessage: string;
    if (summary) {
      // If a summary already exists, we use a different system prompt
      // to summarize it than if one didn't
      summaryMessage =
        `This is summary of the conversation to date: ${summary}\n\n` +
        "Extend the summary by taking into account the new messages above:";
    } else {
      summaryMessage = "Create a summary of the conversation above:";
    }

    const allMessages = [
      ...messages,
      new HumanMessage({ id: crypto.randomUUID(), content: summaryMessage }),
    ];

    const response = await model.invoke(allMessages);

    // We now need to delete messages that we no longer want to show up
    // I will delete all but the last two messages, but you can change this
    const deleteMessages = messages
      .slice(0, -2)
      .map((m) => new RemoveMessage({ id: m.id! }));

    if (typeof response.content !== "string") {
      throw new Error("Expected a string response from the model");
    }

    return { summary: response.content, messages: deleteMessages };
  };

  // Define a new graph
  const workflow = new StateGraph(GraphState)
    // Define the conversation node and the summarize node
    .addNode("conversation", callModel)
    .addNode("summarize_conversation", summarizeConversation)
    // Set the entrypoint as conversation
    .addEdge(START, "conversation")
    // We now add a conditional edge
    .addConditionalEdges(
      // First, we define the start node. We use `conversation`.
      // This means these are the edges taken after the `conversation` node is called.
      "conversation",
      // Next, we pass in the function that will determine which node is called next.
      shouldContinue,
    )
    // We now add a normal edge from `summarize_conversation` to END.
    // This means that after `summarize_conversation` is called, we end.
    .addEdge("summarize_conversation", END);

  // Finally, we compile it!
  const app = workflow.compile({ checkpointer: memory });
  

管理检查点

您可以查看和删除由检查点存储的信息。

<a id="checkpoint"></a> #### 查看线程状态

const config = {
  configurable: {
    thread_id: "1",
    // optionally provide an ID for a specific checkpoint,
    // otherwise the latest checkpoint is shown
    // checkpoint_id: "1f029ca3-1f5b-6704-8004-820c16b69a5a"
  },
};
await graph.getState(config);
{
  values: { messages: [HumanMessage(...), AIMessage(...), HumanMessage(...), AIMessage(...)] },
  next: [],
  config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1f029ca3-1f5b-6704-8004-820c16b69a5a' } },
  metadata: {
    source: 'loop',
    writes: { call_model: { messages: AIMessage(...) } },
    step: 4,
    parents: {},
    thread_id: '1'
  },
  createdAt: '2025-05-05T16:01:24.680462+00:00',
  parentConfig: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1f029ca3-1790-6b0a-8003-baf965b6a38f' } },
  tasks: [],
  interrupts: []
}

<a id="checkpoints"></a> #### 查看线程的历史记录

const config = {
  configurable: {
    thread_id: "1",
  },
};

const history = [];
for await (const state of graph.getStateHistory(config)) {
  history.push(state);
}

删除线程的所有检查点

const threadId = "1";
await checkpointer.deleteThread(threadId);

数据库管理

If you are using any database-backed persistence implementation (such as Postgres, Redis, or Oracle) to store short and/or long-term memory, you will need to run migrations to set up the required schema before you can use it with your database.

按照惯例,大多数特定于数据库的库会定义一个 setup() 检查点或存储实例上运行所需迁移的方法。但是,您应该查看您的特定实现BaseCheckpointSaver] or @[BaseStore以确认确切的方法名称和用法。

我们建议将迁移作为专门的部署步骤运行,或者您可以确保它们在服务器启动时运行。