引言
随着大型语言模型(LLM)的普及,用户对 AI 交互体验的要求也日益提高。传统的“请求-等待-响应”模式在面对 LLM 动辄数十秒的生成时间时,会造成显著的用户体验延迟。为了解决这一问题,现代 AI 产品普遍采用了**流式界面(Streaming UI)**技术,即在模型生成内容的同时,将文本以“打字机”效果实时推送到前端 [1]。这种技术极大地提升了用户的感知速度和满意度。
本文将深入探讨实现 AI 流式界面的各种前端技术,包括核心协议、现代实现方法,以及提升用户体验的最佳实践。
核心流式协议:SSE 与 Fetch/ReadableStream
在前端实现流式传输,主要依赖于两种核心技术:Server-Sent Events (SSE)和Fetch API 结合 ReadableStream。
1. Server-Sent Events (SSE)
Server-Sent Events是一种基于 HTTP 的单向通信协议,允许服务器通过一个持久的 HTTP 连接向客户端推送数据 [2]。
核心特点:
- 单向性:仅支持服务器到客户端的数据推送,非常适合 LLM 这种“一次请求,持续响应”的场景。
- 协议简单:使用
Content-Type: text/event-stream,数据格式为简单的文本,以data:开头,并以双换行符\n\n结束一个事件。 - 原生支持:浏览器提供了原生的
EventSource接口,内置了自动重连机制,简化了客户端的错误处理和连接维护。
局限性:
- 仅支持 GET 请求,无法发送自定义请求头(如认证 Token)或 POST 请求体,这在实际应用中通常需要通过 URL 参数或 Cookie 来弥补。
2. Fetch API 结合 ReadableStream
随着 Web 标准的发展,现代浏览器中的Fetch API提供了对 HTTP 响应体的更底层访问能力,即ReadableStream[3]。当服务器返回的响应头包含Transfer-Encoding: chunked时,response.body将是一个ReadableStream对象,允许前端逐块读取数据。
核心特点:
- 灵活性:支持所有 HTTP 方法(GET, POST 等)和自定义请求头,完美解决了原生
EventSource的局限性。 - 底层控制:开发者可以完全控制数据流的读取、解码和解析过程。
- 现代标准:成为许多现代框架和库(如 Vercel AI SDK)实现流式传输的首选底层机制。
3. 技术对比
下表对比了 AI 流式界面中常见的三种通信技术:
| 特性 | Server-Sent Events (SSE) | Fetch API + ReadableStream | WebSockets |
|---|---|---|---|
| 通信方向 | 单向 (Server -> Client) | 单向 (Server -> Client) | 双向 (Full-Duplex) |
| 底层协议 | HTTP/1.1 或 HTTP/2 | HTTP/1.1 或 HTTP/2 | WS 协议 (升级自 HTTP) |
| 请求方法 | 仅 GET | 所有 (GET, POST, etc.) | N/A (连接建立后) |
| 自定义 Header | 不支持原生EventSource | 支持 | 支持 (握手阶段) |
| 自动重连 | 原生支持 | 需手动实现 | 需手动实现 |
| 典型场景 | LLM 文本流、实时通知 | LLM 文本流、复杂数据流 | 实时聊天、多人协作、语音/视频 |
| AI 流式推荐 | 简单场景,快速实现 | 推荐:灵活、现代、可控 | 复杂交互场景 |
前端实现方法与代码示例
1. 方案一:原生 EventSource (简单场景)
对于不需要自定义 Header 或 POST 请求的简单场景,原生EventSource是最简洁的选择。
// 示例 1: 原生 EventSource 实现functionstartSSEStream(){// 假设后端流式接口为 /api/streamconsteventSource=newEventSource('/api/stream');eventSource.onmessage=function(event){// SSE 协议要求数据以 data: 开头,EventSource 会自动解析constdata=JSON.parse(event.data);// 假设数据包含文本片段consttoken=data.text;// 将新片段追加到界面document.getElementById('output').textContent+=token;};eventSource.onerror=function(error){console.error('SSE Error:',error);// EventSource 会自动尝试重连};// 接收到结束信号后关闭连接// 实际应用中,通常由后端发送一个特殊的 [DONE] 事件,// 但 EventSource 接口本身没有内置的“结束”事件,需要手动处理// eventSource.close();}2. 方案二:Fetch + ReadableStream (现代与灵活)
这是现代 AI 应用中最常用的方法,它允许使用 POST 请求并发送请求体(如用户 Prompt),同时对数据解析拥有完全控制权。
// 示例 2: Fetch API + ReadableStream 实现asyncfunctionstartFetchStream(prompt){constresponse=awaitfetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer YOUR_TOKEN'// 可自定义 Header},body:JSON.stringify({prompt:prompt,stream:true})});if(!response.body){console.error('Response body is not a readable stream.');return;}// 1. 获取 Readerconstreader=response.body// 2. 解码器:将 Uint8Array 转换为文本.pipeThrough(newTextDecoderStream()).getReader();letbuffer='';constoutputElement=document.getElementById('output');// 3. 循环读取数据块while(true){const{done,value}=awaitreader.read();if(done){console.log('Stream finished.');break;}// 4. 核心:处理 SSE 格式的文本块buffer+=value;// SSE 事件以双换行符 \n\n 分隔constparts=buffer.split('\n\n');// 最后一个部分可能不完整,保留在 buffer 中buffer=parts.pop()||'';for(constpartofparts){if(part.startsWith('data: ')){try{// 移除 'data: ' 前缀并解析 JSONconstjsonString=part.substring(6).trim();if(jsonString==='[DONE]'){reader.cancel();// 遇到结束标记,取消读取return;}constdata=JSON.parse(jsonString);// 假设数据包含文本片段consttoken=data.choices[0].delta.content||'';outputElement.textContent+=token;// 实时滚动到底部 (UX 最佳实践)outputElement.scrollTop=outputElement.scrollHeight;}catch(e){console.error('Failed to parse JSON:',e,'Part:',part);}}}}}进阶应用:结构化数据流协议
随着 AI 应用的复杂化,仅仅流式传输文本已经不能满足需求。现代 AI 界面需要实时显示工具调用(Tool Calls)、思考过程(Reasoning)、引文(Citations)甚至动态 UI 组件(Artifacts)。
为了支持这些功能,一些先进的 AI 框架(如 Vercel AI SDK)在 SSE 协议之上定义了结构化数据流协议[4]。
结构化数据流的优势:
- 多模态支持:在同一个流中传输文本、图片 URL、工具调用参数等不同类型的数据。
- 细粒度控制:通过
type字段(如tool-input-start、reasoning-delta)精确控制前端 UI 的状态和行为。 - Agentic Workflow:支持多步骤 Agent 流程的实时反馈,如“思考中” -> “调用工具” -> “工具结果” -> “生成最终答案”。
例如,一个工具调用事件在流中可能表现为:
data: {"type":"tool-input-start","toolCallId":"call_123","toolName":"getWeather"} data: {"type":"tool-input-available","toolCallId":"call_123","input":{"city":"San Francisco"}}前端接收到这些事件后,可以实时渲染一个“正在调用天气工具”的 UI 状态,极大地提高了 AI 响应的透明度。
用户体验 (UX) 最佳实践
流式传输的成功不仅在于技术实现,更在于流畅的用户体验。
1. 智能自动滚动 (Smart Auto-Scrolling)
在文本流式输出时,确保聊天容器自动滚动到最新消息是至关重要的。然而,如果用户正在向上滚动查看历史消息,强制滚动到底部会打断用户体验。
最佳实践:
- 使用
useRef引用消息容器底部元素。 - 在每次接收到新 token 并更新 UI 后,检查用户当前的滚动位置。
- 仅当用户当前滚动位置接近底部(例如,距离底部 100 像素以内)时,才触发自动滚动 [5]。
2. 高效的 Markdown 渲染
LLM 生成的内容通常包含 Markdown 格式(如代码块、列表)。前端需要实时解析和渲染这些 Markdown。
挑战与优化:
- 性能问题:在每个 token 到达时都重新解析和渲染整个 Markdown 字符串会导致性能瓶颈。
- 优化方案:使用像
react-markdown这样的库,并结合React 的 Key 机制,确保只有新增的文本片段触发最小化的 DOM 更新。更高级的优化是实现增量解析,只解析新到达的文本块,而不是整个消息 [6]。
3. 健壮的错误处理与重连机制
流式连接容易受到网络波动、服务器重启等因素的影响。
处理策略:
- 原生 SSE:依赖
EventSource的内置重连机制,通过设置retry字段控制重连间隔。 - Fetch 流:必须手动实现指数退避(Exponential Backoff)重试逻辑。
- 用户反馈:在连接中断或服务器返回错误(如 429 Rate Limit)时,应立即向用户显示清晰的错误信息和重试按钮,而不是让界面卡住。
结论
现代 AI 流式界面的实现是一个结合了网络协议、前端工程和用户体验设计的综合性任务。从基础的SSE到灵活的Fetch/ReadableStream,再到复杂的结构化数据流协议,前端技术栈为构建高性能、高透明度的 AI 交互提供了坚实的基础。开发者应根据项目的复杂度和需求,选择最合适的流式技术,并结合智能滚动、高效渲染和健壮的错误处理等最佳实践,为用户带来流畅、即时、富有生命力的 AI 体验。
参考文献
[1] Medium.How does AI (GPT) use Server Side Events and How it renders images?(https://advancedwebdev.substack.com/p/how-does-ai-gpt-use-server-side-events)
[2] MDN Web Docs.Server-Sent Events. (https://developer.mozilla.org/en-US/docs/Web/API/Server-Sent_Events)
[3] MDN Web Docs.ReadableStream. (https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
[4] Vercel AI SDK.AI SDK UI: Stream Protocols. (https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol)
[5] Grapestech Solutions.How to Build React AI Chatbot Interfaces (2026 Guide). (https://www.grapestechsolutions.com/blog/build-react-ai-chatbot-interface/)
[6] Dev.to.Eliminate Redundant Markdown Parsing: Typically 2-10x Faster AI Streaming. (https://dev.to/kingshuaishuai/eliminate-redundant-markdown-parsing-typically-2-10x-faster-ai-streaming-4k94)