1.流式输出概述
流式输出(Streaming Output) 是一种数据传输或处理模式,指在数据生成过程中逐步输出结果,而非等待整个处理完成后一次性返回。它广泛应用于实时数据处理、长文本生成、视频流媒体、日志监控等场景,能够显著提升交互体验和系统资源利用率。它允许服务器向客户端持续发送数据流,而无需客户端发出额外的请求。这种方式与传统的请求-响应模式不同,传统模式下客户端需要定期向服务器发送请求来获取更新的数据,而流式输出则可以实现服务器主动向客户端推送数据,从而实现实时更新。
1.1案例
例如gpt或者ai工具处理用户输入时,不是一次性生成一整个回答而是一字一句地生成,类似于前端JS实现的打字机效果,而流式传输区别与打字机效果在本质上就是不同的东西。基于流式传输的处理方式有利于大模型快速响应,在逐步细化回答的同时能够留给大模型一定的思考时间,使对话过程更加流畅和自然。
1.2流式传输与普通请求的区别
流式输出的典型应用场景包括实时消息推送、股票行情更新、实时通知等,任何需要服务器向客户端实时传输数据的场合都可以使用。而普通请求就比较简单,发送请求服务器响应即可。
1.3 典型场景
-
实时数据分析:如日志监控、股票行情推送。
-
长文本生成:如大语言模型(LLM)的逐步输出(如 ChatGPT 的逐字回复)。
-
视频/音频流媒体:如 YouTube、Spotify 的流式播放。
-
Web API 响应:如分页数据逐步返回、服务器推送(Server-Sent Events, SSE)。
-
大文件传输:如文件下载/上传的断点续传。
1.4 优势对比
| 场景 | 传统输出 | 流式输出 |
|---|---|---|
| 实时性 | 需等待全部数据完成 | 逐步输出,实时反馈 |
| 资源占用 | 可能占用大量内存 | 逐块处理,内存占用低 |
| 用户体验 | 延迟高,可能等待时间长 | 即时响应,交互更流畅 |
2.流式输出的理论基础
2.1 基本概念
-
流式传输:数据以连续的“块”(chunk)形式分批次传输,而非一次性传输全部内容。
-
渐进式处理:接收端在收到数据块后立即处理,无需等待完整数据。
-
低延迟:减少等待时间,提升实时性。
2.2. 技术特点
-
Server-Sent Events(SSE) SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
-
分块传输:数据被分割为多个小块,类似于超文本传输中的分块传输编码机制。
-
异步处理:通常结合异步编程模型(如事件循环、协程)。
-
资源高效:避免内存占用过高,适合处理超大文件或无线数据流。
3.SSE(Server-Sent Events)相关介绍
3.1本质
在通常的服务器向浏览器信息推送中,我们通常的思路是WebSocket,除了WebSocket之外还有一种方法叫做SSE。这种方法服务器会向客户端声明发送的是流信息,也就是说发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
支持特性:SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
3.2特点
-
SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
-
区别于WebSocket,WebSocket是全双工通道可双向通信,而SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载内容,比如我们在系统中的下载文件等都属于流式传输的内容。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
3.3SSE对比WebSocket的优势
-
SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
-
SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
-
SSE 默认支持断线重连,WebSocket 需要自己实现。
-
SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
-
SSE 支持自定义发送的消息类型。
4.流式传输在AI工具中具体表现形式(基于kimi)
4.1浏览器中的流式传输请求
在浏览器中我们发送的请求例如在ai工具中输入框点击发送至之后传给接口中的数据经过处理的,接口请求中会在请求头中添加流式传输请求的关键配置。
-
Content-Type: application/json,用于告诉服务器,客户端发送的请求体数据是以何种格式编码的。当设置为 application/json 时,表示请求体中的数据是以 JSON 格式编码的。
-
Authorization: Bearer <API Key>,服务器接收到请求后,会解析 Authorization 头中的令牌,并验证其有效性(例如,检查令牌是否过期、是否被篡改、是否属于合法用户等)。如果令牌有效,服务器会处理请求;如果无效,服务器会返回错误。
-
Accept: text/event-stream,是 HTTP 请求头中的一个字段,用于告诉服务器客户端希望接收的数据格式。当设置为 text/event-stream 时,它表明客户端希望服务器以 Server-Sent Events (SSE) 的形式发送数据。
4.2在Apifox中自定义请求
4.2.1.请求接口:http://api.moonshot.cn/v1/chat/completions
4.2.2.请求headers配置
| 参数名 | 参数值 | 参数类型 |
|---|---|---|
| Authorization | api key | string |
| Accept | text/event-stream | string |
| Content-Type | application/json | string |
4.2.3.Authorization,本文是基于kimi接入,所以需要在kimi开放平台中注册信息,平台地址(https://platform.moonshot.cn/console/api-keys)
4.2.4.请求体配置
{
"model": "moonshot-v1-8k",
"messages": [
{
"role": "user",
"content": "介绍一下什么是流式传输"
}
],
"temperature": 0.3,
"stream": true
}
| 参数 | 声明 |
|---|---|
| model | 大模型版本 |
| messages | 输入 role: 表示角色以用户角色请求 content:问题 |
| temperature | 这个参数在大模型中是一个重要参数,用来控制生成内容的随机性与多样性,可以配置不同的参数,拿kimi来举例: temperature = 0:生成最确定性的结果。 temperature = 1:生成最随机的结果。 0 < temperature < 1:平衡稳定性和多样性。 |
| stream | 表明接收流式传输的形式响应,如果设置为false那返回的就是普通的请求形式。 false: true: |
5.实现一个流式输出的案例
代码实现
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI流式输出演示</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#output {
min-height: 100px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
margin: 20px 0;
white-space: pre-wrap;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.button-group {
margin-bottom: 20px;
}
.button-group button {
margin-right: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>流式输出演示</h1>
<div class="button-group">
<button id="startBtn">开始生成</button>
<button id="pauseBtn" disabled>暂停</button>
</div>
<div id="output" class="output"></div>
</div>
<script src="script.js"></script>
</body>
</html>
document.addEventListener("DOMContentLoaded", () => {
const output = document.getElementById("output");
const startBtn = document.getElementById("startBtn");
const pauseBtn = document.getElementById("pauseBtn");
let eventSource = null;
let isPaused = false;
startBtn.addEventListener("click", () => {
startBtn.disabled = true;
pauseBtn.disabled = false;
output.innerHTML = "";
// 如果存在之前的连接,先关闭它
if (eventSource) {
eventSource.close();
}
// 创建新的 EventSource 连接
eventSource = new EventSource("http://localhost:3000/stream");
// 监听开始事件
eventSource.addEventListener("start", (event) => {
const data = JSON.parse(event.data);
output.innerHTML += `<p class="status">${data.status}</p>`;
});
// 监听消息事件
eventSource.addEventListener("message", (event) => {
if (!isPaused) {
const data = JSON.parse(event.data);
output.innerHTML += `<p>${data.content}</p>`;
output.scrollTop = output.scrollHeight;
}
});
// 监听结束事件
eventSource.addEventListener("end", (event) => {
const data = JSON.parse(event.data);
output.innerHTML += `<p class="status">${data.status}</p>`;
eventSource.close();
// 重置按钮状态
startBtn.disabled = false;
pauseBtn.disabled = true;
isPaused = false;
});
// 错误处理
eventSource.onerror = () => {
output.innerHTML += '<p class="error">连接错误</p>';
eventSource.close();
// 重置按钮状态
startBtn.disabled = false;
pauseBtn.disabled = true;
isPaused = false;
};
});
pauseBtn.addEventListener("click", () => {
isPaused = !isPaused;
const pauseBtn = document.getElementById("pauseBtn");
pauseBtn.textContent = isPaused ? "继续" : "暂停";
});
});
const express = require('express');
const cors = require('cors');
const mockResponse = require('./text.js');
const app = express();
const port = 3000;
app.use(cors());
app.use(express.static('.'));
app.get('/stream', (req, res) => {
// 设置SSE相关的响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*');
// 发送一个初始事件
res.write('event: start\n');
res.write('data: ' + JSON.stringify({ status: "开始生成响应..." }) + '\n\n');
let index = 0;
const interval = setInterval(() => {
if (index < mockResponse.messages.length) {
// 发送数据事件,将每个消息对象转换为JSON字符串
res.write('data: ' + JSON.stringify(mockResponse.messages[index]) + '\n\n');
index++;
} else {
// 发送结束事件
res.write('event: end\n');
res.write('data: ' + JSON.stringify({ status: "生成完成" }) + '\n\n');
clearInterval(interval);
res.end();
}
}, 60);
// 当客户端断开连接时清理
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
// 模拟AI响应的数据
const mockResponse = {
messages: [],
};
// 使用for循环生成100条消息
for (let i = 1; i <= 100; i++) {
mockResponse.messages.push({
role: "assistant",
content: `这是第${i}条消息,用于测试流式输出效果。`,
});
}
module.exports = mockResponse;
{
"name": "stream-output-demo",
"version": "1.0.0",
"description": "AI流式输出演示",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
}
}
6.总结
流式输出通过分块传输和渐进处理,显著提升了系统的实时性和资源利用率。在实现时需关注协议选择、错误处理和性能优化。结合具体场景(如 Web API、大数据处理),合理设计流式架构能有效解决传统输出模式的瓶颈问题。
7.参考文档
-
tdesign关于流式传输的介绍:tdesign.tencent.com/chat/sse#%E…
-
ChatGpt流式输出的分析:juejin.cn/post/734800…
-
月影大佬关于AI流式输出的应用案例:www.youtube.com/watch?v=Zh5…
-
前端智能体开发教程:time.geekbang.org/column/arti…