AI流式输出解决方案

608 阅读8分钟

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配置

image.png

参数名参数值参数类型
Authorizationapi keystring
Accepttext/event-streamstring
Content-Typeapplication/jsonstring

4.2.3.Authorization,本文是基于kimi接入,所以需要在kimi开放平台中注册信息,平台地址(https://platform.moonshot.cn/console/api-keys)

image.png

4.2.4.请求体配置

image.png

{
  "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:image.png
true:
image.png

5.实现一个流式输出的案例

代码实现

image.png

<!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.参考文档