一、什么是流式输出与 SSE?

在传统的 HTTP 请求-响应模型中,客户端发送一个请求,服务器处理完毕后,将完整的响应体一次性返回给客户端。这在处理耗时任务,如大模型推理时,会造成明显的等待延迟。流式输出 则是一种不同的策略:服务器在数据生成过程中,就分批次、逐步地将结果发送给客户端,客户端可以立即开始处理和展示已收到的部分数据。

Server-Sent Events (SSE) 是一种基于标准 HTTP 协议的、允许服务器向客户端单向、实时推送数据的技术。它非常适合实现流式输出。SSE 连接保持打开状态,服务器可以持续发送由特定格式分隔的文本数据(称为“事件”),客户端通过内置的 EventSource API 来监听和处理这些事件。与 WebSocket 的双向通信相比,SSE 更简单、更轻量,且天然具备断线重连机制。

二、为什么 MiMo 需要流式输出?

对于像 MiMo 这样的大型语言模型,生成一段较长的文本回答可能需要数秒甚至更久。如果采用一次性输出,用户需要眼睁睁地看着空白的界面等待,体验非常糟糕。流式输出的核心价值在于极大提升了用户体验。

它模拟了人类“边思考边输出”的过程,实现了 “打字机效果” 。用户能够看到模型正在“思考”和生成内容,感觉响应更迅速、交互更自然,焦虑感显著降低。此外,在某些场景下,流式输出还允许客户端在接收到部分结果后就提前进行一些处理(如渲染 Markdown),进一步优化了整体的响应速度。

三、SSE 协议的核心要点

理解 SSE 的工作方式,我们需要知道它的几个关键特征:

提示:SSE 事件流的数据格式非常简单,但正是这种简洁性使其易于实现和调试。每个事件块就像一条短消息。

四、实现概览:如何连接与处理流

要从客户端发起一个 SSE 请求并处理流式数据,基本步骤如下:

  1. 发起请求:使用支持流式响应的 HTTP 客户端(如 JavaScript 的 fetch, Python 的 httpx 或 requests 的流模式)发起一个指向 SSE 端点的 GET 请求。
  2. 建立连接:服务器接受请求,保持连接,并开始发送事件流。
  3. 持续读取:客户端在一个循环中持续读取来自响应的文本块。
  4. 解析事件:根据 SSE 的格式规则,将文本块解析为一个个独立的事件,并提取 data 字段。
  5. 实时处理:对解析出的数据(即模型输出的片段)进行实时处理,例如更新 UI。

五、代码示例:Python 处理 MiMo 流式响应

下面是一个使用 Python httpx 库消费 SSE 流并打印 MiMo 输出的简化示例:

import httpx
import json

def stream_mimo_response(prompt: str):
    url = "https://api.example.com/mimo/chat/completions"
    headers = {"Accept": "text/event-stream"}
    payload = {
        "model": "MiMo",
        "messages": [{"role": "user", "content": prompt}],
        "stream": True  # 关键:启用流式输出
    }

    with httpx.Client(http2=True) as client:
        with client.stream("POST", url, json=payload, headers=headers) as response:
            response.raise_for_status()
            buffer = ""
            for chunk in response.iter_text():
                buffer += chunk
                # 按照SSE规范,用双换行分割事件
                while "\n\n" in buffer:
                    event_str, buffer = buffer.split("\n\n", 1)
                    for line in event_str.split("\n"):
                        if line.startswith("data: "):
                            data_str = line[len("data: "):]
                            if data_str.strip() == "[DONE]":
                                print("\n[流式输出结束]")
                                return
                            try:
                                data = json.loads(data_str)
                                # 提取并打印模型输出的文本片段
                                delta_content = data["choices"][0]["delta"].get("content", "")
                                if delta_content:
                                    print(delta_content, end="", flush=True)
                            except json.JSONDecodeError:
                                pass  # 忽略格式解析错误

# 调用示例
# stream_mimo_response("请用简单的语言解释量子计算")

这段代码演示了如何解析持续到达的文本块,拼接缓冲区,并根据 data: 前缀提取有效的 JSON 数据片段,最终实现逐字打印的效果。

六、实际应用中的关键考量

在生产环境中应用流式输出,需要考虑几个重要问题:

提示:在使用流式输出时,前端界面需要频繁更新。对于复杂内容(如代码块、表格),等流结束后一次性渲染比边流边渲染更稳定,可以避免布局抖动。

七、总结与对比

总而言之,流式输出是优化大模型交互体验的关键技术,而 SSE 协议是实现它的绝佳选择。它基于成熟的 HTTP 生态,实现简单,浏览器支持良好。与 WebSocket 相比,SSE 在不需要客户端向服务器频繁推送消息的场景下(如 AI 对话),显得更为轻量和专注。

对于开发者而言,掌握 SSE 消费端的编码技巧,理解其背后的协议细节,能够帮助我们构建出响应更迅速、交互更友好的 AI 应用。下次当你使用那些流畅的 AI 聊天产品时,背后很可能就是 SSE 在默默地工作。