- 流式传输子代理进度—跟踪每个子代理在并行运行时的执行情况。
- 流式传输 LLM 令牌—从主代理和每个子代理流式传输令牌。
- 流式传输工具调用—查看子代理执行中的工具调用和结果。
- 流式传输自定义更新—从子代理节点内部发出用户定义的信号。
启用子图流式传输
深度代理使用 LangGraph 的子图流式传输来显示来自子代理执行的事件。要接收子代理事件,请在流式传输时启用stream_subgraphs。
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{ messages: [{ role: "user", content: "Research quantum computing advances" }] },
{
streamMode: "updates",
subgraphs: true,
}
)) {
if (namespace.length > 0) {
// 子代理事件 - 命名空间标识来源
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// 主代理事件
console.log("[main agent]");
}
console.log(chunk);
}
命名空间
当启用subgraphs 时,每个流式传输事件都包含一个命名空间,用于标识生成该事件的代理。命名空间是表示代理层次结构的节点名称和任务 ID 的路径。
| 命名空间 | 来源 |
|---|---|
() (空) | 主代理 |
("tools:abc123",) | 由主代理的 task 工具调用 abc123 生成的子代理 |
("tools:abc123", "model_request:def456") | 子代理内部的模型请求节点 |
for await (const [namespace, chunk] of await agent.stream(
{ messages: [{ role: "user", content: "Plan my vacation" }] },
{ streamMode: "updates", subgraphs: true }
)) {
// 检查此事件是否来自子代理
const isSubagent = namespace.some(
(segment: string) => segment.startsWith("tools:")
);
if (isSubagent) {
// 从命名空间中提取工具调用 ID
const toolCallId = namespace
.find((s: string) => s.startsWith("tools:"))
?.split(":")[1];
console.log(`Subagent ${toolCallId}:`, chunk);
} else {
console.log("Main agent:", chunk);
}
}
子代理进度
使用stream_mode="updates" 跟踪子代理在每个步骤完成时的进度。这对于显示哪些子代理处于活动状态以及它们完成了哪些工作非常有用。
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
systemPrompt:
"You are a project coordinator. Always delegate research tasks " +
"to your researcher subagent using the task tool. Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// 主代理更新(空命名空间)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// 子代理结果返回给主代理
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// 子代理更新(非空命名空间)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
Output
Main agent step: model_request
[tools:call_abc123] step: model_request
[tools:call_abc123] step: tools
[tools:call_abc123] step: model_request
Subagent complete: task
Result: ## AI Safety Report...
Main agent step: model_request
[tools:call_def456] step: model_request
[tools:call_def456] step: model_request
Subagent complete: task
Result: # Comprehensive Report on AI Safety...
Main agent step: model_request
LLM 令牌
使用stream_mode="messages" 从主代理和子代理流式传输单个令牌。每个消息事件都包含标识来源代理的元数据。
let currentSource = "";
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Research quantum computing advances",
},
],
},
{ streamMode: "messages", subgraphs: true },
)) {
const [message] = chunk;
// 检查此事件是否来自子代理(命名空间包含 "tools:")
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
// 来自子代理的令牌
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
if (subagentNs !== currentSource) {
process.stdout.write(`\n\n--- [subagent: ${subagentNs}] ---\n`);
currentSource = subagentNs;
}
if (message.text) {
process.stdout.write(message.text);
}
} else {
// 来自主代理的令牌
if ("main" !== currentSource) {
process.stdout.write(`\n\n--- [main agent] ---\n`);
currentSource = "main";
}
if (message.text) {
process.stdout.write(message.text);
}
}
}
process.stdout.write("\n");
工具调用
当子代理使用工具时,您可以流式传输工具调用事件以显示每个子代理正在执行的操作。工具调用块出现在messages 流模式中。
import { AIMessageChunk, ToolMessage } from "langchain";
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Research recent quantum computing advances",
},
],
},
{ streamMode: "messages", subgraphs: true },
)) {
const [message] = chunk;
// 识别来源:"main" 或子代理命名空间段
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
const source = isSubagent
? namespace.find((s: string) => s.startsWith("tools:"))!
: "main";
// 工具调用块(流式工具调用)
if (AIMessageChunk.isInstance(message) && message.tool_call_chunks?.length) {
for (const tc of message.tool_call_chunks) {
if (tc.name) {
console.log(`\n[${source}] Tool call: ${tc.name}`);
}
// 参数以块形式流式传输 - 增量写入
if (tc.args) {
process.stdout.write(tc.args);
}
}
}
// 工具结果
if (ToolMessage.isInstance(message)) {
console.log(
`\n[${source}] Tool result [${message.name}]: ${message.text?.slice(0, 150)}`,
);
}
// 常规 AI 内容(跳过工具调用消息)
if (
AIMessageChunk.isInstance(message) &&
message.text &&
!message.tool_call_chunks?.length
) {
process.stdout.write(message.text);
}
}
process.stdout.write("\n");
自定义更新
在子代理工具内部使用config.writer 发出自定义进度事件:
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* 一个通过 config.writer 发出自定义进度事件的工具。
* 写入器将数据发送到 "custom" 流模式。
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}
Output
[tools:call_abc123] { status: 'fetching', progress: 0 }
[tools:call_abc123] { status: 'analyzing', progress: 50 }
[tools:call_abc123] { status: 'complete', progress: 100 }
流式传输多种模式
组合多种流式传输模式以获取代理执行的完整视图:// 跳过内部中间件步骤 - 仅显示有意义的节点名称
const INTERESTING_NODES = new Set(["model_request", "tools"]);
let lastSource = "";
let midLine = false; // true when we've written tokens without a trailing newline
for await (const [namespace, mode, data] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze the impact of remote work on team productivity",
},
],
},
{ streamMode: ["updates", "messages", "custom"], subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
const source = isSubagent ? "subagent" : "main";
if (mode === "updates") {
for (const nodeName of Object.keys(data)) {
if (!INTERESTING_NODES.has(nodeName)) continue;
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
console.log(`[${source}] step: ${nodeName}`);
}
} else if (mode === "messages") {
const [message] = data;
if (message.text) {
// 当来源更改时打印标题
if (source !== lastSource) {
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
process.stdout.write(`\n[${source}] `);
lastSource = source;
}
process.stdout.write(message.text);
midLine = true;
}
} else if (mode === "custom") {
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
console.log(`[${source}] custom event:`, data);
}
}
process.stdout.write("\n");
常见模式
跟踪子代理生命周期
监控子代理何时启动、运行和完成:for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research the latest AI safety developments" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
for (const [nodeName, data] of Object.entries(chunk)) {
// ─── 阶段 1:检测子代理启动 ────────────────────────
// 当主代理的 model_request 包含 task 工具调用时,
// 表示已生成子代理。
if (namespace.length === 0 && nodeName === "model_request") {
for (const msg of (data as any).messages ?? []) {
for (const tc of msg.tool_calls ?? []) {
if (tc.name === "task") {
activeSubagents.set(tc.id, {
type: tc.args?.subagent_type,
description: tc.args?.description?.slice(0, 80),
status: "pending",
});
console.log(
`[lifecycle] PENDING → subagent "${tc.args?.subagent_type}" (${tc.id})`,
);
}
}
}
}
// ─── 阶段 2:检测子代理运行 ─────────────────────────
// 当我们从 tools:UUID 命名空间接收事件时,该
// 子代理正在主动执行。
if (namespace.length > 0 && namespace[0].startsWith("tools:")) {
const pregelId = namespace[0].split(":")[1];
// 检查是否有任何待处理的子代理需要标记为运行。
// 注意:pregel 任务 ID 与 tool_call_id 不同,
// 因此我们在第一个子代理事件时将任何待处理的子代理标记为运行。
for (const [id, sub] of activeSubagents) {
if (sub.status === "pending") {
sub.status = "running";
console.log(
`[lifecycle] RUNNING → subagent "${sub.type}" (pregel: ${pregelId})`,
);
break;
}
}
}
// ─── 阶段 3:检测子代理完成 ──────────────────────
// 当主代理的 tools 节点返回工具消息时,
// 表示子代理已完成并返回其结果。
if (namespace.length === 0 && nodeName === "tools") {
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
const subagent = activeSubagents.get(msg.tool_call_id);
if (subagent) {
subagent.status = "complete";
console.log(
`[lifecycle] COMPLETE → subagent "${subagent.type}" (${msg.tool_call_id})`,
);
console.log(
` Result preview: ${String(msg.content).slice(0, 120)}...`,
);
}
}
}
}
}
}
// 打印最终状态
console.log("\n--- Final subagent states ---");
for (const [id, sub] of activeSubagents) {
console.log(` ${sub.type}: ${sub.status}`);
}
相关
- 子代理—使用深度代理配置和使用子代理
- 前端流式传输—使用
useStream为深度代理构建 React UI - LangChain 流式传输概述—LangChain 代理的通用流式传输概念
通过 MCP 将这些文档连接到 Claude、VSCode 等 以获取实时答案。

