在本文中,我們將探討主流的大型語言模型(LLM)提供商如何實現(xiàn)其流式輸出的 HTTP API。我們將深入研究流式輸出的工作原理,探討其優(yōu)勢,并提供示例代碼以幫助您理解如何在實際應(yīng)用中使用流式輸出。


什么是流式輸出?

流式輸出(Streaming Output)是一種使后端將數(shù)據(jù)分塊、逐步發(fā)送到前端的技術(shù)。通過這種方法,前端應(yīng)用能夠即時接收和渲染數(shù)據(jù),不必等到整個響應(yīng)體生成完畢后再處理。

傳統(tǒng)的API 通常會一次性返回所有數(shù)據(jù),然后客戶端一次性接收。

流式輸出則允許服務(wù)器在生成數(shù)據(jù)的同時將其發(fā)送給客戶端,從而實現(xiàn)實時更新。

流式輸出通常用于以下幾種場景:

流式 API 的優(yōu)勢

流式 API 提供了即時響應(yīng)的體驗,允許用戶在內(nèi)容生成過程中即時查看部分結(jié)果。相比等待整個響應(yīng)完成,流式輸出極大提高了用戶體驗。適用于多種場景,例如:

流式輸出的實現(xiàn)方式

在具體實現(xiàn)流式輸出時,常用的技術(shù)包括:

本文主要講解SSE的實現(xiàn)。

SSE數(shù)據(jù)格式

Server-Sent Events(SSE)返回的數(shù)據(jù)格式是由一系列文本流組成,每行包含一個鍵值對,表示一個數(shù)據(jù)事件。每條事件消息由事件名稱、數(shù)據(jù)內(nèi)容等字段組成,并且這些字段具有特定的格式和規(guī)則。

  1. SSE 格式的基本結(jié)構(gòu)
    SSE 使用 Content-Type: text/event-stream,將數(shù)據(jù)以純文本的方式分塊傳輸?shù)娇蛻舳耍看蝹鬏斠粋€事件,數(shù)據(jù)傳輸結(jié)束時不需要關(guān)閉連接。每個事件消息有幾個常用字段:
  1. SSE 數(shù)據(jù)格式示例
    在每條事件中,字段通過換行分隔,格式如下:
event: custom-event
id: 1
retry: 5000
data: {"message": "Hello, World!"}
  1. 多行數(shù)據(jù)
    data 字段支持多行。對于多行內(nèi)容,在每行前都需要加 data: 前綴, 并且以兩個換行符(\n\n)結(jié)尾,SSE 會自動將其拼接為單個字符串傳遞到客戶端。例如:
data: {"message": "Part 1 of the message"}

data: {"message": "Part 2 of the message"}

data: {"message": "Part 3 of the message"}

在客戶端收到時,這幾行會被拼接成一條數(shù)據(jù)。

示例:使用SSE實現(xiàn)流式輸出

  1. 后端實現(xiàn)
    后端需要實現(xiàn)一個 HTTP 接口,該接口返回一個流式響應(yīng)。在 C# 中,可以使用 ASP.NET Core 來實現(xiàn)。以下是一個簡單的示例:
[HttpPost, HttpGet]
[ActionTitle(Name = "聊天")]
[Route("chat")]
public async Task Completions([FromBody] ChatDto chatDto)
{
Response.ContentType = "text/event-stream";

await foreach( var message in GetStreamingResponseAsync(chatDto.Input) ) {
var data = $"data: {message}\n\n";
Console.Write(data);
var bytes = Encoding.UTF8.GetBytes(data);
await Response.Body.WriteAsync(bytes);
await Response.Body.FlushAsync();
await Task.Delay(100);
}
}

public static async IAsyncEnumerable<string> GetStreamingResponseAsync(string userInput)
{
// 隨機獲取一個配置
GptConfig gptConfig = new GptConfig() {
ApiKey = "your-api-key",
Version = "2023-03-15-preview"
};

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"URL_ADDRESS");
request.Headers.Add("api-key", gptConfig.ApiKey);

var requestBody = new {
messages = new[]
{
new { role = "user", content = userInput }
},
stream = true
};

var jsonRequestBody = JsonSerializer.Serialize(requestBody);
request.Content = new StringContent(jsonRequestBody, Encoding.UTF8, "application/json");

using HttpClient httpClient = new HttpClient();

using( var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) ) {
response.EnsureSuccessStatusCode();
var responseStream = await response.Content.ReadAsStreamAsync();

using( var reader = new StreamReader(responseStream) ) {
while( !reader.EndOfStream ) {
var line = await reader.ReadLineAsync();
if( !string.IsNullOrWhiteSpace(line) && line.StartsWith("data:") ) {
var jsonData = line.Substring(5).Trim();
if( jsonData == "[DONE]" )
break;

var data = JsonSerializer.Deserialize<JsonElement>(jsonData);

// 檢查是否包含 content 字段,避免報錯
if( data.TryGetProperty("choices", out var choices) &&
choices[0].TryGetProperty("delta", out var delta) &&
delta.TryGetProperty("content", out var content) ) {
yield return content.GetString();
}
}
}
}
}

}

前端實現(xiàn)
在前端,我們可以使用 vue3來實現(xiàn)。以下是一個簡單的示例:

chat() {
fetch(/v20/openai/chat, { method: 'POST', body: JSON.stringify({ input: this.input }), headers: { 'Content-Type': 'application/json' } }).then((res) => { const reader = res.body.getReader(); this.handleReadStream(reader) }).finally(() => { this.input = '' }) }, // 流式對話 handleReadStream(stream) { stream.read().then(({ done, value }) => { if (done) { return } const data = new TextDecoder().decode(value) if (!data) { return } this.message += data.replaceAll('data: ', '') // 強制 Vue 渲染更新 this.$nextTick(() => { console.log("Stream updated"); }); // 遞歸處理流 this.handleReadStream(stream) }) },

實現(xiàn)效果

需要注意的是,vue3項目在本地開發(fā)代理api接口的時候似乎默認啟用了gzip壓縮,導致前端無法正常解析SSE的數(shù)據(jù)格式。可以在vue.config.js中配置關(guān)閉gzip壓縮。

devServer: {
port: 9588,
compress: false,
allowedHosts: "all",
proxy: {
'v20': { target: 'http://localhost:2222', changeOrigin: true },
}
}

結(jié)論

流式輸出是一種強大的工具,能夠顯著改善數(shù)據(jù)傳輸體驗,特別適用于實時和大數(shù)據(jù)場景。合理選擇適合的流式輸出技術(shù)并處理好前后端的數(shù)據(jù)解析和錯誤恢復(fù),可以顯著提升應(yīng)用的交互性和性能。

文章轉(zhuǎn)自微信公眾號@ITProHub

上一篇:

Python 云計算接口集成秘籍:十招搞定云服務(wù) API

下一篇:

Web Audio API 太強了,讓我們一起領(lǐng)略音頻之美
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費