스트리밍
스트리밍 (Streaming)
deep agent 실행과 서브에이전트 실행에서 실시간 업데이트를 스트리밍하세요.
Deep Agents는 서브에이전트 스트림에 대한 일급 지원과 함께 LangGraph의 스트리밍 인프라 위에 구축됩니다. deep agent가 작업을 서브에이전트에 위임할 때 각 서브에이전트의 업데이트를 독립적으로 스트리밍할 수 있습니다. 진행 상황, LLM 토큰, 도구 호출을 실시간으로 추적합니다.
deep agent 스트리밍으로 가능한 것:
서브에이전트 진행 상황 스트리밍 — 병렬로 실행되는 각 서브에이전트의 실행을 추적합니다. LLM 토큰 스트리밍 — 주 에이전트와 각 서브에이전트에서 토큰을 스트리밍합니다. 도구 호출 스트리밍 — 서브에이전트 실행 안에서 도구 호출과 결과를 봅니다. 커스텀 업데이트 스트리밍 — 서브에이전트 노드 안에서 사용자 정의 신호를 방출합니다.
서브그래프 스트리밍 활성화 (Enable subgraph streaming)
Deep Agents는 LangGraph의 서브그래프 스트리밍을 사용해 서브에이전트 실행의 이벤트를 표면화합니다. 서브에이전트 이벤트를 받으려면 스트리밍할 때 stream_subgraphs를 활성화하세요.
const agent = createDeepAgent({ model: "google-genai:gemini-3.6-flash", 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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log([subagent: ${namespace.join("|")}]);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
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, // [!code highlight]
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}
네임스페이스 (Namespaces)
subgraphs가 활성화되면 각 스트리밍 이벤트는 그것을 생성한 에이전트를 식별하는 namespace를 포함합니다. 네임스페이스는 에이전트 계층을 나타내는 노드 이름과 태스크 ID의 경로입니다.
| 네임스페이스 | 소스 |
|---|---|
() (빈 값) |
주 에이전트 |
("tools:abc123",) |
주 에이전트의 task 도구 호출 abc123이 생성한 서브에이전트 |
("tools:abc123", "model_request:def456") |
서브에이전트 안의 모델 요청 노드 |
네임스페이스를 사용해 이벤트를 올바른 UI 컴포넌트로 라우팅하세요:
for await (const [namespace, chunk] of await agent.stream(
{ messages: [{ role: "user", content: "Plan my vacation" }] },
{ streamMode: "updates", subgraphs: true },
)) {
// Check if this event came from a subagent
const isSubagent = namespace.some((segment: string) =>
segment.startsWith("tools:"),
);
if (isSubagent) {
// Extract the tool call ID from the namespace
const toolCallId = namespace
.find((s: string) => s.startsWith("tools:"))
?.split(":")[1];
console.log(`Subagent ${toolCallId}:`, chunk);
} else {
console.log("Main agent:", chunk);
}
}
서브에이전트 진행 상황 (Subagent progress)
stream_mode="updates"를 사용해 각 단계가 완료될 때 서브에이전트 진행 상황을 추적하세요. 이것은 어떤 서브에이전트가 활성이고 어떤 작업을 완료했는지 보여주는 데 유용합니다.
const agent = createDeepAgent({ model: "google-genai:gemini-3.6-flash", systemPrompt: "You are a project coordinator with no research knowledge. " + "For every user request, you must call the task() tool with " + "subagent_type set to researcher. Never answer research questions yourself. " + "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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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});
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log( [${namespace[0]}] step: ${nodeName});
}
}
}
```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"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 },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
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}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}
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 토큰 (LLM tokens)
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;
// Check if this event came from a subagent (namespace contains "tools:")
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
// Token from a subagent
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 {
// Token from the main agent
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");
도구 호출 (Tool calls)
서브에이전트가 도구를 사용할 때 도구 호출 이벤트를 스트리밍해 각 서브에이전트가 무엇을 하는지 표시할 수 있습니다. 도구 호출 청크는 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;
// Identify source: "main" or the subagent namespace segment
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
const source = isSubagent
? namespace.find((s: string) => s.startsWith("tools:"))!
: "main";
// Tool call chunks (streaming tool invocations)
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}`);
}
// Args stream in chunks - write them incrementally
if (tc.args) {
process.stdout.write(tc.args);
}
}
}
// Tool results
if (ToolMessage.isInstance(message)) {
console.log(
`\n[${source}] Tool result [${message.name}]: ${message.text?.slice(0, 150)}`,
);
}
// Regular AI content (skip tool call messages)
if (
AIMessageChunk.isInstance(message) &&
message.text &&
!message.tool_call_chunks?.length
) {
process.stdout.write(message.text);
}
}
process.stdout.write("\n");
커스텀 업데이트 (Custom updates)
서브에이전트 도구 안에서 config.writer를 사용해 커스텀 진행 이벤트를 방출하세요:
/**
-
A tool that emits custom progress events via config.writer.
-
The writer sends data to the "custom" stream mode. */ 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({ model: "google-genai:gemini-3.6-flash", 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);
}
}
```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "openai:gpt-5.5",
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);
}
}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "anthropic:claude-sonnet-5",
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);
}
}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "openrouter:z-ai/glm-5.2",
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);
}
}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "fireworks:accounts/fireworks/models/glm-5p2",
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);
}
}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "baseten:zai-org/GLM-5.2",
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);
}
}
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
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({
model: "ollama:north-mini-code-1.0",
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);
}
}
[tools:call_abc123] { status: 'fetching', progress: 0 }
[tools:call_abc123] { status: 'analyzing', progress: 50 }
[tools:call_abc123] { status: 'complete', progress: 100 }
여러 모드 스트리밍 (Stream multiple modes)
여러 스트리밍 모드를 결합해 에이전트 실행의 완전한 그림을 얻으세요:
// Skip internal middleware steps - only show meaningful node names
const INTERESTING_NODES = new Set(["model", "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) {
// Print a header when the source changes
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");
일반적인 패턴 (Common patterns)
서브에이전트 수명 주기 추적 (Track subagent lifecycle)
서브에이전트가 시작, 실행, 완료되는 시점을 모니터링하세요:
function getToolCalls(message: unknown): Array<{
id?: string;
name?: string;
args?: Record<string, unknown>;
}> {
if (!message || typeof message !== "object") {
return [];
}
const record = message as Record<string, unknown>;
const toolCalls = record.tool_calls ?? record.toolCalls;
return Array.isArray(toolCalls)
? (toolCalls as Array<{
id?: string;
name?: string;
args?: Record<string, unknown>;
}>)
: [];
}
const activeSubagents = new Map<
string,
{ type?: string; description?: string; status: string }
>();
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)) {
// ─── Phase 1: Detect subagent starting ────────────────────────
// When the main agent emits a task tool call, a subagent has been spawned.
if (namespace.length === 0) {
for (const msg of (data as { messages?: unknown[] }).messages ?? []) {
for (const tc of getToolCalls(msg)) {
if (tc.name === "task" && tc.id) {
activeSubagents.set(tc.id, {
type: tc.args?.subagent_type as string | undefined,
description: String(tc.args?.description ?? "").slice(0, 80),
status: "pending",
});
console.log(
`[lifecycle] PENDING → subagent "${tc.args?.subagent_type}" (${tc.id})`,
);
}
}
}
}
// ─── Phase 2: Detect subagent running ─────────────────────────
// When we receive events from a tools:UUID namespace, that
// subagent is actively executing.
if (namespace.length > 0 && namespace[0].startsWith("tools:")) {
const pregelId = namespace[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.
let markedRunning = false;
for (const [, sub] of activeSubagents) {
if (sub.status === "pending") {
sub.status = "running";
markedRunning = true;
console.log(
`[lifecycle] RUNNING → subagent "${sub.type}" (pregel: ${pregelId})`,
);
break;
}
}
if (!markedRunning && activeSubagents.size === 0) {
activeSubagents.set(pregelId, {
type: "researcher",
status: "running",
});
console.log(
`[lifecycle] RUNNING → subagent "researcher" (pregel: ${pregelId})`,
);
}
}
// ─── 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 (namespace.length === 0 && nodeName === "tools") {
for (const msg of (data as { messages?: Array<Record<string, unknown>> })
.messages ?? []) {
if (msg.type === "tool") {
const toolCallId = String(msg.tool_call_id ?? msg.toolCallId ?? "");
const subagent = activeSubagents.get(toolCallId);
if (subagent) {
subagent.status = "complete";
console.log(
`[lifecycle] COMPLETE → subagent "${subagent.type}" (${toolCallId})`,
);
console.log(
` Result preview: ${String(msg.content).slice(0, 120)}...`,
);
}
}
}
}
}
}
// Print final state
console.log("\n--- Final subagent states ---");
for (const [id, sub] of activeSubagents) {
console.log(` ${sub.type}: ${sub.status}`);
}
관련 자료 (Related)
- Subagents — Deep Agents에서 서브에이전트 구성 및 사용
- 프론트엔드 스트리밍 — Deep Agents용
useStream으로 React UI 구축 - LangChain 이벤트 스트리밍 — LangChain 에이전트의 일반적인 스트리밍 개념
더 알아보기
- 이 문서를 MCP로 연결하면 Claude, VSCode 등에서 실시간 답변을 받을 수 있어요.
- GitHub에서 이 페이지 편집하기 또는 이슈 제출하기.