LangGraph框架研究-开发测试

上篇

引言

本篇主要介绍一下langgraph开发部署问题。langchain底层执行已经切换成了langgraph,从这个层面来看,langchain依赖的于langgrap,所以在新版的langchain看不到了那种让人迷惑的管道操作链式调用。从这点来看,我们应该使用langchain开发。但是当你学习langgraph时,你发现langgraph在使用langchain。这看起来交互式有点复杂。如果是简单的智能体应用,可以考虑直接使用langchain。langchain的子代理、移交、路由、技能多agent模式如果能满足架构需求,就可以不用langgraph,越复杂的东西越不好维护,以后想升级也困难。如果涉及复杂工作流或者图结构,那么可以考虑使用图。

1 应用架构

LangGraph 应用程序由一个或多个图(Graph)、一个配置文件(langgraph.json)、一个指定依赖关系的文件,以及一个可选的用于定义环境变量的 .env 文件组成。本指南将展示应用程序的典型结构,并演示如何提供必要的配置,以便通过 LangSmith 部署应用程序。

LangSmith Deployment 是一个托管式平台,专门用于部署和扩展 LangGraph 智能体。它负责处理底层基础设施、弹性伸缩以及运维相关的事务,让你可以直接从代码仓库部署那些有状态的、长期运行的智能体。你可以在部署文档中了解更多详情

1.1 核心概念

  1. LangGraph 配置文件 (langgraph.json):用于指定应用程序所需的依赖项、图结构以及环境变量。
  2. 应用逻辑图 (Graphs):即实现应用程序核心业务逻辑的代码。
  3. 依赖文件 :用于指定运行应用程序所需的各种库和依赖项(通常指 requirements.txtpyproject.toml)。
  4. 环境变量:应用程序运行所必需的环境配置参数(如 API 密钥等)

1.2 文件架构

推荐使用后面这种方式,依赖管理可以使用uv或者poetry

1.3 配置文件

langgraph.json 文件是一个 JSON 格式的配置文件,它专门用于指定部署 LangGraph 应用程序所需的依赖项、图结构、环境变量以及其他设置。

关于该 JSON 文件中支持的所有配置键(keys)的详细信息,请查阅 LangGraph 配置文件参考文档。

LangGraph 命令行工具(CLI)默认会使用当前目录下的 langgraph.json 配置文件

示例

  • 依赖项 :包含一个自定义的本地包以及 langchain_openai 包。

  • 图加载 :将从 ./your_package/your_file.py 文件中加载一个图,对应的变量名为 variable

  • 环境变量 :从 .env 文件中加载

    复制代码
    {
      "dependencies": ["langchain_openai", "./your_package"],
      "graphs": {
        "my_agent": "./your_package/your_file.py:agent"
      },
      "env": "./.env"
    }

    ​上面的配置依赖项写成了''.'',表示依赖由层的requirements.txt或者pyproject.toml提供

1.4 依赖

LangGraph 应用程序可能会依赖其他的 Python 包。

为了确保依赖项能正确配置,你通常需要提供以下信息:

  1. 依赖文件 :在目录中包含一个用于指定依赖的文件(例如 requirements.txtpyproject.tomlpackage.json)。
  2. 配置键 :在 LangGraph 配置文件(langgraph.json)中包含一个 dependencies 键,用于指定运行 LangGraph 应用程序所需的依赖项(通常是指向上述文件的引用)。
  3. 系统级依赖 :任何额外的二进制文件或系统库,可以通过 LangGraph 配置文件中的 dockerfile_lines 键来指定。

1.5 图

请使用 LangGraph 配置文件中的 graphs 键,来指定哪些图(Graphs)将在部署后的 LangGraph 应用程序中可用。你可以在配置文件中指定一个或多个图。每个图都需要通过一个唯一的名称和一个路径来标识,该路径指向以下两者之一:已编译的图对象(即直接指向图实例)。

定义图的函数(即指向一个能构建并返回图的函数)。

1.6 环境变量

如果你在本地运行已部署的 LangGraph 应用程序(进行调试),你可以在 LangGraph 配置文件的 env 键中配置环境变量。对于生产环境的部署,你通常需要在部署环境(即云端的设置面板)中配置环境变量

2 测试

在你完成了 LangGraph 智能体的原型设计后,接下来的自然步骤就是添加测试。本指南将介绍一些在编写单元测试时可以使用的实用模式。

请注意 :本指南是专门针对 LangGraph 的,主要涉及具有自定义结构 的图(Graphs)的测试场景。如果你只是刚入门,建议先查阅关于使用 LangChain 内置 create_agent 的测试指南

2.1 准备

复制代码
pip install -U pytest

2.2 开始

由于许多 LangGraph 智能体都依赖于状态(State),一个实用的模式是:在每次使用图之前先构建它,然后在测试内部使用一个新的检查点(Checkpointer)实例对其进行编译。下面的示例展示了这一过程是如何运作的:它使用了一个简单的线性图,依次经过 node1node2。每个节点都会更新唯一的状态键 my_key

python 复制代码
import pytest

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver

def create_graph() -> StateGraph:
    class MyState(TypedDict):
        my_key: str

    graph = StateGraph(MyState)
    graph.add_node("node1", lambda state: {"my_key": "hello from node1"})
    graph.add_node("node2", lambda state: {"my_key": "hello from node2"})
    graph.add_edge(START, "node1")
    graph.add_edge("node1", "node2")
    graph.add_edge("node2", END)
    return graph

def test_basic_agent_execution() -> None:
    checkpointer = MemorySaver()
    graph = create_graph()
    compiled_graph = graph.compile(checkpointer=checkpointer)
    result = compiled_graph.invoke(
        {"my_key": "initial_value"},
        config={"configurable": {"thread_id": "1"}}
    )
    assert result["my_key"] == "hello from node2"

2.3 测试独立的节点和边

已编译的 LangGraph 智能体会通过 graph.nodes 暴露对每个独立节点的引用。你可以利用这一点来测试智能体内部的单个节点。

请注意 :这样做会绕过编译图时传入的任何检查点(Checkpointer):

python 复制代码
import pytest

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver

def create_graph() -> StateGraph:
    class MyState(TypedDict):
        my_key: str

    graph = StateGraph(MyState)
    graph.add_node("node1", lambda state: {"my_key": "hello from node1"})
    graph.add_node("node2", lambda state: {"my_key": "hello from node2"})
    graph.add_edge(START, "node1")
    graph.add_edge("node1", "node2")
    graph.add_edge("node2", END)
    return graph

def test_individual_node_execution() -> None:
    # Will be ignored in this example
    checkpointer = MemorySaver()
    graph = create_graph()
    compiled_graph = graph.compile(checkpointer=checkpointer)
    # Only invoke node 1
    result = compiled_graph.nodes["node1"].invoke(
        {"my_key": "initial_value"},
    )
    assert result["my_key"] == "hello from node1"

2.4 部分执行

对于由大型图构成的智能体,你可能希望测试智能体内部的部分执行路径 ,而不是整个端到端的流程。在某些情况下,将这些部分重构为子图并在隔离状态下调用它们在语义上是合理的。

但是,如果你不想更改智能体图的整体结构,你可以利用 LangGraph 的持久化机制 来模拟一种状态:让智能体在目标部分开始前暂停 ,并在目标部分结束后再次暂停

具体步骤如下:

  1. 使用检查点(Checkpointer)编译你的智能体(对于测试而言,内存检查点 InMemorySaver 就足够了)。
  2. 调用智能体的 update_state 方法,并将 as_node 参数设置为你想要开始测试的那个节点之前的节点名称。
  3. 使用与更新状态时相同的 thread_id 调用你的智能体,并将 interrupt_after 参数设置为你想要停止的那个节点的名称。

这是一个示例,展示了如何在线性图中仅执行第二个和第三个节点

python 复制代码
import pytest

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver

def create_graph() -> StateGraph:
    class MyState(TypedDict):
        my_key: str

    graph = StateGraph(MyState)
    graph.add_node("node1", lambda state: {"my_key": "hello from node1"})
    graph.add_node("node2", lambda state: {"my_key": "hello from node2"})
    graph.add_node("node3", lambda state: {"my_key": "hello from node3"})
    graph.add_node("node4", lambda state: {"my_key": "hello from node4"})
    graph.add_edge(START, "node1")
    graph.add_edge("node1", "node2")
    graph.add_edge("node2", "node3")
    graph.add_edge("node3", "node4")
    graph.add_edge("node4", END)
    return graph

def test_partial_execution_from_node2_to_node3() -> None:
    checkpointer = MemorySaver()
    graph = create_graph()
    compiled_graph = graph.compile(checkpointer=checkpointer)
    compiled_graph.update_state(
        config={
          "configurable": {
            "thread_id": "1"
          }
        },
        # The state passed into node 2 - simulating the state at
        # the end of node 1
        values={"my_key": "initial_value"},
        # Update saved state as if it came from node 1
        # Execution will resume at node 2
        as_node="node1",
    )
    result = compiled_graph.invoke(
        # Resume execution by passing None
        None,
        config={"configurable": {"thread_id": "1"}},
        # Stop after node 3 so that node 4 doesn't run
        interrupt_after="node3",
    )
    assert result["my_key"] == "hello from node3"

3 LangSmith Studio

请参看langchain篇

4 Agent Chat UI

请参看langchain篇

5 LangSmith 部署

请参看langchain篇

6 LangSmith 监测

请参看langchain篇

7 前端

7.1 概览

构建能够实时可视化 LangGraph 管道(pipelines)的前端。这些模式展示了如何渲染多步骤的图执行过程,包括每个节点的状态 以及来自自定义 StateGraph 工作流的流式内容

7.1.1 架构

LangGraph 的图是由通过边连接的命名节点 组成的。每个节点执行一个步骤(如分类、研究、分析、综合),并将输出写入特定的状态键(state key) 。在前端方面,useStream 提供了对节点输出、流式 Token 和图元数据的响应式访问 ,使你可以将每个节点映射为一个 UI 卡片

在前端,useStream 暴露了 stream.values 用于获取已完成节点的输出,以及 getMessagesMetadata 用于识别每个流式 Token 是由哪个节点生成的

复制代码
import { useStream } from "@langchain/react";

function Pipeline() {
  const stream = useStream<typeof graph>({
    apiUrl: "http://localhost:2024",
    assistantId: "pipeline",
  });

  const classification = stream.values?.classification;
  const research = stream.values?.research;
  const analysis = stream.values?.analysis;
}

7.1.2 模式

**图执行:**可视化多步骤的图管道,展示每个节点的状态和流式内容

7.1.3 相关模式

LangChain 的前端模式------包括 Markdown 消息、工具调用、乐观更新等------适用于任何 LangGraph 图。无论你使用的是 createAgentcreateDeepAgent 还是自定义的 StateGraphuseStream 钩子都提供相同的核心 API

7.2 图执行

LangGraph 智能体并不是黑盒 。每个图都是由按顺序或并行执行的命名节点 组成的:比如分类、研究、分析、综合。图执行卡片 通过为每个节点渲染一个卡片,让这条管道变得可见 。这些卡片会显示节点的状态,实时流式展示其内容,并追踪整个工作流的完成情况。用户可以确切地看到智能体正在做什么、当前处于哪一步,以及每一步产出了什么结果。

7.2.1 图节点如何映射为 UI 卡片

一个 LangGraph 图定义了一系列节点,每个节点负责一项特定的任务。例如,一个研究管道可能包含:

分类:对用户查询进行归类

研究:收集相关信息

分析:从研究中得出结论

综合:生成最终完善的回复

每个节点都会将其输出写入图的状态中的特定键。通过将这些节点名称和状态键映射到 UI 组件,你就可以创建整个管道的可视化表示

复制代码
const PIPELINE_NODES = [
  { name: "classify", stateKey: "classification", label: "Classify" },
  { name: "do_research", stateKey: "research", label: "Research" },
  { name: "analyze", stateKey: "analysis", label: "Analyze" },
  { name: "synthesize", stateKey: "synthesis", label: "Synthesize" },
];

const PIPELINE_NODE_NAMES = new Set(PIPELINE_NODES.map((n) => n.name));

7.2.2 设置 useStream

像往常一样连接 useStream。你将使用的关键属性有:

  • messages:用于流式内容的路由。
  • values:用于获取已完成的节点输出。
  • getMessagesMetadata:用于识别每个 Token 是由哪个节点生成的。

定义一个与你的智能体状态模式匹配的 TypeScript 接口 ,并将其作为类型参数传递给 useStream。这样可以让你类型安全地 访问状态值,包括每个管道节点的自定义状态键。在下面的示例中,请用你的接口名称替换 typeof myAgent

复制代码
import type { BaseMessage } from "@langchain/core/messages";

interface AgentState {
  messages: BaseMessage[];
  classification: string;
  research: string;
  analysis: string;
  synthesis: string;
}

7.2.3 将流式 Token 路由到节点

当智能体进行流式输出时,每条消息都会附带元数据 ,用于标识它是由哪个图节点生成的。请使用 getMessagesMetadata 提取 langgraph_node 的值,并将 Token 路由到正确的卡片

复制代码
function getStreamingContent(
  messages: BaseMessage[],
  getMetadata: (msg: BaseMessage) => MessageMetadata | undefined
): Record<string, string> {
  const content: Record<string, string> = {};

  for (const message of messages) {
    if (message.type !== "ai") continue;

    const metadata = getMetadata(message);
    const node = metadata?.streamMetadata?.langgraph_node;

    if (node && PIPELINE_NODE_NAMES.has(node)) {
      content[node] = typeof message.content === "string"
        ? message.content
        : "";
    }
  }

  return content;
}

function getStreamingContent(
  messages: BaseMessage[],
  getMetadata: (msg: BaseMessage) => MessageMetadata | undefined
): Record<string, string> {
  const content: Record<string, string> = {};

  for (const message of messages) {
    if (message.type !== "ai") continue;

    const metadata = getMetadata(message);
    const node = metadata?.streamMetadata?.langgraph_node;

    if (node && PIPELINE_NODE_NAMES.has(node)) {
      content[node] = typeof message.content === "string"
        ? message.content
        : "";
    }
  }

  return content;
}

这样你就得到了一个从节点名称 到其当前流式内容 的映射。随着 Token(字符)的不断到达,对应的卡片会实时更新

7.2.4 确定节点状态

每个节点可能处于四种状态之一:未开始流式中已完成空闲。你需要从两个来源推导(计算)出状态:

  • 流式内容映射 :用于判断哪些节点是**活跃(正在运行)**的。

  • stream.values :用于判断哪些节点已经完成

    type NodeStatus = "idle" | "streaming" | "complete";

    function getNodeStatus(
    node: { name: string; stateKey: string },
    streamingContent: Record<string, string>,
    values: Record<string, unknown>
    ): NodeStatus {
    if (values?.[node.stateKey]) return "complete";
    if (streamingContent[node.name]) return "streaming";
    return "idle";
    }

7.2.5 构建管道进度条

顶部的水平进度条可以让用户对整个管道有一个鸟瞰视角(全局概览)。每个步骤都是一个带有标签的区段,随着节点的完成,这些区段会逐渐填充:

复制代码
function PipelineProgress({
  nodes,
  values,
  streamingContent,
}: {
  nodes: typeof PIPELINE_NODES;
  values: Record<string, unknown>;
  streamingContent: Record<string, string>;
}) {
  return (
    <div className="flex items-center gap-1">
      {nodes.map((node, i) => {
        const status = getNodeStatus(node, streamingContent, values);
        const colors = {
          idle: "bg-gray-200 text-gray-500",
          streaming: "bg-blue-400 text-white animate-pulse",
          complete: "bg-green-500 text-white",
        };

        return (
          <div key={node.name} className="flex items-center">
            <div
              className={`rounded-full px-3 py-1 text-xs font-medium ${colors[status]}`}
            >
              {node.label}
            </div>
            {i < nodes.length - 1 && (
              <div
                className={`mx-1 h-0.5 w-6 ${
                  status === "complete" ? "bg-green-500" : "bg-gray-200"
                }`}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}function PipelineProgress({
  nodes,
  values,
  streamingContent,
}: {
  nodes: typeof PIPELINE_NODES;
  values: Record<string, unknown>;
  streamingContent: Record<string, string>;
}) {
  return (
    <div className="flex items-center gap-1">
      {nodes.map((node, i) => {
        const status = getNodeStatus(node, streamingContent, values);
        const colors = {
          idle: "bg-gray-200 text-gray-500",
          streaming: "bg-blue-400 text-white animate-pulse",
          complete: "bg-green-500 text-white",
        };

        return (
          <div key={node.name} className="flex items-center">
            <div
              className={`rounded-full px-3 py-1 text-xs font-medium ${colors[status]}`}
            >
              {node.label}
            </div>
            {i < nodes.length - 1 && (
              <div
                className={`mx-1 h-0.5 w-6 ${
                  status === "complete" ? "bg-green-500" : "bg-gray-200"
                }`}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}function PipelineProgress({
  nodes,
  values,
  streamingContent,
}: {
  nodes: typeof PIPELINE_NODES;
  values: Record<string, unknown>;
  streamingContent: Record<string, string>;
}) {
  return (
    <div className="flex items-center gap-1">
      {nodes.map((node, i) => {
        const status = getNodeStatus(node, streamingContent, values);
        const colors = {
          idle: "bg-gray-200 text-gray-500",
          streaming: "bg-blue-400 text-white animate-pulse",
          complete: "bg-green-500 text-white",
        };

        return (
          <div key={node.name} className="flex items-center">
            <div
              className={`rounded-full px-3 py-1 text-xs font-medium ${colors[status]}`}
            >
              {node.label}
            </div>
            {i < nodes.length - 1 && (
              <div
                className={`mx-1 h-0.5 w-6 ${
                  status === "complete" ? "bg-green-500" : "bg-gray-200"
                }`}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}

7.2.6 构建可折叠的 NodeCard 组件

每个节点都有属于自己的卡片,用于显示状态徽章内容 (流式中或最终结果),以及一个用于展示长篇输出的可折叠主体

复制代码
function NodeCard({
  node,
  status,
  streamingContent,
  completedContent,
}: {
  node: { name: string; stateKey: string; label: string };
  status: NodeStatus;
  streamingContent: string | undefined;
  completedContent: unknown;
}) {
  const [collapsed, setCollapsed] = useState(false);

  const displayContent =
    status === "complete"
      ? formatContent(completedContent)
      : streamingContent ?? "";

  const statusBadge = {
    idle: { text: "Waiting", className: "bg-gray-100 text-gray-600" },
    streaming: {
      text: "Running",
      className: "bg-blue-100 text-blue-700 animate-pulse",
    },
    complete: { text: "Done", className: "bg-green-100 text-green-700" },
  };

  const badge = statusBadge[status];

  return (
    <div className="rounded-lg border bg-white shadow-sm">
      <button
        onClick={() => setCollapsed(!collapsed)}
        className="flex w-full items-center justify-between p-4"
      >
        <div className="flex items-center gap-3">
          <h3 className="font-semibold">{node.label}</h3>
          <span
            className={`rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`}
          >
            {badge.text}
          </span>
        </div>
        <ChevronIcon direction={collapsed ? "down" : "up"} />
      </button>

      {!collapsed && displayContent && (
        <div className="border-t px-4 py-3">
          <div className="prose prose-sm max-w-none">
            {displayContent}
            {status === "streaming" && (
              <span className="inline-block h-4 w-1 animate-pulse bg-blue-500" />
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function formatContent(value: unknown): string {
  if (typeof value === "string") return value;
  if (value == null) return "";
  return JSON.stringify(value, null, 2);
}

7.2.7 流式内容与已完成内容

每个节点都有两个内容来源,为了获得流畅的用户体验,选择正确的来源至关重要

来源 何时使用
streamingContent[node.name] 当节点正在活动流式输出时,这里包含实时到达的 Token(字符)。
stream.values[node.stateKey] 当节点完成后,这里包含最终的、已提交的输出。

模式是 :在实时更新阶段显示流式内容,一旦节点完成,就**回退(切换)**到已提交的状态值。

复制代码
for (const node of PIPELINE_NODES) {
  const status = getNodeStatus(node, streamingContent, stream.values);

  const content =
    status === "streaming"
      ? streamingContent[node.name]
      : stream.values?.[node.stateKey];
}

流式内容可能包含尚未完全形成的局部 Token 或 Markdown 。如果你要渲染 Markdown,请务必确保你的渲染器能够优雅地处理不完整的语法 (例如:一个未闭合的加粗标记 **

7.2.6 整合所有内容

这里是完整的卡片列表代码,它结合了路由、状态检测和卡片渲染:

复制代码
function NodeCardList({
  nodes,
  messages,
  values,
  getMetadata,
}: {
  nodes: typeof PIPELINE_NODES;
  messages: BaseMessage[];
  values: Record<string, unknown>;
  getMetadata: (msg: BaseMessage) => MessageMetadata | undefined;
}) {
  const streamingContent = getStreamingContent(messages, getMetadata);

  return (
    <div className="space-y-3">
      {nodes.map((node) => {
        const status = getNodeStatus(node, streamingContent, values);
        return (
          <NodeCard
            key={node.name}
            node={node}
            status={status}
            streamingContent={streamingContent[node.name]}
            completedContent={values?.[node.stateKey]}
          />
        );
      })}
    </div>
  );
}

7.2.6 应用场景

图执行卡片非常适用于任何需要可见性的多步骤管道

  • 研究管道:分类 → 收集来源 → 分析 → 综合生成报告
  • 内容生成:大纲 → 起草 → 事实核查 → 编辑 → 发布
  • 数据处理:摄取 → 验证 → 转换 → 聚合 → 导出
  • 代码生成:理解需求 → 规划架构 → 编写代码 → 审查 → 测试
  • 决策工作流:收集背景信息 → 评估选项 → 评分备选方案 → 推荐

7.2.7 处理动态管道

并非所有的图(Graph)都拥有一组固定的节点 。有些管道会根据输入动态添加或跳过节点 。处理方法是:检查哪些状态键(state keys)实际上拥有值

复制代码
const activeNodes = PIPELINE_NODES.filter(
  (node) =>
    streamingContent[node.name] ||
    values?.[node.stateKey] ||
    node.name === currentNode
);

这能确保你的 UI 只显示与当前执行相关的节点卡片 ,避免出现空的占位卡片

如果你的图包含条件分支 (例如:对于简单的事实性问题,跳过"研究"步骤),那么被跳过的节点永远不会出现在流式内容或状态值中。你的管道进度条应该反映这一点,通过变暗隐藏来标识被跳过的步骤

7.2.7 最佳实践

  • 以声明方式定义节点 :将 PIPELINE_NODES 数组作为单一事实来源,统一映射节点名称、状态键和显示标签。

  • 优先使用活跃节点的内容流 :这能给用户即时反馈。仅在节点完成后才回退到已提交的状态值。

  • 自动折叠已完成的节点 :在长管道中,自动折叠已完成的卡片,以便用户聚焦于当前活跃的步骤

  • 显示预估时间 :如果你有每个节点耗时的历史数据,请显示时间预估以设定用户预期。

  • 添加全局进度指示器:在管道视图顶部添加一个整体进度条(例如"第 2 步,共 4 步"),作为单节点卡片的补充。

  • 按节点处理错误 :如果某个节点失败,在其卡片中显示错误,而不要导致整个管道崩溃。其他节点仍可能成功完成。

    def my_node(state: State) -> Command[Literal["my_other_node"]]:
    if state["foo"] == "bar":
    return Command(update={"foo": "baz"}, goto="my_other_node")

当你需要同时更新状态并路由 到不同的节点时,请使用 Command。如果你只需要路由而不需要更新状态 ,请改用条件边

当在节点函数中返回 Command 时,你必须添加返回类型注解 ,列出该节点可能路由到的节点名称列表,例如 Command[Literal["my_other_node"]]。这对于图的渲染 是必要的,它告诉 LangGraph:my_node 可以导航到 my_other_node。这点稍微有点诡异。不好确保两个地方拼写一致

Command 只会添加动态边------使用 add_edge / addEdge 定义的静态边仍然会执行。

例如,如果 node_a 返回 Command(goto="my_other_node"),而你也定义了 graph.add_edge("node_a", "node_b"),那么 node_b 和 my_other_node 都会运行

请查看这篇****操作指南**** ,获取关于如何使用 Command 的****端到端示例****。

如果你正在使用子图 ,可以通过在 Command 中指定 graph=Command.PARENT,从子图内的节点导航到父图中的不同节点

复制代码
def my_node(state: State) -> Command[Literal["other_subgraph"]]:
    return Command(
        update={"foo": "bar"},
        goto="other_subgraph",  # where `other_subgraph` is a node in the parent graph
        graph=Command.PARENT
    )

graph 设置为 Command.PARENT 将会导航到最近的父图 。当你从子图节点向父图节点发送更新,且更新的是父图和子图状态模式中共享的键 时,你必须在父图状态 中为该键定义一个Reducer(归约器)。请参见此示例

这在实现多智能体移交 时特别有用。请查看 "导航到父图中的节点" 以获取详细信息

传入 invokestream 的输入

只有 Command(resume=...) 这种模式是专门设计用来作为输入传递给 invoke() 或 stream() 的。

不要使用 Command(update=...) 作为输入来继续多轮对话------因为一旦你把任何 Command 对象作为输入传进去,系统就会从最新的检查点(即最后运行的那一步,而不是 start 起始节点)恢复运行。如果之前的流程已经跑完了,再传 Command 进去,图表(Graph)看起来就会像是卡住了一样。

要想在现有的线程上继续对话,请直接传递一个普通的输入字典(plain input dict)。

复制代码
# WRONG - graph resumes from the latest checkpoint
# (last step that ran), appears stuck
graph.invoke(Command(update={
    "messages": [{"role": "user", "content": "follow up"}]
}), config)

# CORRECT - plain dict restarts from __start__
graph.invoke( {
    "messages": [{"role": "user", "content": "follow up"}]
}, config)

resume(恢复)

请使用 Command(resume=...) 来提供具体的值,并在中断发生后恢复图表的执行。你传递给 resume 的那个值,将会成为被暂停节点内部 interrupt() 调用的返回值

复制代码
from langgraph.types import Command, interrupt

def human_review(state: State):
    # Pauses the graph and waits for a value
    answer = interrupt("Do you approve?")
    return {"messages": [{"role": "user", "content": answer}]}

# First invocation - hits the interrupt and pauses
result = graph.invoke({"messages": [...]}, config)

# Resume with a value - the interrupt() call returns "yes"
result = graph.invoke(Command(resume="yes"), config)

请查看 中断概念指南,以获取关于中断模式的完整详情,其中包括多重中断和验证循环等内容。

从工具中返回

你可以在工具中返回 Command,以此来更新图表状态并控制流程。

  • 使用 update 来修改状态(例如,保存对话过程中查找到的客户信息)。
  • 使用 goto 在工具执行完成后,将流程路由到指定的节点。

goto 在工具内部使用时,它会添加一条动态边 ------调用该工具的节点上已定义的任何静态边仍然会被执行。

有关详细信息,请参阅"在工具中使用"部分。

总结

本章主要讲开发和测试相关主题,最主要的是文件架构和部署模式。langgraph到此收尾

相关推荐
禹凕2 小时前
PyTorch——安装(有无 NVIDIA 显卡的完整配置方案)
人工智能·pytorch·python
卷心菜狗2 小时前
Python进阶--迭代器
开发语言·python
dragen_light2 小时前
5.ROS2-Topics-Publisher-Subscriber
python
jr-create(•̀⌄•́)2 小时前
LeakyRelu链式法则
开发语言·python·深度学习
vx_biyesheji00013 小时前
计算机毕业设计:Python股价预测与可视化系统 Flask框架 数据分析 可视化 机器学习 随机森林 大数据(建议收藏)✅
python·机器学习·信息可视化·数据分析·flask·课程设计
lulu12165440788 小时前
Claude Code项目大了响应慢怎么办?Subagents、Agent Teams、Git Worktree、工作流编排四种方案深度解析
java·人工智能·python·ai编程
deephub8 小时前
LangChain 还是 LangGraph?一个是编排一个是工具包
人工智能·langchain·大语言模型·langgraph
Ares-Wang9 小时前
Flask》》 Flask-Bcrypt 哈希加密
后端·python·flask
kongba0079 小时前
项目打包 Python Flask 项目发布与打包专家 提示词V1.0
开发语言·python·flask