引言

随着大型语言模型(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 实现
function startSSEStream() {
  // 假设后端流式接口为 /api/stream
  const eventSource = new EventSource('/api/stream');

  eventSource.onmessage = function(event) {
    // SSE 协议要求数据以 data: 开头,EventSource 会自动解析
    const data = JSON.parse(event.data);
    
    // 假设数据包含文本片段
    const token = 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 实现
async function startFetchStream(prompt) {
  const response = await fetch('/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. 获取 Reader
  const reader = response.body
    // 2. 解码器:将 Uint8Array 转换为文本
    .pipeThrough(new TextDecoderStream())
    .getReader();

  let buffer = '';
  const outputElement = document.getElementById('output');

  // 3. 循环读取数据块
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      console.log('Stream finished.');
      break;
    }

    // 4. 核心:处理 SSE 格式的文本块
    buffer += value;
    
    // SSE 事件以双换行符 \n\n 分隔
    const parts = buffer.split('\n\n');
    // 最后一个部分可能不完整,保留在 buffer 中
    buffer = parts.pop() || ''; 

    for (const part of parts) {
      if (part.startsWith('data: ')) {
        try {
          // 移除 'data: ' 前缀并解析 JSON
          const jsonString = part.substring(6).trim();
          if (jsonString === '[DONE]') {
            reader.cancel(); // 遇到结束标记,取消读取
            return;
          }
          const data = JSON.parse(jsonString);
          
          // 假设数据包含文本片段
          const token = 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-startreasoning-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)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐