当我们在使用gpt时,会发现他的回复不是一下子显示,而是一段段输出,很有意思,那我们能不能模拟出这种效果呢?
首先,我们分析一下他的请求
可以看到,他发送的post请求中,返回的是EventStream。
EventStream是什么
EventStream为事件流处理,服务端返回的数据以流的形式传输。
由此可见,GPT使用的是SSE的技术传输。
需求分析
通过观察接口,我们可以发现有两个feature
服务端实现
我们先来模拟服务端,这里我们以koa为例
import koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import { PassThrough } from 'stream';
const app = new koa();
const router = new Router();
app.use(bodyParser(
{
enableTypes: ['json', 'form', 'text']
}
));
app.use(router.routes());
router.post('/sse', async (ctx) => {
const { message } = JSON.parse(ctx.request.body);
// sse
ctx.set({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const sseStream = new PassThrough()
ctx.body = sseStream
sendEvent()
async function sendEvent() {
let count = 0;
const timer = setInterval(() => {
sseStream.write(JSON.stringify({data: message}))
count++;
if (count === 5) {
clearInterval(timer)
sseStream.end()
}
}, 300)
}
})
app.listen(3000, () => {
console.log('server is running at http://localhost:3000');
})
client 端实现
那我们应该怎么接收服务端传过来的消息呢
- 接收推送过来的消息
- 发送一个
post请求 - 传统的
eventSource只能接受get请求
这时候,我们可以找到一个微软的一个库Fetch Event Source
github.com/Azure/fetch…
我们可以简单的实现功能
如果我们想要自己实现,应该怎么实现呢 通过参考源码,我们可以写出一个简易实现的版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="output"></div>
<div>
<button>发送请求</button>
</div>
<script>
const output = document.getElementById("output");
const button = document.querySelector("button");
button.addEventListener("click", () => {
const controller = new AbortController();
fetchEventSource("http://127.0.0.1:3000/sse", {
method: "POST",
mode: "cors",
body: JSON.stringify({
message: "Hello JueJin",
}),
signal: controller.signal,
onopen() {
output.innerHTML += "连接已建立<br>";
},
onmessage(event) {
const { data } = JSON.parse(event);
output.innerHTML += `收到数据:${data}<br>`;
},
onerror(error) {
output.innerHTML += `连接已断开:${error}<br>`;
},
onclose() {
output.innerHTML += "连接已关闭<br>";
},
});
});
function fetchEventSource(url, options) {
curRequestController = new AbortController();
options.signal?.addEventListener("abort", () => {
dispose();
});
fetch(url, { ...options, signal: curRequestController.signal })
.then((response) => {
if (response.status === 200) {
options.onopen && options.onopen();
return response.body;
}
})
.then((readableStream) => {
const reader = readableStream.getReader();
const push = () => {
return reader.read().then(({ done, value }) => {
if (done) {
options.onclose && options.onclose();
return;
}
options.onmessage &&
options.onmessage(new TextDecoder().decode(value));
return push();
});
};
return push();
})
.catch((error) => {
console.log(error);
dispose();
options.onerror && options.onerror(error);
});
function dispose() {
curRequestController.abort();
}
}
</script>
</body>
</html>
总结
通过上述的实现和分析,我们能够了解gpt 逐字回复的原理,以及我们通过观察源码,简易的模拟出这个过程。