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

安装

在您的 React 应用中安装 LangGraph SDK 以使用 useStream Hook:

基本用法

要从带有子智能体的深度智能体进行流式传输,请为 useStream 配置 filterSubagentMessages,并在提交时传入 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,  // Keep subagent messages separate
  });

  const handleSubmit = (message: string) => {
    stream.submit(
      { messages: [{ content: message, type: "human" }] },
      { streamSubgraphs: true }  // Enable subagent streaming
    );
  };

  return (
    <div>
      {/* Main conversation messages (subagent messages filtered out) */}
      {stream.messages.map((message, idx) => (
        <div key={message.id ?? idx}>
          {message.type}: {message.content}
        </div>
      ))}

      {/* Subagent progress */}
      {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>
所有子智能体的映射,以工具调用 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 {
  // Identity
  id: string;                    // Tool call ID
  toolCall: {                    // Original task tool call
    subagent_type: string;
    description: string;
  };

  // Lifecycle
  status: "pending" | "running" | "complete" | "error";
  startedAt: Date | null;
  completedAt: Date | null;
  isLoading: boolean;

  // Content
  messages: Message[];           // Subagent's messages
  values: Record<string, any>;   // Subagent's state
  result: string | null;         // Final result
  error: string | null;          // Error message

  // Tool calls
  toolCalls: ToolCallWithResult[];
  getToolCalls: (message: Message) => ToolCallWithResult[];

  // Hierarchy
  depth: number;                 // Nesting depth (0 for top-level subagents)
  parentId: string | null;       // Parent subagent ID (for nested subagents)
}

渲染子智能体流

子智能体卡片

构建展示每个子智能体流式内容、状态和进度的卡片:
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;
  }
}

/** Extract text content from subagent messages */
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,
  });

  // Map subagents to the human message that triggered them
  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;

      // The next message should be the AI message with task tool calls
      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} />

          {/* Show subagent pipeline after the human message that triggered it */}
          {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">
      {/* Progress header */}
      <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>

      {/* Progress bar */}
      <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>

      {/* Subagent cards */}
      <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>

      {/* Tool calls */}
      {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>

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

          {/* Tool result */}
          {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>
      ))}

      {/* Streaming content */}
      <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,  // Auto-resume stream after page reload
  });

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

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

类型安全

Python 类型安全方面,访问流状态时请使用类型注解:
from deepagents import create_deep_agent

agent = create_deep_agent(
    system_prompt="You are a helpful assistant",
    subagents=[...],
)

# Type hints are inferred from the agent definition
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Hello"}]}
)
# result["messages"] is typed as list of messages

完整示例

有关结合以上所有模式的完整可运行实现,请参阅 LangGraph.js 代码库中的以下示例:

深度智能体示例

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

带工具调用的深度智能体

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

相关资源