一、 为什么需要流式输出?从“等待”到“边生成边看”
在与AI大模型,如MiMo交互时,传统的一次性返回全部答案(non-streaming)的模式有一个明显的用户体验痛点:用户必须等待整个响应生成完毕才能看到第一个字。如果响应较长,这种等待会感觉非常漫长,仿佛程序卡住了。流式输出的核心思想就是打破这种等待,它指的是模型在生成答案的过程中,就将已生成的“片段”(chunk)实时地、连续地发送给客户端。
这样做的好处显而易见:即时反馈。用户几乎可以在提问后瞬间就开始阅读回答,极大地提升了交互的流畅感和效率。这就像阅读一个正在实时书写的文档,而不是等待作者写完一整本书再寄给你。对于聊天机器人、代码生成、长文本总结等场景,流式输出几乎是提升用户体验的标配。
二、 SSE 协议:实现流式输出的理想“信使”
那么,如何将服务端(如MiMo)源源不断生成的“片段”可靠地推送到浏览器或其他客户端呢?这正是 Server-Sent Events(SSE) 协议的用武之地。SSE是一种基于HTTP的轻量级协议,它允许服务器向客户端单向、持续地推送事件流。
为什么SSE特别适合大模型流式输出?
- 基于HTTP:无需特殊协议,能轻松穿越防火墙和代理,与现有的Web基础设施兼容。
- 简单轻量:相比WebSocket的双向复杂通信,SSE是服务器到客户端的单向流,协议头简单,客户端处理容易。
- 自动重连与事件ID:SSE协议内建了断线重连机制,客户端(如浏览器
EventSourceAPI)会自动尝试重新连接,适合长时间运行的流。 - 文本友好:SSE传输的是文本格式的事件,与大模型输出的文本数据天然契合。
三、 动手实践:在客户端接收MiMo的SSE流
了解了原理,我们来看看实际代码如何与SSE流交互。假设MiMo的API端点为 /v1/chat/completions,并支持 stream: true 参数。
使用Python (requests库) 的示例:
import requests
import json
url = "https://api.example.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"
}
data = {
"model": "mimo",
"messages": [{"role": "user", "content": "用Python写一个快速排序"}],
"stream": True # 关键:启用流式
}
with requests.post(url, json=data, headers=headers, stream=True) as response:
for line in response.iter_lines():
if line:
decoded_line = line.decode('utf-8')
# SSE 格式:每行以 `data: ` 开头,最后有 `[DONE]` 表示结束
if decoded_line.startswith('data: '):
json_str = decoded_line[6:] # 去掉 'data: ' 前缀
if json_str.strip() == '[DONE]':
break
try:
chunk = json.loads(json_str)
# 从 chunk 中提取 token 并打印
token = chunk['choices'][0]['delta'].get('content', '')
print(token, end='', flush=True)
except json.JSONDecodeError:
pass
在浏览器中使用 JavaScript 的 0 :
const eventSource = new EventSource('/api/mimo/stream?prompt=...');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
const token = data.choices[0]?.delta?.content || '';
// 将 token 追加到页面元素中
document.getElementById('output').innerText += token;
};
eventSource.onerror = function() {
console.log("SSE connection error.");
eventSource.close();
};
四、 协议格式深潜:data: \n\n 的奥秘
从上面的代码可以看到,SSE的数据流格式非常规整。一个典型的SSE事件看起来像这样:
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"你"}, "index":0}]}\n\n
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"好"}, "index":0}]}\n\n
...
data: [DONE]\n\n
关键点解析: