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)