问答题10/1816如何实现 ChatGPT 类似的流式输出?

难度:
2026-06-08 创建

参考答案:

类似 ChatGPT 的流式输出,本质是:服务端不要等完整答案生成后再返回,而是把模型生成的 token 或文本片段持续写入 HTTP 响应;前端边读边渲染,并在完成、取消、异常时维护好状态。

实际项目里最常见的是 SSEfetch + ReadableStream。如果只是服务端向前端单向推送,SSE 足够稳定;如果需要双向实时交互,比如语音、多人协作、实时 Agent 控制,可以用 WebSocket。在 ChatGPT 这类文本生成场景中,fetch 读取流也很常用,因为它支持 POST、请求体、鉴权、AbortController,控制能力比原生 EventSource 更强。

前端核心代码大致是这样:

1async function streamChat(messages: any[], onDelta: (text: string) => void) { 2 const controller = new AbortController(); 3 4 const response = await fetch('/api/chat/stream', { 5 method: 'POST', 6 headers: { 7 'Content-Type': 'application/json', 8 }, 9 body: JSON.stringify({ messages }), 10 signal: controller.signal, 11 }); 12 13 if (!response.ok || !response.body) { 14 throw new Error('stream request failed'); 15 } 16 17 const reader = response.body.getReader(); 18 const decoder = new TextDecoder('utf-8'); 19 20 let buffer = ''; 21 22 while (true) { 23 const { value, done } = await reader.read(); 24 25 if (done) break; 26 27 buffer += decoder.decode(value, { stream: true }); 28 29 const events = buffer.split('\n\n'); 30 buffer = events.pop() || ''; 31 32 for (const event of events) { 33 const line = event 34 .split('\n') 35 .find((item) => item.startsWith('data:')); 36 37 if (!line) continue; 38 39 const data = line.replace(/^data:\s*/, ''); 40 41 if (data === '[DONE]') { 42 return; 43 } 44 45 const payload = JSON.parse(data); 46 47 if (payload.type === 'delta') { 48 onDelta(payload.content); 49 } 50 } 51 } 52 53 controller.abort(); 54}

服务端返回时通常使用类似这样的协议:

1data: {"type":"delta","content":"你好"} 2 3data: {"type":"delta","content":",这是流式输出"} 4 5data: {"type":"done"} 6

前端不要直接假设每个 chunk 都是完整 JSON,因为网络分片可能把一个 JSON 拆开,也可能把多个事件合在一起。所以需要 buffer,按约定分隔符,比如 \n\n,拆成完整事件再解析。

渲染层需要注意两点。第一,状态更新不能过于频繁,否则 React 会因为每个 token 都 setState 而出现卡顿。更稳的做法是先把增量内容追加到临时变量里,再用 requestAnimationFrame 或节流批量刷新 UI。第二,如果内容需要 Markdown 渲染,流式阶段可以渲染一个“尽量正确”的中间态,最终完成后再做一次完整 Markdown 渲染,避免代码块、表格、链接在半截状态下解析异常。

在 Agent 场景里,协议最好不要只传纯文本,而是传结构化事件。例如:

1type StreamEvent = 2 | { type: 'message_delta'; content: string } 3 | { type: 'tool_start'; toolName: string; input: unknown } 4 | { type: 'tool_result'; toolName: string; output: unknown } 5 | { type: 'status'; message: string } 6 | { type: 'error'; message: string } 7 | { type: 'done' };

这样前端可以把“模型正在思考”“调用工具”“工具返回结果”“继续生成回答”这些状态分开展示,而不是把所有内容混成一段字符串。ChatGPT 类产品的体验重点不只是逐字输出,还包括中断、重试、滚动跟随、错误恢复、消息状态、工具调用状态这些细节。

还要处理取消请求。用户点击停止生成时,前端用 AbortController.abort() 取消请求,后端也需要监听连接关闭,停止模型调用和工具执行,避免资源继续消耗。

1const controller = new AbortController(); 2 3fetch('/api/chat/stream', { 4 method: 'POST', 5 signal: controller.signal, 6}); 7 8// 点击停止 9controller.abort();

最近更新时间:2026-06-09

赞赏支持

题库维护不易,您的支持就是我们最大的动力!