一、从“一问一答”到“涓涓细流”:理解流式输出的核心价值
在与大模型交互的传统模式中,我们发送一个请求,服务器处理完整个问题后,才一次性返回全部结果。这就像你去餐厅点餐,必须等所有菜都做完,服务员才会端上来。如果模型生成一个长答案,用户就需要盯着屏幕等待很长时间,体验非常糟糕。流式输出 的核心思想就是将这个过程变得像“上菜”一样:做好一道,就端上一道。模型每生成一个或几个词(token),就立即推送给客户端,客户端随即显示,从而实现“边生成边显示”的流畅效果。
这种模式的优势非常明显:
- 提升用户体验:用户能立刻看到反馈,极大缓解等待焦虑,感觉模型“正在思考”。
- 增强交互性:对于需要生成长文本或代码的场景,用户可以随时看到进展,并可能提前中断不理想的生成。
- 节省资源:客户端可以更早地开始处理或渲染内容,而不是在最后才接收一整块大数据。
二、SSE协议:实现服务器“单向”实时推送的利器
那么,服务器如何“主动”、“持续”地向浏览器推送数据呢?HTTP协议本身是请求-响应模式的。虽然有轮询(Polling)和长轮询(Long Polling)等变通方案,但它们效率低下。SSE(Server-Sent Events,服务器发送事件) 协议正是为此而生的一种轻量级、标准化的解决方案。
SSE基于标准的HTTP协议,利用一个长期保持打开的HTTP连接,由服务器向客户端单向地、持续地推送文本事件流。它的特点是:
- 协议简单:客户端使用浏览器原生支持的
EventSourceAPI 即可连接。 - 自动重连:连接意外断开后,浏览器能自动尝试重新连接。
- 轻量高效:数据格式是简单的纯文本,由特定的事件流格式(
data:,event:,id:,retry:)定义,开销小。
提示:SSE 与 WebSocket 常被比较。SSE是“服务器->客户端”的单向通道,基于HTTP,实现简单;WebSocket是全双工双向通道,功能更强大但复杂度更高。对于大模型流式输出这种“服务器说,客户端听”的场景,SSE是更合适的选择。
三、MiMo 流式架构:服务端如何“编织”事件流
MiMo 的流式输出正是构建在 SSE 协议之上。当用户发起一个流式请求时,MiMo 服务端会执行以下关键步骤:
- 接收请求并初始化:服务端收到带有
stream: true(或类似参数)的API请求,启动模型推理。 - 建立长连接:服务端向客户端返回一个
Content-Type: text/event-stream的HTTP响应头,表明这是一个SSE通道,连接保持打开。 - 循环生成与推送:模型生成器开始逐token生成文本。每生成一个token(或一小批),服务端就立即将其封装成SSE事件格式,并通过长连接推送出去。
- 结束信号:当模型生成完毕或遇到停止词时,服务端会推送一个特殊的
data: [DONE]事件,告知客户端本次输出结束,随后关闭连接。
整个流程的关键在于服务端必须异步地处理生成过程,并能将微小的生成单元(token)即时地、低延迟地转化为网络数据包发送出去。
四、客户端实践:使用 Python 监听流式响应
理解了原理,我们来看看客户端如何接收并处理这些流式数据。下面是一个使用 Python requests 库(需设置 stream=True)和 httpx 库的简化示例,模拟了调用MiMo流式API的过程。
import httpx
import json
def stream_chat_with_mimo(prompt: str):
api_url = "https://api.mimo.example.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"
}
payload = {
"model": "MiMo-7B",
"messages": [{"role": "user", "content": prompt}],
"stream": True # 关键参数:开启流式输出
}
# 使用 httpx 同步客户端,开启流式模式
with httpx.Client(timeout=None) as client:
with client.stream("POST", api_url, json=payload, headers=headers) as response:
full_response = []
for line in response.iter_lines():
# SSE数据以 "data: " 前缀开头
if line.startswith("data: "):
data_str = line[6:] # 跳过 "data: "
if data_str.strip() == "[DONE]":
break # 结束信号
try:
data = json.loads(data_str)
delta = data["choices"][0]["delta"]
content = delta.get("content", "")
if content:
print(content, end="", flush=True) # 实时打印
full_response.append(content)
except json.JSONDecodeError:
continue # 忽略非JSON行(如空行)
print("\n\n--- 生成结束 ---")
return "".join(full_response)
# 调用示例
stream_chat_with_mimo("用简单的语言解释量子力学是什么。")
这段代码的核心是 stream=True 和 response.iter_lines()。它逐行读取响应,解析SSE事件流,提取出模型生成的 content 并即时打印,实现了流式输出的客户端体验。
五、深入数据格式:SSE事件的“暗语”
客户端收到的并非杂乱的文本,而是遵循严格格式的SSE流。每一行都可能是以下几种类型之一,它们共同构成了服务器与客户端之间的“暗语”: