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>
);
}
除了 标准 useStream 参数 之外,深度智能体流式传输还支持:当为 true 时,子智能体消息将从主 stream.messages 数组中排除。请通过 stream.subagents.get(id).messages 访问它们。这可以保持主对话的整洁。
subagentToolNames
string[]
default:"['task']"
生成子智能体的工具名称。默认情况下,深度智能体使用 task 工具将工作委托给子智能体。仅当您自定义了工具名称时才更改此设置。
除了 标准返回值 之外,深度智能体流式传输还提供:subagents
Map<string, SubagentStream>
所有子智能体的映射(Map),以工具调用 ID 为键。每个子智能体包括其消息、状态、工具调用和结果。
当前正在运行的子智能体数组(状态为 "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
工具调用可见性、线程持久化、可扩展的子智能体卡片以及页面重新加载时的自动重新连接。
相关内容
通过 MCP 将这些文档连接 到 Claude、VSCode 等,以获取实时解答。