Deep Agents 在 LangGraph 的流式传输基础设施上构建,提供一流的对子智能体流的支持。当深度智能体将工作委托给子智能体时,您可以独立地从每个子智能体流式传输更新——实时跟踪进度、LLM 令牌和工具调用。
深度智能体流式传输可以实现的功能:
- * **流式传输子智能体进度**——跟踪每个子智能体并行运行时的执行情况。
- * **流式传输 LLM 令牌**——从主智能体和每个子智能体流式传输令牌。
- * **流式传输工具调用**——查看子智能体执行过程中的工具调用和结果。
- * **流式传输自定义更新**——从子智能体节点内部发出用户定义的信号。
启用子图流式传输
Deep Agents 使用 LangGraph 的子图流式传输来展示子智能体执行中的事件。要接收子智能体事件,请在流式传输时启用 stream_subgraphs 。
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.5-flash",
system_prompt="You are a helpful research assistant",
subagents=[
{
"name": "researcher",
"description": "Researches a topic in depth",
"system_prompt": "You are a thorough researcher.",
},
],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing advances"}]},
stream_mode="updates",
subgraphs=True, # [!code highlight]
version="v2", # [!code highlight]
):
if chunk["type"] == "updates":
if chunk["ns"]:
# Subagent event - namespace identifies the source
print(f"[subagent: {chunk['ns']}]")
else:
# Main agent event
print("[main agent]")
print(chunk["data"])
命名空间
当 subgraphs 启用时,每个流式传输事件都包含一个 **命名空间** ,用于标识生成该事件的智能体。命名空间是由节点名称和任务 ID 组成的路径,代表智能体层级结构。
| 命名空间 | 来源 |
|---|---|
() (空) | 主代理 |
("tools:abc123",) | 主代理的子代理 task 工具调用 abc123 |
("tools:abc123", "model_request:def456") | 子代理内部的模型请求节点 |
使用命名空间将事件路由到正确的UI组件:
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Plan my vacation"}]},
stream_mode="updates",
subgraphs=True,
version="v2",
):
if chunk["type"] == "updates":
# Check if this event came from a subagent
is_subagent = any(
segment.startswith("tools:") for segment in chunk["ns"]
)
if is_subagent:
# Extract the tool call ID from the namespace
tool_call_id = next(
s.split(":")[1] for s in chunk["ns"] if s.startswith("tools:")
)
print(f"Subagent {tool_call_id}: {chunk['data']}")
else:
print(f"Main agent: {chunk['data']}")
子代理进度
使用 stream_mode="updates" 来跟踪子代理进度,因为每个步骤完成。这对于显示哪些子代理处于活动状态以及它们完成了哪些工作非常有用。
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.5-flash",
system_prompt=(
"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",
"system_prompt": (
"You are a thorough researcher. Research the given topic "
"and provide a concise summary in 2-3 sentences."
),
},
],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Write a short summary about AI safety"}]},
stream_mode="updates",
subgraphs=True,
version="v2",
):
if chunk["type"] == "updates":
# Main agent updates (empty namespace)
if not chunk["ns"]:
for node_name, data in chunk["data"].items():
if node_name == "tools":
# Subagent results returned to main agent
for msg in data.get("messages", []):
if msg.type == "tool":
print(f"\nSubagent complete: {msg.name}")
print(f" Result: {str(msg.content)[:200]}...")
else:
print(f"[main agent] step: {node_name}")
# Subagent updates (non-empty namespace)
else:
for node_name, data in chunk["data"].items():
print(f" [{chunk['ns'][0]}] step: {node_name}")
[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
LLM令牌
使用 stream_mode="messages" 来流式传输来自主代理和子代理的各个令牌。每条消息事件都包含标识源代理的元数据。
current_source = ""
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing advances"}]},
stream_mode="messages",
subgraphs=True,
version="v2",
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
# Check if this event came from a subagent (namespace contains "tools:")
is_subagent = any(s.startswith("tools:") for s in chunk["ns"])
if is_subagent:
# Token from a subagent
subagent_ns = next(s for s in chunk["ns"] if s.startswith("tools:"))
if subagent_ns != current_source:
print(f"\n\n--- [subagent: {subagent_ns}] ---")
current_source = subagent_ns
if token.content:
print(token.content, end="", flush=True)
else:
# Token from the main agent
if "main" != current_source:
print("\n\n--- [main agent] ---")
current_source = "main"
if token.content:
print(token.content, end="", flush=True)
print()
工具调用
当子代理使用工具时,您可以流式传输工具调用事件以显示每个子代理正在做什么。工具调用块出现在 messages 流模式中。
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research recent quantum computing advances"}]},
stream_mode="messages",
subgraphs=True,
version="v2",
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
# Identify source: "main" or the subagent namespace segment
is_subagent = any(s.startswith("tools:") for s in chunk["ns"])
source = next((s for s in chunk["ns"] if s.startswith("tools:")), "main") if is_subagent else "main"
# Tool call chunks (streaming tool invocations)
if token.tool_call_chunks:
for tc in token.tool_call_chunks:
if tc.get("name"):
print(f"\n[{source}] Tool call: {tc['name']}")
# Args stream in chunks - write them incrementally
if tc.get("args"):
print(tc["args"], end="", flush=True)
# Tool results
if token.type == "tool":
print(f"\n[{source}] Tool result [{token.name}]: {str(token.content)[:150]}")
# Regular AI content (skip tool call messages)
if token.type == "ai" and token.content and not token.tool_call_chunks:
print(token.content, end="", flush=True)
print()
自定义更新
在子代理工具内部使用@get_stream_writer]来发出自定义进度事件:
from langchain.tools import tool
from langgraph.config import get_stream_writer
from deepagents import create_deep_agent
@tool
def analyze_data(topic: str) -> str:
"""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.
"""
writer = get_stream_writer()
writer({"status": "starting", "topic": topic, "progress": 0})
time.sleep(0.5)
writer({"status": "analyzing", "progress": 50})
time.sleep(0.5)
writer({"status": "complete", "progress": 100})
return (
f'Analysis of "{topic}": Customer sentiment is 85% positive, '
"driven by product quality and support response times."
)
agent = create_deep_agent(
model="google_genai:gemini-3.5-flash",
system_prompt=(
"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",
"system_prompt": (
"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": [analyze_data],
},
],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Analyze customer satisfaction trends"}]},
stream_mode="custom",
subgraphs=True,
version="v2",
):
if chunk["type"] == "custom":
is_subagent = any(s.startswith("tools:") for s in chunk["ns"])
if is_subagent:
subagent_ns = next(s for s in chunk["ns"] if s.startswith("tools:"))
print(f"[{subagent_ns}]", chunk["data"])
else:
print("[main]", chunk["data"])
[tools:call_abc123] {'status': 'starting', 'topic': 'customer satisfaction trends', 'progress': 0}
[tools:call_abc123] {'status': 'analyzing', 'progress': 50}
[tools:call_abc123] {'status': 'complete', 'progress': 100}
流式传输多个模式
结合多个流模式以获得代理执行的完整图景:
# Skip internal middleware steps - only show meaningful node names
INTERESTING_NODES = {"model_request", "tools"}
last_source = ""
mid_line = False # True when we've written tokens without a trailing newline
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Analyze the impact of remote work on team productivity"}]},
stream_mode=["updates", "messages", "custom"],
subgraphs=True,
version="v2",
):
is_subagent = any(s.startswith("tools:") for s in chunk["ns"])
source = "subagent" if is_subagent else "main"
if chunk["type"] == "updates":
for node_name in chunk["data"]:
if node_name not in INTERESTING_NODES:
continue
if mid_line:
print()
mid_line = False
print(f"[{source}] step: {node_name}")
elif chunk["type"] == "messages":
token, metadata = chunk["data"]
if token.content:
# Print a header when the source changes
if source != last_source:
if mid_line:
print()
mid_line = False
print(f"\n[{source}] ", end="")
last_source = source
print(token.content, end="", flush=True)
mid_line = True
elif chunk["type"] == "custom":
if mid_line:
print()
mid_line = False
print(f"[{source}] custom event:", chunk["data"])
print()
常见模式
跟踪子代理生命周期
监控子代理的启动、运行和完成:
active_subagents = {}
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research the latest AI safety developments"}]},
stream_mode="updates",
subgraphs=True,
version="v2",
):
if chunk["type"] == "updates":
for node_name, data in chunk["data"].items():
# ─── Phase 1: Detect subagent starting ────────────────────────
# When the main agent's model_request contains task tool calls,
# a subagent has been spawned.
if not chunk["ns"] and node_name == "model_request":
for msg in data.get("messages", []):
for tc in getattr(msg, "tool_calls", []):
if tc["name"] == "task":
active_subagents[tc["id"]] = {
"type": tc["args"].get("subagent_type"),
"description": tc["args"].get("description", "")[:80],
"status": "pending",
}
print(
f'[lifecycle] PENDING → subagent "{tc["args"].get("subagent_type")}" '
f'({tc["id"]})'
)
# ─── Phase 2: Detect subagent running ─────────────────────────
# When we receive events from a tools:UUID namespace, that
# subagent is actively executing.
if chunk["ns"] and chunk["ns"][0].startswith("tools:"):
pregel_id = chunk["ns"][0].split(":")[1]
# Check if any pending subagent needs to be marked running.
# Note: the pregel task ID differs from the tool_call_id,
# so we mark any pending subagent as running on first subagent event.
for sub_id, sub in active_subagents.items():
if sub["status"] == "pending":
sub["status"] = "running"
print(
f'[lifecycle] RUNNING → subagent "{sub["type"]}" '
f"(pregel: {pregel_id})"
)
break
# ─── Phase 3: Detect subagent completing ──────────────────────
# When the main agent's tools node returns a tool message,
# the subagent has completed and returned its result.
if not chunk["ns"] and node_name == "tools":
for msg in data.get("messages", []):
if msg.type == "tool":
sub = active_subagents.get(msg.tool_call_id)
if sub:
sub["status"] = "complete"
print(
f'[lifecycle] COMPLETE → subagent "{sub["type"]}" '
f"({msg.tool_call_id})"
)
print(f" Result preview: {str(msg.content)[:120]}...")
# Print final state
print("\n--- Final subagent states ---")
for sub_id, sub in active_subagents.items():
print(f" {sub['type']}: {sub['status']}")
v2 流式传输格式
本页所有示例都使用 v2 流式传输格式(version="v2"),这是推荐的方法。每个数据块都是一个 StreamPart 字典,包含 type, ns和 data 键——无论流模式、流模式数量或子图设置如何,结构都相同。
v2 格式消除了嵌套元组解包,使在深度代理中处理子图流式传输变得简单。比较两种格式:
# Unified format — no nested tuple unpacking
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing"}]},
stream_mode=["updates", "messages", "custom"],
subgraphs=True,
version="v2",
):
print(chunk["type"]) # "updates", "messages", or "custom"
print(chunk["ns"]) # () for main agent, ("tools:<id>",) for subagent
print(chunk["data"]) # payload
# Must handle (namespace, (mode, data)) nested tuples
for namespace, chunk in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing"}]},
stream_mode=["updates", "messages", "custom"],
subgraphs=True,
):
mode, data = chunk[0], chunk[1]
print(mode) # "updates", "messages", or "custom"
print(namespace) # () for main agent, ("tools:<id>",) for subagent
print(data) # payload
参见 LangGraph 流式传输文档 for more details on the v2 format, including type narrowing and Pydantic/dataclass coercion.
相关资源
- - 子代理—使用深度代理配置和使用子代理
- - 前端流式传输—使用
useStream构建深度代理的 React UI - - LangChain 事件流式传输—LangChain 代理的通用流式传输概念