From 93160b898693475278b82b3ffa0d5e83c460ea35 Mon Sep 17 00:00:00 2001 From: josc146 Date: Thu, 6 Apr 2023 20:05:42 +0800 Subject: [PATCH] fix: cannot stop when using azure openai api --- package-lock.json | 48 ++-------- package.json | 2 - src/background/apis/azure-openai-api.mjs | 110 ++++++++++------------- src/background/apis/openai-api.mjs | 6 +- 4 files changed, 57 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index f07546ad..77bcd0b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "@nem035/gpt-3-encoder": "^1.1.7", "@picocss/pico": "^1.5.7", "@primer/octicons-react": "^18.2.0", - "@vespaiach/axios-fetch-adapter": "^0.3.1", - "azure-openai": "^0.9.4", "countries-list": "^2.6.1", "eventsource-parser": "^0.1.0", "expiry-map": "^2.0.0", @@ -2300,14 +2298,6 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, - "node_modules/@vespaiach/axios-fetch-adapter": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/@vespaiach/axios-fetch-adapter/-/axios-fetch-adapter-0.3.1.tgz", - "integrity": "sha512-+1F52VWXmQHSRFSv4/H0wtnxfvjRMPK5531e880MIjypPdUSX6QZuoDgEVeCE1vjhzDdxCVX7rOqkub7StEUwQ==", - "peerDependencies": { - "axios": ">=0.26.0" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -2761,7 +2751,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { "version": "1.0.5", @@ -2772,23 +2763,6 @@ "node": ">= 0.4" } }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/azure-openai": { - "version": "0.9.4", - "resolved": "https://registry.npmmirror.com/azure-openai/-/azure-openai-0.9.4.tgz", - "integrity": "sha512-7uii4ZInxzu2zjLg45PdvgOaw3ps18tEAw0Yux9mo8anX4PwnCMSS9xdlKNiNQyyEKPogvAcxH2PIufHXFLx6Q==", - "dependencies": { - "axios": "^0.26.0", - "form-data": "^4.0.0" - } - }, "node_modules/babel-loader": { "version": "9.1.2", "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-9.1.2.tgz", @@ -3107,6 +3081,7 @@ "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3606,6 +3581,7 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -4373,19 +4349,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz", @@ -4399,6 +4362,7 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6331,6 +6295,7 @@ "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -6339,6 +6304,7 @@ "version": "2.1.35", "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, diff --git a/package.json b/package.json index 085106a7..1c7b54ce 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,6 @@ "@nem035/gpt-3-encoder": "^1.1.7", "@picocss/pico": "^1.5.7", "@primer/octicons-react": "^18.2.0", - "@vespaiach/axios-fetch-adapter": "^0.3.1", - "azure-openai": "^0.9.4", "countries-list": "^2.6.1", "eventsource-parser": "^0.1.0", "expiry-map": "^2.0.0", diff --git a/src/background/apis/azure-openai-api.mjs b/src/background/apis/azure-openai-api.mjs index fd15a37e..702e48fd 100644 --- a/src/background/apis/azure-openai-api.mjs +++ b/src/background/apis/azure-openai-api.mjs @@ -1,8 +1,8 @@ -import { Configuration, OpenAIApi } from 'azure-openai' import { getUserConfig, maxResponseTokenLength } from '../../config/index.mjs' import { getChatSystemPromptBase, pushRecord, setAbortController } from './shared.mjs' import { getConversationPairs } from '../../utils/get-conversation-pairs' -import fetchAdapter from '@vespaiach/axios-fetch-adapter' +import { fetchSSE } from '../../utils/fetch-sse' +import { isEmpty } from 'lodash-es' /** * @param {Runtime.Port} port @@ -17,72 +17,54 @@ export async function generateAnswersWithAzureOpenaiApi(port, question, session) prompt.unshift({ role: 'system', content: await getChatSystemPromptBase() }) prompt.push({ role: 'user', content: question }) - const openAiApi = new OpenAIApi( - new Configuration({ - apiKey: config.azureApiKey, - azure: { - apiKey: config.azureApiKey, - endpoint: config.azureEndpoint, - deploymentName: config.azureDeploymentName, + let answer = '' + await fetchSSE( + `${config.azureEndpoint.replace(/\/$/, '')}/openai/deployments/${ + config.azureDeploymentName + }/chat/completions?api-version=2023-03-15-preview`, + { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'application/json', + 'api-key': config.azureApiKey, }, - }), - ) - - const response = await openAiApi - .createChatCompletion( - { + body: JSON.stringify({ messages: prompt, stream: true, max_tokens: maxResponseTokenLength, + }), + onMessage(message) { + console.debug('sse message', message) + let data + try { + data = JSON.parse(message) + } catch (error) { + console.debug('json error', error) + return + } + if ('content' in data.choices[0].delta) { + answer += data.choices[0].delta.content + port.postMessage({ answer: answer, done: false, session: null }) + } + if (data.choices[0].finish_reason === 'stop') { + pushRecord(session, question, answer) + console.debug('conversation history', { content: session.conversationRecords }) + port.postMessage({ answer: null, done: true, session: session }) + } }, - { - signal: controller.signal, - responseType: 'stream', - adapter: fetchAdapter, + async onStart() {}, + async onEnd() { + port.onMessage.removeListener(messageListener) }, - ) - .catch((err) => { - port.onMessage.removeListener(messageListener) - throw err - }) - - let chunkData = '' - const step = 1500 - let length = 0 - for await (const chunk of response.data) { - chunkData += chunk - length += 1 - if (length % step !== 0 && !chunkData.endsWith('[DONE]')) continue - - const lines = chunkData - .toString('utf8') - .split('\n') - .filter((line) => line.trim().startsWith('data: ')) - - let answer = '' - let message = '' - let data - for (const line of lines) { - message = line.replace(/^data: /, '') - try { - data = JSON.parse(message) - } catch (error) { - continue - } - if ('content' in data.choices[0].delta) answer += data.choices[0].delta.content - } - if (data) { - console.debug('sse message', data) - port.postMessage({ answer: answer, done: false, session: null }) - } - if (message === '[DONE]') { - console.debug('sse message', '[DONE]') - pushRecord(session, question, answer) - console.debug('conversation history', { content: session.conversationRecords }) - port.postMessage({ answer: null, done: true, session: session }) - break - } - } - - port.onMessage.removeListener(messageListener) + async onError(resp) { + port.onMessage.removeListener(messageListener) + if (resp instanceof Error) throw resp + const error = await resp.json().catch(() => ({})) + throw new Error( + !isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`, + ) + }, + }, + ) } diff --git a/src/background/apis/openai-api.mjs b/src/background/apis/openai-api.mjs index b7a277ce..f1489eec 100644 --- a/src/background/apis/openai-api.mjs +++ b/src/background/apis/openai-api.mjs @@ -125,8 +125,10 @@ export async function generateAnswersWithChatgptApi(port, question, session, api console.debug('json error', error) return } - if ('content' in data.choices[0].delta) answer += data.choices[0].delta.content - port.postMessage({ answer: answer, done: false, session: null }) + if ('content' in data.choices[0].delta) { + answer += data.choices[0].delta.content + port.postMessage({ answer: answer, done: false, session: null }) + } }, async onStart() {}, async onEnd() {