Server-Sent Events (SSE) 流式输出教程
LLM 生成回复需要时间,尤其是生成长文时,首字延迟 (TTFT) 可能只有 0.5 秒,但生成完整内容可能需要 30 秒。
5分钟
2025-12-26
API开发集成Server
Server-Sent Events (SSE) 流式输出教程
当你和 ChatGPT 对话时,字是一个一个蹦出来的,而不是等了几秒钟后整段一起显示。这种“打字机效果”背后的技术就是 SSE (Server-Sent Events)。对于 AI 应用来说,流式输出是提升用户体验的必修课。
1. 为什么 AI 需要流式输出
LLM 生成回复需要时间,尤其是生成长文时,首字延迟 (TTFT) 可能只有 0.5 秒,但生成完整内容可能需要 30 秒。
- 非流式 (Blocking): 用户请求 -> (等待 30s 空白) -> 啪,显示全文。体验极差。
- 流式 (Streaming): 用户请求 -> (等待 0.5s) -> 显 -> 示 -> 内 -> 容... 用户感觉系统反应很快。
2. SSE 原理
SSE 是基于 HTTP 的一种单向通信机制。
- 客户端发送一个普通请求。
- 服务端保持连接打开,并将 Response Content-Type 设为
text/event-stream。 - 服务端不断发送数据块,每块以
data:开头,以\n\n结尾。
数据格式示例:
data: {"content": "你"}
data: {"content": "好"}
data: {"content": ","}
data: [DONE]
3. 实现指南 (Python/FastAPI)
大多数 AI API(OpenAI, Anthropic)都原样提供了 stream=True 参数。你的后端需要做的就是做一个“二传手”,把上游的流转发给前端。
3.1 后端代码 (FastAPI)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import OpenAI
import asyncio
app = FastAPI()
client = OpenAI()
async def generate_response_stream(prompt: str):
# 1. 调用上游 API,开启 stream=True
stream = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
# 2. 迭代生成器
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
# 3. 按照 SSE 标准格式 yield 数据
yield f"data: {content}\n\n"
# 4. 结束标记
yield "data: [DONE]\n\n"
@app.get("/chat")
async def chat(prompt: str):
return StreamingResponse(
generate_response_stream(prompt),
media_type="text/event-stream"
)
3.2 前端代码 (JavaScript)
前端可以使用原生的 EventSource API,或者 fetch API 来处理。
async function fetchStream() {
const response = await fetch('/chat?prompt=你好');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 解析数据
const text = decoder.decode(value);
// 注意:这里需要处理 TCP 粘包和 SSE 格式解析,实际生产中推荐使用 fetch-event-source 库
console.log(text);
// updateUI(text);
}
}
4. 常见坑点
- Nginx 缓冲: 如果你使用了 Nginx 反向代理,默认情况下它会缓存后端响应直到一定大小才发给客户端,这会破坏流式效果。需要关闭 buffering:
proxy_buffering off; - JSON 解析: 在前端解析时,要注意可能一次收到多条
data: ...,或者一条data被切分在两个数据包里。需要编写鲁棒的 parser。
最后更新:2025-12