mcpField
L
langgraph-persistence

langgraph-persistence

github.com/langchain-aiAgent 工作流11957 次安装10 周前更新
代码仓库

关于 langgraph-persistence

langgraph-persistence 是面向需要在 LangGraph 应用中保存状态的开发者的技能,涵盖检查点、线程级历史、跨线程记忆,以及查看或修改既有图状态。

主要功能

  1. 01保存图状态检查点
  2. 02按线程 ID 隔离状态
  3. 03存储跨线程记忆
  4. 04浏览并重放状态历史
  5. 05手动更新图状态
  6. 06配置子图检查点

适用场景

  1. 01跨图调用保存对话
  2. 02隔离不同用户的状态
  3. 03跨线程共享偏好
  4. 04从较早的检查点重放

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/langchain-ai/skills-benchmarks --skill langgraph-persistence

安装后在 Claude Code 中输入/langgraph-persistence确认已加载。

详细介绍

LangGraph 的持久化层通过检查点保存图状态,从而支持持久执行:
  • 检查点保存器:在每个超级步骤保存/加载图状态
  • 线程 ID:标识不同的检查点序列(对话)
  • 存储:跨线程保存用户偏好、事实等记忆

两种记忆类型:

  • 短期(检查点保存器):限定在线程内的对话历史
  • 长期(存储):跨线程保存用户偏好、事实
检查点保存器使用场景是否适合生产环境
InMemorySaver测试、开发否
SqliteSaver本地开发部分适用
PostgresSaver生产环境是

检查点保存器设置

设置一个使用内存检查点保存和基于线程的状态持久化的基本图。 ```python from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import StateGraph, START, END from typing_extensions import TypedDict, Annotated import operator

class State(TypedDict): messages: Annotated[list, operator.add]

def add_message(state: State) -> dict: return {"messages": ["Bot response"]}

checkpointer = InMemorySaver()

graph = ( StateGraph(State) .add_node("respond", add_message) .add_edge(START, "respond") .add_edge("respond", END) .compile(checkpointer=checkpointer) # Pass at compile time )

ALWAYS provide thread_id

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

result1 = graph.invoke({"messages": ["Hello"]}, config) print(len(result1["messages"])) # 2

result2 = graph.invoke({"messages": ["How are you?"]}, config) print(len(result2["messages"])) # 4 (previous + new)

</python>
<typescript>
设置一个使用内存检查点保存和基于线程的状态持久化的基本图。
```typescript
import { MemorySaver, StateGraph, StateSchema, MessagesValue, START, END } from "@langchain/langgraph";
import { HumanMessage } from "@langchain/core/messages";

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

const addMessage = async (state: typeof State.State) => {
  return { messages: [{ role: "assistant", content: "Bot response" }] };
};

const checkpointer = new MemorySaver();

const graph = new StateGraph(State)
  .addNode("respond", addMessage)
  .addEdge(START, "respond")
  .addEdge("respond", END)
  .compile({ checkpointer });

// ALWAYS provide thread_id
const config = { configurable: { thread_id: "conversation-1" } };

const result1 = await graph.invoke({ messages: [new HumanMessage("Hello")] }, config);
console.log(result1.messages.length);  // 2

const result2 = await graph.invoke({ messages: [new HumanMessage("How are you?")] }, config);
console.log(result2.messages.length);  // 4 (previous + new)
为生产部署配置由 PostgreSQL 支持的检查点保存。 ```python from langgraph.checkpoint.postgres import PostgresSaver

with PostgresSaver.from_conn_string( "postgresql://user:pass@localhost/db" ) as checkpointer: checkpointer.setup() # only needed on first use to create tables graph = builder.compile(checkpointer=checkpointer)

</python>
<typescript>
为生产部署配置由 PostgreSQL 支持的检查点保存。
```typescript
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

const checkpointer = PostgresSaver.fromConnString(
  "postgresql://user:pass@localhost/db"
);
await checkpointer.setup(); // only needed on first use to create tables

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

线程管理

演示不同线程 ID 之间的状态隔离。 ```python # Different threads maintain separate state alice_config = {"configurable": {"thread_id": "user-alice"}} bob_config = {"configurable": {"thread_id": "user-bob"}}

graph.invoke({"messages": ["Hi from Alice"]}, alice_config) graph.invoke({"messages": ["Hi from Bob"]}, bob_config)

Alice's state is isolated from Bob's

</python>
<typescript>
演示不同线程 ID 之间的状态隔离。
```typescript
// Different threads maintain separate state
const aliceConfig = { configurable: { thread_id: "user-alice" } };
const bobConfig = { configurable: { thread_id: "user-bob" } };

await graph.invoke({ messages: [new HumanMessage("Hi from Alice")] }, aliceConfig);
await graph.invoke({ messages: [new HumanMessage("Hi from Bob")] }, bobConfig);

// Alice's state is isolated from Bob's

状态历史与时间旅行

时间旅行:浏览检查点历史,并从过去的状态重放或分支。 ```python config = {"configurable": {"thread_id": "session-1"}}

result = graph.invoke({"messages": ["start"]}, config)

Browse checkpoint history

states = list(graph.get_state_history(config))

Replay from a past checkpoint

past = states[-2] result = graph.invoke(None, past.config) # None = resume from checkpoint

Or fork: update state at a past checkpoint, then resume

fork_config = graph.update_state(past.config, {"messages": ["edited"]}) result = graph.invoke(None, fork_config)

</python>
<typescript>
时间旅行:浏览检查点历史,并从过去的状态重放或分支。
```typescript
const config = { configurable: { thread_id: "session-1" } };

const result = await graph.invoke({ messages: ["start"] }, config);

// Browse checkpoint history (async iterable, collect to array)
const states: Awaited<ReturnType<typeof graph.getState>>[] = [];
for await (const state of graph.getStateHistory(config)) {
  states.push(state);
}

// Replay from a past checkpoint
const past = states[states.length - 2];
const replayed = await graph.invoke(null, past.config);  // null = resume from checkpoint

// Or fork: update state at a past checkpoint, then resume
const forkConfig = await graph.updateState(past.config, { messages: ["edited"] });
const forked = await graph.invoke(null, forkConfig);
在恢复执行前手动更新图状态。 ```python config = {"configurable": {"thread_id": "session-1"}}

Modify state before resuming

graph.update_state(config, {"data": "manually_updated"})

Resume with updated state

result = graph.invoke(None, config)

</python>
<typescript>
在恢复执行前手动更新图状态。
```typescript
const config = { configurable: { thread_id: "session-1" } };

// Modify state before resuming
await graph.updateState(config, { data: "manually_updated" });

// Resume with updated state
const result = await graph.invoke(null, config);

子图检查点保存器作用域

编译子图时,checkpointer 参数用于控制持久化行为。对于使用中断、需要多轮记忆或并行运行的子图,这一点至关重要。

功能checkpointer=FalseNone(默认)True
中断(HITL)否是是
多轮记忆否否是
多次调用(不同子图)是是警告(可能发生命名空间冲突)
多次调用(同一子图)是是否
状态检查否警告(仅限当前调用)是
各模式的适用场景
  • checkpointer=False — 子图不需要中断或持久化。这是最简单的选项,不会产生检查点开销。
  • None(默认值/省略 checkpointer) — 子图需要 interrupt(),但不需要多轮记忆。每次调用都会从头开始,但可以暂停/恢复。并行执行可正常工作,因为每次调用都会获得唯一的命名空间。
  • checkpointer=True — 子图需要在多次调用之间记住状态(多轮对话)。每次调用都会从上次结束的地方继续。

警告:有状态子图(checkpointer=True)不支持在单个节点内多次调用同一个子图实例——这些调用会写入同一个检查点命名空间并产生冲突。

为子图选择合适的检查点保存器模式。 ```python # No interrupts needed — opt out of checkpointing subgraph = subgraph_builder.compile(checkpointer=False)

Need interrupts but not cross-invocation persistence (default)

subgraph = subgraph_builder.compile()

Need cross-invocation persistence (stateful)

subgraph = subgraph_builder.compile(checkpointer=True)

</python>
<typescript>
为子图选择合适的检查点保存器模式。
```typescript
// No interrupts needed — opt out of checkpointing
const subgraph = subgraphBuilder.compile({ checkpointer: false });

// Need interrupts but not cross-invocation persistence (default)
const subgraph = subgraphBuilder.compile();

// Need cross-invocation persistence (stateful)
const subgraph = subgraphBuilder.compile({ checkpointer: true });
### 并行子图命名空间

当多个不同的有状态子图并行运行时,请将每个子图包装在各自的 StateGraph 中,并为其指定唯一的节点名称,以确保命名空间隔离稳定:

```python from langgraph.graph import MessagesState, StateGraph

def create_sub_agent(model, *, name, **kwargs): """Wrap an agent with a unique node name for namespace isolation.""" agent = create_agent(model=model, name=name, **kwargs) return ( StateGraph(MessagesState) .add_node(name, agent) # unique name -> stable namespace .add_edge("start", name) .compile() )

fruit_agent = create_sub_agent( "gpt-4.1-mini", name="fruit_agent", tools=[fruit_info], prompt="...", checkpointer=True, ) veggie_agent = create_sub_agent( "gpt-4.1-mini", name="veggie_agent", tools=[veggie_info], prompt="...", checkpointer=True, )

</python>
<typescript>
```typescript
import { StateGraph, StateSchema, MessagesValue, START } from "@langchain/langgraph";

function createSubAgent(model: string, { name, ...kwargs }: { name: string; [key: string]: any }) {
  const agent = createAgent({ model, name, ...kwargs });
  return new StateGraph(new StateSchema({ messages: MessagesValue }))
    .addNode(name, agent)  // unique name -> stable namespace
    .addEdge(START, name)
    .compile();
}

const fruitAgent = createSubAgent("gpt-4.1-mini", {
  name: "fruit_agent", tools: [fruitInfo], prompt: "...", checkpointer: true,
});
const veggieAgent = createSubAgent("gpt-4.1-mini", {
  name: "veggie_agent", tools: [veggieInfo], prompt: "...", checkpointer: true,
});

注意:作为节点添加的子图(通过 add_node)已经会自动获得基于名称的命名空间,无需使用此包装器。


长期记忆(Store)

使用 Store 在不同对话线程间共享用户偏好。 ```python from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

Save user preference (available across ALL threads)

store.put(("alice", "preferences"), "language", {"preference": "short responses"})

Node with store — access via runtime

from langgraph.runtime import Runtime

def respond(state, runtime: Runtime): prefs = runtime.store.get((state["user_id"], "preferences"), "language") return {"response": f"Using preference: {prefs.value}"}

Compile with BOTH checkpointer and store

graph = builder.compile(checkpointer=checkpointer, store=store)

Both threads access same long-term memory

graph.invoke({"user_id": "alice"}, {"configurable": {"thread_id": "thread-1"}}) graph.invoke({"user_id": "alice"}, {"configurable": {"thread_id": "thread-2"}}) # Same preferences!

</python>
<typescript>
使用 Store 在不同对话线程间共享用户偏好。
```typescript
import { MemoryStore } from "@langchain/langgraph";

const store = new MemoryStore();

// Save user preference (available across ALL threads)
await store.put(["alice", "preferences"], "language", { preference: "short responses" });

// Node with store — access via runtime
const respond = async (state: typeof State.State, runtime: any) => {
  const item = await runtime.store?.get(["alice", "preferences"], "language");
  return { response: `Using preference: ${item?.value?.preference}` };
};

// Compile with BOTH checkpointer and store
const graph = builder.compile({ checkpointer, store });

// Both threads access same long-term memory
await graph.invoke({ userId: "alice" }, { configurable: { thread_id: "thread-1" } });
await graph.invoke({ userId: "alice" }, { configurable: { thread_id: "thread-2" } });  // Same preferences!
Store 的基本操作:put、get、search 和 delete。 ```python from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

store.put(("user-123", "facts"), "location", {"city": "San Francisco"}) # Put item = store.get(("user-123", "facts"), "location") # Get results = store.search(("user-123", "facts"), filter={"city": "San Francisco"}) # Search store.delete(("user-123", "facts"), "location") # Delete

</python>
</ex-store-operations>

---

## 修复

<fix-thread-id-required>
<python>
始终在 config 中提供 thread_id,以启用状态持久化。
```python
# WRONG: No thread_id - state NOT persisted!
graph.invoke({"messages": ["Hello"]})
graph.invoke({"messages": ["What did I say?"]})  # Doesn't remember!

# CORRECT: Always provide thread_id
config = {"configurable": {"thread_id": "session-1"}}
graph.invoke({"messages": ["Hello"]}, config)
graph.invoke({"messages": ["What did I say?"]}, config)  # Remembers!
始终在 config 中提供 thread_id,以启用状态持久化。 ```typescript // WRONG: No thread_id - state NOT persisted! await graph.invoke({ messages: [new HumanMessage("Hello")] }); await graph.invoke({ messages: [new HumanMessage("What did I say?")] }); // Doesn't remember!

// CORRECT: Always provide thread_id const config = { configurable: { thread_id: "session-1" } }; await graph.invoke({ messages: [new HumanMessage("Hello")] }, config); await graph.invoke({ messages: [new HumanMessage("What did I say?")] }, config); // Remembers!

</typescript>
</fix-thread-id-required>


<fix-inmemory-not-for-production>
<python>
生产环境持久化请使用 PostgresSaver,而不是 InMemorySaver。
```python
# WRONG: Data lost on process restart
checkpointer = InMemorySaver()  # In-memory only!

# CORRECT: Use persistent storage for production
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string("postgresql://...") as checkpointer:
    checkpointer.setup()  # only needed on first use to create tables
    graph = builder.compile(checkpointer=checkpointer)
生产环境持久化请使用 PostgresSaver,而不是 MemorySaver。 ```typescript // WRONG: Data lost on process restart const checkpointer = new MemorySaver(); // In-memory only!

// CORRECT: Use persistent storage for production import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"; const checkpointer = PostgresSaver.fromConnString("postgresql://..."); await checkpointer.setup(); // only needed on first use to create tables

</typescript>
</fix-inmemory-not-for-production>


<fix-update-state-with-reducers>
<python>
使用 Overwrite 替换状态值,而不是让值经过 reducer。
```python
from langgraph.types import Overwrite

# State with reducer: items: Annotated[list, operator.add]
# Current state: {"items": ["A", "B"]}

# update_state PASSES THROUGH reducers
graph.update_state(config, {"items": ["C"]})  # Result: ["A", "B", "C"] - Appended!

# To REPLACE instead, use Overwrite
graph.update_state(config, {"items": Overwrite(["C"])})  # Result: ["C"] - Replaced
使用 Overwrite 替换状态值,而不是让值经过 reducer。 ```typescript import { Overwrite } from "@langchain/langgraph";

// State with reducer: items uses concat reducer // Current state: { items: ["A", "B"] }

// updateState PASSES THROUGH reducers await graph.updateState(config, { items: ["C"] }); // Result: ["A", "B", "C"] - Appended!

// To REPLACE instead, use Overwrite await graph.updateState(config, { items: new Overwrite(["C"]) }); // Result: ["C"] - Replaced

</typescript>
</fix-update-state-with-reducers>

<fix-store-injection>
<python>
在图节点中通过 Runtime 对象访问 store。
```python
# WRONG: Store not available in node
def my_node(state):
    store.put(...)  # NameError! store not defined

# CORRECT: Access store via runtime
from langgraph.runtime import Runtime

def my_node(state, runtime: Runtime):
    runtime.store.put(...)  # Correct store instance
在图节点中通过 runtime 参数访问 store。 ```typescript // WRONG: Store not available in node const myNode = async (state) => { store.put(...); // ReferenceError! };

// CORRECT: Access store via runtime const myNode = async (state, runtime) => { await runtime.store?.put(...); // Correct store instance };

</typescript>
</fix-store-injection>

<boundaries>
### 不应该做的事

- 在生产环境中使用 `InMemorySaver`——重启后数据会丢失;请使用 `PostgresSaver`
- 忘记 `thread_id`——没有它,状态就无法持久化
- 认为 `update_state` 会绕过 reducer——它会经过 reducer;请使用 `Overwrite` 进行替换
- 在同一节点中并行运行相同的有状态子图(`checkpointer=True`)——这会导致命名空间冲突
- 在节点中直接访问 store——请通过 `Runtime` 参数使用 `runtime.store`
</boundaries>

评论

支持 Markdown