游乐游手机版
首页/AI教程/文章详情

大模型流式输出实现原理:ReadableStream、Uint8Array与SSE

时间:2026-07-21 18:34
传统 HTTP 接口通常等待所有内容生成完毕,才一次性将完整结果返回给客户端。 然而,当大模型需要生成一篇较长的文章时,用户面对的是几秒甚至更久的空白页面。即使模型内部正在正常计算,产品却看起来像是“卡住了”。 流式输出彻底改变了这一局面。模型每生成一小段内容,服务端便立即向客户端推送,页面再将这些

传统 HTTP 接口通常等待所有内容生成完毕,才一次性将完整结果返回给客户端。

大模型流式输出是怎么实现的?从 ReadableStream、Uint8Array 到 SSE

然而,当大模型需要生成一篇较长的文章时,用户面对的是几秒甚至更久的空白页面。即使模型内部正在正常计算,产品却看起来像是“卡住了”。

流式输出彻底改变了这一局面。模型每生成一小段内容,服务端便立即向客户端推送,页面再将这些增量内容持续追加到已有文本之后。

用户提交问题→ 大模型生成部分 Token→ 服务端发送一个数据事件→ 浏览器读取并解析→ Vue 更新页面→ 继续等待下一段内容

接下来,我们来看看用 Vue 3 和 Fetch API,如何具体拆解浏览器读取并解析大模型返回的数据流。

stream: true 改变了什么?

非流式请求返回的是一个完整的 JSON 对象:

{"choices": [{"message": {"content": "完整回答"}}]}

前端只需等待并解析:

const data = await response.json(); content.value = data.choices[0].message.content;

开启流式输出后,响应体不再是一个可以直接 response.json() 的完整对象,而是持续到达的数据流:

data: {"choices":[{"delta":{"content":"你"}}]} data: {"choices":[{"delta":{"content":"好"}}]} data: [DONE]

其中每个 delta.content 都是本次新增的内容,前端需要逐段读取并拼接。

ReadableStream 是浏览器里的数据管道

Fetch 收到响应后,response.body 实际上是一个 ReadableStream

const response = await fetch('/api/stream', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: question.value, }), }); if (!response.ok) { throw new Error(`请求失败:${response.status}`); } if (!response.body) { throw new Error('当前浏览器不支持流式响应'); } const reader = response.body.getReader();

这里请求的是自己的 /api/stream 接口。浏览器只负责提交问题,不保存大模型服务的 API Key,这个安全边界必须守住。

getReader() 相当于在数据管道上安装了一个读取器。每次调用:

const { value, done } = await reader.read();

会得到两个值:

value:本次读到的二进制数据 done:数据流是否已经结束

如果暂时没有新数据,reader.read() 会等待;服务端继续发送后,Promise 才会完成。

Uint8Array 为什么不能直接显示?

reader.read() 返回的 value 通常是 Uint8Array

它保存的是一组 0~255 的无符号整数,本质上是网络传输过来的原始字节:

Uint8Array(6) [228, 189, 160, 229, 165, 189]

浏览器无法直接把这些数字当成“你好”显示,因此需要使用 TextDecoder 解码:

const decoder = new TextDecoder('utf-8');

读取数据时使用流式解码:

const text = decoder.decode(value, { stream: true, });

UTF-8 中文字符通常由多个字节组成,而网络数据可以在任意字节位置拆分。

stream: true 会让解码器保留尚未完整的字节,等下一批数据到达后再继续解码,避免一个中文字符正好被拆在两次 read() 之间。

数据全部读取完成后,再调用一次:

buffer += decoder.decode();

把解码器内部剩余的字节刷新出来。

ReadableStream 和 SSE 不是一回事

这两个概念经常一起出现,但职责截然不同。

ReadableStream:浏览器读取响应体的方式 SSE:服务端组织文本事件的格式

SSE 是 Server-Sent Events 的缩写,常见格式如下:

data: 第一条数据 data: 第二条数据

每个事件可以包含 eventidretrydata 等字段,事件之间使用空行分隔。

大模型兼容接口通常把 JSON 放进 data: 字段,并使用 [DONE] 表示结束。

一次 read() 不等于一次 SSE 事件

网络只负责传输字节,它不理解 JSON 和 SSE 的业务边界。

一次 reader.read() 可能拿到:

半条 SSE 事件 一条完整事件 多条完整事件 上一条的后半段和下一条的前半段

因此,不能假设每个 value 都能直接执行 JSON.parse()

正确做法是准备一个 buffer

新文本追加到 buffer→ 按 SSE 空行拆出完整事件→ 保留最后一段不完整内容→ 等待下一批数据继续拼接

封装一个 SSE 事件解析函数

先处理一条已经完整的 SSE 事件:

function parseSSEEvent(eventText, onDelta) { const payload = eventText.split(/r?n/).filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('n'); if (!payload) return false; if (payload === '[DONE]') { return true; } const data = JSON.parse(payload); const delta = data.choices?.[0]?.delta?.content; if (delta) { onDelta(delta); } return false; }

这个函数完成三件事:

  • 提取事件中的 data: 内容
  • 识别 [DONE]
  • 从 JSON 中取得本次新增的 delta.content

返回 true 表示服务端已经发送结束标记。

完整读取并解析响应流

接下来把 ReadableStreamTextDecoder 和 SSE 事件边界连接起来:

async function readSSEStream(response, onDelta) { if (!response.body) { throw new Error('响应体不是可读流'); } const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; let finished = false; while (!finished) { const { value, done } = await reader.read(); if (done) { buffer += decoder.decode(); break; } buffer += decoder.decode(value, { stream: true, }); const events = buffer.split(/r?nr?n/); buffer = events.pop() ?? ''; for (const eventText of events) { finished = parseSSEEvent(eventText, onDelta); if (finished) break; } } if (!finished && buffer.trim()) { parseSSEEvent(buffer, onDelta); } }

这里最关键的是:

buffer = events.pop() ?? '';

按照空行拆分后,数组最后一项可能只是半条事件。它不能被丢弃,需要留到下一轮继续拼接。

在 Vue 中持续更新页面

Vue 使用响应式状态保存问题、回答和加载状态:

每当解析出新的 delta

content.value += delta;

Vue 就会更新对应的页面内容,形成类似打字机的输出效果。

这里使用文本插值 {{ content }},而不是直接使用 v-html 渲染模型回答,避免把未经处理的模型输出当成 HTML 执行,这是一个重要的安全边界。

为什么聊天请求通常使用 Fetch,而不是 EventSource?

浏览器原生的 EventSource 也能接收 SSE,但它主要面向 GET 请求,并且不方便自定义请求体和请求头。

大模型聊天通常需要:

  • 使用 POST 请求
  • 发送 messages 或 prompt
  • 携带业务参数
  • 处理自定义错误响应
  • 主动取消请求

因此,fetch() 配合 ReadableStream 更适合聊天场景。

SSE 和 WebSocket 怎么选?

对比项SSEWebSocket
通信方向服务端向客户端持续发送双向通信
协议基础HTTPWebSocket
数据格式文本事件文本或二进制
聊天生成适合服务端持续返回回答也能实现,但通常更复杂

大模型生成回答的核心数据方向是:

客户端提交一次问题 服务端持续返回内容

因此,SSE 已经能够满足大多数文本生成场景。只有当业务需要持续的双向实时通信时,才需要进一步考虑 WebSocket。

服务端需要提供什么?

本文重点是浏览器端解析,/api/stream 的 BFF 实现放在下一篇。

从客户端视角看,服务端至少需要:

接收前端问题→ 携带服务端保存的 API Key 请求大模型→ 开启 stream→ 保持流式响应→ 把 SSE 数据持续转发给浏览器

典型响应头包括:

Content-Type: text/event-stream; charset=utf-8 Cache-Control: no-cache Connection: keep-alive

API Key 保存在 BFF 服务端,不能使用 VITE_ 环境变量暴露给浏览器。

总结

大模型的“打字机效果”不是前端定时器模拟出来的,而是一条持续工作的数据管道:

LLM 生成 Token→ 服务端封装为 SSE 事件→ HTTP 响应流→ ReadableStream→ Uint8Array→ TextDecoder→ buffer 拼接完整事件→ JSON.parse→ Vue 响应式更新

其中有三个关键边界:

  • 字节边界:使用 TextDecoder 的流式模式处理 UTF-8 拆分
  • 事件边界:使用 buffer 保留未完成的 SSE 事件
  • 安全边界:前端只请求 BFF,不保存大模型 API Key

理解这三个边界后,流式输出就不再只是“循环读取 response.body”,而是一套可以稳定处理网络拆包、中文解码和增量渲染的数据流程。

来源:https://juejin.cn/post/7664476065624571947
上一篇款MCP工具助AI深度理解业务 下一篇钩子如何解决智能体循环中条件判断混乱
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
TalkVisions实时视频翻译应用,消除语言障碍
AI教程 · 2026-07-25

TalkVisions实时视频翻译应用,消除语言障碍

TalkVisions是一款实时视频翻译应用,能将视频中的口语实时转录为文本并翻译成用户所选语言,以字幕形式叠加在画面上,支持多语言、低延迟,还可保存录制视频,有效消除跨语言沟通障碍。

AI驱动的日历管理工具Ipso
AI教程 · 2026-07-25

AI驱动的日历管理工具Ipso

IpsoAI是一款专为专业人士及助手打造的AI日历管理工具,能够自动协调多方日程、智能草拟邮件,并通过快速安排会议、提供智能建议及自动化工作流程,显著减少琐碎操作,帮助用户高效管理时间、提升工作效率。

Spectate企业级专业高效监控与事故管理一体化平台
AI教程 · 2026-07-25

Spectate企业级专业高效监控与事故管理一体化平台

Spectate是一款高效监控和事故管理工具,能在30秒内检测故障并推送告警。它支持Slack、PagerDuty等主流集成,提供自定义状态页面和全球性能监控。系统自动更新状态并推送修复建议,帮助团队减少沟通成本,快速解决问题。

阿里云通义千问2.5大模型发布 多项能力赶超GPT-4
AI教程 · 2026-07-25

阿里云通义千问2.5大模型发布 多项能力赶超GPT-4

通义千问2 5大模型发布,多项能力宣称赶超GPT-4,中文语境下文本理解、生成、知识问答等表现优异。相比2 1版本,理解提升9%、逻辑推理提升16%、指令遵循提升19%。开源1100亿参数模型超越Llama-3-70B,获评开源最强。已服务超9万家企业,与小米、微博等达成合作。

万知个人AI工作站:一站式智能阅读创作分享平台
AI教程 · 2026-07-25

万知个人AI工作站:一站式智能阅读创作分享平台

万知是集成多种AI能力的个人工作站,支持自然语言交互、文档快速阅读与摘要生成、PPT自动设计与优化,覆盖学术研究、商务报告、写作辅助及日常问答等场景,全方位提升工作效率。