Vue3 + DeepSeek API 实现大模型流式输出:打造丝滑 AI 交互体验

11 阅读3分钟

前言:在 LLM(大语言模型)应用开发中,用户体验至关重要。传统“等待-加载-显示”的交互模式早已不能满足用户对即时反馈的期待。本文将带你从零搭建一个基于 Vue3 的前端项目,通过调用 DeepSeek API 实现真正的流式输出(Streaming) ,让用户看到文字逐字生成的“魔法”过程。


🌟 为什么需要流式输出?

想象一下,你向 AI 提问:“讲一个喜羊羊与灰太狼的故事,20字”。
如果系统要等模型完整生成全部内容后再一次性返回,用户可能要等待 2~5 秒——在这段时间里,界面一片空白,用户会怀疑“是不是卡了?”、“请求失败了吗?”

流式输出则完全不同:

“青青草原上,喜羊羊智斗灰太……”

文字像打字机一样逐字出现,用户立刻获得反馈,感知到“AI 正在思考”,心理等待时间大幅缩短,体验更自然、更“智能”。

这正是现代 AI 应用(如 ChatGPT、Claude、DeepSeek)的标准交互范式。


🛠️ 技术栈与核心思路

  • 前端框架:Vue 3(<script setup> + ref 响应式)
  • API 服务:DeepSeek 官方 Chat Completions 接口
  • 关键特性:支持 stream: true 的 SSE(Server-Sent Events)流式响应
  • 核心挑战:如何解析 data: {...}\n\n 格式的流式 chunk,并实时更新 DOM?

💡 代码实现详解

1. 响应式数据定义

const question = ref('讲一个喜羊羊与灰太狼的故事,20字');
const stream = ref(true); // 是否启用流式
const content = ref("");  // AI 回复内容

使用 ref 创建响应式变量,Vue 会自动追踪依赖并在 content.value 变化时更新视图。


2. 调用 DeepSeek API(流式模式)

const askLLM = async () => {
  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 }]
    })
  });

🔐 安全提示:API Key 务必通过 VITE_* 环境变量注入,避免硬编码泄露。


3. 流式响应解析(核心逻辑)

stream: true 时,API 返回的是 二进制流(ReadableStream) ,需手动读取并解析:

const reader = response.body?.getReader();
const decoder = new TextDecoder(); // 将 Uint8Array 转为字符串
let buffer = ''; // 用于拼接不完整的 chunk

while (!done) {
  const { value, done: doneReading } = await reader.read();
  done = doneReading;
  
  // 解码当前 chunk 并拼接到 buffer
  const chunkValue = buffer + decoder.decode(value, { stream: !done });
  buffer = '';

  // 按行分割,只处理以 "data: " 开头的行
  const lines = chunkValue.split('\n').filter(line => line.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;
      if (delta) {
        content.value += delta; // 响应式更新!
      }
    } catch (err) {
      // 若 JSON 解析失败(如 chunk 被截断),暂存回 buffer
      buffer += `data: ${jsonStr}`;
    }
  }
}

关键细节

  • 使用 TextDecoder({ stream: true }) 处理跨 chunk 的 UTF-8 字符(如 emoji)
  • buffer 缓存不完整的 JSON 行,避免解析错误
  • 仅提取 delta.content 并累加到 content.value,实现“打字机”效果

4. 非流式模式(兜底方案)

// 非流式:直接解析完整 JSON
const data = await response.json();
content.value = data.choices[0].message.content;

适合调试或低延迟场景。


🎨 UI 与交互设计

<template>
  <div class="container">
    <div>
      <label>输入:</label>
      <input v-model="question" />
      <button @click="askLLM">提交</button>
    </div>
    <div class="output">
      <label>Streaming</label>
      <input type="checkbox" v-model="stream" />
      <div>{{ content }}</div>
    </div>
  </div>
</template>
  • 支持动态切换流式/非流式模式
  • 输入框双向绑定 question
  • 输出区域实时渲染 content

🧠 深度思考:流式输出背后的技术哲学

  1. 用户体验 > 技术简洁性
    流式虽增加前端复杂度,但换来的是用户感知速度的飞跃——这是值得的工程权衡。
  2. 响应式编程的力量
    Vue 的 ref 让我们无需手动操作 DOM,只需关注“数据如何变化”,框架自动同步视图。
  3. Web Streams API 的潜力
    HTML5 的 ReadableStream 是处理大文件上传、实时日志、AI 流式输出的基石,值得深入掌握。

🚀 扩展建议

  • 添加 loading 动画、复制按钮、历史记录
  • 支持多轮对话(维护 messages 数组)
  • 错误处理(网络失败、API 限流)
  • 使用 AbortController 实现“停止生成”功能

✅ 结语

通过不到 100 行代码,我们就实现了一个具备生产级交互体验的 AI 前端 Demo。这不仅是技术实现,更是对“用户为中心”理念的践行。

好的产品,让用户感觉不到技术的存在,只感受到流畅与智能。