首页
看点啥
插画图片
首页 看点啥 从零打造LLM流式输出:让你的AI聊天像打字机一样丝滑

从零打造LLM流式输出:让你的AI聊天像打字机一样丝滑

2026-07-14 0


一、痛点:用户等不起

使用过早期 AI 产品的同学一定有过这种体验:问一个问题,页面卡住,几十秒后"啪"地一下甩出一大段文字。这种一次性返回的模式有三个致命问题:

从零实现 LLM 流式输出:让你的 AI 聊天像打字机一样丝滑

  1. 等待焦虑:用户盯着空白屏幕,不知道 AI 是在思考还是已经卡死,也不知道还要等多久;
  2. 首字时间(TTFB)过长:必须等整个推理过程全部完成后才能看到第一个字;
  3. 体验割裂:生成结果一瞬间全量渲染,缺乏"AI 正在为你工作"的过程感。

对于复杂的推理计算(比如处理长文本、代码生成、多步推理),LLM 的耗时可能长达数十秒甚至数分钟。作为前端工程师,我们有责任把这段等待时间变成一个渐进式、可感知的交互过程——这就是流式输出(Streaming)的价值。

二、本质:从"一桶水"到"水龙头"

流式输出的核心思想非常朴素:

 复制代码 传统方式:服务端生成完整回答 → 一次性返回 → 客户端渲染
 流式方式:服务端每生成一个 token → 立即发送 → 客户端逐个拼接渲染

打个比方:传统方式就像等一桶水接满后一次性提走,而流式输出就像拧开水龙头,水(token)源源不断地流向客户端。

在技术实现上,这要求服务端和客户端达成一个共同约定——请求体中设置 stream: true。服务端收到这个标记后,不再等所有 token 生成完毕才返回一个完整的 JSON,而是每生成一个 token 就立即以 SSE(Server-Sent Events)或分块传输的方式推送给客户端。

三、客户端如何消费流式数据?

服务端返回的不再是一个 JSON 对象,而是一个 ReadableStream(可读流)。客户端需要:

  1. 获取 Reader:通过 response.body.getReader() 拿到流的读取器;
  2. 逐块解码:用 TextDecoder 将二进制数据解码为文本;
  3. 解析 SSE 格式:流式数据通常是 data: {...}nn 的格式,需要逐行解析;
  4. 拼接渲染:每解析出一个 token,就追加到界面上。

整个过程可以用一个 while 循环来完成——只要流没有关闭(done !== true),就不停地读取、解码、拼接、渲染,直到收到 [DONE] 标记。

四、用 Vue 3 实战:搭建 DeepSeek 流式聊天

下面我们基于 Vite + Vue 3 搭建一个最小化的流式输出 Demo,对接 DeepSeek API。

4.1 项目结构

 复制代码stream-demo/
├── index.html          # 入口 HTML,提供 #app 挂载点
├── src/
│   ├── main.js         # Vue 应用初始化
│   ├── App.vue         # 核心组件(聊天界面 + 流式逻辑)
│   └── style.css       # 全局样式
└── .env.local          # 存放 VITE_DEEPSEEK_API_KEY

4.2 入口文件:index.html

 复制代码<div id="app">div>
<script type="module" src="/src/main.js">script>

非常简单——一个 #app 挂载点,加上 ES Module 方式引入入口脚本。Vite 的开发服务器(默认 :5173)会将 / 映射到 index.html,之后完全由 Vue 接管 DOM。

4.3 应用初始化:main.js

 复制代码import { createApp } from 'vue'
import App from './App.vue'createApp(App).mount('#app')

createApp 创建一个 Vue 应用实例,以 App.vue 作为根组件,挂载到 #app 节点上。这就是整个 Vue 应用的起点。

4.4 核心组件:App.vue

这是整个 Demo 的灵魂所在,我们拆成三部分来看。

Template:模板层

 复制代码<template>
  <div class="container">
    <div>
      <label>输入:label>
      <input type="text" class="input" v-model="question">
      <button @click="update">提交button>
    div>
    <div class="output">
      <input type="checkbox" v-model="stream" />
      <label>Streaminglabel>
      <div>{{ content }}div>
    div>
  div>
template>

这里展示了 Vue 的两个核心能力:

Script:逻辑层

 复制代码import { ref } from 'vue'const question = ref('讲一个关于中国龙的故事')
const stream = ref(false)
const content = ref('')const update = async () => {
  if (!question.value) return
  content.value = '思考中....'  const response = await fetch('https://api.deepseek.com/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${import.meta.env.VITE_DEEPSEEK_API_KEY}`
    },
    body: JSON.stringify({
      model: 'deepseek-v4-flash',
      messages: [{ role: 'user', content: question.value }],
      stream: stream.value  //  关键参数:是否开启流式
    })
  })  if (stream.value) {
    // === 流式输出路径 ===
    content.value = ''
    const reader = response.body?.getReader()
    const decoder = new TextDecoder()
    let done = false    while (!done) {
      const { value, done: doneReading } = await reader.read()
      done = doneReading
      if (value) {
        // 解码二进制 → 文本 → 解析 SSE → 拼接 token
        const chunk = decoder.decode(value, { stream: true })
        // 解析 data: {...} 行,提取 delta content
        // 追加到 content.value
      }
    }
  } else {
    // === 非流式路径 ===
    const data = await response.json()
    content.value = data.choices[0].message.content
  }
}

这里的核心设计是:

  1. ref() 响应式数据questionstreamcontent 都是响应式状态。Vue 3 的 ref 包装后,.value 的修改会自动触发模板重新渲染。这就是数据驱动视图——你只需要关心数据,不用碰 DOM。

  2. API Key 管理:通过 import.meta.env.VITE_DEEPSEEK_API_KEY 读取环境变量。Vite 会自动加载 .env.local 中以 VITE_ 为前缀的变量。把 Key 放在环境变量里,既安全又方便切换。

  3. stream 参数分叉:代码通过 if (stream.value) 优雅地分出了两条路径——勾选 Streaming 走流式读取,不勾选走传统 response.json() 一次性解析。这让 Demo 可以直观对比两种模式。

  4. ReadableStream 消费response.body.getReader() 返回一个 ReadableStreamDefaultReaderreader.read() 每次返回 { value: Uint8Array, done: boolean }。循环读取直到 done === true

Style:样式层

样式层保持了极简风格,但在实际 AI 产品中,这里大有可为——打字机光标动画、逐字淡入效果、Markdown 实时渲染……这些都是在流式输出的基础上叠加的体验增强。

五、Vue 组件化思想:乐高积木

整个 Demo 体现了一个重要的现代前端理念——组件化

一个 .vue 文件就是一个组件,它将 Template(结构) + Script(逻辑) + Style(样式) 封装在一起,形成一个独立、可复用、可维护的业务单元。

想象一下 Facebook 的网页由超过一万个组件构成——就像用乐高积木搭城堡,每个积木块独立且可替换,但组合起来就能构建出复杂的应用界面。

六、总结:流式输出 ≠ 技术炫技,是体验刚需

流式输出看起来只是"一个字一个字蹦出来",但它其实解决了 AI 产品中一个本质的用户体验问题——在不可消除的延迟面前,让用户感知到进展,而不是在空白中焦虑

实现上,它的技术链路是:

 复制代码服务端 stream:true → ReadableStream → getReader() → TextDecoder → SSE 解析 → 逐 token 拼接渲染

前端工程师掌握流式输出,几乎是做 LLM 应用的必修课,也是面试中的高频考点。搞懂了它,你就抓住了 AI 产品用户体验的第一把钥匙。


本文 Demo 源码基于 Vite + Vue 3 + DeepSeek API,完整实现了从 API 调用、流式读取、到界面响应式渲染的全链路。希望这篇文章能帮助你理解流式输出的原理和实现方式,也欢迎在评论区交流你的 AI 产品开发经验!

喜欢(0)

上一篇

从大模型“幻觉”到RAG实战:我借7个故事片段搭了个语义搜索引擎

从大模型“幻觉”到RAG实战:我借7个故事片段搭了个语义搜索引擎

下一篇

AI 编程真正需要的不是提示词技巧:而是上下文工程

AI 编程真正需要的不是提示词技巧:而是上下文工程
猜你喜欢