🐛 fix: Prevent Node Server Crash Due to Unhandled ChatCompletionMessage Error (#1278)

* refactor(addTitle): avoid generating title when a request was aborted

* chore: bump openai to latest

* fix: catch OpenAIError Uncaught error as last resort

* fix: handle final messages excludes role=assistant

* Update OpenAIClient.js

* chore: fix linting errors
This commit is contained in:
Danny Avila 2023-12-04 22:58:23 -05:00 committed by GitHub
parent 076a9b9b9c
commit f1bc711cd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 30 deletions

View file

@ -771,6 +771,7 @@ ${convo}
...opts,
});
let UnexpectedRoleError = false;
if (modelOptions.stream) {
const stream = await openai.beta.chat.completions
.stream({
@ -782,6 +783,12 @@ ${convo}
})
.on('error', (err) => {
handleOpenAIErrors(err, errorCallback, 'stream');
})
.on('finalMessage', (message) => {
if (message?.role !== 'assistant') {
stream.messages.push({ role: 'assistant', content: intermediateReply });
UnexpectedRoleError = true;
}
});
for await (const chunk of stream) {
@ -794,10 +801,12 @@ ${convo}
}
}
if (!UnexpectedRoleError) {
chatCompletion = await stream.finalChatCompletion().catch((err) => {
handleOpenAIErrors(err, errorCallback, 'finalChatCompletion');
});
}
}
// regular completion
else {
chatCompletion = await openai.chat.completions
@ -809,7 +818,11 @@ ${convo}
});
}
if (!chatCompletion && error) {
if (!chatCompletion && UnexpectedRoleError) {
throw new Error(
'OpenAIError: Invalid final message: OpenAI expects final message to include role=assistant',
);
} else if (!chatCompletion && error) {
throw new Error(error);
} else if (!chatCompletion) {
throw new Error('Chat completion failed');
@ -829,7 +842,9 @@ ${convo}
return '';
}
if (
err?.message?.includes('stream ended') ||
err?.message?.includes(
'OpenAIError: Invalid final message: OpenAI expects final message to include role=assistant',
) ||
err?.message?.includes('The server had an error processing your request') ||
err?.message?.includes('missing finish_reason') ||
(err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason'))

View file

@ -17,17 +17,40 @@ class AzureAISearch extends StructuredTool {
super();
// Initialize properties using helper function
this.serviceEndpoint = this._initializeField(fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT, 'AZURE_AI_SEARCH_SERVICE_ENDPOINT');
this.indexName = this._initializeField(fields.AZURE_AI_SEARCH_INDEX_NAME, 'AZURE_AI_SEARCH_INDEX_NAME');
this.serviceEndpoint = this._initializeField(
fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT,
'AZURE_AI_SEARCH_SERVICE_ENDPOINT',
);
this.indexName = this._initializeField(
fields.AZURE_AI_SEARCH_INDEX_NAME,
'AZURE_AI_SEARCH_INDEX_NAME',
);
this.apiKey = this._initializeField(fields.AZURE_AI_SEARCH_API_KEY, 'AZURE_AI_SEARCH_API_KEY');
this.apiVersion = this._initializeField(fields.AZURE_AI_SEARCH_API_VERSION, 'AZURE_AI_SEARCH_API_VERSION', AzureAISearch.DEFAULT_API_VERSION);
this.queryType = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE, 'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', AzureAISearch.DEFAULT_QUERY_TYPE);
this.top = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP, 'AZURE_AI_SEARCH_SEARCH_OPTION_TOP', AzureAISearch.DEFAULT_TOP);
this.select = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT, 'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT');
this.apiVersion = this._initializeField(
fields.AZURE_AI_SEARCH_API_VERSION,
'AZURE_AI_SEARCH_API_VERSION',
AzureAISearch.DEFAULT_API_VERSION,
);
this.queryType = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE,
'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE',
AzureAISearch.DEFAULT_QUERY_TYPE,
);
this.top = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP,
'AZURE_AI_SEARCH_SEARCH_OPTION_TOP',
AzureAISearch.DEFAULT_TOP,
);
this.select = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT,
'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT',
);
// Check for required fields
if (!this.serviceEndpoint || !this.indexName || !this.apiKey) {
throw new Error('Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.');
throw new Error(
'Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.',
);
}
// Create SearchClient
@ -35,7 +58,7 @@ class AzureAISearch extends StructuredTool {
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{ apiVersion: this.apiVersion }
{ apiVersion: this.apiVersion },
);
// Define schema

View file

@ -17,17 +17,40 @@ class AzureAISearch extends StructuredTool {
super();
// Initialize properties using helper function
this.serviceEndpoint = this._initializeField(fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT, 'AZURE_AI_SEARCH_SERVICE_ENDPOINT');
this.indexName = this._initializeField(fields.AZURE_AI_SEARCH_INDEX_NAME, 'AZURE_AI_SEARCH_INDEX_NAME');
this.serviceEndpoint = this._initializeField(
fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT,
'AZURE_AI_SEARCH_SERVICE_ENDPOINT',
);
this.indexName = this._initializeField(
fields.AZURE_AI_SEARCH_INDEX_NAME,
'AZURE_AI_SEARCH_INDEX_NAME',
);
this.apiKey = this._initializeField(fields.AZURE_AI_SEARCH_API_KEY, 'AZURE_AI_SEARCH_API_KEY');
this.apiVersion = this._initializeField(fields.AZURE_AI_SEARCH_API_VERSION, 'AZURE_AI_SEARCH_API_VERSION', AzureAISearch.DEFAULT_API_VERSION);
this.queryType = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE, 'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', AzureAISearch.DEFAULT_QUERY_TYPE);
this.top = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP, 'AZURE_AI_SEARCH_SEARCH_OPTION_TOP', AzureAISearch.DEFAULT_TOP);
this.select = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT, 'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT');
this.apiVersion = this._initializeField(
fields.AZURE_AI_SEARCH_API_VERSION,
'AZURE_AI_SEARCH_API_VERSION',
AzureAISearch.DEFAULT_API_VERSION,
);
this.queryType = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE,
'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE',
AzureAISearch.DEFAULT_QUERY_TYPE,
);
this.top = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP,
'AZURE_AI_SEARCH_SEARCH_OPTION_TOP',
AzureAISearch.DEFAULT_TOP,
);
this.select = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT,
'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT',
);
// Check for required fields
if (!this.serviceEndpoint || !this.indexName || !this.apiKey) {
throw new Error('Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.');
throw new Error(
'Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.',
);
}
// Create SearchClient
@ -35,7 +58,7 @@ class AzureAISearch extends StructuredTool {
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{ apiVersion: this.apiVersion }
{ apiVersion: this.apiVersion },
);
// Define schema

View file

@ -59,7 +59,7 @@
"multer": "^1.4.5-lts.1",
"nodejs-gpt": "^1.37.4",
"nodemailer": "^6.9.4",
"openai": "^4.16.1",
"openai": "^4.20.1",
"openai-chat-tokens": "^0.2.8",
"openid-client": "^5.4.2",
"passport": "^0.6.0",

View file

@ -105,7 +105,7 @@ process.on('uncaughtException', (err) => {
return;
}
if (err.message.includes('OpenAIError')) {
if (err.message.includes('OpenAIError') || err.message.includes('ChatCompletionMessage')) {
console.error(
'\n\nAn Uncaught `OpenAIError` error may be due to your reverse-proxy setup or stream configuration, or a bug in the `openai` node package.',
);

View file

@ -1,5 +1,5 @@
const { isEnabled } = require('../../../utils');
const { saveConvo } = require('../../../../models');
const { saveConvo } = require('~/models');
const { isEnabled } = require('~/server/utils');
const addTitle = async (req, { text, response, client }) => {
const { TITLE_CONVO = 'true' } = process.env ?? {};
@ -7,6 +7,11 @@ const addTitle = async (req, { text, response, client }) => {
return;
}
// If the request was aborted, don't generate the title.
if (client.abortController.signal.aborted) {
return;
}
const title = await client.titleConvo({ text, responseText: response?.text });
await saveConvo(req.user.id, {
conversationId: response.conversationId,

9
package-lock.json generated
View file

@ -7,7 +7,6 @@
"": {
"name": "LibreChat",
"version": "0.6.1",
"hasInstallScript": true,
"license": "ISC",
"workspaces": [
"api",
@ -74,7 +73,7 @@
"multer": "^1.4.5-lts.1",
"nodejs-gpt": "^1.37.4",
"nodemailer": "^6.9.4",
"openai": "^4.16.1",
"openai": "^4.20.1",
"openai-chat-tokens": "^0.2.8",
"openid-client": "^5.4.2",
"passport": "^0.6.0",
@ -18170,9 +18169,9 @@
}
},
"node_modules/openai": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.17.4.tgz",
"integrity": "sha512-ThRFkl6snLbcAKS58St7N3CaKuI5WdYUvIjPvf4s+8SdymgNtOfzmZcZXVcCefx04oKFnvZJvIcTh3eAFUUhAQ==",
"version": "4.20.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.20.1.tgz",
"integrity": "sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",