
const respOnse= await fetch(...); const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); while (!done) { const { value, done: readerDone } = await reader.read(); if (value) { const char = decoder.decode(value); console.log(char); } } 代码如上,有时候打印出来的 char 为:
data: {"id":"chatcmpl-7Y79egENb17GOU20IaW5KgJJhbf4M","object":"chat.completion.chunk","created":1688365010,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} data: {"id":"chatcmpl-7Y79egEN 图示: https://i.imgur.com/P1YQs4q.png
也就是从"id"中间被切断了,导致内容少 1 到 2 个字。
请问有啥可改进的方法吗?
听了@Opportunity的建议,上了 '@fortaine/fetch-event-source' 库。
测试之后内容切断的问题应该是解决了,,但是另一个问题出现了。。。无语。
错误信息:Uncaught Error: The error you provided does not contain a stack trace.
代码如下:
await fetchEventSource('xxx', { async onopen(response) { if (response.ok && response.headers.get('content-type') === EventStreamContentType) { return; // everything's good } else if (response.status >= 400 && response.status < 500 && response.status !== 429) { // client-side errors are usually non-retriable: throw new FatalError(); } else { throw new RetriableError(); } }, onmessage(msg) { if (msg.data === '[DONE]' || finished) { return finish(); } const text = msg.data; try { const json = JSON.parse(text); const finishReason = json.finish_reason; const choices = json.choices[0]; const delta = choices.delta; if (delta.hasOwnProperty('content') && delta.content) { reply += delta.content; } else if (delta.hasOwnProperty('function_call')) { ... } else if (finishReason === 'function_call' || finishReason === 'stop') { return finish(); } } catch (e) { console.error('[Request] parse error', text, msg); } }, onclose() { finish(); }, onerror(err) { console.error('[Request] error', err); throw err; }, }); 再次请教改怎么改进、、、代码参考:@link https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/app/client/platforms/openai.ts
这个错误 Uncaught Error: The error you provided does not contain a stack trace. 不影响运行,但看着不顺眼、、
1 Opportunity 2023-07-03 14:30:44 +08:00 为啥不直接用 EventSource 读,要自己手写这玩意?非要手写的话可以去参考下 EventSource 的 polyfill 怎么实现的。 |
&nbs; 2 s609926202 OP @Opportunity #1 不会。现在都是还是网上东拼西凑来的。。 |
3 Erroad 2023-07-03 14:44:28 +08:00 当服务器端向客户端发送一段 HTTP 流( HTTP Streaming )时,数据是以块( chunks )的形式发送的,而不是一次性发送全部。在浏览器环境中,我们可以使用 Fetch API 的流( stream )读取器读取到这些数据。 这是一个基本的例子: ```Javascript fetch('/your-http-streaming-url') .then(respOnse=> { const reader = response.body.getReader(); const stream = new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); push(); }) .catch(error => { console.error(error); controller.error(error); }) } push(); } }); return new Response(stream, { headers: { "Content-Type": "text/html" } }); }) .then(respOnse=> response.text()) .then(result => { console.log(result); }) .catch(err => { console.error(err); }); ``` 这个示例做了以下事情: 1. 使用 `fetch` API 获取数据流。 2. 创建一个流读取器( stream reader )读取响应主体。 3. 创建一个新的 `ReadableStream`,在它的 `start` 函数中读取数据,并通过 `controller.enqueue` 方法将数据加入队列中。 4. 如果读取过程中出现错误,使用 `controller.error` 将错误信息发送出去。 5. 当数据全部读取完毕,关闭控制器 `controller.close`。 6. 最后,获取到的数据通过 `Response.text()` 转化为文本格式,并输出。 注意,上述示例仅适用于文本数据流,如果你需要处理的是二进制数据流,可能需要进行适当的调整。例如,你可能需要使用 `Response.blob()` 代替 `Response.text()`。 chatGPT 的回答 |
4 zhuisui 2023-07-03 14:45:18 +08:00 你好像没有正确处理 done |
5 s609926202 OP @zhuisui #4 在循环体中处理的 ``` if (choices.finish_reason === 'stop' || choices.finish_reason === 'function_call') { dOne= true; break; } ``` |
6 mmdsun 2023-07-03 17:06:21 +08:00 @Opportunity EventSource 只能 url 吧,我看 openAi 接口都是 POST 有 request body 的,EventSource 没法用。 curl https://api.openai.com/v1/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "prompt": "Say this is a test", "max_tokens": 7, "steam": true, "temperature": 0 }' |
7 yowot0088 2023-07-03 20:44:06 +08:00 我的解决方法是,先判断一个 chunk 里最后的 data: 是否为一个合法的 json ,如果不是,则将下一次最开始接收到的字符串与前一次的非法 json 拼接,可以完美解决 |
8 yowot0088 2023-07-03 20:45:44 +08:00 附上我做的 ws api 的源码 ```js wss.on('connection', ws => { let isCOnnected= true ws.on('message', async e => { let message = JSON.parse(e.toString()) if(message.type == 'conversation') { let es = await fetch('https://api.openai.com/v1/chat/completions', { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + 'YOUR_OPENAI_API_KEY' }, method: 'POST', body: JSON.stringify({ model: message.data.model, messages: message.data.messages, stream: true }) }) const reader = es.body.pipeThrough(new TextDecoderStream()).getReader() let errObj = '' while(true) { if(!isConnected) { process.stdout.write('\n') break } const res = await reader.read() if(res.done) { break } let chunk = res.value chunk = chunk.replace(/data: /g, '').split('\n') chunk.map(item => { if(item != '[DONE]' && item != '' && item != undefined) { let json try { if(errObj != '') { item = errObj + item errObj = '' } json = JSON.parse(item) if(json.choices[0].delta.cOntent== undefined) return ws.send(JSON.stringify({ type: 'conversation', data: { type: 'continue', text: json.choices[0].delta.content } })) process.stdout.write(json.choices[0].delta.content) }catch { errObj = item return } }else if(item == '[DONE]') { ws.send(JSON.stringify({ type: 'conversation', data: { type: 'done', text: null } })) process.stdout.write('\n') } }) } } }) ws.Onclose= () => { isCOnnected= false } }) ``` |
9 MEIerer 2023-07-04 09:20:11 +08:00 我发现原生 fetch 在手机端直连 gpt 的接口时一点数据都出不来,但在 pc 端就没问题,这是为什么? |
10 s609926202 OP @yowot0088 #8 这倒是一个解决方法。不过我改用 '@fortaine/fetch-event-source' 库了,效果比手写好些。。 |