记忆与时间旅行
LangGraphGo 的一个核心区别在于其对状态持久化的深度支持。这不仅实现了基本的"记忆"功能,还开启了强大的"时间旅行"能力,允许开发者像操作 Git 一样操作应用的执行历史。
1. 记忆 (Memory)
背景与功能
无状态的 LLM 无法记住上下文。为了构建聊天机器人或多轮对话应用,我们需要在轮次之间保存状态。LangGraphGo 将这一责任从应用层下沉到了框架层。你不需要手动管理数据库读写,只需配置一个 Checkpointer。
实现原理
Checkpointer 是一个接口,负责保存和加载 Checkpoint。Checkpoint 是图在特定步骤(Superstep)结束时的完整状态快照。每次状态更新时,LangGraphGo 都会自动创建一个新的 Checkpoint,并将其保存到底层存储(如 Postgres, Redis, SQLite)。
通过 ThreadID,我们可以将一系列 Checkpoint 关联到同一个对话线程中。
代码展示
// 1. 初始化 Checkpointer (以 Memory 为例,生产环境请用 Postgres/Redis)
checkpointer := memory.NewSaver()
// 2. 编译图时启用 Checkpointer
runnable, _ := g.Compile(graph.WithCheckpointer(checkpointer))
// 3. 调用时指定 ThreadID
config := &graph.Config{
ThreadID: "conversation-123",
}
// 第一次对话
runnable.Invoke(ctx, "Hello", config)
// 第二次对话,框架会自动加载之前的状态(记忆)
runnable.Invoke(ctx, "My name is Bob", config)
2. 时间旅行 (Time Travel)
背景与功能
既然我们保存了每一步的快照,我们就可以随时"回到过去"。这对于调试(重现错误)、人工介入(修正中间步骤)以及探索性执行(尝试不同的分支)非常有价值。
实现原理
时间旅行本质上是基于 Checkpoint ID 的状态加载。当你指定一个历史的 CheckpointID 时,LangGraphGo
会加载那个时刻的状态,而不是最新的状态。如果你从那个状态继续执行,系统会创建一个新的分支(Fork),保留原始历史不变。
代码展示:查看历史
// 列出该线程的所有历史 Checkpoint
checkpoints, _ := runnable.ListCheckpoints(ctx, config)
for _, cp := range checkpoints {
fmt.Printf("Step: %d, ID: %s, Time: %s\n",
cp.Metadata["step"], cp.ID, cp.Timestamp)
// 你可以查看当时的状态数据
// fmt.Println(cp.State)
}
代码展示:回到过去 (Forking)
// 假设我们想回到倒数第二步
pastCheckpoint := checkpoints[1]
forkConfig := &graph.Config{
ThreadID: "conversation-123",
CheckpointID: pastCheckpoint.ID, // 指定要恢复的 ID
}
// 从过去的状态继续执行,但这会产生一个新的分支
runnable.Invoke(ctx, "Let's try a different approach", forkConfig)
代码展示:修改历史 (UpdateState)
更进一步,你甚至可以修改过去的状态。这在人工介入场景中非常有用,例如用户纠正了 Agent 在某一步的错误记忆。
// 假设我们在某个 Checkpoint 发现 Agent 记错了名字
// 我们手动更新状态
runnable.UpdateState(ctx, config, map[string]interface{}{
"user_name": "Alice", // 修正名字
})
// 这里的 config 如果包含 CheckpointID,就是修改历史;
// 如果不包含,就是修改当前最新状态。
// 修改后,下一次 Invoke 将基于这个修正后的状态运行。