流式输出 Streaming
LLM 生成一个完整回答常需数秒。同步等待会让用户对着转圈。流式(Streaming) 让模型边生成边返回,前端逐字渲染,体验质的提升。
1. 基础:.stream()
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({ model: "gpt-4o-mini", streaming: true });
const stream = await model.stream("讲一个关于单元测试的短故事");
for await (const chunk of stream) {
process.stdout.write(chunk.content as string);
}ℹ️streaming 开关
streaming: true 让底层走 SSE/分块接口。接了 StringOutputParser 的链也能 .stream(),解析器会逐块吐字符串。
2. LCEL 链的流式
const chain = prompt.pipe(model).pipe(new StringOutputParser());
const stream = await chain.stream({ topic: "向量数据库" });
for await (const text of stream) {
process.stdout.write(text);
}3. streamEvents:细粒度事件
想要「检索了哪些文档」「工具调用了什么」这类中间事件,用 streamEvents:
const stream = chain.streamEvents({ topic: "RAG" }, { version: "v2" });
for await (const event of stream) {
if (event.event === "on_llm_stream") {
process.stdout.write(event.data.chunk.content as string);
}
if (event.event === "on_retriever_end") {
console.log("检索到:", event.data.output.length, "块");
}
}💡事件名速查
on_llm_start/stream/end、on_chain_*、on_tool_*、on_retriever_*。配合第 14 章回调做可视化。
4. 在 Next.js 中逐字推到前端
用 ReadableStream 把链流式结果转发给浏览器:
// app/api/chat/route.ts
export const runtime = "nodejs";
export async function POST(req: Request) {
const { topic } = await req.json();
const stream = await chain.stream({ topic });
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
});
return new Response(readable, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}// 前端
const res = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ topic }) });
const reader = res.body!.getReader();
const dec = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
document.body.append(dec.decode(value)); // 逐字追加
}⚠️流式下错误处理要小心
流式响应已 200 发出后无法再改状态码。业务逻辑错误应在开始流之前校验,或用特殊标记(如 [ERROR])在前端识别。
🎯练习
把第 15 章的 /api/chat 改成流式返回,并在前端用 reader.read() 逐字渲染到页面。
小结
streaming: true+.stream()实现基础逐字输出streamEvents(version:"v2")暴露检索/工具等中间事件- Next.js 用
ReadableStream把链结果转发到浏览器 - 流式响应的错误要前置校验或用标记识别
- 下章实战:端到端文档问答 Bot →