调用大模型接口时,如果让用户盯着空白页面等待完整响应,卡顿感会非常明显。模型的推理结果其实是一个token接一个token生成的,这些增量内容完全可以边生成边展示。Server-Sent Events(SSE)正好提供了这样一条从服务端到客户端的单向实时通道:它基于普通HTTP协议,浏览器原生支持,实现成本低,非常适合AI对话、代码补全、日志跟踪等场景。

一、为什么SSE比轮询和WebSocket更合适
SSE是一种基于HTTP长连接的推送机制。客户端通过 EventSource 对象发起请求,服务端返回 Content-Type: text/event-stream 的响应,之后连接保持打开,数据按照规定的字段格式持续写入。一个最简单的事件流如下:
data: {"token": "你好"}
data: {"token": ","}
data: [DONE]
事件格式以 data: 开头,两个换行符表示一个事件结束。多行 data 会被合并成一个字段,还可以通过 event 和 id 字段自定义事件类型和消息编号。与传统轮询相比,SSE避免了客户端反复建立连接带来的额外开销;与WebSocket相比,SSE只处理服务端到客户端的单向数据,无需复杂的帧协议和心跳维护,也不用升级协议。对于AI推理这种服务端持续输出、客户端只负责接收的场景,SSE的复杂度明显更低。
另一个容易被忽略的优势是自动重连。浏览器内置的 EventSource 在网络断开后会按默认间隔重新发起请求,服务端也可以通过 id 字段配合 Last-Event-ID 请求头实现断点恢复。这对长时间运行的推理任务非常实用,能减少用户手动重试的次数。
二、服务端实现:把模型输出转换成SSE事件
后端的关键任务是持续读取模型推理结果,并把每个增量片段包装成符合SSE格式的字符串。这里以FastAPI和OpenAI兼容流式接口为例,使用 StreamingResponse 包装一个异步生成器。先安装依赖:pip install fastapi uvicorn openai。
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import openai
import json
app = FastAPI()
async def generate_ai_stream(prompt: str):
client = openai.AsyncOpenAI()
stream = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
stream=True
)
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
event_data = json.dumps({"token": delta}, ensure_ascii=False)
yield f"data: {event_data}\n\n"
yield "data: [DONE]\n\n"
@app.get("/api/chat/stream")
async def chat_stream(prompt: str):
return StreamingResponse(
generate_ai_stream(prompt),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive"
}
)
StreamingResponse 不会等待生成器全部执行完才返回响应,而是边生成边发送,这正是流式输出的核心。生成器中的每一次 yield 都会立即写入响应体。必须把 media_type 设置为 text/event-stream,浏览器才会按SSE协议解析。每个事件字符串要以两个换行符结尾,否则客户端可能无法正确切分事件。
这里还通过 json.dumps 处理token内容,避免token中出现换行、引号等字符破坏事件格式。如果你使用的是本地模型,例如Transformers或LlamaCpp,只需要把调用部分替换为相应的流式生成循环,事件包装逻辑保持不变。服务端生成器还需注意客户端断开的情况,可以在生成器内部捕获 asyncio.CancelledError 并释放模型资源,避免大量断连后占用显存或连接数。
三、前端EventSource接收与增量渲染
浏览器原生提供 EventSource 接口,只需要传入服务端URL。连接建立后,服务端写入的每个 data 事件都会触发 onmessage 回调,通过 event.data 可以拿到字符串内容。因为后端约定返回JSON,解析后把 token 追加到页面即可完成实时展示。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI流式输出演示</title>
<style>
#output {
white-space: pre-wrap;
font-family: monospace;
min-height: 200px;
border: 1px solid #ccc;
padding: 12px;
}
</style>
</head>
<body>
<h2>AI推理实时展示</h2>
<textarea id="prompt" rows="3" cols="60">请解释一下SSE的应用场景</textarea>
<button id="send">开始生成</button>
<div id="output"></div>
<script>
const output = document.getElementById('output');
const sendBtn = document.getElementById('send');
const promptInput = document.getElementById('prompt');
function startStream() {
output.textContent = '';
const prompt = promptInput.value.trim();
if (!prompt) return;
const url = '/api/chat/stream?prompt=' + encodeURIComponent(prompt);
const eventSource = new EventSource(url);
eventSource.onmessage = function(event) {
if (event.data === '[DONE]') {
eventSource.close();
return;
}
try {
const payload = JSON.parse(event.data);
output.textContent += payload.token;
} catch (e) {
// 忽略单条异常数据,保证后续事件继续处理
}
};
eventSource.onerror = function() {
console.warn('连接异常,等待自动重连');
};
}
sendBtn.addEventListener('click', startStream);
</script>
</body>
</html>
EventSource 在建立连接时会自动发送 Accept: text/event-stream 请求头,并处理网络断开后的自动重连。收到 [DONE] 事件后调用 close() 可以主动释放连接,避免浏览器一直挂起。如果服务端在事件中设置了 id 字段,重连时浏览器会自动携带 Last-Event-ID 请求头,服务端可以据此实现断点续传。
高频token直接操作DOM可能导致页面频繁重排,尤其是长文本输出时。可以在前端维护一个缓冲区,使用 requestAnimationFrame 按帧合并更新,或者直接操作 textContent 减少DOM节点创建。错误处理方面,onerror 只能知道连接出现异常,无法获取具体状态码,因此不建议在这里做终止逻辑,而应依赖自动重连,并在服务端设置合理的超时时间。
四、生产环境必须处理的缓冲与连接细节
很多反向代理默认会对HTTP响应进行缓冲,这会导致SSE事件被积压到一定大小后才一次性推给前端,完全失去流式效果。以Nginx为例,需要针对SSE路由关闭代理缓冲,并保持长连接配置:
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_buffering off;
proxy_cache off;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_read_timeout 3600s;
}
proxy_buffering off 让响应边生成边发送,proxy_cache off 防止流式内容被缓存,proxy_read_timeout 设置为较长时间可以避免模型推理较慢时被反向代理误判为连接超时。如果应用前面还有云负载均衡或CDN,建议在后端响应头中增加 X-Accel-Buffering: no,部分网关会识别该头并关闭自身缓冲。
长连接还需要考虑超时和心跳。服务端可以在没有实际数据时发送注释行 : keepalive,注释行不会触发前端事件,但能保持连接活跃。跨域场景下需要配置 Access-Control-Allow-Origin,并注意如果使用HTTP/1.1,同一域名对同一主机的并发连接数有限制,HTTP/2下会改善很多。最后,流式接口暴露公网时建议启用HTTPS,避免token内容在传输过程中被篡改或截获。
SSE让AI推理从等待完整结果变成了持续的视觉反馈,既降低了用户焦虑,也让系统更容易处理长任务。把后端生成器、前端 EventSource 和反向代理配置三部分配合起来,就能稳定实现AI输出的实时展示。
Server-Sent Events流式输出AI推理修改时间:2026-09-19 16:36:30