一、 什么是流式输出(Streaming)?

在传统的 HTTP 请求-响应模型中,客户端发送一个请求,服务器处理完毕后,一次性将完整的响应体返回给客户端。这在网络延迟高或服务器处理复杂任务(如生成一篇长文章或进行一次大模型推理)时,会导致客户端长时间等待,用户体验很差。

流式输出 则打破了这一限制。它允许服务器将响应数据分成多个小块(chunk),逐步、连续地发送给客户端。客户端无需等待整个响应完成,就可以立即开始处理和渲染已收到的数据。这就像你看视频时是边缓冲边播放,而不是下载完整部电影才能开始看。对于聊天机器人、实时日志、股票行情等场景,流式输出提供了至关重要的实时感更好的用户体验

二、 SSE(Server-Sent Events)协议简介

SSE 是一种服务器到客户端的单向通信标准,它建立在普通的 HTTP 协议之上。其核心思想是:客户端通过一次 HTTP 请求与服务器建立一个持久的连接,之后服务器就可以通过这个连接,不断地向客户端“推送”事件(event)。

与 WebSocket 的双向通信不同,SSE 专注于服务器向客户端的单向数据流,这恰好与流式输出的需求完美匹配。它的实现简单、基于文本、并自带断线重连机制,是实现流式 API 响应的理想选择。当你看到 AI 聊天应用里的文字像打字机一样一个个蹦出来时,背后大概率就是 SSE 在工作。

提示:SSE 使用 text/event-stream 作为 MIME 类型,每个事件由 data: 字段和可选的 event:id: 字段组成,事件之间用空行分隔。

三、 为什么不用轮询或 WebSocket?

在 SSE 出现之前,实现类似功能通常依赖 长轮询(Long Polling)WebSocket。我们可以简单对比一下:

因此,对于 AI 模型生成流式响应这种明确且主要的服务器推送场景,SSE 是在简单性、兼容性和功能性上的最佳平衡点。

四、 用 Python 实现一个简单的 SSE 服务端(以 MiMo 为例)

让我们用 Python 的 FastAPI 框架来模拟一个类似 MiMo 的流式输出服务端。首先安装依赖:pip install fastapi uvicorn

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json
import time

app = FastAPI()

async def generate_mimo_response(question: str):
    """
    模拟 MiMo 大模型的流式文本生成。
    这里我们简单地逐字输出预设的回答。
    实际中,这可能是调用模型API的流式接口。
    """
    response_text = f"这是对问题“{question}”的流式回答。流式输出的核心在于将长文本分割成小块,实时传输给客户端。"
    for char in response_text:
        # 构造SSE格式的数据块
        # data 字段的值需要是JSON字符串,便于客户端解析
        chunk_data = {
            "choices": [{"delta": {"content": char}}]
        }
        yield f"data: {json.dumps(chunk_data)}\n\n"
        await asyncio.sleep(0.05)  # 模拟模型生成每个字的延迟

@app.get("/chat/stream")
async def stream_chat(question: str):
    return StreamingResponse(
        generate_mimo_response(question),
        media_type="text/event-stream"
    )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

代码解读

  1. generate_mimo_response 是一个异步生成器,它逐字产出数据。
  2. 每个数据块都遵循 data: <JSON>\n\n 的 SSE 格式。我们仿照了 OpenAI 的流式响应格式,使用一个 choices 数组,其中 delta 字段包含当前的文本片段 content
  3. StreamingResponse 告诉 FastAPI 我们将发送一个流式响应,媒体类型设置为 text/event-stream

五、 客户端如何接收与解析

在浏览器或 Node.js 环境中,我们可以使用原生的 EventSource API 或 fetch 的流式读取能力来消费 SSE 流。

// 使用原生 EventSource API
const eventSource = new EventSource('http://localhost:8000/chat/stream?question=什么是流式输出?');
let fullResponse = '';

eventSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    const content = data.choices[0].delta.content;
    fullResponse += content;
    console.log('实时收到片段:', content);
    console.log('当前完整回答:', fullResponse);
    // 这里可以实时更新UI,实现打字机效果
};

eventSource.onerror = function(error) {
    console.error('EventSource failed:', error);
    eventSource.close(); // 出错时关闭连接
};

// 或者使用 fetch API 的流式读取(兼容性更好)
fetch('http://localhost:8000/chat/stream?question=什么是流式输出?')
.then(response => {
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    
    function read() {
        return reader.read().then(({ done, value }) => {
            if (done) {
                console.log('流式接收完成');
                return;
            }
            const textChunk = decoder.decode(value, { stream: true });
            // 解析SSE格式的文本块
            textChunk.split('\n\n').forEach(line => {
                if (line.startsWith('data: ')) {
                    const data = JSON.parse(line.slice(6));
                    console.log('fetch 收到:', data.choices[0].delta.content);
                }
            });
            return read();
        });
    }
    return read();
});

六、 开发与调试中的关键点

在实际集成或调试基于 SSE 的流式功能时,有几个点需要特别注意:

七、 总结与思考

流式输出极大地提升了生成式 AI 应用的交互体验,而 SSE 为此提供了一种优雅、轻量的 HTTP 原生解决方案。从 MiMo 这类大模型的角度看,支持 SSE 意味着它的推理能力能够以更自然、更符合人类预期的方式展现出来。

理解 SSE 不仅是学习一个协议,更是理解实时 Web 通信的一个重要范式。它的设计哲学——简单、专注、基于现有标准——非常值得借鉴。下次当你看到文字在屏幕上流畅地“流淌”出来时,你便知道背后是 text/event-stream 在默默地传递着每一个字节。