布景
公司项目期望在客户端开发 AI 方法,支撑 AI 问答,要求完成 ChatGPT 的打字机作用
方针
-
了解 ChatGPT 流式呼应背面的技能(打字机)
-
调研 AI 服务流式呼应的可行性(技能层面、服务器资源耗费层面)
打字机是怎么完成的
众所周知,ChatGPT API 是一个OpenAI 的谈天机器人接口,它能够根据用户的输入生成智能的回复,为了进步谈天的流畅性和呼应速度,选用丢失输出的呼应方法,相似打字机的出现作用
这其实是选用了 SSE(Sever-sent Events) 服务端推送技能,答应服务器向客户端发送事情,然后完成服务器端推送
与 webSocket
不同的是,服务端推送是单向的。数据信息被单向从服务端到客户端分发. 当不需求以消息方法将数据从客户端发送到服务器时,这使它们成为绝佳的挑选
SSE 的通讯协议
SSE 通讯协议很简单,本质上就是一个客户端建议的 HTTP GET恳求,服务器在接纳到该恳求后,回来 200 OK状况,并附带以下呼应头:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
- Content-Type: text/event-stream 表明呼应的内容类型是SSE格局的文本流。
- Cache-Control: no-cache 表明呼应的内容不应该被缓存,以确保实时性。
- Connection: keep-alive 表明呼应的衔接应该坚持打开,以便服务器端持续发送数据。
一般,客户端的恳求中会包含特殊的头信息:
"Accept: text/event-stream"
,表明客户端体系接纳 SSE 数据
SSE 支撑以下几种字段:
-
event
: 表明事情的类型,用于区分不同的事情,默许事情为message
-
data
: 表明事情的数据内容,能够有多行,每行都以data: 开头。 -
id
: 表明事情的唯一标识符,用于断线重连和消息追踪。 -
retry
: 表明断线重连的时刻距离,单位是毫秒。
SSE 事情流数据示例:
流式输出「够钟下班啦」,并以 event:data
标记事情流完毕
event:message
data:够
event:message
data:钟
event:message
data:下
event:message
data:班
event:message
data:啦
event:end
data:
SSE 接口示例
编写一个支撑 SSE 协议的接口
'use strict';
const Controller = require('egg').Controller;
class SSEController extends Controller {
async index() {
// Set SSE header
const { ctx } = this
ctx.response.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
ctx.res.statusCode = 200;
ctx.res.write(':流式呼应开端\n');
let count = 0;
while (count < 6) {
const cur = count++
const data = {
message: `Hello, world ${cur}`,
time: new Date(),
};
await new Promise(resolve => setTimeout(resolve, 1000));
// Send SSE event
ctx.res.write(`data: ${JSON.stringify(data)}\n`);
}
// event:end 是事情类型,表明完毕事情;客户端识别到服务器现已完毕呼应,然后封闭衔接
ctx.res.write('event:end\ndata:end\n\n');
// 监听客户端封闭衔接的事情,然后调用 ctx.res.end() 完毕呼应并封闭衔接。
ctx.req.on('close', () => {
ctx.res.end();
});
}
}
module.exports = SSEController;
恳求 SSE 接口,流式呼应:
curl -N --location --request GET 'http://127.0.0.1:7001/sse' \
--header 'Accept: text/event-stream'
>>>>
:流式呼应开端
data: {"message":"Hello, world 0","time":"2023-05-19T07:08:51.661Z"}
data: {"message":"Hello, world 1","time":"2023-05-19T07:08:52.662Z"}
data: {"message":"Hello, world 2","time":"2023-05-19T07:08:53.663Z"}
data: {"message":"Hello, world 3","time":"2023-05-19T07:08:54.665Z"}
data: {"message":"Hello, world 4","time":"2023-05-19T07:08:55.666Z"}
data: {"message":"Hello, world 5","time":"2023-05-19T07:08:56.667Z"}
:end
OK
打字机完成
后端代码
const express = require('express');
const router = express.Router();
router.get('/sse', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
res.statusCode = 200;
res.write('开端答复:\n');
const answer = '众所周知,ChatGPT API 是一个OpenAI 的谈天机器人接口,它能够根据用户的输入生成智能的回复,为了进步谈天的流畅性和呼应速度,选用丢失输出的呼应方法,相似打字机的出现作用';
let i = 0;
const intervalId = setInterval(() => {
res.write(`event:message\ndata:${answer[i]}\n\n`);
i++;
if (i === answer.length) {
res.write('event:end\ndata: \n\n'); // event:end 表明事情流完毕
clearInterval(intervalId);
}
}, 100);
res.end(); // 事情流推送完毕,服务端主动断开衔接
});
前端接入 SSE:
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<title>SSE Example</title>
</head>
<body>
<h1>SSE Example</h1>
<button id="startButton">开端</button>
<div id="output">答复:</div>
<script>
const startButton = document.getElementById('startButton');
const outputElement = document.getElementById('output');
let eventSource;
startButton.addEventListener('click', function() {
if (!eventSource) {
eventSource = new EventSource('http://localhost:7001/sse2');
eventSource.onmessage = function(event) {
const message = event.data;
outputElement.innerHTML += message;
};
eventSource.onerror = function(event) {
console.error('Error: ' + event);
};
// 服务器界说了事情流:event:end,因而监听 :end 事情来完毕 eventSource
eventSource.addEventListener('end', function(event) {
console.log('SSE 衔接已封闭');
eventSource.close();
});
}
});
</script>
</body>
</html>
前端呼应示例:
安卓接入 SSE:
运用 HttpURLConnection 或 OkHttp 等网络库来树立与服务器的衔接,并通过监听服务器发送的数据流来接纳事情
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SSEClient {
public void connectToSSE() {
try {
URL url = new URL("http://your-server/sse");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "text/event-stream");
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// 处理接纳到的事情数据流
}
reader.close();
inputStream.close();
} else {
// 处理衔接错误
}
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
iOS 接入 SSE
运用NSURLSession来树立与服务器的衔接,并通过监听服务器发送的数据流来接纳事情
NSURL *url = [NSURL URLWithString:@"http://your-server/sse"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// 处理衔接错误
} else {
NSString *eventData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// 处理接纳到的事情数据流
}
}];
[task resume];
打字机视频演示作用
gzoffice.mojidict.com:9001/moji-test/%…
服务端推送技能安全性比照
服务端推送技能涉及到客户端和服务器之间的数据传输,因而需求考虑安全性问题。不同的服务端推送技能有不同的安全性特色:
-
Ajax 短轮询和长轮询和根据 iframe 的流都是根据 HTTP 协议的,因而能够运用 HTTPS 协议来加密数据,避免中间人进犯或数据走漏。可是,这些技能都需求频频地发送恳求和呼应,这可能会添加服务器的负载和网络的拥塞,也可能会被一些歹意的恳求或呼应干扰。
-
SSE(Sever-sent Events)
也是根据 HTTP 协议的,因而也能够运用 HTTPS 协议来确保数据的安全性。SSE 比较于 Ajax 轮询技能,只需求树立一次衔接,就能够持续地接纳服务器的事情,这样能够削减网络开支和服务器压力。可是,SSE 只支撑单向的通讯,即服务器向客户端发送数据,客户端不能向服务器发送数据。这可能会约束一些交互功用的完成。SSE 多用在例如,谈天运用、股票行情、新闻更新等场景 -
WebSockets 是根据 TCP/IP 协议的,因而能够运用 WSS 协议来加密数据,避免数据被盗取或篡改。WebSockets支撑双向的通讯,客户端和服务器能够随时互相发送数据,这样能够完成更丰厚和灵敏的交互功用。可是,WebSockets 需求额定的端口号和组件来支撑,在一些环境中可能会遇到兼容性或安全性的问题。
综上所述,SSE 技能在 ChatGPT API 中有着重要的运用,它能够进步谈天机器人的呼应速度和用户体会。不同的服务端推送技能有各自的优缺点和安全性特色,需求根据详细的场景和需求来挑选适宜的技能。
SSE 运用在服务端的考虑
问题场景:客户端 > 服务器(调用 openAI),是否要选用 SSE ?
SSE 需求坚持衔接,是否会占用服务器资源?
运用 SSE 时,坚持衔接(keep-alive)会对服务器资源发生一些影响:
- 衔接开支:坚持衔接意味着服务器需求坚持与客户端之间的长时刻衔接。这会占用一定的服务器内存和其他资源来处理这些衔接。
- 并发衔接:假如有很多客户端一起运用 SSE 与服务器树立衔接,服务器需求一起办理和处理这些并发衔接。这可能会添加服务器的负载和资源耗费。
- 带宽占用:坚持衔接需求坚持持续的数据传输,即使是小量的数据也会占用一定的带宽。这可能对服务器的网络带宽和传输能力发生一定的压力。
- 状况办理:坚持衔接可能需求服务器维护客户端的衔接状况,以便正确地处理和传输数据。这需求服务器进行额定的状况办理和资源分配。
SSE 相关于其他实时通讯机制(如 WebSocket)来说,它的开支相对较低,因为SSE是根据标准的HTTP协议,运用简单的文本格局进行数据传输,而且不需求双向通讯。SSE 在呼应完成后,能够主动推送 event:end
完毕事情来通知客户端呼应完毕封闭衔接,避免一向坚持衔接
假如不运用 SSE 实时地流式传输,而是让客户端等候服务器完好的呼应后再回来,那么当前恳求的呼应时刻会变长,而且在这期间衔接也会占用服务器的资源。
在传统的同步恳求-呼应方法中,客户端发送恳求后会一向等候服务器生成完好的呼应,期间衔接坚持打开状况,占用服务器的衔接资源。这种等候时刻会添加恳求的呼应时刻,而且服务器需求坚持衔接的状况,耗费一定的资源。
比较之下,SSE 答应服务器实时地将部分数据流式传输给客户端,以供给更好的实时性和用户体会。服务器能够在核算过程中逐步发送部分答复,使客户端能够即时获取到部分结果,而无需等候完好的呼应生成。
运用 SSE 的优势在于在核算过程中能够逐步回来结果,削减客户端等候时刻,并下降服务器资源的占用。而在传统的同步恳求-呼应方法中,客户端有必要一向等候完好的呼应,期间衔接会一向坚持打开状况,占用服务器的资源。
SSE 的流量耗费?
流式传输在某些情况下可能会耗费较多的流量,特别是在实时传输很多数据时。流式传输的特色是将数据逐步传输给客户端,而不需求等候完好的呼应生成。这意味着在传输过程中,数据会逐步发送给客户端,而不是一次性发送一切数据。
因而,假如流式传输的数据量很大或许传输速度较快,可能会占用更多的网络带宽和耗费更多的流量。关于大规模的流式传输,特别是关于长时刻的传输,流量耗费可能会变得更显着。
但是,关于一些小规模的流式传输,比如 逐字
或逐段地传输文本数据,相关于一次性传输一切数据,流量耗费的添加可能是能够承受的,而且能够供给更好的用户体会。
openAI 官方关于 stream completion 的阐明
github.com/openai/open…
在官方事例中,选用流式 & 非流式恳求,让 gpt-3.5-turbo
数到100,看看各需求多长时刻:
- 两个恳求都花了大约 3 秒才完全完成
- 关于流恳求,咱们在 0.1 秒后收到第一个令牌呼应,而且每隔约 0.01-0.02 秒收到后续令牌
在相同的整体呼应时刻内,流式恳求加快了呼应功率,优化了运用体会
总结
关于 AI 问答方法运用场景,能够考虑在服务器调用 openAI 接口时敞开 stream: true
提取 openAI 的 stream completion,而且在业务接口中,选用 SSE,逐字回来调用结果,削减客户端等候时刻,并在呼应完毕后及时封闭衔接
参考资料
How_to_stream_completions.ipynb
了解ChatGPT流式呼应背面的技能,优化数据流处理功率
www.v2ex.com/t/921810