人工介入 (Human-in-the-loop)

在许多实际的 Agent 应用中,完全自动化并不总是理想的,甚至可能是危险的。你可能需要人工介入来批准关键操作(如转账)、提供 Agent 缺失的信息,或者修正 Agent 的错误推理。LangGraphGo 将"人"视为图中的一等公民。

1. 静态中断 (InterruptBefore)

背景与功能

这是最常见的人工介入模式:在执行某个特定节点之前,总是暂停下来等待批准。这类似于 CI/CD 流水线中的人工审批环节。

实现原理

在编译或运行图时,我们配置 InterruptBefore 列表。当运行时准备执行列表中的节点时,它会检查是否收到了恢复指令。如果没有,它会保存当前状态(Checkpoint)并挂起执行,返回一个特殊的错误。

代码展示

// 1. 设置中断配置
config := &graph.Config{
    InterruptBefore: []string{"human_approval_node"},
}

// 2. 运行图
// 当执行到 "human_approval_node" 时,Invoke 调用将返回 GraphInterrupt 错误
res, err := runnable.InvokeWithConfig(ctx, initialState, config)

// 3. 检查中断
var interrupt *graph.GraphInterrupt
if errors.As(err, &interrupt) {
    fmt.Println("图已暂停,等待人工审批...")
    // 此时可以将 ThreadID 返回给前端,让用户进行操作
}

2. 动态中断 (Dynamic Interrupt)

背景与功能

有时我们无法预知何时需要中断。例如,只有当 Agent 的置信度低于某个阈值,或者检测到潜在的敏感内容时,才需要人工介入。动态中断允许节点在运行时自主决定是否暂停。

实现原理

节点函数可以返回一个 graph.Interrupt 类型的错误。运行时捕获到这个错误后,会立即停止后续执行,并保存当前状态。

代码展示

func sensitiveActionNode(ctx context.Context, state interface{}) (interface{}, error) {
    data := state.(MyState)
    
    // 动态检查:如果金额超过 1000,触发中断
    if data.Amount > 1000 {
        return nil, graph.Interrupt("Amount too high, requesting human review")
    }
    
    // 否则直接执行
    return executeTransfer(data), nil
}

3. 恢复执行 (Resume)

背景与功能

暂停只是为了更好地继续。在人工完成审批或提供输入后,我们需要恢复图的执行。LangGraphGo 允许你带着新的输入或修改后的状态恢复执行。

实现原理

使用 ResumeFrom 配置或直接调用 Invoke 并传入相同的 ThreadID。框架会加载最新的 Checkpoint。如果在这个 Checkpoint 上有挂起的中断,框架会清除中断标志,并使用可选的新输入继续执行被暂停的节点。

代码展示

// 用户批准了操作
// 我们更新状态(例如设置 Approved = true)
runnable.UpdateState(ctx, config, map[string]interface{}{"approved": true})

// 恢复执行
// 注意:这里不需要再次指定 InterruptBefore,除非你想再次中断
resumeConfig := &graph.Config{
    ThreadID: "conversation-123",
}

// 再次调用 Invoke,图会从上次暂停的地方继续
runnable.Invoke(ctx, nil, resumeConfig)