流式传输
前言
刷视频的时候恰好刷到了一个视频讲为什么 fetch 要等两次
js
const res = await fetch('./json');
const data = await res.json();
像这样. 明明只有一次请求却需要 await 两次. 视频里说第一次 await 是等待 header, 第二次 await 是等待 body, 总共等待两次. 还给了一个例子来展示了流式传输. 这里
效果跟 gpt 很像, 我恰好也有一个聊天网站, 但是用的是 websocket, 而且很容易断连, 于是就整了一下.
开始尝试
简单的 nodejs 后端:
js
app.post('/stream/chat', async (req, res) => {
try {
res.writeHead(200, { 'Content-Type': 'text/plain' });
const { model, messages } = req.body;
const response = await openaiClient.completions.create({
model: model,
messages: messages,
stream: true,
});
try {
for await (const chunk of response) {
const value = chunk.choices[0].delta.content;
value && res.write(chunk.choices[0].delta.content);
}
} catch (e) {
console.error(e);
}
} catch (err) {
console.log(err);
res.status(500).json({ error: '这个模型暂时用不了:-(试试别的模型吧' });
}
});
前端:
js
async function fetchStreamData() {
const res = await fetch('http://localhost:3000/stream/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
accept: '*,application/json',
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [
{
role: 'user',
content: '使用C++实现一个简单的计算器',
},
],
}),
});
const decoder = new TextDecoder('utf-8');
for await (const value of res.body) {
const chunk = decoder.decode(value);
document.body.innerHTML += chunk;
console.log(chunk);
}
}
这样就成功打印出很多行了, 这说明是一点点传输的而不是完成之后一下子全传过来.
使用 EventStream
之后我去看了一下 chatgpt 的流式传输是怎么做的, 发现是用了 SSE, 感觉很神奇. 因为我记得这个是只能用于 GET 请求的, 但是开发者工具看它是 POST 请求. 然后我也写了一个简单的使用 POST 的 SSE.
js
app.post('/stream/chat', async (req, res) => {
try {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
const { model, messages } = req.body;
const response = await openaiClient.completions.create({
model: model,
messages: messages,
stream: true,
});
try {
for await (const chunk of response) {
const value = chunk.choices[0].delta.content;
value && res.write(chunk.choices[0].delta.content);
}
} catch (e) {
console.error(e);
}
} catch (err) {
console.log(err);
res.status(500).json({ error: '这个模型暂时用不了:-(试试别的模型吧' });
}
});
前端部分改成:
js
async function fetchStreamData() {
const res = await fetch('http://localhost:3000/stream/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
accept: '*,application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{
role: 'user',
content: '使用C++实现一个简单的计算器',
},
],
}),
});
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('Received', value);
}
}
同样能实现流式传输, 果然需要自己试一试吧. 这里前端不改也可以流式传输, 只是开发者工具看不到EventStream, 虽然说改了之后里面也是空的.