在前兩篇文章中,咱們大概理解了 LangChain 與 LangGraph 的功用,但我們他們的文件中,發現兩個都有所謂的 Memory 功能,所以這篇文章將會來談談以下三個主題 :
在 LangChain 1.0.0 總共有兩篇文件專門在討論這個主題 :
🤔 在 LangChain 中如何讓 AI Agent 記得事情呢 ?
就像如下的範例一樣,主要就是會用 checkpointer 這個概念,來儲放,然後有個重點 :
它是以 thread_id 為記憶單位
所以我們在實務上時,會根據需求來決定如何產生這個 thread_id,例如是要記得這個人今天過的話,那就會是以『 user_id + date 』來產生個 thread_id 之類的,反正就是以需求為主。
下面的範例我們就是簡單用記憶功能,來讓他記得我是誰。
import { createAgent, MemorySaver } from "langchain";
const checkpointer = new MemorySaver();
const agent = createAgent({
model: "openai:gpt-5-nano",
tools: [],
checkpointer,
});
const systemMessage = { role: "system", content: "你是一個助理,請用簡單的方式回答問題,用繁體中文回答" };
const threadId = "1";
const result1 = await agent.invoke(
{ messages: [systemMessage, { role: "user", content: "你好我是馬克" }] },
{ configurable: { thread_id: threadId } }
);
const result2 = await agent.invoke(
{ messages: [systemMessage, { role: "user", content: "我是誰" }] },
{ configurable: { thread_id: threadId } }
);
// 1. "嗨,馬克!很高興認識你。需要我幫忙做什麼呢?告訴我你想做的事就好。
// 2. "你是馬克。很高興認識你。需要我幫忙做什麼嗎?"
🤔 要注意上面的範例不適合一些情況,因為是儲在 Memory 中
大部份的工程師應該都知道這樣會發生幾個問題 :
所以當然 LangChain 也有提到儲放在資料庫中的方案 :
例如 postgres 如下範例 :
import { createAgent } from "langchain";
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 agent = createAgent({
model: "openai:gpt-5-nano",
tools: [],
checkpointer,
});
它還有以下幾種 Saver 然後我是在這下面找到的,你在 LangChain 的文件找不到,看起來它是寫在 LangGraph 但好像也有缺,然後直接去 LangGraph 的 repo 看有以下幾個:
https://github.com/langchain-ai/langgraphjs/tree/main/libs
然後 LangGraph 的文件連結在這裡,但是它只列了以下幾個 :
https://docs.langchain.com/oss/javascript/langgraph/persistence#checkpointer-libraries
看起來 repo 中有 mongodb 與 redis,但是文件也沒有,真有點搞不太懂它們的文件同步機制,不過也有可能這兩個的版本還在早期 0.1 版所以才沒更新到文件上,大概。
🤔 奇怪,看起來上面就有長期記憶的機制了,那 LangChain 文件的長期記憶章節是在說啥
https://docs.langchain.com/oss/javascript/langchain/long-term-memory
嗯嗯嗯… 我完全看不太懂他在做什麼,只知道以下幾件事情 :
這個 Store 主要是用在跨 thread_id 上,但我看到的時後在想,那這個用我們平常常用的資料庫不就行了 ? 我現在也還沒悟出他的真實用法。
https://docs.langchain.com/oss/javascript/langgraph/persistence
以下為官網的使用範例,事實上就和上面 LangChain 的範例有 90 % 像,就只是 :
所以 LangGraph 整個 Memory 機制是以 workflow 為單位。
import { StateGraph, START, END, MemorySaver } from "@langchain/langgraph";
import { registry } from "@langchain/langgraph/zod";
import * as z from "zod";
const State = z.object({
foo: z.string(),
bar: z.array(z.string()),
});
const workflow = new StateGraph(State)
.addNode("nodeA", (state) => {
return { foo: "a", bar: ["a"] };
})
.addNode("nodeB", (state) => {
return { foo: "b", bar: ["b"] };
})
.addEdge(START, "nodeA")
.addEdge("nodeA", "nodeB")
.addEdge("nodeB", END);
const checkpointer = new MemorySaver();
const graph = workflow.compile({ checkpointer });
const config = { configurable: { thread_id: "1" } };
const result = await graph.invoke({ foo: "" }, config);
console.log(result);
🤔 在 LangGraph 上用這個實際可以幹麻? LangChain 用的可以讓 Agent 記得上下文,那這個呢?
對 ~ LangGraph 這裡主要的目的不是為了 Agent,而是為了讓 Workflow 做到以下幾個事情 :
🤔 那它有分長期模式嗎 ?
當然,而且 LangGraph 的文件總於不像 LangChain 分開來還沒寫什麼。
import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres";
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 });
簡單的說分類如下 :
🤔 LangChain(Agent 層)
🤔 LangGraph( Workflow 層)
🤔 那適合所有包含 Workflow 與 Agent 都用同一個 checkpointer 嗎?
可以,要注意太長的問題,如果在 agent 用 checkpointer 會自動記得之前整個 workflow 說過的話喔,這也代表會送給 LLM。
如下範例,我的 createAgent 與 workflow 都是同一個 checkpointer 與相同的 thread_id,所以下面這個範例的執行結果就會記得我是誰。
import { StateGraph, START, END, MemorySaver } from "@langchain/langgraph";
import * as z from "zod";
import { createAgent } from "langchain";
const checkpointer = new MemorySaver();
const config = { configurable: { thread_id: "1" } };
const State = z.object({
messages: z.array(z.object({ role: z.string(), content: z.string() })),
});
const agent = createAgent({
model: "openai:gpt-5-nano", // 可換你實際可用的模型
tools: [],
checkpointer,
});
const workflow = new StateGraph(State)
.addNode("nodeA", (state) => {
return {
messages: [
...(state.messages ?? []),
{ role: "user", content: "我是馬克" },
],
};
})
.addNode("nodeB", async (state) => {
const res = await agent.invoke(
{
messages: [
{ role: "system", content: "請用繁體中文、簡潔回答。" },
{
role: "user",
content: `我是誰`,
},
],
},
config
);
return {
messages: res.messages,
};
})
.addEdge(START, "nodeA")
.addEdge("nodeA", "nodeB")
.addEdge("nodeB", END);
const graph = workflow.compile({ checkpointer });
const result = await graph.invoke({ messages: [] }, config);
console.log(result);
這是 console 結果。
{
messages: [
HumanMessage {
"id": "4939039d-7141-4a6c-a974-035654d20897",
"content": "我是馬克",
"additional_kwargs": {},
"response_metadata": {}
},
SystemMessage {
"id": "39cde393-6fe4-4f5d-bdfa-2c72d3fc49bf",
"content": "請用繁體中文、簡潔回答。",
"additional_kwargs": {},
"response_metadata": {}
},
HumanMessage {
"id": "1981b111-4fcd-45c1-8402-a96ccc18dbaf",
"content": "我是誰",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-CH3S8jYUCMPUSHYMoTeRTpzhopiM4",
"content": "你是馬克。",
"name": "model",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 29,
"completionTokens": 270,
"totalTokens": 299
},
"finish_reason": "stop",
"model_provider": "openai",
"model_name": "gpt-5-nano-2025-08-07"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 270,
"input_tokens": 29,
"total_tokens": 299,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 256
}
}
}
]
}
🤔 由於 Token 要錢,所以理想上還是會希望將 worfklow 與 agent 的分開,然後走到 agent 時,再自已判斷要不要抓整個 workflow 的資料進入到自已的 agent
所以大概會改成如下,會將 checkpointer 分開來,然後在 nodeB 中自已判斷要不要從 state 抓整個 workflow 的資料後,再帶入到自已的 agent 中。執行後得到的結果和上面一樣。
import { StateGraph, START, END, MemorySaver } from "@langchain/langgraph";
import * as z from "zod";
import { createAgent } from "langchain";
const workflowCheckpointer = new MemorySaver();
const agentCheckpointer = new MemorySaver();
const config = { configurable: { thread_id: "1" } };
const State = z.object({
messages: z.array(z.object({ role: z.string(), content: z.string() })),
});
const agent = createAgent({
model: "openai:gpt-5-nano", // 可換你實際可用的模型
tools: [],
checkpointer: agentCheckpointer, // <---------- 重點是這裡 !!!!!!
});
const workflow = new StateGraph(State)
.addNode("nodeA", (state) => {
return {
messages: [
...(state.messages ?? []),
{ role: "user", content: "我是馬克" },
],
};
})
.addNode("nodeB", async (state) => {
const needMessages = state.messages ?? []; // <---------- 重點是這裡 !!!!!!
const res = await agent.invoke(
{
messages: [
...needMessages,
{ role: "system", content: "請用繁體中文、簡潔回答。" },
{
role: "user",
content: `我是誰`,
},
],
},
config
);
return {
messages: res.messages,
};
})
.addEdge(START, "nodeA")
.addEdge("nodeA", "nodeB")
.addEdge("nodeB", END);
const graph = workflow.compile({ checkpointer: workflowCheckpointer }); // <---------- 重點是這裡 !!!!!!
const result = await graph.invoke({ messages: [] }, config);
console.log(result);
這個章節中,咱們 LangChain X LangGraph 兩個一起看的角度來看看要如何記得你,然後這兩個都有提到記憶功能差別在於 :
然後在同一個 thread 的情況下,都是用 checkpointer 來運行,然後跨 thread 的情況則是用 store,然後最後還有個建議是記得打理 checkpointer 如果裡面的東西越來越多,那你的 LLM 可能就會不知不覺得送很多沒用的 token 出去了。