Skip to content

05-流式输出:stream 方法

💡 注意: 本教程适用于 LangChain.js v1.x,需要 Node.js 20+

🎯 本章目标

  • ✅ 理解流式输出的原理
  • ✅ 掌握 stream() 方法
  • ✅ 实现打字机效果

🤔 什么是流式输出?

非流式(普通模式)

用户提问 → AI 思考 3 秒 → 一次性显示全部文字

等待时间长,用户体验差。

流式输出

用户提问 → AI 边思考边输出 → 文字逐字显示

立即看到内容,体验更流畅。


📦 stream() 方法

基本用法

javascript
const stream = await model.stream(messages);

for await (const chunk of stream) {
  process.stdout.write(chunk.content);
}

chunk 是什么?

chunk 是 AI 生成的一小段文本(通常几个字):

javascript
{
  content: "你",
  role: "assistant"
}
// 然后...
{
  content: "好",
  role: "assistant"
}
// 然后...
{
  content: "!我",
  role: "assistant"
}

💡 完整示例

javascript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

const model = new ChatOpenAI({
  modelName: "MiniMax/MiniMax-M2.5",
  apiKey: process.env.MODELSCOPE_API_KEY,
  configuration: {
    baseURL: "https://api-inference.modelscope.cn/v1",
  },
});

const messages = [
  new SystemMessage("你是一个友好的 AI 助手。"),
  new HumanMessage("请用 100 字左右介绍一下人工智能。"),
];

console.log("🤖 AI 正在回答:\n");

// 流式输出
const stream = await model.stream(messages);

for await (const chunk of stream) {
  process.stdout.write(chunk.content || '');
}

console.log("\n\n✅ 生成结束");

🎨 进阶用法

1. 添加打字机延迟(模拟)

javascript
const stream = await model.stream(messages);

for await (const chunk of stream) {
  process.stdout.write(chunk.content || '');
  // 模拟打字延迟
  await new Promise(resolve => setTimeout(resolve, 50));
}

2. 收集完整回复

javascript
let fullResponse = "";

const stream = await model.stream(messages);

for await (const chunk of stream) {
  fullResponse += chunk.content || '';
}

console.log("\n完整回复:", fullResponse);

3. 发送到前端(WebSocket 示例)

javascript
const stream = await model.stream(messages);

for await (const chunk of stream) {
  // 通过 WebSocket 发送给前端
  websocket.send(JSON.stringify({
    type: 'token',
    content: chunk.content
  }));
}

4. 处理流式输出的元数据

javascript
const stream = await model.stream(messages);

for await (const chunk of stream) {
  console.log("当前 token:", chunk.content);
  console.log("元数据:", chunk.response_metadata);
}

📋 invoke() vs stream() 对比

特性invoke()stream()
返回时间等待完成立即返回
内存占用一次性加载分块加载
用户体验一般
使用场景短文本/批处理长文本/实时显示

⚙️ stream() 参数详解

javascript
const stream = await model.stream(messages, options);

// options 参数:
// - signal: AbortSignal - 用于取消流式输出
// - streamUsage: boolean - 是否返回 token 使用统计

示例:取消流式输出

javascript
const controller = new AbortController();

// 5 秒后取消
setTimeout(() => controller.abort(), 5000);

try {
  const stream = await model.stream(messages, {
    signal: controller.signal
  });
  
  for await (const chunk of stream) {
    process.stdout.write(chunk.content);
  }
} catch (e) {
  if (e.name === 'AbortError') {
    console.log("\n流式输出已取消");
  }
}

⚠️ 注意事项

1. 不要混用

javascript
// ❌ 错误:stream() 后不要再用 invoke()
const stream = await model.stream(messages);
const response = await model.invoke(messages);  // 这会重新调用

// ✅ 正确:选择一种方式
const stream = await model.stream(messages);
for await (const chunk of stream) {
  process.stdout.write(chunk.content);
}

2. 处理空 chunk

某些模型可能返回空 chunk:

javascript
for await (const chunk of stream) {
  process.stdout.write(chunk.content || '');  // 使用 || 避免 undefined
}

3. 错误处理

javascript
try {
  const stream = await model.stream(messages);
  
  for await (const chunk of stream) {
    process.stdout.write(chunk.content || '');
  }
} catch (error) {
  console.error("流式输出失败:", error.message);
}

🔧 完整实战示例

javascript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

async function runWithStream() {
  const model = new ChatOpenAI({
    modelName: "MiniMax/MiniMax-M2.5",
    apiKey: process.env.MODELSCOPE_API_KEY,
    temperature: 0.7,
    configuration: {
      baseURL: "https://api-inference.modelscope.cn/v1",
    },
  });

  const messages = [
    new SystemMessage("You are a helpful assistant."),
    new HumanMessage("你好,请介绍一下你自己,并用流式输出。"),
  ];

  try {
    console.log("🤖 AI 正在回答:\n");
    const stream = await model.stream(messages);
    
    for await (const chunk of stream) {
      process.stdout.write(chunk.content || '');
    }
    
    console.log("\n\n✅ 生成结束");
  } catch (error) {
    console.error("调用失败:", error.message);
  }
}

runWithStream();

📝 本章小结

  • stream() 实现流式输出
  • 使用 for await...of 循环处理 chunk
  • chunk.content 包含当前生成的文本
  • 适合长文本和实时显示场景

🏃 下一章

[06-Prompt 模板 →](./06-Prompt 模板.md)

基于 LangChain.js v1.x 版本