讲透 SSE:从流式响应到 LangChain model.stream()

大模型回答问题时,如果等整段内容全部生成完再返回,用户会经历明显的等待。

因此聊天应用通常采用流式输出:

复制代码
模型生成一点
→ 返回一点
→ 页面显示一点

现在使用 LangChain,实现已经非常简单:

arduino 复制代码
const stream = await model.stream(prompt);

for await (const chunk of stream) {
  process.stdout.write(chunk.content);
}

但要真正理解这几行代码,还得知道底层发生了什么。


一、流式输出最底层是什么

以前直接使用 fetch 请求模型接口时,开启:

vbnet 复制代码
stream: true

不能再简单地:

csharp 复制代码
await response.json();

而是读取:

vbscript 复制代码
response.body

大概过程是:

ini 复制代码
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { value, done } = await reader.read();

  if (done) break;

  console.log(decoder.decode(value));
}

数据经历:

javascript 复制代码
HTTP Response
    ↓
ReadableStream
    ↓
Uint8Array
    ↓
TextDecoder
    ↓
字符串

这里要明确一点:

read() 每次得到的是一个数据块 chunk,不保证刚好是一条完整消息。

例如:

css 复制代码
data: {"content":"hello"}

可能被拆成:

css 复制代码
data: {"cont

和:

css 复制代码
ent":"hello"}

所以以前还需要 buffer 保存残缺内容,再和下一块数据拼起来。

这也是为什么自己处理模型原始流会比较麻烦。


二、SSE 到底是什么

SSE,全称:

arduino 复制代码
Server-Sent Events

它是一种基于 HTTP 的服务器单向推送机制。

最小 Node 服务:

javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/stream') {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive'
    });

    res.write('data: 你\n\n');

    setTimeout(() => {
      res.write('data: 好\n\n');
    }, 1000);

    setTimeout(() => {
      res.write('data: SSE\n\n');
      res.end();
    }, 2000);
  }
});

server.listen(3000);

SSE 最重要的是两个东西。

响应头:

vbnet 复制代码
Content-Type: text/event-stream

数据格式:

kotlin 复制代码
data: 你

data: 好

其中:

kotlin 复制代码
data:

表示事件的数据。

而:

复制代码
\n\n

表示一个事件结束。


三、浏览器如何接收 SSE

浏览器原生提供了:

复制代码
EventSource

使用方式:

ini 复制代码
const eventSource =
  new EventSource('/stream');

eventSource.onmessage = event => {
  console.log(event.data);
};

服务器:

lua 复制代码
res.write('data: hello\n\n');

浏览器最终得到:

ini 复制代码
event.data === 'hello';

所以完整过程是:

vbnet 复制代码
Browser
   ↓
GET /stream
   ↓
Node Server
   ↓
Content-Type: text/event-stream
   ↓
data: hello\n\n
   ↓
EventSource
   ↓
onmessage

注意,SSE 并不是服务器返回很多个 HTTP Response。

仍然是:

vbscript 复制代码
1 个 Request
+
1 个 Response

只不过这个 Response 没有立即结束,而是不断:

ini 复制代码
res.write();

向其中追加内容。


四、Stream 和 SSE 不是一回事

Node 中读取文件也可以:

scss 复制代码
fs.createReadStream('./index.html')
  .pipe(res);

这同样叫 Stream。

所以:

arduino 复制代码
Stream

是一个更大的概念:

数据可以边产生、边读取、边处理。

而 SSE 是:

建立在 HTTP 流式响应之上的一种事件通信格式。

可以这样记:

arduino 复制代码
Stream
→ 数据怎么持续流动

SSE
→ 流动的数据怎么组织成事件

因此:

arduino 复制代码
Stream ≠ SSE

但 SSE 会利用 Stream。


五、为什么后来又需要 BFF

浏览器直接调用大模型接口会出现几个问题:

vbnet 复制代码
API Key 暴露
跨域
不同模型接口格式不同
流式解析逻辑堆在前端

因此真实项目通常变成:

复制代码
Browser
   ↓
Node / BFF
   ↓
LLM

BFF 负责:

vbnet 复制代码
保存 API Key
请求大模型
处理跨域
统一模型接口
转发流式响应

本质还是:

markdown 复制代码
上游模型流
    ↓
Node
    ↓
下游浏览器流

六、现在为什么只需要 model.stream()

理解完底层,再回来看 LangChain:

arduino 复制代码
const stream =
  await model.stream('介绍一下莫扎特');

for await (const chunk of stream) {
  process.stdout.write(chunk.content);
}

以前自己处理的是:

javascript 复制代码
HTTP Response
↓
ReadableStream
↓
Uint8Array
↓
TextDecoder
↓
buffer
↓
SSE / JSON
↓
模型增量内容

现在 LangChain 给我们的已经是:

复制代码
AIMessageChunk

所以只需要:

复制代码
chunk.content

这里的 chunk 和以前网络层的 chunk 已经不是一个层级。

以前:

ini 复制代码
chunk = 字节数据块

现在:

ini 复制代码
chunk = AI 消息增量

LangChain 把不同模型供应商底层的流式协议进行了统一封装。


七、最终只需要记住三层

markdown 复制代码
HTTP Stream
    ↓
数据可以持续传输

SSE
    ↓
在 HTTP Stream 上组织事件

LangChain
    ↓
model.stream()
    ↓
AIMessageChunk

真正写业务时:

arduino 复制代码
const stream = await model.stream(prompt);

for await (const chunk of stream) {
  process.stdout.write(chunk.content);
}

就够了。

但理解 SSE 和 Stream 的意义在于:当以后遇到代理缓存、流式中断、BFF 转发等问题时,不会把 model.stream() 当成一个完全无法解释的黑盒。

相关推荐
品牌常新5 分钟前
安防巡检机器人推荐:园区仓储楼宇怎么选
人工智能
释厄62323 分钟前
多黑洞具现论·物理/生物/金融三域显化·虚拟宇宙黑洞·奇点=0 圆圈
开发语言·数据结构·人工智能·程序人生·安全威胁分析
具身AGI25 分钟前
大脑之外还要一套脊髓,物理AI 交互具身 的下一关
人工智能
haliu30 分钟前
【FHE】(十二):为什么我们把 OpenMP 换成了自研线程池
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理
瞬维AI37 分钟前
RPA与AI智能体的跨平台自动化执行架构:从任务编排到异常处理
人工智能·自动化·rpa
呆呆槑_Xiong1 小时前
2026年AI网文写作指南:灵蟹创作与主流AI工具全解析
人工智能
SJZR1 小时前
图解归一化
人工智能·机器学习
aneasystone本尊1 小时前
学习大模型推理的加速技术:量化、投机采样与 PD 分离
人工智能
IT_陈寒1 小时前
Vite热更新失效?你可能漏了这个配置项
前端·人工智能·后端
EatFan1 小时前
从 Prompt 到 Harness:AI Agent 的竞争层为什么转移到了“外骨骼“
人工智能·ai agent