stream() 单模式 vs 多模式 完整梳理
核心分水岭:
streamMode传字符串 (单模式) vs 字符串数组 (多模式) 两种场景迭代返回的数据结构完全不一样,
一、定义区分
1)单模式:streamMode: string
js
// 单选一种
{ streamMode: "messages" }
// 或者
{ streamMode: "updates" }
// 或者
{ streamMode: "custom" }
✅ 每条迭代项结构:[messageChunk, metadata]
ts
[AIMessageChunk, LangGraphMetadata]
- 下标0:主体数据(分片对象)
- 下标1:附加元数据(节点路径、分支信息等)
示例解构:
js
for await (const [chunk, meta] of stream) {
// chunk = AIMessageChunk
// meta = 元数据对象
}
⚠️ 这里没有模式名字符串!数组第一位不是标记,是消息本体。
2)多模式:streamMode: string\[\]
js
{ streamMode: ["updates", "messages", "custom"] }
✅ 每条迭代项结构:[modeName, payload]
ts
["messages" | "updates" | "custom", payloadData]
- 下标0:模式标识字符串 (重点!就是你打印出来的
streamMode:messages) - 下标1:载荷 payload,不同mode载荷结构不同
解构标准写法:
js
for await (const [mode, payload] of stream) {
// mode:"messages" / "updates" / "custom"
// payload:对应模式的数据
}
二、多模式下 payload 内部结构(重中之重)
- mode === "messages"
ini
payload = [AIMessageChunk, metadata]
👉 和【单模式】迭代出来的数组一模一样!
js
if(mode === "messages"){
const [chunk, meta] = payload;
console.log(chunk.content);
}
- mode === "updates"
css
payload = State变更对象 { nodeName: {messages:[...]} }
用于观测图节点执行、状态更新。
- mode === "custom" payload = 自定义事件,
writer函数手动推送的数据。
三、一张对照表看懂差异
| 配置方式 | streamMode 值 | 循环内每一项结构 | 标准解构 |
|---|---|---|---|
| 单模式 | "messages" |
[chunk, meta] |
[chunk, meta] |
| 多模式 | ["messages","updates"] |
[mode字符串, payload] |
[mode, payload] |
四、高频踩坑清单
坑1:两套结构混用
js
// 多模式配置
{streamMode: ["messages","updates"]}
// ❌错误!沿用单模式解构
for await (const [chunk, meta] of stream){}
此时 chunk 拿到的是 "messages" 字符串,直接崩溃。
坑2:变量命名迷惑自己
js
// 多模式
for await (const [streamMode, chunk] of stream) {
// streamMode = "messages"(字符串)
// chunk = payload
}
名字没问题,但一定要记住:只有数组形式streamMode才会拿到mode字符串。
坑3:误以为 payload 直接等于 chunk
js
if(mode === "messages"){
const payload = xxx;
console.log(payload.content) // ❌ undefined
// payload 是 [chunk,meta] 数组,必须二次解构
const [chunk] = payload;
console.log(chunk.content) // ✅
}
坑4:单模式下试图判断 mode
js
// 单模式
{streamMode:"messages"}
for await (const [chunk,meta] of stream){
if(chunk === "messages"){} // ❌永远false,chunk是对象不是字符串
}
五、两套可直接复制的标准模板(打印详细信息)
模板1:单模式(仅messages流式输出)
js
const stream = agent.stream(input, { streamMode: "messages" });
for await (const [chunk, meta] of stream) {
console.log('chunk:',chunk)
console.log('meta:',meta)
}
模板2:多模式(同时监听 updates + messages + custom,和你的日志格式匹配)
js
const stream = await agent.stream(
{ messages: [{ role: "user", content: userInput }] },
{ streamMode: ["updates", "messages", "custom"] }
);
let index = 0;
for await (const [mode, payload] of stream) {
index++;
console.log(`\n===== streamMode #${index} =====`);
console.log(`streamMode:`, mode);
if (mode === "messages") {
console.log(`messages:`, payload);
const [chunk] = payload;
if (chunk?.content) process.stdout.write(chunk.content);
} else if (mode === "updates") {
console.log(`updates:`, payload);
} else if (mode === "custom") {
console.log(`custom:`, payload);
}
}
六、终极一句话口诀
- 字符串配置(单模式):第一项是chunk;
- 数组配置(多模式):第一项是mode名字字符串,数据藏在第二个payload里;
- messages模式下,payload内部又是
[chunk, meta],需要二次解构。