跳至内容

如何结合控制流和状态更新与 Command

先决条件

本指南假设您熟悉以下内容

此功能还需要 @langchain/langgraph>=0.2.29

将控制流(边)和状态更新(节点)结合起来会很有用。例如,您可能希望在**同一个节点**中既执行状态更新,又决定接下来要转到哪个节点。LangGraph 提供了一种方法来实现这一点,即从节点函数返回一个 Command 对象。

const myNode = (state: typeof StateAnnotation.State) => {
  return new Command({
    // state update
    update: {
      foo: "bar",
    },
    // control flow
    goto: "myOtherNode",
  });
};

如果您正在使用子图,您可能希望从子图中的某个节点导航到另一个子图(即父图中的不同节点)。为此,您可以在 Command 中指定 graph: Command.PARENT

const myNode = (state: typeof StateAnnotation.State) => {
  return new Command({
    update: { foo: "bar" },
    goto: "other_subgraph", // where `other_subgraph` is a node in the parent graph
    graph: Command.PARENT,
  });
};

本指南展示了如何使用 Command 在 LangGraph 应用中添加动态控制流。

设置

首先,让我们安装所需的包

yarn add @langchain/langgraph @langchain/core

设置 LangSmith 以进行 LangGraph 开发

注册 LangSmith,以便快速发现问题并提高您的 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控您使用 LangGraph 构建的 LLM 应用 — 在此处了解更多入门信息。

让我们创建一个包含 3 个节点(A、B 和 C)的简单图。我们将首先执行节点 A,然后根据节点 A 的输出决定接下来是转到节点 B 还是节点 C。

定义图

import { Annotation, Command } from "@langchain/langgraph";

// Define graph state
const StateAnnotation = Annotation.Root({
  foo: Annotation<string>,
});

// Define the nodes
const nodeA = async (_state: typeof StateAnnotation.State) => {
  console.log("Called A");
  // this is a replacement for a real conditional edge function
  const goto = Math.random() > .5 ? "nodeB" : "nodeC";
  // note how Command allows you to BOTH update the graph state AND route to the next node
  return new Command({
    // this is the state update
    update: {
      foo: "a",
    },
    // this is a replacement for an edge
    goto,
  });
};

// Nodes B and C are unchanged
const nodeB = async (state: typeof StateAnnotation.State) => {
  console.log("Called B");
  return {
    foo: state.foo + "|b",
  };
}

const nodeC = async (state: typeof StateAnnotation.State) => {
  console.log("Called C");
  return {
    foo: state.foo + "|c",
  };
}

现在我们可以使用上述节点创建 StateGraph。请注意,该图没有用于路由的条件边!这是因为控制流是在 nodeA 内部使用 Command 定义的。

import { StateGraph } from "@langchain/langgraph";

// NOTE: there are no edges between nodes A, B and C!
const graph = new StateGraph(StateAnnotation)
  .addNode("nodeA", nodeA, {
    ends: ["nodeB", "nodeC"],
  })
  .addNode("nodeB", nodeB)
  .addNode("nodeC", nodeC)
  .addEdge("__start__", "nodeA")
  .compile();

重要提示

您可能已经注意到,我们在使用 Command 的节点中添加了一个额外的参数 ends 字段。这对于图的编译和验证是必需的,并且告诉 LangGraph nodeA 可以导航到 nodeBnodeC

import * as tslab from "tslab";

const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const arrayBuffer = await image.arrayBuffer();

await tslab.display.png(new Uint8Array(arrayBuffer));

如果我们多次运行该图,会看到它根据节点 A 中的随机选择而采取不同的路径(A -> B 或 A -> C)。

await graph.invoke({ foo: "" });
Called A
Called B
{ foo: 'a|b' }

现在让我们演示如何从子图内部导航到父图中的另一个节点。我们将通过将上述示例中的 node_a 更改为一个单节点图来实现,然后将其作为子图添加到我们的父图中。

// Define the nodes
const nodeASubgraph = async (_state: typeof StateAnnotation.State) => {
  console.log("Called A");
  // this is a replacement for a real conditional edge function
  const goto = Math.random() > .5 ? "nodeB" : "nodeC";
  // note how Command allows you to BOTH update the graph state AND route to the next node
  return new Command({
    update: {
      foo: "a",
    },
    goto,
    // this tells LangGraph to navigate to node_b or node_c in the parent graph
    // NOTE: this will navigate to the closest parent graph relative to the subgraph
    graph: Command.PARENT,
  });
};

const subgraph = new StateGraph(StateAnnotation)
  .addNode("nodeA", nodeASubgraph)
  .addEdge("__start__", "nodeA")
  .compile();

const parentGraph= new StateGraph(StateAnnotation)
  .addNode("subgraph", subgraph, { ends: ["nodeB", "nodeC"] })
  .addNode("nodeB", nodeB)
  .addNode("nodeC", nodeC)
  .addEdge("__start__", "subgraph")
  .compile();

await parentGraph.invoke({ foo: "" });
Called A
Called C
{ foo: 'a|c' }