
如何快速實現REST API集成以優化業務流程
在本文中,我們將探討主流的大型語言模型(LLM)提供商如何實現其流式輸出的 HTTP API。我們將深入研究流式輸出的工作原理,探討其優勢,并提供示例代碼以幫助您理解如何在實際應用中使用流式輸出。
流式輸出(Streaming Output)是一種使后端將數據分塊、逐步發送到前端的技術。通過這種方法,前端應用能夠即時接收和渲染數據,不必等到整個響應體生成完畢后再處理。
傳統的API 通常會一次性返回所有數據,然后客戶端一次性接收。
流式輸出則允許服務器在生成數據的同時將其發送給客戶端,從而實現實時更新。
流式輸出通常用于以下幾種場景:
流式 API 提供了即時響應的體驗,允許用戶在內容生成過程中即時查看部分結果。相比等待整個響應完成,流式輸出極大提高了用戶體驗。適用于多種場景,例如:
在具體實現流式輸出時,常用的技術包括:
本文主要講解SSE的實現。
Server-Sent Events(SSE)返回的數據格式是由一系列文本流組成,每行包含一個鍵值對,表示一個數據事件。每條事件消息由事件名稱、數據內容等字段組成,并且這些字段具有特定的格式和規則。
event: custom-event
id: 1
retry: 5000
data: {"message": "Hello, World!"}
data: {"message": "Part 1 of the message"}
data: {"message": "Part 2 of the message"}
data: {"message": "Part 3 of the message"}
在客戶端收到時,這幾行會被拼接成一條數據。
[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();
}
}
}
}
}
}
前端實現
在前端,我們可以使用 vue3來實現。以下是一個簡單的示例:
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)
})
},
實現效果
需要注意的是,vue3項目在本地開發代理api接口的時候似乎默認啟用了gzip壓縮,導致前端無法正常解析SSE的數據格式。可以在vue.config.js中配置關閉gzip壓縮。
devServer: {
port: 9588,
compress: false,
allowedHosts: "all",
proxy: {
'v20': { target: 'http://localhost:2222', changeOrigin: true },
}
}
流式輸出是一種強大的工具,能夠顯著改善數據傳輸體驗,特別適用于實時和大數據場景。合理選擇適合的流式輸出技術并處理好前后端的數據解析和錯誤恢復,可以顯著提升應用的交互性和性能。
文章轉自微信公眾號@ITProHub