deepagents.js教程03——全部输出与流式输出
在实际使用中,不同场景对输出形式有不同要求:短任务可以等待完整结果,长对话、复杂任务则需要实时反馈。本节我们详细讲解 Deep Agents 的两种输出模式,以及
stream与streamEvents两个流式 API 的区别与用法。
一、方式一:invoke 全部输出(一次性返回)
invoke 是前两节我们使用的基础调用方式,也是最简单的输出模式。
1. 核心特点
- 阻塞式调用:发起请求后,等待 智能体 完全执行结束才返回结果
- 一次性返回:最终返回完整的对话状态与 AI 最终回答
- 无中间过程:无法获取 AI 生成进度、工具调用状态等中间信息
2. 适用场景
- 短平快的简单问答任务
- 后台自动化脚本、不需要展示中间过程的场景
- 批量任务、离线处理
3. 代码示例
沿用我们上一节的基础写法,环境与依赖完全复用:
import "dotenv/config";
import { createDeepAgent } from "deepagents";
import { model } from "./00-model.js";
const agent = createDeepAgent({
model: model,
systemPrompt: "你是一个简洁友好的中文助手。",
});
async function runInvoke() {
const userInput = "写一首关于夏天的短诗";
console.log("用户:", userInput);
// invoke 一次性返回完整结果
const result = await agent.invoke({
messages: [{ role: "user", content: userInput }],
});
// 取最后一条消息即为 AI 最终回答
const answer = result.messages.at(-1).content;
console.log("\nAI 最终回答:\n", answer);
}
runInvoke().catch(console.error);
4. 优缺点
- ✅ 优点:用法极简,结果完整,适合脚本与后台任务
- ❌ 缺点:长任务时用户需要全程等待,无任何反馈,交互体验差
二、方式二:流式输出(实时反馈)
流式输出可以让智能体边生成边返回内容,类似常见的打字机效果,大幅提升长文本、多步任务的交互体验。
Deep Agents 提供了两个不同粒度的流式 API:
stream():轻量流式,仅返回最终文本内容块streamEvents():事件流式,可订阅消息、工具调用、子代理等全流程事件
2.1 stream () 轻量流式
stream() 是最简单的流式方法,按块返回 AI 生成的最终文本内容,适合只需要展示对话内容的基础聊天场景。
核心特点
- 仅返回最终回答的文本片段,不包含工具调用、思考过程等中间信息
- 用法简单,几行代码即可实现打字机效果
- 输出粒度为文本块,而非单个字符
适用场景
- 基础聊天界面、对话机器人
- 只需要展示最终回答,不需要监控执行过程的场景
代码示例
import "dotenv/config";
import { createDeepAgent } from "deepagents";
import { model } from "./00-model.js";
const agent = createDeepAgent({
model: model,
systemPrompt: "你是一个简洁友好的中文助手。",
});
async function runStream() {
const userInput = "写一首关于夏天的短诗";
console.log("用户:", userInput);
console.log("AI:");
// 调用 stream 方法获取流式输出
const stream = await agent.stream({
messages: [{ role: "user", content: userInput }],
});
// 遍历流式块,逐段打印实现打字机效果
for await (const chunk of stream) {
const text = chunk.messages?.at(-1)?.content || "";
if (text) process.stdout.write(text);
}
console.log("\n");
}
runStream().catch(console.error);
2.2 streamEvents () 事件流式
streamEvents() 是功能更强大的流式 API,它以事件流的形式运行,支持订阅多种「投影」,可以细粒度监控智能体的完整执行过程。
核心特点
- 多投影订阅:可同时监听消息、工具调用、子代理等多种事件
- 细粒度控制:可以拿到工具调用的参数、状态、结果,子代理的执行进度等
- 结构化事件:每个事件都有明确的类型与状态,便于前端渲染与调试
核心投影(Projections)
| 投影 | 说明 |
|---|---|
stream.messages | AI 生成的文本消息片段,对应聊天回复内容 |
stream.toolCalls | 工具调用全流程:入参、执行状态、返回结果 / 错误 |
stream.subagents | 子代理的委派、执行与返回,支持嵌套监听 |
适用场景
- 完整的智能体交互界面(需要展示工具调用、思考过程)
- 调试与排查问题
- 复杂多步任务、带子代理的任务
- 需要实时展示执行进度的场景
代码示例
结合「消息流式打印 + 工具调用监控」的完整示例:
import "dotenv/config";
import { createDeepAgent } from "deepagents";
import { tool } from "langchain";
import * as z from "zod";
import { model } from "./00-model.js";
// 定义一个简单的天气工具,用于演示工具调用事件
const getWeather = tool(
({ city }) => `${city} 今日晴,气温 22-28℃,微风。`,
{
name: "get_weather",
description: "查询指定城市的实时天气",
schema: z.object({
city: z.string().describe("城市名称"),
}),
}
);
const agent = createDeepAgent({
model: model,
systemPrompt: "你是一个简洁友好的中文助手。",
tools: [getWeather],
});
async function runStreamEvents() {
const userInput = "北京今天天气怎么样?";
console.log("用户:", userInput);
console.log("AI:");
// 调用 streamEvents,指定版本 v3
const stream = await agent.streamEvents(
{ messages: [{ role: "user", content: userInput }] },
{ version: "v3" }
);
// 并行订阅多个投影
await Promise.all([
(async () => {
for await (const message of stream.messages) {
if (message.reasoning) {
process.stdout.write("\n💭 思考: ");
for await (const thinkToken of message.reasoning) {
if (thinkToken) process.stdout.write(thinkToken);
}
}
if (message.text) {
process.stdout.write("\n🤖 回答: ");
for await (const textToken of message.text) {
if (textToken) {
process.stdout.write(textToken);
lastText += textToken;
}
}
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\n🔧 [工具调用] ${call.name}(${JSON.stringify(call.input)})`);
const status = await call.status;
if (status === "finished") {
console.log(`\n📦 [工具结果] ${await call.output}`);
} else if (status === "error") {
console.error(`\n❌ [工具错误] ${await call.error}`);
}
}
})(),
]);
console.log("\n");
}
runStreamEvents().catch(console.error);
注意:
stream.messages中每条消息的text属性是一个 Promise,需要await后才能拿到文本内容。
三、stream 与 streamEvents 核心区别
| 对比维度 | stream() | streamEvents() |
|---|---|---|
| 输出内容 | 仅最终文本消息片段 | 消息、工具调用、子代理等全流程事件 |
| 信息粒度 | 粗粒度,只关注最终输出 | 细粒度,可监控每一步执行过程 |
| 用法复杂度 | 简单,直接遍历即可 | 稍复杂,需要订阅对应投影 |
| 工具调用信息 | 不返回 | 完整返回参数、状态、结果、错误 |
| 子代理信息 | 不返回 | 支持嵌套监听子代理执行 |
| 适用场景 | 基础聊天、纯文本生成 | 复杂智能体、调试、带工具的交互界面 |
四、选型建议
- 入门、简单对话场景:优先用
invoke,逻辑最简单,心智成本最低 - 聊天界面、只需要打字机效果:用
stream()足够,轻量简洁 - 带工具调用、子代理的复杂 智能体:用
streamEvents(),完整掌控执行过程 - 调试、排查问题:必用
streamEvents(),可清晰看到每一步执行细节
将代码保存为 03-output-modes.js,在终端执行 node 03-output-modes.js 即可运行测试。