AI聊天中使用SSE

ChatGPT火了一年了,技术上的话也带动了SSE的使用。这里也总结下我对它的认识。

https://static.1991421.cn/2024/2024-04-13-153102.jpeg

SSE的优势

  1. 使用标准的HTTP协议,并非WebSocket。相对比WebSocket全双工资源开销相对较小。
  2. SSE传输的文本数据,使用开销简单。

服务端实现

服务端实现相对简单,只要responseType设置为text/event-stream;,持续发送数据到前端直到结束即可。

客户端实现

  1. EventSource

    1
    2
    3
    4
    const evtSource = new EventSource("sse.php");
    evtSource.onmessage = (e) => {
    console.log(`message: ${e.data}`);
    };
  2. Fetch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    fetch('/api/openai/stream', {
    method: 'post',
    headers: {
    'Content-Type': 'application/json;charset=utf-8',
    },
    body: JSON.stringify({
    messages
    })
    }).then(res => {
    const reader = res.body.getReader();
    let buffer = '';
    const readChunk = () => {
    reader.read()
    .then(({
    value,
    done
    }) => {
    if (done) {
    console.log('Stream finished');
    return;
    }

    const chunkString = new TextDecoder().decode(value);
    buffer += chunkString;

    let position;
    while ((position = buffer.indexOf('\n\n')) !== -1) {
    const completeMessage = buffer.slice(0, position);
    buffer = buffer.slice(position + 2);
    completeMessage.split('\n').forEach(line => {
    if (line.startsWith('data:')) {
    const jsonText = line.slice(5).trim();
    if (jsonText === '[DONE]') {
    console.log('done');
    return;
    }
    try {
    const dataObject = JSON.parse(jsonText);
    console.log(dataObject);
    } catch (error) {
    console.error('JSON parse error:', error, jsonText);
    }
    }
    });
    }
    readChunk();
    })
    .catch(error => {
    console.error(error);
    });
    };
    readChunk();
    })

如上两个办法

对比

  1. 两者兼容性差不多,当然IE是都不考虑。
  2. EventSource的缺点是无法发送请求体,所以一般需要先建立一个请求发送消息,之后再发起EventSource。而Fetch与XHR一样可以携带请求体。所以没特殊需求fetch是更好的选择。

ChatGPT的打字机效果

ChatGPT很早就支持了流返回,用户体验到的是打字机效果。那么GPT是如何做的呢。

抓包分析ChatGPT使用的是fetch,发起请求是将message信息携带,返回结果类型为text/event-stream; charset=utf-8,即服务端不断返回回答信息直到结束。

具体请求为network中检索https://chat.openai.com/backend-api/conversation

done。