langchain.js中stream()的单模式 vs 多模式梳理

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 内部结构(重中之重)

  1. mode === "messages"
ini 复制代码
payload = [AIMessageChunk, metadata]

👉 和【单模式】迭代出来的数组一模一样!

js 复制代码
if(mode === "messages"){
  const [chunk, meta] = payload;
  console.log(chunk.content);
}
  1. mode === "updates"
css 复制代码
payload = State变更对象 { nodeName: {messages:[...]} }

用于观测图节点执行、状态更新。

  1. 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],需要二次解构。
相关推荐
张彦峰ZYF7 小时前
「能跑」不等于「可上线」:生产级 Agent 的验收标准
人工智能·agent·分层抽样、难例、信息不足、对抗·人工、规则、上一版本三条基线·go 与 no-go 决策·内测、灰度、全量三套标准
goehou8 小时前
Agent 卡在第三步,怎么查,一次请求的可观测性
llm·agent·日志·trace·可观测性
Morning的Tech8 小时前
Codex 中智能体的执行机制分析和对比
agent
goehou9 小时前
Agent 把用户地址发给了模型,数据脱敏该在哪一层做
llm·agent·数据脱敏·隐私合规·工程实践
frcoder9 小时前
TackHarness打造属于自己的编程工作流
agent·ai编程·1024程序员节·研发·harness
墨心@10 小时前
PI分层设计的经验
agent·pi·coding agent·harness
羞儿10 小时前
【DSH】创造模式与内核边界:一切皆插件的递归与自证
agent·1024程序员节·harness
编程的一拳超人11 小时前
【元宝搜索开发者征文大赛】四语实战:Java/Python/Go/Node.js 构建 AI 搜索应用全攻略
java·python·golang·node.js·agent