AI 流式输出实践:基于 Vue + DeepSeek 的 Streaming 实现详解

116 阅读3分钟

AI 流式输出实践:基于 Vue + DeepSeek 的 Streaming 实现详解

附带流式解析代码、Buffer 解码原理讲解

大语言模型(LLM)已经进入前端开发者的日常,而“响应速度”与“交互体验”则决定了一个 AI 产品是否顺滑可用。传统接口请求需要等待模型生成完整回答后再一次性返回,而 AI 流式输出(Streaming) 则支持 边生成边返回,像 ChatGPT 一样逐字实时显示。这不仅显著减少等待时间,还极大提升交互质感。


流式输出到底解决了什么?

传统模式:

  • 请求 → 等待模型生成全部内容 → 一次性返回 → 页面渲染

流式输出(Streaming):

  • 模型生成一点发一点 → 前端逐段解析 → 立刻渲染内容
  • 体验更贴近实时对话,不再“卡住等结果”
  • Tokens 边计算边返回,更省时

一句话总结:

stream = true → 前端可以逐字接收 AI 返回内容,实现自然的打字效果。


Vue 代码示例:核心逻辑版

下面是你提供的核心代码(已梳理注释逻辑),重点关注 ReadableStream + TextDecoder + Buffer 拼接处理 这一段:


import { ref } from 'vue'

// 绑定输入与响应式内容
const question = ref('讲一个喜洋洋和灰太狼的故事,不低于200字')
const stream = ref(true)
const content = ref("")

// 调用大模型
const askLLM = async () => { 
  if (!question.value) return

  content.value = '思考中...'

  const response = await fetch('https://api.deepseek.com/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${import.meta.env.VITE_DEEPSEEK_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'deepseek-chat',
      stream: stream.value,
      messages: [{ role: 'user', content: question.value }]
    })
  })

  // ---  进入流式解析模式 ---
  if (stream.value) {
    content.value = "" // 清空旧内容
    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    let buffer = ''
    let done = false

    while (!done) {
      const { value, done: doneReading } = await reader.read()
      done = doneReading
      
      const chunk = buffer + decoder.decode(value)
      buffer = ''  // 暂存区

      // 拆分多行 data: 消息块
      const lines = chunk.split('\n').filter(l => l.startsWith('data: '))
      for (const line of lines) {
        const jsonStr = line.slice(6) // 去掉 "data: "

        if (jsonStr === '[DONE]') { done = true; break }

        try {
          const data = JSON.parse(jsonStr)
          const delta = data.choices[0].delta.content
          delta && (content.value += delta)
        } catch {
          // 若 JSON 被截断 → 暂存,等待下一块拼接
          buffer += jsonStr
        }
      }
    }
  } else {
    const data = await response.json()
    content.value = data.choices[0].message.content
  }
}

上面这段就是 完整 Streaming 实战逻辑,拆解核心思路如下:


流式输出的底层解析流程

Fetch 取回的 body 不是一次性数据,而是可读数据流 ReadableStream

  1. response.body.getReader() —— 获取流读取器
  2. reader.read() —— 读取一小块二进制数据(value=Uint8Array)
  3. TextDecoder() 将二进制转为可读文本
  4. 模型返回采用 SSE(Server-Sent Events)格式 → 按行切割 data: {...}
  5. 解析 JSON 中的 delta.content,逐字更新视图
  6. 若遇到未完整 JSON → 临时存入 buffer 等待下一块补齐
  7. 直到收到 [DONE] 结束信号

一句话总结:

AI 每吐出一点 token → 前端就读一点 → 解码 → 累积呈现


HTML5 Buffer、编码与 TextDecoder 是什么?

你必须知道两个事实:

  1. 网络传输不直接传字符串,而是二进制
  2. AI 输出会被切成若干 chunk(大小与 token、网络状态有关)

因此需要:

概念作用对应代码
Buffer/Uint8Array二进制数据块value
TextDecoder把二进制→文本decoder.decode()
暂存区 buffer防止JSON截断导致解析失败buffer += jsonStr

为什么要 buffer?

  • 假设系统返回一段 JSON 被拆成了两半:
data: {"choices":[{ "delta":{"content":""}}]
data: {"choices":[{ "delta":{"content":""}}]

如果你直接解析第一段 → 报错
所以必须 等待下一段拼接后再 parse
这就是 buffer 的意义!


写在最后

流式输出是构建 AI 应用的核心体验点,它带来的好处显而易见:

  • 结果实时呈现,更自然的 AI 对话体验
  • 等待时间更短,响应更即时
  • 节省带宽,不必一次性返回大文本
  • 有助构建 Chat、写作助手、代码生成器等产品

如果你正在做 AI 应用,那么 Streaming 必须掌握
掌握 ReadableStream + TextDecoder + Buffer 逻辑后,你就能理解大部分 LLM 前端交互实现方式。