SSE 事件流
SSE 事件流中,一个数据包就是一个 Token,其 content-type 应指定为 text/eventstream。
data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"role": "assistant"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "苹"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "果"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "公司"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "是"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "一"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "家"}, "index": 0, "finish_reason": null}]}data: {"id": "chatcmpl-6w****KZb6hx****RzIghUz****Qy", "object": "chat.completion.chunk", "created": 16******79, "model": "gpt-3.5-turbo-0301", "choices": [{"delta": {"content": "科"}, "index": 0, "finish_reason": null}]}...
前端处理
一种方式是后台处理上述 SSE 事件流返回单纯的文本流,另一种方式是前端处理 SSE 事件流,需要注意 ReadableStream 传输的是字节流,处理完成后的数据需要用 encoder 去编码。
const response = await fetch('your fetch url', {method: 'POST',headers: {'Content-Type': 'application/json;charset=UTF-8',...},body: JSON.stringify({messages,stream: true,}),});// 确保服务器响应是成功的if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}// 获取 readerconst reader = response.body.getReader();const decoder = new TextDecoder('utf-8');// 注意const encoder = new TextEncoder();// 构造新的 ReadableStream.const readableStream = new ReadableStream({async start(controller) {while (true) {const { done, value } = await reader.read();if (done) {break;}const message = decoder.decode(value, { stream: true });const lines = message.toString().split('\n\n').filter((line) => line.trim() !== '');for (const line of lines) {const message = line.replace('data: ', '');const parsed = JSON.parse(message);controller.enqueue(encoder.encode(parsed.choices[0].delta.content));}}controller.close();},});return new Response(readableStream);
