流式输出
Server-Sent Events(SSE)技术,让大模型回答逐字/词输出而非一次性返回,提升用户体验和系统响应性
简介
流式输出(Streaming Output)是指服务器将数据分块逐步推送给客户端,而非等待所有数据生成完毕后一次性返回。在大模型应用中,流式输出通常采用 SSE(Server-Sent Events)技术,让 AI 的回答像”打字机效果”一样逐字/词显示,而不是等待 10-30 秒后突然出现一大段文字。这种技术不仅提升用户体验(让等待过程更友好),还能降低首字延迟(TTFT, Time To First Token),让用户更早看到响应。
关键信息
| 项目 | 内容 |
|---|---|
| 技术标准 | SSE(Server-Sent Events) |
| 协议基础 | HTTP/1.1 长连接 |
| 数据格式 | text/event-stream |
| 典型应用 | ChatGPT、Claude、通义千问等大模型对话界面 |
| 相关技术 | WebSocket(双向通信)、Long Polling(长轮询) |
核心特性
SSE vs 一次性返回的对比
| 维度 | 一次性返回 | 流式输出(SSE) |
|---|---|---|
| 用户感知延迟 | 等待 10-30 秒,突然出现完整回答 | 0.5-1 秒后开始逐字显示 |
| 首字延迟 | 高(需等待全部生成) | 低(生成即推送) |
| 用户体验 | ”卡死了?" | "正在思考,有进度感” |
| 适用场景 | 短文本生成、批量处理 | 长文本对话、实时反馈 |
| 实现复杂度 | ⭐ | ⭐⭐⭐ |
SSE 技术原理
服务器端推送流程:
客户端发起请求 → 服务器建立长连接 →
逐步生成内容 → 每生成一小段就推送一次 →
生成结束后发送 "finished" 标记 → 关闭连接
HTTP 响应头:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive数据格式:
data: {"content": "AI", "status": "loading"}
data: {"content": "在零售", "status": "loading"}
data: {"content": "场景应用", "status": "loading"}
data: {"content": "完整回答", "status": "finished"}
每个 data: 块是一个独立的推送事件,客户端逐条接收并渲染。
流式输出的状态管理
在 RAG 知识库等大模型应用中,流式输出通常包含状态字段:
加载中:
{
"content": "当前生成的文本片段",
"status": "loading"
}生成结束:
{
"content": "完整回答",
"status": "finished",
"sources": ["文档1.pdf", "文档2.pdf"]
}客户端处理逻辑:
- 持续监听 SSE 事件流
- 每收到一个
status: loading的片段,追加到界面 - 收到
status: finished,停止监听,显示”来源文档”等元信息
不同素材中的观点
来自 2026-06-17-woshipm-ai-knowledge-base-product-design:
Yuxi API 的流式输出实现:
- Yuxi 提供标准 REST API,其中问答接口支持流式输出(SSE 风格)
- 使用 APIFOX 测试发现响应结构特点:大模型不是一次性返回所有结果,而是一个字或一个词逐步输出
- 每个响应是 JSON 片段:加载过程中
"status": "loading",程序需持续读取 - 当
"status": "finished"时,意味着大模型输出结束
产品端技术实现(C#.NET MVC):
- 后端调用 Yuxi API 完成问答,流式转发给前端
- 前端采用响应式设计(Bootstrap),流式输出渲染(逐字显示)
- 移动端优化:底部固定输入框,流式输出避免界面突然跳动
用户价值:
- 产品数据表明,70% 的用户在 30 秒内希望看到响应,流式输出让”等待”变成”进度感”
- 智能检索助理页面设计为类似 ChatGPT 的对话界面,流式输出让用户感知”AI 正在思考”
- 每个回答下方显示来源文件,点击可下载原文档——流式输出完成后才显示元信息,避免干扰阅读
技术选型的关键考量:
- 作者在对比 Yuxi、Dify、MaxKB 等方案时,明确提出”提供完整的 RESTful API(包括流式输出)“是必选项
- Yuxi 之所以胜出,部分原因是其流式输出接口设计合理,易于后端集成和前端渲染
产品哲学洞察:
“当你意识到一个人打开知识库的动机,往往不是’浏览’,而是’求救’——他正被某个具体问题困住,急需一个能听懂人话的副驾——你就会把所有傲慢的复杂设计砍掉,留下最简单的对话框。”
流式输出正是这种”简单对话框”体验的核心技术保障。
实用信息
前端实现示例(JavaScript)
使用原生 EventSource API:
const eventSource = new EventSource('/api/chat/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === 'loading') {
// 追加文本到界面
document.getElementById('answer').innerText += data.content;
} else if (data.status === 'finished') {
// 显示来源文档
document.getElementById('sources').innerText =
'来源:' + data.sources.join(', ');
eventSource.close(); // 关闭连接
}
};
eventSource.onerror = (error) => {
console.error('SSE 连接错误', error);
eventSource.close();
};使用 fetch + ReadableStream(更灵活):
const response = await fetch('/api/chat/stream', {
method: 'POST',
body: JSON.stringify({ question: '用户问题' })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.status === 'loading') {
appendText(data.content);
} else if (data.status === 'finished') {
showSources(data.sources);
}
}
}
}后端实现示例(Python FastAPI)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
app = FastAPI()
async def generate_stream(question: str):
# 调用大模型 API(如 OpenAI / 通义千问)
for chunk in llm_client.stream(question):
yield f"data: {json.dumps({'content': chunk, 'status': 'loading'})}\n\n"
# 发送结束标记
yield f"data: {json.dumps({'content': '完整回答', 'status': 'finished'})}\n\n"
@app.post("/api/chat/stream")
async def chat_stream(question: str):
return StreamingResponse(
generate_stream(question),
media_type="text/event-stream"
)适用场景
适合使用流式输出的场景:
- 大模型对话:ChatGPT、Claude、通义千问等长文本生成
- RAG 知识库问答:检索后生成的长回答
- 代码生成:AI 编程助手逐行输出代码
- 实时翻译:边输入边翻译显示
- 日志推送:构建日志、部署日志实时显示
不适合使用流式输出的场景:
- 短文本生成(如标题生成、关键词提取)
- 批量处理任务(如批量文档解析)
- 需要完整结果才能后续处理的场景(如结构化数据抽取)
常见问题与解决方案
问题 1:SSE 连接中断
- 原因:网络波动、代理超时、服务器重启
- 解决:客户端实现自动重连机制,记录已接收内容
问题 2:移动端流式输出界面跳动
- 原因:内容增加导致滚动条位置变化
- 解决:锁定滚动条到底部,或使用虚拟滚动
问题 3:流式输出速度控制
- 原因:网络带宽有限,推送过快导致积压
- 解决:服务端实现背压(backpressure)机制,根据客户端消费速度调节推送