Server-Sent Events (SSE) :ChatGPT打印機式輸出
Server-Sent Events 提供了一種健壯、簡潔且高效的解決方案,用于實現(xiàn)從服務(wù)器到客戶端的實時、單向數(shù)據(jù)傳輸。其基于 HTTP 的基礎(chǔ)、內(nèi)置的重連機制以及易于實現(xiàn)的特點,使其非常適合用于廣播更新、通知以及流式傳輸大型語言模型 (LLM) 響應(yīng)等增量內(nèi)容。
1.實時Web通信簡介
2. 理解Server-Sent Events (SSE)
2.1. 關(guān)鍵特性
2.2. 常見場景
3. SSE協(xié)議的消息結(jié)構(gòu)
3.1 完整HTTP響應(yīng)示例
4. 使用SSE實現(xiàn)類似ChatGPT的打字機效果
4.2. 前端實現(xiàn)(HTML+JS)
4.1. 后端實現(xiàn)(FastAPI)
4.3. 運行方法
4.4. 效果圖
4.5. 抓包來往HTTP消息
4.6 SSE流式通信時序圖
5. 瀏覽器兼容性
6. SSE 與 WebSockets
在大語言模型(LLM)蓬勃發(fā)展的今天,我們經(jīng)??吹搅奶旖缑嬷蠰LM能夠像打字機一樣逐字輸出回復(fù)。這種流暢的交互體驗背后,離不開實時Web通信技術(shù)的支持。
1.實時Web通信簡介
歷史上,為了獲取新數(shù)據(jù),客戶端通常需要反復(fù)向服務(wù)器發(fā)送請求,這種方式被稱為輪詢。這種傳統(tǒng)方法效率低下,會產(chǎn)生大量的網(wǎng)絡(luò)流量,并引入不可避免的延遲。
為了解決這些問題,Server-Sent Events (SSE) 和 WebSocket 等推送技術(shù)應(yīng)運而生,它們允許服務(wù)器主動向客戶端發(fā)起數(shù)據(jù)傳輸,從而實現(xiàn)即時更新,無需客戶端持續(xù)請求。
2. 理解Server-Sent Events (SSE)
Server-Sent Events (SSE) 是一種服務(wù)器推送技術(shù),它允許服務(wù)器通過單個 HTTP 連接向客戶端發(fā)送數(shù)據(jù)更新。
SSE 定義了服務(wù)器如何在建立初始客戶端連接后,主動向客戶端發(fā)起數(shù)據(jù)傳輸。它通常用于向瀏覽器客戶端發(fā)送消息更新或連續(xù)的數(shù)據(jù)流。
2.1. 關(guān)鍵特性
- 單向性
SSE 的一個顯著特點是其單向通信模式:數(shù)據(jù)消息僅從服務(wù)器流向客戶端(例如,用戶的網(wǎng)絡(luò)瀏覽器)。
SSE 的單向性并非局限,而是一種設(shè)計選擇。通過限制通信方向,協(xié)議本身的復(fù)雜性得以降低,從而優(yōu)化了其在廣播場景中的性能。這意味著 SSE 完美地適用于客戶端主要接收更新的場景,避免了管理雙向協(xié)議中固有的開銷和復(fù)雜性。 - HTTP 協(xié)議基礎(chǔ)
SSE 完全基于標(biāo)準 HTTP 協(xié)議運行。它使用標(biāo)準的端口(80/443),并且通常無需特殊配置即可穿透代理和防火墻。SSE 依賴于長連接和分塊傳輸編碼(transfer-encoding: chunked),因此至少需要 HTTP/1.1 版本。 - 自動重連
SSE 的一個重要優(yōu)勢是其內(nèi)置的自動重連機制。如果連接意外中斷,客戶端會自動嘗試重新連接,確保數(shù)據(jù)流不中斷。在重新連接時,客戶端可以發(fā)送一個???Last-Event-ID?? 頭,允許服務(wù)器從上次已知的位置恢復(fù)數(shù)據(jù)流。 - 文本數(shù)據(jù)
SSE 只能發(fā)送基于文本的事件數(shù)據(jù),通常以 UTF-8 編碼的字符串形式。它不支持發(fā)送二進制數(shù)據(jù)。 
2.2. 常見場景
- 實時更新: 新聞推送、社交媒體狀態(tài)更新、實時體育賽事比分、股票價格波動 。
 - 監(jiān)控: 服務(wù)器或應(yīng)用程序監(jiān)控儀表板、物聯(lián)網(wǎng)設(shè)備數(shù)據(jù) 。
 - 通知: Web 應(yīng)用程序的實時通知、電子郵件通知、系統(tǒng)警報 。
 - AI 生成內(nèi)容流式傳輸: SSE 非常適合流式傳輸大型語言模型 (LLM) 的增量響應(yīng),以創(chuàng)建類似“打字機”的效果 。
 
3. SSE協(xié)議的消息結(jié)構(gòu)
SSE(Server-Sent Events)協(xié)議基于HTTP協(xié)議,服務(wù)端通過標(biāo)準的HTTP響應(yīng)持續(xù)推送事件。
3.1 完整HTTP響應(yīng)示例
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: 你好,世界
data: 這是第二條消息
event: custom
data: 自定義事件內(nèi)容
id: 42響應(yīng)頭:
- ?
?Content-Type: text/event-stream?? 必須,表明是SSE流。 - ?
?Cache-Control: no-cache?? 建議,防止中間緩存。 - ?
?Connection: keep-alive?? 建議,保持連接不斷開。 
消息體:
- 每條消息以?
?\n\n??結(jié)尾。 - ?
?data:??字段為主要內(nèi)容,可多行。 - 可選?
?event:???、??id:???、??retry:??等字段。 
字段名稱  | 描述  | 
data  | 事件的主要內(nèi)容。  | 
id  | 可選的事件標(biāo)識符,用于客戶端重連時恢復(fù)流。  | 
event  | 可選的事件類型字符串。如果指定,客戶端將分派命名事件。  | 
retry  | 可選的毫秒數(shù),指定連接丟失后的重連等待時間。  | 
4. 使用SSE實現(xiàn)類似ChatGPT的打字機效果
AI 并不是等待一段時間再一次性返回完整的答案,而是 邊生成邊顯示,逐步展示 AI 的思考過程。這種方式不僅減少了等待時間,而且讓 AI 的回答看起來更自然,仿佛它在思考時逐漸給出答案。這種交互體驗顯得更加生動、真實,用戶可以感覺到 AI “在實時作答”。
如果不使用流式輸出,模型需要等到所有文本生成完畢后才返回結(jié)果,用戶可能會感到延遲很長。而流式調(diào)用能夠逐步生成輸出,減少等待時間,提升交互體驗,類似于 ChatGPT 的打字效果。
普通 vs. 流式輸出
模式  | 特點  | 
普通輸出(非流式)  | 用戶提交問題 → 等待幾秒 → 一次性返回完整答案  | 
流式輸出(像 ChatGPT)  | 用戶提交問題 → AI 邊生成邊顯示 → 逐步輸出答案  | 
相關(guān)閱讀:???從0開始:用 Streamlit + LangChain 搭建個簡易ChatGPT??
SSE 非常適合這種增量輸出生成,因為它專為從服務(wù)器到客戶端的連續(xù)、單向數(shù)據(jù)流而設(shè)計。LLM 后端生成的每個“令牌”或小塊文本都可以作為獨立的 SSE ??data?? 字段發(fā)送。
服務(wù)器只需將新生成的內(nèi)容逐塊寫入 ??text/event-stream??? 連接,并在每個塊后加上 ??\n\n?? 分隔符 。這種“原生令牌流式傳輸”使用戶能夠自然地體驗 AI 交互,仿佛正在觀察 AI 思考的過程。
4.1. 前端實現(xiàn)(HTML+JS)
建立連接
要打開連接并開始從服務(wù)器接收事件,需要創(chuàng)建一個新的 EventSource 對象,并提供生成事件的服務(wù)器端的 URL 。
例如:
const evtSource = new EventSource("/sse");監(jiān)聽 ??message?? 或自定義 ??event?? 類型
- ?
?onmessage?? 處理程序: 
服務(wù)器如果沒有發(fā)送 ??event??? 字段,那么默認將作為 ??message??? 事件接收,并由 ??EventSource??? 對象的 ??onmessage??? 屬性處理 。數(shù)據(jù)可以通過 ??event.data?? 訪問 。
例如:
evtSource.onmessage = (e) => { console.log(e.data); };- ?
?addEventListener()?? 用于命名事件: 
服務(wù)器可以發(fā)送帶有 ??event??? 字段的消息,指定自定義事件類型??蛻舳丝梢允褂?nbsp;??EventSource.addEventListener()?? 監(jiān)聽這些命名事件。
例如:
sse.addEventListener("notice", (e) => { console.log(e.data); });實際代碼示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE 打字機 Demo</title>
    <style>
        body { font-family: Consolas, monospace; background: #f9f9f9; padding: 2em; }
        #output { font-size: 1.5em; background: #fff; padding: 1em; border-radius: 8px; min-height: 2em; }
    </style>
</head>
<body>
    <h2>SSE 打字機效果演示</h2>
    <div id="output"></div>
    <script>
        // 連接SSE接口
        const output = document.getElementById('output');
        const evtSource = new EventSource('/sse');
        evtSource.onmessage = function(event) {
            if (event.data === '[END]') {
                evtSource.close();
                return;
            }
            output.textContent += event.data;
        };
        evtSource.onerror = function() {
            output.textContent += '\n[連接已斷開]';
        };
    </script>
</body>
</html>- ?
?EventSource('/sse')?? 連接后端SSE接口,自動接收流式數(shù)據(jù)。 - 每收到一個字符就追加到頁面,實現(xiàn)打字機效果。
 - 收到?
?[END]??時關(guān)閉連接。 
4.2. 后端實現(xiàn)(FastAPI)
設(shè)置 Content-Type: text/event-stream 頭
負責(zé)發(fā)送事件的服務(wù)器端腳本必須以 MIME 類型 Content-Type: text/event-stream 響應(yīng) 。這會告知瀏覽器將傳入數(shù)據(jù)解釋為事件流。
實際代碼示例
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from fastapi.staticfiles import StaticFiles
import asyncio
app = FastAPI()
# 掛載靜態(tài)文件目錄
app.mount('/static', StaticFiles(directory='static'), name='static')
asyncdef gpt_stream(text: str, delay: float = 0.05):
    """
    模擬GPT打字機效果,逐字發(fā)送文本。
    :param text: 要發(fā)送的完整文本
    :param delay: 每個字符之間的延遲(秒)
    """
    for char in text:
        yieldf"data: {char}\n\n"
        await asyncio.sleep(delay)
    yield"data: [END]\n\n"
@app.get('/sse')
asyncdef sse():
    """
    SSE接口,流式返回文本內(nèi)容。
    """
    with open('novel.txt', 'r', encoding='utf-8') as f:
        text = f.read()
    return StreamingResponse(gpt_stream(text), media_type='text/event-stream')4.3. 運行方法
1.啟動服務(wù):
uvicorn main:app --reload2.在瀏覽器訪問:
http://127.0.0.1:8000/static/index.html即可看到SSE打字機效果。
4.4. 效果圖

4.5. 抓包來往HTTP消息
- 每個?
?data:??字段后跟一個字符或一段文本,前端逐條接收。 - 結(jié)尾用?
?[END]??標(biāo)記流式輸出結(jié)束。 

4.6 SSE流式通信時序圖

5. 瀏覽器兼容性

6. SSE 與 WebSockets
特性  | Server-Sent Events (SSE)  | WebSockets  | 
通信方向  | 單向(服務(wù)器到客戶端)  | 雙向(客戶端與服務(wù)器)  | 
底層協(xié)議  | HTTP/HTTPS  | 自定義 WebSocket 協(xié)議 (ws://, wss://)  | 
支持數(shù)據(jù)格式  | 僅文本(UTF-8)  | 文本(UTF-8)和二進制  | 
實現(xiàn)復(fù)雜性  | 簡單  | 相對復(fù)雜  | 
防火墻/代理兼容性  | 良好(基于標(biāo)準 HTTP)  | 可能需要特定配置  | 
理想用例  | 實時新聞、股票報價、通知、儀表板、LLM流式響應(yīng)  | 聊天應(yīng)用、在線游戲、協(xié)作工具、實時交互式儀表板  | 
Server-Sent Events 提供了一種健壯、簡潔且高效的解決方案,用于實現(xiàn)從服務(wù)器到客戶端的實時、單向數(shù)據(jù)傳輸。其基于 HTTP 的基礎(chǔ)、內(nèi)置的重連機制以及易于實現(xiàn)的特點,使其非常適合用于廣播更新、通知以及流式傳輸大型語言模型 (LLM) 響應(yīng)等增量內(nèi)容。
本文轉(zhuǎn)載自??AI取經(jīng)路??,作者:AI取經(jīng)路


















