为什么大模型回答总是一个字一个字蹦出来?聊聊 SSE 流式输出与 BFF 层的那些事

你有没有好奇过,ChatGPT、DeepSeek 这些大模型的回答,为什么不是一次性返回,而是一个字一个字地"蹦"出来?这背后藏着前端、后端、甚至一个"中间层"的精妙协作。今天我们就来拆解它。

一、先抛问题:流式输出到底难在哪?

如果你写过一个普通的接口请求,大概是这样:

js 复制代码
fetch('/api/data').then(res => res.json()).then(data => console.log(data))

一次性返回,简单粗暴。但大模型的回答动辄几百上千字,等它全部生成完再返回,用户早就跑了。

于是有了流式输出(Streaming):模型生成一个 token,就推一个 token 给前端,边生成边显示。

可问题来了------前端处理流式输出并不简单:

  • 二进制流对象(ReadableStream)要解码
  • data: 字段要解析
  • 各种异常、断流要处理

前端业务已经够复杂了,能不能把这些脏活累活剥离出去?

二、BFF 层:前端的"私人助理"

2.1 什么是 BFF?

复制代码
前端(Vue/React)  ------>  BFF(Node)  ------>  Backend(Java/Go)

BFF = Backend For Frontend,为前端服务的后端。

它不是纯后端,也不是纯前端,而是夹在中间的一层 Node 服务,专门替前端"挡子弹":

  • 接口改字段?BFF 改一下就行,不用等后端排期
  • 流式解码复杂?BFF 处理完,吐给前端干净的数据
  • API Key 不想暴露?BFF 帮你藏起来

2.2 为什么 BFF 特别适合"大前端工程师"?

传统分工里,前端写 Vue/React,后端写 Java/Go。但现实是------前端有太多细碎的需求(改个字段、聚合几个接口、做个流式解析),等后端排期太慢。

Node 的出现让前端工程师能自己写轻量后端:一个 server.mjs 文件就是一个服务。于是"大前端"的概念诞生了------前端 + BFF,一个人全包

三、Node 框架开发:三行代码起一个服务

BFF 层用 Node 怎么写?最经典的就是 Express:

js 复制代码
import express from 'express';

const app = express();        // 实例化一个 server app
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');   // 一次性发送
});

app.listen(port, () => {
  console.log(`服务器在${port}端口启动了`);
});

三步走:引入框架 → 实例化 app → 监听端口。就这么简单,一个 HTTP 服务就起来了。

四、流式输出的 BFF 封装

前端不想碰 ReadableStream 的解码细节?那就让 BFF 来。前端只需要:

js 复制代码
fetch('/api/stream?prompt=hello')
  .then(res => res.json())
  .then(data => console.log(data));

干净、清爽。复杂的流式逻辑全在 BFF 层的 /stream 路由里:

js 复制代码
app.get('/stream', async (req, res) => {
  const { prompt } = req.query;
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.VITE_DEEPSEEK_API_KEY}`
    },
    body: JSON.stringify({
      model: 'deepseek-v4-flash',
      stream: true,
      messages: [{ role: 'user', content: prompt }]
    })
  });
  // response.body 是一个 ReadableStream
  // 在 BFF 层解码、解析 data: 字段、再吐给前端
});

为什么要放在 BFF 层做?

原因 说明
降低前端复杂度 二进制流解码、data: 解析全在 BFF 完成,前端只管拿干净数据
API Key 安全 纯前端放 key,右键看源码就泄露;放 BFF,前端永远看不到
职责清晰 前端专注 UI,BFF 专注数据加工,各司其职

五、跨域问题:同源策略挡了路

BFF 写好了,前端 fetch('/api/stream') 一请求------跨域报错

5.1 什么是跨域?

只要域名、端口、协议 有一个不同,浏览器就会触发同源策略保护机制,拦截请求。

  • 前端 Vite:http://localhost:5173
  • BFF Express:http://localhost:3000

端口不同 → 跨域。

5.2 Vite Proxy:让请求"假装"同源

既然跨域是浏览器机制,那让请求不出浏览器 不就行了?Vite 的 proxy 配置就是这个思路:

js 复制代码
// vite.config.js
export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',  // 转发到 BFF
        secure: false,
        rewrite: path => path.replace(/^\/api/, '')  // /api/stream → /stream
      }
    }
  }
});

请求流程变成这样:

bash 复制代码
前端 fetch('/api/stream')
       ↓
Vite 拦截(同源,5173,不跨域)
       ↓
proxy 去掉 /api 前缀,转发到 :3000/stream
       ↓
BFF 的 /stream 路由处理

浏览器看到的始终是 5173,同源,不触发跨域保护。 Vite 在背后悄悄帮你把请求转发到了 3000 端口。

六、完整请求链路一览

javascript 复制代码
┌──────────┐   fetch('/api/stream')   ┌──────────┐   fetch + stream   ┌──────────┐
│  Vue 前端 │ ──────────────────────▶ │ Vite Proxy│ ─────────────────▶ │   BFF    │ ────────▶ DeepSeek
│ :5173    │   (同源,不跨域)         │ (5173 内) │  (:3000/stream)    │ Express  │
└──────────┘                          └──────────┘                    └──────────┘
       ▲                                                                    │
       │              BFF 解码 ReadableStream,吐干净 JSON                     │
       └──────────────────────────────────────────────────────────────────────┘

三层职责清晰:

  1. 前端:只管发请求、渲染 UI
  2. Vite Proxy:解决跨域,转发请求
  3. BFF:藏 Key、调 LLM、解码流、返回干净数据

七、关键概念速查表

概念 一句话理解
SSE Server Sent Event,服务器单向推送事件,流式输出的底层机制
BFF Backend For Frontend,替前端挡子弹的 Node 中间层
流式输出 边生成边返回,用户不用等全部生成完
同源策略 浏览器安全机制,域名/端口/协议任一不同即拦截
Vite Proxy 让请求"假装同源",在背后转发到真实后端
ReadableStream 二进制流对象,需要解码后才能读取

八、总结

大模型的流式输出,表面上是"一个字一个字蹦",背后却是一条精心设计的链路:

  • 流式输出解决"用户等待"问题
  • BFF 层解决"前端复杂度"问题
  • Vite Proxy解决"跨域"问题
  • 环境变量解决"API Key 安全"问题

每一层都在替下一层挡麻烦,这就是工程化的魅力。

相关推荐
CIO_Alliance1 小时前
AI微调系列(1)| 全量微调、LoRA、QLoRA 三种方案怎么选
前端·人工智能·神经网络·机器学习·embedding·企业ai转型
hanbo17C21 小时前
企业官网能不能看出公司实力?选建站公司前,先看这6条硬标准
运维·服务器·前端
桃西西呀1 小时前
苹果刚发布 iPhone Duo,可 3104 款手机参数一聚类,参数分了档,价格却不匹配
人工智能·机器学习·llm
zzzll11111 小时前
LLM 学习第 24 课:Agent Harness
前端·人工智能·学习
Bmob后端云1 小时前
Bmob后端云实战|Python实现SSE流式输出,给备忘录AI加上打字机效果
前端·github
nunumaymax1 小时前
【第九章-React Router 6】
前端·react.js
少冰1 小时前
前端埋点如何落地:从事件设计到上线验收
前端·数据分析·数据可视化
toooooop81 小时前
thinkphp查询数据表最后的自增id
前端·javascript·数据库
晚安日记wanna1 小时前
简历写精通浏览器原理一问 URL 到渲染直接哑火...
前端·面试