掘金 人工智能 07月10日 10:38
LLM流式输出实现
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了LLM流式输出的工作机制、关键技术、与传统输出的差异、应用价值以及React项目中的代码实现。流式输出通过“碎片化推送”实现实时交互,提升用户体验,优化资源效率,并拓展应用边界。文章详细介绍了模型层、传输层、应用层的协同工作,以及React项目中的代码实现细节,为读者提供了全面的理解。

💡 流式输出基于LLM的自回归生成特性,将内容“碎片化推送”,而非等待完整结果生成后一次性呈现,类似人类“边思考边表达”的过程。

💡 实现流式输出的关键技术包括:模型层分块生成与阈值控制,传输层低延迟通信协议(HTTP/1.1分块传输或WebSocket),以及应用层实时渲染与体验优化。

💡 流式输出与传统输出的核心差异在于:前者将“模型生成、全量传输、客户端渲染”三个步骤改为并行,大幅缩短了用户感知到的等待时间,提升用户体验。

一、LLM 流式输出的工作机制

LLM 流式输出的核心逻辑是将模型生成的内容 “碎片化推送”,而非等待完整结果生成后一次性呈现。其底层依托大语言模型的自回归生成特性:模型每次生成一个 token(可能是字、词或子词),并将该 token 纳入下一次生成的输入,循环往复直至完成整段文本生成。

在流式输出模式中,这一过程被赋予了 “实时推送” 的能力。当模型生成的 token 积累到设定阈值(如一个短句或 10 个字符),就会立即通过网络传输至用户端,用户端接收后即时渲染显示。例如,当用户询问 “如何提升阅读效率” 时,模型会先输出 “提升阅读效率可尝试以下方法:”,随后逐句生成 “1. 采用 SQ3R 阅读法,即 Survey(浏览)、Question(提问)、Read(阅读)、Recite(复述)、Review(复习)……”,整个过程中,用户无需等待全部内容生成,就能同步跟进模型的 “思路”。

这种机制类似人类 “边思考边表达” 的过程,让模型从 “黑箱式的结果输出工具” 转变为 “可实时互动的对话伙伴”。

二、实现 LLM 流式输出的关键技术

LLM 流式输出的顺畅运行,需要模型层、传输层与应用层的协同配合,三者共同构成完整的技术链路。

(一)模型层:分块生成与阈值控制

大语言模型本身的生成逻辑是流式输出的基础。以 GPT 系列模型为例,其采用的 Transformer 架构通过自注意力机制实现 token 的序列生成,每一步生成的 token 都可被独立提取。开发者通过设置 “推送阈值”,决定何时将已生成的 token 打包推送 —— 阈值可以是固定的 token 数量(如每生成 8 个 token 推送一次),也可以是语义断点(如遇到句号、逗号时触发推送),后者能让推送内容更符合人类阅读的语义习惯。

(二)传输层:低延迟通信协议支撑

为实现生成内容的实时传输,流式输出依赖高效的通信协议。目前主流方案包括两种:

一是基于 HTTP/1.1 的分块传输(Chunked Transfer Encoding),服务器通过 “Transfer-Encoding: chunked” 头字段告知客户端数据将分块传输,每块数据前附带长度标识,客户端接收后逐块解析;客户端通过response.body.getReader()获取响应体的可读流(ReadableStream),这是浏览器提供的原生 API,支持异步逐块读取服务器推送的二进制数据块,无需等待整个响应完成。

二是基于 WebSocket 的全双工通信,一旦建立连接,服务器可主动向客户端推送数据,省去了 HTTP 请求的反复握手,延迟更低,尤其适合长对话场景。例如,OpenAI 的 API 采用 WebSocket 协议实现流式输出,客户端发送请求时指定 “stream: true” 参数,服务器便会以 “SSE(Server-Sent Events)” 格式持续推送数据,每条数据包含新增的 token 内容,客户端通过监听事件实时更新界面。

(三)应用层:实时渲染与体验优化

客户端的渲染逻辑直接影响用户对流式输出的感知。优秀的应用层设计会包含三大核心功能:

一是增量渲染,仅更新新增的文本内容,避免整体刷新导致的视觉跳跃;

二是节奏控制,通过调整 token 显示速度(如模拟打字机效果),让输出节奏贴合人类阅读习惯,避免信息过载;

三是异常处理,当网络中断时,能记录已接收的 token 序列,网络恢复后仅请求后续内容,实现 “断点续传”,减少重复生成带来的资源浪费。

三、流式输出与传统输出的核心差异

传统 LLM 输出模式中,用户发出请求后需经历 “模型完整生成→全量传输→客户端渲染” 三个串行步骤,整个过程处于 “无反馈等待” 状态。而流式输出将这三个步骤改为并行:模型生成部分内容后立即启动传输,客户端接收后同步渲染,三个环节重叠进行,大幅缩短了用户感知到的等待时间。

以生成一篇 500 字的回答为例,传统模式可能需要 10 秒生成完整内容,用户需等待 10 秒后才能看到结果;流式输出则可能在 1 秒后开始推送首段内容,随后每秒推送 50 字,总耗时仍为 10 秒,但用户从第 1 秒起就能获取信息,等待焦虑显著降低。此外,在长文本生成场景中,用户可基于已输出内容提前判断是否需要调整提问方向,减少无效等待 —— 例如,当模型输出的前半部分偏离需求时,用户可及时中断生成,节省后续时间。

四、流式输出的应用价值

(一)提升用户体验,降低交互门槛

人类对 “无反馈等待” 的容忍度极低,流式输出通过 “实时响应” 让用户感受到模型的 “积极性”,尤其对非技术用户而言,这种 “边说边想” 的交互模式更贴近日常对话习惯,降低了使用大模型的心理门槛。在客服对话、教育答疑等场景中,流式输出能让用户快速获取初步答案,减少因等待产生的负面情绪。

(二)优化资源效率,适配复杂场景

对服务器而言,流式输出无需缓存完整结果,可减少内存占用,尤其适合生成超长文本(如万字报告、代码文件)时的资源管理;对客户端而言,分块接收数据降低了一次性加载大文本的内存压力,使低配置设备(如手机、平板)也能流畅运行大模型应用;在网络带宽有限的环境中,流式输出通过 “小批量传输” 减少单次数据量,降低了传输超时的风险。

(三)拓展应用边界,赋能实时场景

流式输出让 LLM 在实时交互场景中发挥更大价值。在实时翻译场景中,模型可边接收原文边生成译文,实现 “同声传译” 级别的实时性;在直播弹幕互动中,能基于观众实时发送的弹幕内容,流式生成回应并推送给主播,提升互动效率;在代码辅助工具中,开发者输入需求后,模型流式输出代码片段,开发者可边看边调试,大幅缩短开发周期。

五、流式输出实现代码

在React项目中让请求deep seek接口返回的内容流式输出。

1. 引入依赖与组件定义

import { useState } from "react"; // 引入React的useState钩子,用于管理组件状态import "./index.css"; // 引入样式文件function Deepseek() { // 定义名为Deepseek的函数组件

2. 状态管理

  const [question, setQuestion] = useState(""); // 存储用户输入的问题,初始值为空字符串  const [content, setContent] = useState(""); // 存储AI返回的回答内容,初始值为空  const [streaming, setStreaming] = useState(false); // 控制是否启用流式输出,默认关闭

3. 核心交互函数update

  const update = async () => { // 异步函数,处理发送请求和接收响应的逻辑    // 获取到用户在input框输入的内容    if (!question) return; // 如果输入为空,直接返回,不执行后续操作    setContent(""); // 发送新请求前,清空之前的回答内容

4. API 请求配置

    // 与deepseek交互    const endpoint = "https://api.deepseek.com/chat/completions"; // Deepseek API的请求地址    const headers = { // 请求头配置      Authorization: `Bearer ${import.meta.env.VITE_DEEKSEEK_KEY}`, // 授权令牌,从环境变量获取      "Content-Type": "application/json", // 声明请求体为JSON格式    };

5. 发送请求

    const response = await fetch(endpoint, { // 发送POST请求      method: "POST", // 请求方法为POST      headers: headers, // 使用上面定义的请求头      body: JSON.stringify({ // 请求体,转换为JSON字符串        model: "deepseek-chat", // 指定使用的模型        messages: [ // 对话消息数组          {            role: "user", // 消息角色为"用户"            content: question, // 消息内容为用户输入的question          },        ],        stream: streaming, // 是否启用流式输出,由复选框状态控制      }),    });

6. 流式输出处理(核心逻辑)

    // 流式输出    if (streaming) { // 如果启用了流式输出      const reader = response.body.getReader(); // 获取响应体的可读流读取器      const decoder = new TextDecoder(); // 创建文本解码器,用于将二进制数据转换为字符串      let done = false; // 标记流式传输是否结束      let buffer = ""; // 缓冲区,用于处理跨块的不完整数据      while (!done) { // 循环读取流,直到传输结束        const { value, done: readerDone } = await reader.read(); // 读取一段二进制数据(value)和是否结束(readerDone)        done = readerDone; // 更新done状态        const chunkValue = buffer + decoder.decode(value); // 将缓冲区数据与当前解码后的数据拼接        buffer = ""; // 清空缓冲区(后续会重新处理未完成的片段)        // 处理SSE格式的数据(Server-Sent Events,服务器发送事件)        const lines = chunkValue          .split("\n") // 按换行分割数据          .filter((line) => line.startsWith("data: ")); // 筛选出SSE格式的行(以"data: "开头)        for (const line of lines) { // 遍历每一行有效数据          const incoming = line.slice(6); // 去除开头的"data: "(长度为6)          if (incoming === "[DONE]") { // 如果收到结束标记            done = true; // 标记传输结束            break; // 跳出循环          }          try {            const data = JSON.parse(incoming); // 解析JSON格式的内容            const delta = data.choices[0].delta.content; // 获取当前片段的内容(流式输出的增量部分)            if (delta) { // 如果有增量内容              setContent((prev) => prev + delta); // 累加更新回答内容(使用函数式更新,确保基于最新状态)              // str += delta              // setContent(str)               // 上面两行是错误示例:因为str会形成闭包,无法获取最新值,导致更新不及时            }          } catch (err) {            console.log(err); // 解析出错时,在控制台打印错误          }        }      }    }

1. 逐块读取二进制数据

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

2. 处理数据完整性:缓冲区拼接

    const chunkValue = buffer + decoder.decode(value);    buffer = "";

3. 解析 SSE 格式:提取增量内容

服务器推送的流式数据遵循 SSE(Server-Sent Events)格式,每条有效数据以data: 开头,例如:

代码通过以下步骤提取增量内容:

// 筛选SSE格式的行(以"data: "开头)const lines = chunkValue.split("\n").filter(line => line.startsWith("data: "));// 提取并解析增量内容for (const line of lines) {  const incoming = line.slice(6); // 移除"数据前缀"data:   if (incoming === "[DONE]") { done = true; break; } // 结束标记  const data = JSON.parse(incoming);   const delta = data.choices[0].delta.content; // 增量文本(如"你好"、"世界")}

4. 实时更新 UI:累加展示

    setContent((prev) => prev + delta);

7.组件 UI 渲染

  return (    <div className="container"> {/* 外层容器 */}      <div className="response"> {/* 输入区域 */}        <label htmlFor="">请输入</label> {/* 输入框标签 */}        <input          type="text"          className="input"          onChange={(e) => { // 输入变化时,更新question状态            setQuestion(e.target.value);          }}        />        <button onClick={update}>发送</button> {/* 点击触发update函数 */}      </div>      <div> {/* 流式输出开关 */}        <label htmlFor="">streaming</label> {/* 复选框标签 */}        <input          type="checkbox"          onChange={(e) => { // 勾选状态变化时,更新streaming状态            setStreaming(e.target.checked);          }}        />      </div>      <div>{content}</div> {/* 展示回答内容 */}    </div>  );}

实现效果

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LLM 流式输出 React 人工智能
相关文章