记忆与时间旅行

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 将基于这个修正后的状态运行。