跳到内容

如何使用 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,
  });
};

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

设置

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

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' }