流式输出

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"]
}

客户端处理逻辑

  1. 持续监听 SSE 事件流
  2. 每收到一个 status: loading 的片段,追加到界面
  3. 收到 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)机制,根据客户端消费速度调节推送

相关页面