跳到主要内容

文档索引

在以下地址获取完整的文档索引:https://docs.langchain.org.cn/llms.txt

在进一步探索之前,请使用此文件发现所有可用页面。

LangGraph 智能体不是黑盒。每个图形都由命名节点组成,这些节点按顺序或并行执行:分类、研究、分析、合成。图形执行卡片通过为每个节点渲染一张卡片,实时显示其状态、流式传输其内容并跟踪整个工作流的完成情况,使此流水线变得直观。用户可以准确看到智能体正在执行的操作、当前所处的步骤以及每一步的产出。

如何将图形节点映射到 UI 卡片

LangGraph 图形定义了一系列节点,每个节点负责特定任务。例如,研究流水线可能包含
  1. 分类 (Classify):对用户查询进行分类
  2. 研究 (Research):收集相关信息
  3. 分析 (Analyze):从研究中得出结论
  4. 合成 (Synthesize):生成最终的润色回复
每个节点将其输出写入图形状态中的特定键。通过将这些节点名称和状态键映射到 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));

设置 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;
}
import { useStream } from "@langchain/react";

const AGENT_URL = "https://:2024";

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

  return (
    <div>
      <PipelineProgress nodes={PIPELINE_NODES} values={stream.values} />
      <NodeCardList
        nodes={PIPELINE_NODES}
        messages={stream.messages}
        values={stream.values}
        getMetadata={stream.getMessagesMetadata}
      />
    </div>
  );
}

将流式传输 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;
}
这为您提供了一个从节点名称到其当前流式内容的映射。随着 Token 的到来,相应的卡片会实时更新。
streamMetadata.langgraph_node 字段由 LangGraph 自动设置。您无需在后端进行任何特殊配置。只需像往常一样流式传输消息,元数据就会包含在内。

确定节点状态

每个节点可以处于四种状态之一:未开始、流式传输中、已完成或空闲。您可以从两个来源获取状态:流式内容映射(用于活动节点)和 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";
}

构建流水线进度条

顶部的水平进度条让用户可以鸟瞰整个流水线。每个步骤都是一个带标签的段落,随着节点的完成而填充。
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>
  );
}

构建可折叠的 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);
}

流式内容与已完成内容的对比

每个节点的内容有两个来源,为流畅的用户体验选择正确的来源至关重要
来源使用场景
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,请确保您的渲染器能够优雅地处理不完整的语法(例如,未闭合的粗体标记 **)。

整合所有部分

以下是结合了路由、状态检测和卡片渲染的完整卡片列表
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>
  );
}

用例

图形执行卡片非常适合任何需要可视化的多步流水线
  • 研究流水线:分类 → 收集来源 → 分析 → 合成报告
  • 内容生成:大纲 → 草稿 → 事实核查 → 编辑 → 发布
  • 数据处理:摄取 → 验证 → 转换 → 聚合 → 导出
  • 代码生成:理解需求 → 规划架构 → 编写代码 → 审查 → 测试
  • 决策工作流:收集上下文 → 评估选项 → 给替代方案打分 → 推荐

处理动态流水线

并非所有图形都有一组固定的节点。有些流水线会根据输入添加或跳过节点。通过检查哪些状态键实际拥有值来处理这种情况。
const activeNodes = PIPELINE_NODES.filter(
  (node) =>
    streamingContent[node.name] ||
    values?.[node.stateKey] ||
    node.name === currentNode
);
这确保您的 UI 仅为当前执行相关的节点显示卡片,避免出现空的占位卡片。
如果您的图形有条件分支(例如,对于简单的事实查询跳过“研究”节点),被跳过的节点将永远不会出现在流式内容或状态值中。您的流水线进度条应通过调暗或隐藏被跳过的步骤来反映这一点。

最佳实践

  • 以声明方式定义节点。保持 PIPELINE_NODES 数组作为单一事实来源,映射节点名称、状态键和显示标签。
  • 对于活动节点,优先使用流式内容。它为用户提供即时反馈。仅在节点完成后才回退到已提交的状态值。
  • 自动折叠已完成的节点。在长流水线中,自动折叠已完成的卡片,以便用户可以专注于当前活动的步骤。
  • 显示预计时间。如果您有关于每个节点所需时间的历史数据,请显示时间估计以设定用户预期。
  • 添加全局进度指示器。在流水线视图顶部,用整体进度条(例如,“第 2 步,共 4 步”)来补充各节点的卡片。
  • 按节点处理错误。如果某个节点失败,请在其卡片中显示错误,而不要折叠整个流水线。其他节点可能仍然会成功完成。

© . This site is unofficial and not affiliated with LangChain, Inc.