.NET 如何實現ChatGPT的Stream傳輸

2023-07-27 06:00:35

.NET 如何實現ChatGPT的Stream傳輸

ChatGPT是如何實現不適用websocket進行一個一個字返回到前端的?

下面我們會介紹一下EventSource

EventSource

EventSource 介面是 web 內容與伺服器傳送事件通訊的介面。

一個 EventSource 範例會對 HTTP 伺服器開啟一個持久化的連線,以 text/event-stream 格式傳送事件,此連線會一直保持開啟直到通過呼叫 EventSource.close() 關閉。

EventTarget <= EventSource

一旦連線開啟,來自伺服器端傳入的訊息會以事件的形式分發至你程式碼中。如果接收訊息中有一個 event 欄位,觸發的事件與 event 欄位的值相同。如果不存在 event 欄位,則將觸發通用的 message 事件。

WebSocket 不同的是,伺服器傳送事件是單向的。資料訊息只能從伺服器端到傳送到使用者端(如使用者的瀏覽器)。這使其成為不需要從使用者端往伺服器傳送訊息的情況下的最佳選擇。例如,對於處理如社交媒體狀態更新、訊息來源(news feed)或將資料傳遞到使用者端儲存機制(如 IndexedDBweb 儲存)之類的,EventSource 無疑是一個有效方案。

使用場景

  • ChatGPT的Stream式對話,可以一個字一個字相應,增加使用者體驗
  • 簡單的巨量資料量的資料進行推播到使用者端
  • 耗時並且持續化的資料傳輸

ASP.NET Core 實現

建立WebApi專案

Controllers中新建一個StreamController.cs檔案,並且提供一個IAsyncEnumerable<out T>的Demo

  • IAsyncEnumerable<out T>
    • 公開對指定型別的值提供非同步迭代的列舉元。

StreamController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[ApiController]
[Route("[controller]")]
public class StreamController : ControllerBase
{
    [HttpPost]
    public async IAsyncEnumerable<char> Test()
    {
        const string value = "這是一個完整的測試資料;為了測試IAsyncEnumerable<T>的使用";

        foreach (var v in value)
        {
            await Task.Delay(500);
            yield return v;
        }

        await Task.CompletedTask;
    }
}

上面案例的介面使用了IAsyncEnumerable<char>,作為返回值,將value字串一個一個字元返回到前端。

每次返回等待500,這是伺服器端的實現,下面寫使用者端的實現,使用者端也是用.NET

使用js實現呼叫

首先啟動api服務,然後在開啟的swagger的瀏覽器介面中開啟開發者工具使用F12開啟開發者工具

在控制檯中新增fetchAsStream方法用於呼叫IAsyncEnumerable<char>的介面服務,程式碼如下

async function fetchAsStream(url,data) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data),
    });
    if (!response.ok) {
      const reader = response.body?.getReader();
      const { done, value } = await reader.read();
      throw new Error(
        `Failed to fetch `
      );
    }
    if (!response.body) {
      throw new Error("ReadableStream not supported in this browser.");
    }

    const reader = response.body.getReader();
    return {
      [Symbol.asyncIterator]() {
        return {
          async next() {
            const { done, value } = await reader.read();
            if (done) {
              return { done: true, value: null };
            }
            return {
              done: false,
              value: new TextDecoder("utf-8").decode(value),
            };
          },
        };
      },
    };

  }

輸入完成按確認鍵會顯示一個undefined

然後下一步就呼叫這個方法,當執行下面這個程式碼會發現控制檯會一個一個字顯示內容。

var stream =  await fetchAsStream("http://localhost:5255/stream");

for await(var c of stream){
    console.log(c);
}

看效果控制檯的字在一個一個輸出,請注意不要使用axios,預設是不支援的。

結尾

來自token的分享

技術交流群:737776595