添加短期记忆
短期记忆(线程级持久化)使智能体能够跟踪多轮对话。要添加短期记忆:Copy
import { MemorySaver, StateGraph } from "@langchain/langgraph";
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" } }
);
在生产环境中使用
在生产环境中,使用由数据库支持的检查点器:Copy
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const checkpointer = PostgresSaver.fromConnString(DB_URI);
const builder = new StateGraph(...);
const graph = builder.compile({ checkpointer });
示例:使用 Postgres 检查点器
示例:使用 Postgres 检查点器
Copy
npm install @langchain/langgraph-checkpoint-postgres
首次使用 Postgres 检查点器时,您需要调用
checkpointer.setup()Copy
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, MessagesZodMeta, START } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const MessagesZodState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
});
const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });
const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const checkpointer = PostgresSaver.fromConnString(DB_URI);
// await checkpointer.setup();
const builder = new StateGraph(MessagesZodState)
.addNode("call_model", async (state) => {
const response = await model.invoke(state.messages);
return { messages: [response] };
})
.addEdge(START, "call_model");
const graph = builder.compile({ checkpointer });
const config = {
configurable: {
thread_id: "1"
}
};
for await (const chunk of await graph.stream(
{ messages: [{ role: "user", content: "hi! I'm bob" }] },
{ ...config, streamMode: "values" }
)) {
console.log(chunk.messages.at(-1)?.content);
}
for await (const chunk of await graph.stream(
{ messages: [{ role: "user", content: "what's my name?" }] },
{ ...config, streamMode: "values" }
)) {
console.log(chunk.messages.at(-1)?.content);
}
在子图中使用
如果您的图包含子图,您只需要在编译父图时提供检查点器。LangGraph 会自动将检查点器传播到子图。Copy
import { StateGraph, START, MemorySaver } from "@langchain/langgraph";
import * as z from "zod";
const State = z.object({ 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 });
Copy
const subgraphBuilder = new StateGraph(...);
const subgraph = subgraphBuilder.compile({ checkpointer: true });
添加长期记忆
使用长期记忆来存储跨对话的用户特定或应用程序特定数据。Copy
import { InMemoryStore, StateGraph } from "@langchain/langgraph";
const store = new InMemoryStore();
const builder = new StateGraph(...);
const graph = builder.compile({ store });
在生产中使用
在生产中,使用由数据库支持的存储:Copy
import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store";
const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const store = PostgresStore.fromConnString(DB_URI);
const builder = new StateGraph(...);
const graph = builder.compile({ store });
Example: using Postgres store
Example: using Postgres store
Copy
npm install @langchain/langgraph-checkpoint-postgres
You need to call
store.setup() the first time you’re using Postgres storeCopy
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, MessagesZodMeta, START, LangGraphRunnableConfig } from "@langchain/langgraph";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store";
import { BaseMessage } from "@langchain/core/messages";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
import { v4 as uuidv4 } from "uuid";
const MessagesZodState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
});
const model = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });
const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const store = PostgresStore.fromConnString(DB_URI);
const checkpointer = PostgresSaver.fromConnString(DB_URI);
// await store.setup();
// await checkpointer.setup();
const callModel = async (
state: z.infer<typeof MessagesZodState>,
config: LangGraphRunnableConfig,
) => {
const userId = config.configurable?.userId;
const namespace = ["memories", userId];
const memories = await config.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 config.store?.put(namespace, uuidv4(), { data: memory });
}
const response = await model.invoke([
{ role: "system", content: systemMsg },
...state.messages
]);
return { messages: [response] };
};
const builder = new StateGraph(MessagesZodState)
.addNode("call_model", callModel)
.addEdge(START, "call_model");
const graph = builder.compile({
checkpointer,
store,
});
const config = {
configurable: {
thread_id: "1",
userId: "1",
}
};
for await (const chunk of await graph.stream(
{ messages: [{ role: "user", content: "Hi! Remember: my name is Bob" }] },
{ ...config, streamMode: "values" }
)) {
console.log(chunk.messages.at(-1)?.content);
}
const config2 = {
configurable: {
thread_id: "2",
userId: "1",
}
};
for await (const chunk of await graph.stream(
{ messages: [{ role: "user", content: "what is my name?" }] },
{ ...config2, streamMode: "values" }
)) {
console.log(chunk.messages.at(-1)?.content);
}
使用语义搜索
在图的内存存储中启用语义搜索,让图智能体通过语义相似性搜索存储中的项目。Copy
import { OpenAIEmbeddings } from "@langchain/openai";
import { InMemoryStore } from "@langchain/langgraph";
// 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
Long-term memory with semantic search
Copy
import { OpenAIEmbeddings, ChatOpenAI } from "@langchain/openai";
import { StateGraph, START, MessagesZodMeta, InMemoryStore } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
const MessagesZodState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
});
const model = new ChatOpenAI({ model: "gpt-4o-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 = async (state: z.infer<typeof MessagesZodState>, config) => {
// Search based on user's last message
const items = await config.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(MessagesZodState)
.addNode("chat", chat)
.addEdge(START, "chat");
const graph = builder.compile({ store });
for await (const [message, metadata] of await graph.stream(
{ messages: [{ role: "user", content: "I'm hungry" }] },
{ streamMode: "messages" }
)) {
if (message.content) {
console.log(message.content);
}
}
管理短期记忆
启用短期记忆后,长对话可能会超过 LLM 的上下文窗口。常见的解决方案是:- 修剪消息:删除前 N 条或后 N 条消息(在调用 LLM 之前)
- 删除消息:从 LangGraph 状态中永久删除
- 摘要消息:摘要历史中的早期消息并用摘要替换它们
- 管理检查点以存储和检索消息历史
- 自定义策略(例如,消息过滤等)
修剪消息
Most LLMs have a maximum supported context window (denominated in tokens). One way to decide when to truncate messages is to count the tokens in the message history and truncate whenever it approaches that limit. If you’re using LangChain, you can use the trim messages utility and specify the number of tokens to keep from the list, as well as thestrategy (e.g., keep the last maxTokens) to use for handling the boundary.
To trim message history, use the trimMessages function:
Copy
import { trimMessages } from "@langchain/core/messages";
const callModel = async (state: z.infer<typeof MessagesZodState>) => {
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(MessagesZodState)
.addNode("call_model", callModel);
// ...
Full example: trim messages
Full example: trim messages
Copy
import { trimMessages, BaseMessage } from "@langchain/core/messages";
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, START, MessagesZodMeta, MemorySaver } from "@langchain/langgraph";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
const MessagesZodState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
});
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const callModel = async (state: z.infer<typeof MessagesZodState>) => {
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(MessagesZodState)
.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);
Copy
Your name is Bob, as you mentioned when you first introduced yourself.
删除消息
您可以从图状态中删除消息以管理消息历史。当您想要删除特定消息或清除整个消息历史时,这很有用。 To delete messages from the graph state, you can use theRemoveMessage. For RemoveMessage to work, you need to use a state key with messagesStateReducer reducer, like MessagesZodState.
To remove specific messages:
Copy
import { RemoveMessage } from "@langchain/core/messages";
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 })),
};
}
};
When deleting messages, make sure that the resulting message history is valid. Check the limitations of the LLM provider you’re using. For example:
- some providers expect message history to start with a
usermessage - most providers require
assistantmessages with tool calls to be followed by correspondingtoolresult messages.
Full example: delete messages
Full example: delete messages
Copy
import { RemoveMessage, BaseMessage } from "@langchain/core/messages";
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, START, MemorySaver, MessagesZodMeta } from "@langchain/langgraph";
import * as z from "zod";
import { registry } from "@langchain/langgraph/zod";
const MessagesZodState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
});
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const deleteMessages = (state: z.infer<typeof MessagesZodState>) => {
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 = async (state: z.infer<typeof MessagesZodState>) => {
const response = await model.invoke(state.messages);
return { messages: [response] };
};
const builder = new StateGraph(MessagesZodState)
.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" } };
for await (const event of await app.stream(
{ messages: [{ role: "user", content: "hi! I'm bob" }] },
{ ...config, streamMode: "values" }
)) {
console.log(event.messages.map(message => [message.getType(), message.content]));
}
for await (const event of await app.stream(
{ messages: [{ role: "user", content: "what's my name?" }] },
{ ...config, streamMode: "values" }
)) {
console.log(event.messages.map(message => [message.getType(), message.content]));
}
Copy
[['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.']]
摘要消息
如上所示,修剪或删除消息的问题在于您可能会因消息队列的清理而丢失信息。因此,某些应用程序受益于使用聊天模型对消息历史进行摘要的更复杂方法。
Prompting and orchestration logic can be used to summarize the message history. For example, in LangGraph you can include a summary key in the state alongside the messages key:
Copy
import { BaseMessage } from "@langchain/core/messages";
import { MessagesZodMeta } from "@langchain/langgraph";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
const State = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
summary: z.string().optional(),
});
summarizeConversation node can be called after some number of messages have accumulated in the messages state key.
Copy
import { RemoveMessage, HumanMessage } from "@langchain/core/messages";
const summarizeConversation = async (state: z.infer<typeof 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
Full example: summarize messages
Copy
import { ChatAnthropic } from "@langchain/anthropic";
import {
SystemMessage,
HumanMessage,
RemoveMessage,
type BaseMessage
} from "@langchain/core/messages";
import {
MessagesZodMeta,
StateGraph,
START,
END,
MemorySaver,
} from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
import { v4 as uuidv4 } from "uuid";
const memory = new MemorySaver();
// We will add a `summary` attribute (in addition to `messages` key,
// which MessagesZodState already has)
const GraphState = z.object({
messages: z
.array(z.custom<BaseMessage>())
.register(registry, MessagesZodMeta),
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 = async (state: z.infer<typeof GraphState>) => {
// 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: uuidv4(),
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 = (state: z.infer<typeof GraphState>) => {
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 = async (state: z.infer<typeof GraphState>) => {
// 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: uuidv4(), 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 });
Manage checkpoints
You can view and delete the information stored by the checkpointer.View thread state
Copy
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);
Copy
{
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: []
}
View the history of the thread
Copy
const config = {
configurable: {
thread_id: "1",
},
};
const history = [];
for await (const state of graph.getStateHistory(config)) {
history.push(state);
}
Delete all checkpoints for a thread
Copy
const threadId = "1";
await checkpointer.deleteThread(threadId);
Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.