流式传输
前言
刷视频的时候恰好刷到了一个视频讲为什么 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,
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,
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, 虽然说改了之后里面也是空的.