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 怎么实现的。
|
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' 库了,效果比手写好些。。
|