以编程方式使用文档

LangGraph 智能体不是黑盒。每个图由 **命名节点** 按顺序或并行执行:分类、研究、分析, 综合。图形执行卡片通过渲染卡片使这个管道可见 为每个节点,显示其状态,实时流式传输其内容,并 跟踪整个工作流程的完成情况。用户清楚地看到智能体 正在做什么,处于哪个步骤,以及每个步骤产生了什么。

这种模式对生产环境中的智能体特别有用,因为它将图 结构转化为产品 UX。与其将运行视为单一的助手 响应,你可以暴露相同的检查点、节点名称、状态键,以及 LangGraph 内部使用的流元数据。

图节点如何映射到 UI 卡片

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

  1. **分类**:对用户查询进行分类
  2. **研究**:收集相关信息
  3. **分析**:从研究中得出结论
  4. **综合**:生成最终、精炼的响应

每个节点将其输出写入图的特定状态键。在 前端,你不需要硬编码该映射,因为 @useStream在运行时发现 每个节点,并通过 stream.subgraphs 为每个观察到的步骤暴露一个 [SubgraphDiscoverySnapshot

// Nodes are discovered automatically — no hardcoded list needed
const graphNodes = [...stream.subgraphs.values()];

// Each snapshot carries the node name and current status
graphNodes.forEach((node) => {
  console.log(node.nodeName, node.status); // "classify", "running"
});

使用 node.nodeName 作为进度条和卡片标题的标签。传递每个 快照到 useMessages(stream, node) 以渲染节点范围的流式内容 而不将 UI 耦合到图状态键名称。

此映射成为图和 UI 之间的契约。后端 作者可以有意识地添加、重命名或重新排序节点,而前端作者 决定如何可视化每个状态键:状态徽章、Markdown 面板、 表格、图表、追踪视图或审批卡片。

设置 useStream

按常规方式连接 @[useStream。你将使用的关键属性是 messages (用于对话)和 subgraphs (用于在 当前运行中发现的图节点)。将每个发现的子图快照传递给选择器以读取 该节点范围内的消息。

const AGENT_URL = "http://localhost:2024";

  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "graph_execution_cards",
  });
  const graphNodes = [...stream.subgraphs.values()];

  return (




  );
}
<script setup lang="ts">


const AGENT_URL = "http://localhost:2024";

const stream = useStream<typeof myAgent>({
  apiUrl: AGENT_URL,
  assistantId: "graph_execution_cards",
});
</script>

<template>




</template>
<script lang="ts">


  const AGENT_URL = "http://localhost:2024";

  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "graph_execution_cards",
  });
</script>



  
const AGENT_URL = "http://localhost:2024";

@Component({
  selector: "app-pipeline-chat",
  template: `

      <app-pipeline-progress
        [nodes]="graphNodes()"
        [isLoading]="stream.isLoading()"
      />
      <app-node-card-list
        [nodes]="graphNodes()"
        [stream]="stream"
        [isLoading]="stream.isLoading()"
      />

  `,
})

  stream = injectStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "graph_execution_cards",
  });

  graphNodes = computed(() => [...this.stream.subgraphs().values()]);
}

将流式令牌路由到节点

当图流式传输时,每个发现的子图快照标识其 所属。将该快照传递给选择器钩子或组合式函数来读取 该节点的作用域消息:

function NodeCard({
  node,
  stream,
}: {
  node: SubgraphDiscoverySnapshot;
  stream: AnyStream;
}) {
  const messages = useMessages(stream, node);
  const lastAIMessage = messages.find(AIMessage.isInstance);
  const streamingContent = lastAIMessage?.text ?? "";

  return ;
}

第一个挂载的选择器会为该节点命名空间打开一个作用域订阅。 当节点卡片卸载时,订阅会自动释放。

确定节点状态

每个发现的节点都带有其当前状态。使用 node.status 直接获取; 发现快照会报告 "pending", "running", "complete", or "error":

type NodeStatus = SubgraphDiscoverySnapshot["status"];

const status: NodeStatus = node.status;

构建管道进度条

顶部的水平进度条让用户可以俯瞰整个 管道。每个步骤都是一个带标签的段落,随着节点完成而填充:

function PipelineProgress({
  nodes,
  isLoading,
}: {
  nodes: SubgraphDiscoverySnapshot[];
  isLoading: boolean;
}) {
  const firstIncompleteIdx = nodes.findIndex((node) => node.status !== "complete");

  return (

      {nodes.map((node, i) => {
        const isRunning =
          isLoading && node.status !== "complete" && firstIncompleteIdx === i;
        const colors = {
          pending: "bg-gray-200 text-gray-500",
          running: "bg-blue-400 text-white animate-pulse",
          complete: "bg-green-500 text-white",
          error: "bg-red-500 text-white",
        };
        const status = isRunning ? "running" : node.status;

        return (


              {node.nodeName}

            {i < nodes.length - 1 && (

            )}

        );
      })}

  );
}

构建可折叠的 NodeCard 组件

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

function NodeCard({
  node,
  stream,
}: {
  node: SubgraphDiscoverySnapshot;
  stream: AnyStream;
}) {
  const [open, setOpen] = useState(node.status === "running");
  const messages = useMessages(stream, node);
  const lastAIMessage = messages.find(AIMessage.isInstance);

  useEffect(() => {
    if (node.status === "running") setOpen(true);
    if (node.status === "complete") setOpen(false);
  }, [node.status]);

  return (

      <button
        onClick={() => setOpen(!open)}
        className="flex w-full items-center justify-between p-4"
      >

          <h3 className="font-semibold">{node.nodeName}</h3>


        <span className={open ? "rotate-90" : ""}>▶</span>
      </button>

      {open && (


            {lastAIMessage?.text?.trim()
              ? {lastAIMessage.text}
              : <p className="italic text-gray-500">Processing...</p>}


      )}

  );
}

流式内容与已完成内容

节点卡片读取流式内容和最终内容的作用域消息。这样 可以避免假设图节点名称与其写入的状态键相匹配(例如 , do_research 写入 research 在 playground 图中):

何时使用
useMessages(stream, node)渲染节点作用域的流式和最终消息
stream.values读取整个图状态,如最终 synthesis 字段,使用实际的状态键

模式是:在节点卡片中显示最近的作用域 AI 消息,并且 仅在 stream.values 需要图状态字段时才使用。

由于作用域消息与生成节点绑定,UI 可以支持 并行图路径,而无需从消息顺序猜测。每个卡片从其所属 节点的流事件更新,完成的值仍然可以通过 获取 stream.values.

function NodeContent({ stream, node }: { stream: AnyStream; node: SubgraphDiscoverySnapshot }) {
  const messages = useMessages(stream, node);
  const content = messages.find(AIMessage.isInstance)?.text ?? "";

  return {content};
}

整合一切

这是结合了路由、状态检测和卡片的完整卡片列表 rendering:

function NodeCardList({
  nodes,
  stream,
  isLoading,
}: {
  nodes: SubgraphDiscoverySnapshot[];
  stream: AnyStream;
  isLoading: boolean;
}) {
  const firstIncompleteIdx = nodes.findIndex((node) => node.status !== "complete");

  return (

      {nodes.map((node, i) => {
        const isComplete = node.status === "complete";
        const isRunning = isLoading && !isComplete && firstIncompleteIdx === i;
        if (!isComplete && !isRunning) return null;

        return ;
      })}

  );
}

使用案例

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

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

处理动态流水线

并非所有图都有固定的节点集。某些流水线会基于输入添加或跳过节点 。发现图仅包含当前线程观察到的节点: 这确保您的 UI 仅显示与当前执行相关的节点卡片,

const activeNodes = [...stream.subgraphs.values()];

避免出现空的占位卡片。 如果您的图有条件分支(例如,为简单的

从流中发现节点

- **,从**渲染卡片,而非硬编码预期节点;条件性或被跳过的步骤在运行前 stream.subgraphs 不会显示。 将状态键视为 UI 契约 - **。决定哪些图输出应该**足够稳定以供前端渲染,并将这些键的文档保留在 图定义旁边。 为节点卡片使用作用域消息 - **。它们在节点流式传输时和完成后都能正常工作,**无需将 UI 卡片耦合到状态键名称。 自动折叠已完成的节点 - **。在长流水线中,自动折叠已完成的**卡片,以便用户专注于当前活跃的步骤。 显示预计时间 - **。如果您有每个节点**耗时的历史数据,请显示时间估计以设定用户期望。 添加全局进度指示器 - **。用**总体进度条(例如,"第 2 步,共 4 步")补充每个节点卡片, 放置在流水线视图的顶部。 - **每个节点单独处理错误**。如果某个节点失败,在其卡片中显示错误, 而不折叠整个流水线。其他节点可能仍会完成。 successfully.