如何用Server-Sent Events实现AI推理结果的流式实时展示?

来源:网站主作者:清原小日向头衔:网络博主
导读:本期聚焦于清原小日向创作的《如何用Server-Sent Events实现AI推理结果的流式实时展示?》,敬请观看详情。大模型生成答案并不是瞬间完成的,推理过程会按token逐步产出。如果仍然采用传统请求-响应模式,用户必须等所有token计算完毕才能看到内容,体验和实时性都很差。Server-Sent Events(SSE)提供了一条轻量的单向推送通道:服务端在响应中声明 text/event-stream,然后持续写入 data 事件,浏览器通过 EventSource 接口即可监听。相比WebSocket,SSE走普通HTTP协议,实现简单、自动重连、无需额外握手,非常适合AI推理结果这类从服务端到客户端的单向流。本文从协议机制入手,给出后端流式生成与前端接收的完整实现,并讨论Nginx缓冲、断线恢复和错误处理等生产细节,帮助你把AI输出稳定地实时展示到页面上。

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

如何用Server-Sent Events实现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

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0919/59309.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。