Skip to main content
useStream React hook 为深度智能体流式传输提供了内置支持。它自动跟踪子智能体的生命周期,将子智能体消息与主对话分离,并公开了丰富的 API 用于构建多智能体 UI。 深度智能体的关键特性:
  • 子智能体跟踪 — 每个子智能体的自动生命周期管理(挂起、运行中、完成、错误)
  • 消息过滤 — 将子智能体消息与主对话流分离
  • 工具调用可见性 — 从子智能体执行中访问工具调用和结果
  • 状态重建 — 页面重新加载时从线程历史记录中恢复子智能体状态

安装

安装 LangGraph SDK 以在您的 React 应用程序中使用 useStream hook:
npm install @langchain/langgraph-sdk

基本用法

要从包含子智能体的深度智能体进行流式传输,请使用 filterSubagentMessages 配置 useStream,并在提交时传递 streamSubgraphs: true
import { useStream } from "@langchain/langgraph-sdk/react";
import type { agent } from "./agent";

function DeepAgentChat() {
  const stream = useStream<typeof agent>({
    assistantId: "deep-agent",
    apiUrl: "http://localhost:2024",
    filterSubagentMessages: true,  // 保持子智能体消息分离
  });

  const handleSubmit = (message: string) => {
    stream.submit(
      { messages: [{ content: message, type: "human" }] },
      { streamSubgraphs: true }  // 启用子智能体流式传输
    );
  };

  return (
    <div>
      {/* 主对话消息(已过滤掉子智能体消息) */}
      {stream.messages.map((message, idx) => (
        <div key={message.id ?? idx}>
          {message.type}: {message.content}
        </div>
      ))}

      {/* 子智能体进度 */}
      {stream.activeSubagents.length > 0 && (
        <div>
          <h3>Active subagents:</h3>
          {stream.activeSubagents.map((subagent) => (
            <SubagentCard key={subagent.id} subagent={subagent} />
          ))}
        </div>
      )}

      {stream.isLoading && <div>Loading...</div>}
    </div>
  );
}
了解如何 将深度智能体部署到 LangSmith 以获得具备内置可观测性、身份验证和扩展性的生产级托管。
除了 标准 useStream 参数 之外,深度智能体流式传输还支持:
filterSubagentMessages
boolean
default:"false"
当为 true 时,子智能体消息将从主 stream.messages 数组中排除。请通过 stream.subagents.get(id).messages 访问它们。这可以保持主对话的整洁。
subagentToolNames
string[]
default:"['task']"
生成子智能体的工具名称。默认情况下,深度智能体使用 task 工具将工作委托给子智能体。仅当您自定义了工具名称时才更改此设置。
除了 标准返回值 之外,深度智能体流式传输还提供:
subagents
Map<string, SubagentStream>
所有子智能体的映射(Map),以工具调用 ID 为键。每个子智能体包括其消息、状态、工具调用和结果。
activeSubagents
SubagentStream[]
当前正在运行的子智能体数组(状态为 "pending""running")。
getSubagent
(toolCallId: string) => SubagentStream | undefined
通过工具调用 ID 获取特定的子智能体。
getSubagentsByMessage
(messageId: string) => SubagentStream[]
获取由特定 AI 消息触发的所有子智能体。用于将子智能体与生成它们的消息关联起来。
getSubagentsByType
(type: string) => SubagentStream[]
根据 subagent_type(例如 "researcher""writer")过滤子智能体。

子智能体流接口

stream.subagents 映射中的每个子智能体都暴露一个类似流的接口:
interface SubagentStream {
  // 身份
  id: string;                    // 工具调用 ID
  toolCall: {                    // 原始任务工具调用
    subagent_type: string;
    description: string;
  };

  // 生命周期
  status: "pending" | "running" | "complete" | "error";
  startedAt: Date | null;
  completedAt: Date | null;
  isLoading: boolean;

  // 内容
  messages: Message[];           // 子智能体的消息
  values: Record<string, any>;   // 子智能体的状态
  result: string | null;         // 最终结果
  error: string | null;          // 错误消息

  // 工具调用
  toolCalls: ToolCallWithResult[];
  getToolCalls: (message: Message) => ToolCallWithResult[];

  // 层级
  depth: number;                 // 嵌套深度(0 表示顶层子智能体)
  parentId: string | null;       // 父子智能体 ID(用于嵌套子智能体)
}

渲染子智能体流

子智能体卡片

构建展示每个子智能体流式内容、状态和进度的卡片:
import { AIMessage } from "langchain";
import { useStream, type SubagentStream } from "@langchain/langgraph-sdk/react";
import type { Message } from "@langchain/langgraph-sdk";
import type { agent } from "./agent";

function SubagentCard({ subagent }: { subagent: SubagentStream<typeof agent> }) {
  const content = getStreamingContent(subagent.messages);

  return (
    <div className="border rounded-lg p-4">
      {/* Header */}
      <div className="flex items-center gap-2 mb-2">
        <StatusIcon status={subagent.status} />
        <span className="font-medium">{subagent.toolCall.subagent_type}</span>
        <span className="text-sm text-gray-500">
          {subagent.toolCall.description}
        </span>
      </div>

      {/* Streaming content */}
      {content && (
        <div className="prose text-sm mt-2">
          {content}
        </div>
      )}

      {/* Result */}
      {subagent.status === "complete" && subagent.result && (
        <div className="mt-2 p-2 bg-green-50 rounded text-sm">
          {subagent.result}
        </div>
      )}

      {/* Error */}
      {subagent.status === "error" && subagent.error && (
        <div className="mt-2 p-2 bg-red-50 rounded text-sm text-red-700">
          {subagent.error}
        </div>
      )}
    </div>
  );
}

function StatusIcon({ status }: { status: string }) {
  switch (status) {
    case "pending":
      return <span className="text-gray-400"></span>;
    case "running":
      return <span className="animate-spin">⚙️</span>;
    case "complete":
      return <span className="text-green-500"></span>;
    case "error":
      return <span className="text-red-500"></span>;
    default:
      return null;
  }
}

/** 从子智能体消息中提取文本内容 */
function getStreamingContent(messages: Message[]): string {
  return messages
    .filter((m) => m.type === "ai")
    .map((m) => {
      if (typeof m.content === "string") return m.content;
      if (Array.isArray(m.content)) {
        return m.content
          .filter((c): c is { type: "text"; text: string } =>
            c.type === "text" && "text" in c
          )
          .map((c) => c.text)
          .join("");
      }
      return "";
    })
    .join("");
}

将子智能体映射到消息

使用 getSubagentsByMessage 将子智能体卡片与触发它们的 AI 消息关联起来:
import { useMemo } from "react";
import { useStream } from "@langchain/langgraph-sdk/react";
import type { agent } from "./agent";

function DeepAgentChat() {
  const stream = useStream<typeof agent>({
    assistantId: "deep-agent",
    apiUrl: "http://localhost:2024",
    filterSubagentMessages: true,
  });

  // 将子智能体映射到触发它们的人类消息
  const subagentsByMessage = useMemo(() => {
    const result = new Map();
    const messages = stream.messages;

    for (let i = 0; i < messages.length; i++) {
      if (messages[i].type !== "human") continue;

      // 下一条消息应该是带有任务工具调用的 AI 消息
      const next = messages[i + 1];
      if (!next || next.type !== "ai" || !next.id) continue;

      const subagents = stream.getSubagentsByMessage(next.id);
      if (subagents.length > 0) {
        result.set(messages[i].id, subagents);
      }
    }
    return result;
  }, [stream.messages, stream.subagents]);

  return (
    <div>
      {stream.messages.map((message, idx) => (
        <div key={message.id ?? idx}>
          <MessageBubble message={message} />

          {/* 在触发它的人类消息之后显示子智能体管道 */}
          {message.type === "human" && subagentsByMessage.has(message.id) && (
            <SubagentPipeline
              subagents={subagentsByMessage.get(message.id)!}
              isLoading={stream.isLoading}
            />
          )}
        </div>
      ))}
    </div>
  );
}

带有进度的子智能体管道

显示进度条和子智能体卡片网格:
function SubagentPipeline({
  subagents,
  isLoading,
}: {
  subagents: SubagentStream[];
  isLoading: boolean;
}) {
  const completed = subagents.filter(
    (s) => s.status === "complete" || s.status === "error"
  ).length;

  const allDone = completed === subagents.length;

  return (
    <div className="my-4 space-y-3">
      {/* 进度标题 */}
      <div className="flex items-center justify-between text-sm">
        <span className="font-medium">
          Subagents ({completed}/{subagents.length})
        </span>
        {allDone && isLoading && (
          <span className="text-blue-500 animate-pulse">
            Synthesizing results...
          </span>
        )}
      </div>

      {/* 进度条 */}
      <div className="h-1.5 bg-gray-200 rounded-full overflow-hidden">
        <div
          className="h-full bg-blue-500 transition-all duration-300"
          style={{ width: `${(completed / subagents.length) * 100}%` }}
        />
      </div>

      {/* 子智能体卡片 */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
        {subagents.map((subagent) => (
          <SubagentCard key={subagent.id} subagent={subagent} />
        ))}
      </div>
    </div>
  );
}

渲染工具调用

使用 toolCalls 属性显示子智能体执行过程中的工具调用和结果:
function SubagentWithTools({ subagent }: { subagent: SubagentStream }) {
  return (
    <div className="border rounded-lg p-4">
      <div className="flex items-center gap-2 mb-3">
        <StatusIcon status={subagent.status} />
        <span className="font-medium">{subagent.toolCall.subagent_type}</span>
        {subagent.toolCalls.length > 0 && (
          <span className="text-xs bg-gray-100 px-2 py-0.5 rounded-full">
            {subagent.toolCalls.length} tool calls
          </span>
        )}
      </div>

      {/* 工具调用 */}
      {subagent.toolCalls.map((tc) => (
        <div key={tc.call.id} className="mb-2 p-2 bg-gray-50 rounded text-sm">
          <div className="flex items-center gap-2">
            <span className="font-mono text-xs">{tc.call.name}</span>
            {tc.result !== undefined ? (
              <span className="text-green-600 text-xs">completed</span>
            ) : (
              <span className="text-yellow-600 text-xs animate-pulse">
                running...
              </span>
            )}
          </div>

          {/* 工具参数 */}
          <pre className="text-xs text-gray-600 mt-1 overflow-x-auto">
            {JSON.stringify(tc.call.args, null, 2)}
          </pre>

          {/* 工具结果 */}
          {tc.result !== undefined && (
            <div className="mt-1 pt-1 border-t text-xs">
              {typeof tc.result === "string"
                ? tc.result.slice(0, 200)
                : JSON.stringify(tc.result, null, 2)}
            </div>
          )}
        </div>
      ))}

      {/* 流式内容 */}
      <div className="mt-2 prose text-sm">
        {getStreamingContent(subagent.messages)}
      </div>
    </div>
  );
}

线程持久化

跨页面重新加载持久化线程 ID,以便用户可以返回到他们的深度智能体对话:
import { useCallback, useState, useEffect } from "react";
import { useStream } from "@langchain/langgraph-sdk/react";
import type { agent } from "./agent";

function useThreadIdParam() {
  const [threadId, setThreadId] = useState<string | null>(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get("threadId");
  });

  const updateThreadId = useCallback((id: string) => {
    setThreadId(id);
    const url = new URL(window.location.href);
    url.searchParams.set("threadId", id);
    window.history.replaceState({}, "", url.toString());
  }, []);

  return [threadId, updateThreadId] as const;
}

function PersistentDeepAgentChat() {
  const [threadId, onThreadId] = useThreadIdParam();

  const stream = useStream<typeof agent>({
    assistantId: "deep-agent",
    apiUrl: "http://localhost:2024",
    filterSubagentMessages: true,
    threadId,
    onThreadId,
    reconnectOnMount: true,  // 页面重新加载后自动恢复流
  });

  return (
    <div>
      {stream.messages.map((message, idx) => (
        <div key={message.id ?? idx}>
          {message.type}: {message.content}
        </div>
      ))}

      {/* 重新加载时从线程历史记录中重建子智能体 */}
      {[...stream.subagents.values()].map((subagent) => (
        <SubagentCard key={subagent.id} subagent={subagent} />
      ))}
    </div>
  );
}
当页面重新加载时,useStream 会从线程历史记录中重建子智能体状态。已完成的子智能体将恢复其最终状态和结果,因此用户可以看到包含子智能体工作的完整对话历史记录。

类型安全

为了获得完整的类型安全,请将您的智能体类型传递给 useStream。这使您可以对状态、消息、工具调用和子智能体数据进行类型化访问:
import { useStream } from "@langchain/langgraph-sdk/react";
import type { agent } from "./agent";

function TypedDeepAgentChat() {
  const stream = useStream<typeof agent>({
    assistantId: "deep-agent",
    apiUrl: "http://localhost:2024",
    filterSubagentMessages: true,
  });

  // stream.values 类型化为您的智能体状态
  // stream.messages 具有类型化的工具调用
  // stream.subagents 具有类型化的子智能体数据
}

完整示例

有关结合了上述所有模式的完整工作实现,请参阅 LangGraph.js 存储库中的这些示例:

Deep agent example

具有网格布局、流式内容、进度跟踪和合成检测的并行子智能体。

Deep agent with tool calls

工具调用可见性、线程持久化、可扩展的子智能体卡片以及页面重新加载时的自动重新连接。

相关内容