善于觀察的朋友一定會敏銳地發(fā)現(xiàn)ChatGPT網(wǎng)頁端是逐句給出問題答案的,同樣评矩,ChatGPT后臺Api接口請求中更卒,如果將Stream參數(shù)設置為True后,Api接口也可以實現(xiàn)和ChatGPT網(wǎng)頁端一樣的流式返回稚照,進而更快地給到前端用戶反饋蹂空,同時也可以緩解連接超時的問題。
Server-sent events(SSE)是一種用于實現(xiàn)服務器到客戶端的單向通信的協(xié)議果录。使用SSE上枕,服務器可以向客戶端推送實時數(shù)據(jù),而無需客戶端發(fā)出請求弱恒。
SSE建立在HTTP協(xié)議上辨萍,使用基于文本的數(shù)據(jù)格式(通常是JSON)進行通信》档客戶端通過創(chuàng)建一個EventSource對象來與服務器建立連接锈玉,然后可以監(jiān)聽服務器發(fā)送的事件。服務器端可以隨時將事件推送給客戶端义起,客戶端通過監(jiān)聽事件來接收這些數(shù)據(jù)拉背。
ChatGPT的Server-sent events應用
首先打開ChatGPT網(wǎng)頁端,隨便問一個問題默终,然后進入網(wǎng)絡選單椅棺,清空歷史請求記錄后犁罩,進行網(wǎng)絡抓包監(jiān)聽:
[圖片上傳失敗...(image-348dd-1678231464780)]
可以看到,在觸發(fā)了回答按鈕之后两疚,頁面會往后端的backend-api/conversation對話接口發(fā)起請求床估,但這個接口的通信方式并非傳統(tǒng)的http接口或者Websocket持久化鏈接協(xié)議,而是基于EventSteam的事件流一段一段地返回ChatGPT后端模型的返回數(shù)據(jù)诱渤。
為什么ChatGPT會選擇這種方式和后端Server進行通信丐巫?ChatGPT網(wǎng)頁端使用Server-sent events通信是因為這種通信方式可以實現(xiàn)服務器向客戶端推送數(shù)據(jù),而無需客戶端不斷地向服務器發(fā)送請求勺美。這種推送模式可以提高應用程序的性能和響應速度递胧,減少了不必要的網(wǎng)絡流量。
與其他實時通信協(xié)議(如WebSocket)相比励烦,Server-sent events通信是一種輕量級協(xié)議谓着,易于實現(xiàn)和部署泼诱。此外坛掠,它也具有廣泛的瀏覽器兼容性,并且可以在不需要特殊網(wǎng)絡配置的情況下使用治筒。
在ChatGPT中屉栓,服務器會將新的聊天消息推送到網(wǎng)頁端,以便實時顯示新的聊天內(nèi)容耸袜。使用Server-sent events通信友多,可以輕松地實現(xiàn)這種實時更新功能,并確保網(wǎng)頁端與服務器之間的通信效率和穩(wěn)定性堤框。
[圖片上傳失敗...(image-4d1c6c-1678231464780)]
說白了域滥,降低成本,提高效率蜈抓,ChatGPT是一個基于深度學習的大型語言模型启绰,處理自然語言文本需要大量的計算資源和時間。因此沟使,返回響應的速度肯定比普通的讀數(shù)據(jù)庫要慢的多委可,Http接口顯然并不合適,因為Http是一次性返回腊嗡,等待時間過長着倾,而Websocket又過重,因為全雙工通信并不適合這種單項對話場景燕少,所謂單項對話場景卡者,就是對話雙方并不會并發(fā)對話,而是串行的一問一答邏輯客们,同時持久化鏈接也會占用服務器資源虎眨,要知道ChatGPT幾乎可以算是日均活躍用戶數(shù)全球最高的Web應用了蟋软。
效率層面,大型語言模型沒辦法一下子返回所有計算數(shù)據(jù)嗽桩,但是可以通過Server-sent events將前面計算出的數(shù)據(jù)先“推送”到前端岳守,這樣用戶也不會因為等待時間過長而關閉頁面,所以ChatGPT的前端觀感就是像打字機一樣碌冶,一段一段的返回答案湿痢,這種“邊計算邊返回”的生成器模式也提高了ChatGPT的回答效率。
Python3.10實現(xiàn)Server-sent events應用
這里我們使用基于Python3.10的Tornado異步非阻塞框架來實現(xiàn)Server-sent events通信扑庞。
首先安裝Tornado框架
pip3 install tornado==6.1
隨后編寫sse_server.py:
import tornado.ioloop
import tornado.web
push_flag = True
from asyncio import sleep
class ServerSentEvent(tornado.web.RequestHandler):
def __init__(self, *args, **kwargs):
super(ServerSentEvent, self).__init__(*args, **kwargs)
self.set_header('Content-Type', 'text/event-stream')
self.set_header('Access-Control-Allow-Origin', "*")
self.set_header("Access-Control-Allow-Headers","*")
# 請求方式
self.set_header("Access-Control-Allow-Methods","*")
# 斷開連接
def on_finish(self):
print("斷開連接")
return super().on_finish()
async def get(self):
print("建立鏈接")
while True:
if push_flag:
print("開始")
self.write("event: message\n");
self.write("data:" + "push data" + "\n\n");
self.flush()
await sleep(2)
建立好推送路由類ServerSentEvent譬重,它繼承Tornado內(nèi)置的視圖類tornado.web.RequestHandler,首先利用super方法調(diào)用父類的初始化方法罐氨,設置跨域臀规,如果不使用super,會將父類同名方法重寫栅隐,隨后建立異步的get方法用來鏈接和推送消息塔嬉,這里使用Python原生異步的寫法,每隔兩秒往前端推送一個事件message,內(nèi)容為push data租悄。
注意谨究,這里只是簡單的推送演示,真實場景下如果涉及IO操作泣棋,比如數(shù)據(jù)庫讀寫或者網(wǎng)絡請求之類胶哲,還需要單獨封裝異步方法。
另外這里假定前端onmessage處理程序的事件名稱為message潭辈。如果想使用其他事件名稱鸯屿,可以使用前端addEventListener來訂閱事件,最后消息后必須以兩個換行為結(jié)尾把敢。
隨后編寫路由和服務實例:
def make_app():
return tornado.web.Application([
(r"/sse/data/", ServerSentEvent),
])
if __name__ == "__main__":
app = make_app()
app.listen(8000)
print("sse服務啟動")
tornado.ioloop.IOLoop.current().start()
隨后在后臺運行命令:
python3 sse_server.py
程序返回:
PS C:\Users\liuyue\www\videosite> python .\sse_server.py
sse服務啟動
至此寄摆,基于Tornado的Server-sent events服務就搭建好了。
前端Vue.js3鏈接Server-sent events服務
客戶端我們使用目前最流行的Vue.js3框架:
sse_init:function(){
var push_data = new EventSource("http://localhost:8000/sse/data/")
push_data.onopen = function (event) {
// open事件
console.log("EventSource連接成功");
};
push_data.onmessage = function (event) {
try {
console.log(event);
} catch (error) {
console.log('EventSource結(jié)束消息異常', error);
}
};
push_data.onerror = function (error) {
console.log('EventSource連接異常', error);
};
}
這里在前端的初始化方法內(nèi)建立EventSource實例技竟,通過onmessage方法來監(jiān)聽后端的主動推送:
[圖片上傳失敗...(image-8fc5af-1678231464780)]
可以看到冰肴,每隔兩秒鐘就可以訂閱到后端的message事件推送的消息,同時榔组,SSE默認支持斷線重連熙尉,而全雙工的WebSocket協(xié)議則需要自己在前端實現(xiàn),高下立判搓扯。
結(jié)語
不僅僅可以實現(xiàn)ChatGPT的流式返回功能检痰,SSE在Web應用程序中的使用場景非常廣泛,例如實時的新聞推送锨推、實時股票報價铅歼、在線游戲等等公壤,比起輪詢和長輪詢,SSE更加高效椎椰,因為只有在有新數(shù)據(jù)到達時才會發(fā)送厦幅;同時SSE支持自定義事件和數(shù)據(jù),具有更高的靈活性和復用性慨飘,為流式數(shù)據(jù)返回保駕護航确憨,ChatGPT的最愛,誰不愛瓤的?最后奉上項目地址休弃,與眾鄉(xiāng)親同饗:github.com/zcxey2911/sse_tornado6_vuejs3