AI 应用工程入门(五):流式输出解决什么问题?
用普通人能听懂的方式解释 Streaming 为什么能改善等待体验,以及前后端需要处理哪些状态。
很多聊天式 AI 的答案不是一次性出现,而是一段一段显示。这就是流式输出。它不直接提高答案正确率,但能显著改善长文本生成时的等待体验。
题目 1:什么是流式输出?
标准答案:流式输出是模型一边生成一边把内容返回给客户端。普通响应要等完整答案生成完才返回,流式响应会把小片段持续推给界面。
工程要点:聊天、长文生成、代码解释、文章润色都适合流式。用户能马上看到系统开始工作,不会一直盯着空白页面。
题目 2:为什么 AI 产品需要 Streaming?
标准答案:因为大模型生成可能很慢。模型排队、Prompt 太长、输出太长、RAG 检索和工具调用都会增加等待时间。
工程要点:AI 产品不能只靠 loading spinner。长任务最好让用户看到生成过程,同时保留停止生成、重试和错误提示。体验上的“正在进行”很重要。
题目 3:客户端如何读取流式响应?
标准答案:客户端通常通过 response.body.getReader() 逐段读取,再用 TextDecoder 解码,把每一段追加到当前答案里。
工程要点:流式状态不是简单的 loading 和 success,而是持续接收内容。要处理开始生成、内容追加、用户中断、网络错误和生成完成。多次点击发送时,还要避免多个 stream 同时写入同一个回答。
客户端读取流式响应时,代码通常围绕 reader 循环展开:
const controller = new AbortController();
const response = await fetch("/api/chat/stream", {
method: "POST",
body: JSON.stringify({ message }),
signal: controller.signal,
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
let currentAnswer = "";
while (reader) {
const { value, done } = await reader.read();
if (done) break;
currentAnswer += decoder.decode(value, { stream: true });
render(currentAnswer);
}
// 用户点击“停止生成”时调用
controller.abort();
这段代码说明了为什么流式输出不是普通 loading:页面不是等一个最终 JSON,而是在不断追加片段,并且要允许用户主动停止。
题目 4:流式输出常见坑是什么?
标准答案:常见坑包括把流式响应当 JSON 读、服务端没有真正转发流、中间层缓存导致不实时、流中出错不好表达、用户关闭页面后服务端仍然继续调用模型。
工程要点:流式接口最好设计取消机制。用户点击停止时,要让客户端和服务端都尽量中断。否则用户已经不看了,系统还在消耗 token。
题目 5:什么时候不该用 Streaming?
标准答案:当任务需要稳定结构化结果、结果很短、必须完整解析时,不一定要流式。比如分类、打标签、审核文本、生成固定 JSON。
工程要点:Streaming 解决的是等待体验问题,不是所有 AI 能力的默认答案。展示型生成适合流式,结构化计算更适合普通响应。
完整示例:Streaming 是一套状态机,不只是逐字显示
流式输出的核心价值是改善等待体验,但工程上它不是把普通 JSON 换成流这么简单。界面要知道什么时候开始、什么时候收到第一段、什么时候完成、什么时候失败、用户什么时候取消。服务端也要尽量把取消信号传给模型服务,避免用户已经关闭页面,后台还在花 token。
客户端至少要维护这些状态:
type StreamState =
| { status: "idle" }
| { status: "connecting" }
| { status: "streaming"; text: string }
| { status: "done"; text: string }
| { status: "error"; message: string }
| { status: "aborted"; text: string };
读取流时,不要用 response.json()。流式响应要通过 reader 一段段读,每读到一段就更新界面。用户点击停止时,通过 AbortController 取消请求。
async function readStream(message: string) {
const controller = new AbortController();
setCurrentAbortController(controller);
const response = await fetch("/api/chat/stream", {
method: "POST",
body: JSON.stringify({ message }),
signal: controller.signal,
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
let text = "";
while (reader) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
setState({ status: "streaming", text });
}
setState({ status: "done", text });
}
服务端也要真正返回 ReadableStream,而不是等模型完整生成后再一次性返回。很多“假流式”的问题出在中间层:服务端先攒完整结果,页面端再逐字播放,看起来是流,实际没有减少首字等待时间。
适合流式:聊天、长文、代码解释、报告生成、逐步推理展示。
不适合流式:分类、审核、标签提取、固定 JSON、短结果计算。
这一课的完整答案是:Streaming 解决的是体验,不解决准确性。准确性仍然来自资料、Prompt、校验和评测;Streaming 只负责让长任务变得可感知、可中断、可恢复。
这一篇最该带走的结论是:流式输出让 AI 产品更顺滑,但可靠性仍然来自资料、约束、校验和错误处理。