🐛 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, ...opts,
}); });
let UnexpectedRoleError = false;
if (modelOptions.stream) { if (modelOptions.stream) {
const stream = await openai.beta.chat.completions const stream = await openai.beta.chat.completions
.stream({ .stream({
@ -782,6 +783,12 @@ ${convo}
}) })
.on('error', (err) => { .on('error', (err) => {
handleOpenAIErrors(err, errorCallback, 'stream'); 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) { for await (const chunk of stream) {
@ -794,9 +801,11 @@ ${convo}
} }
} }
chatCompletion = await stream.finalChatCompletion().catch((err) => { if (!UnexpectedRoleError) {
handleOpenAIErrors(err, errorCallback, 'finalChatCompletion'); chatCompletion = await stream.finalChatCompletion().catch((err) => {
}); handleOpenAIErrors(err, errorCallback, 'finalChatCompletion');
});
}
} }
// regular completion // regular completion
else { else {
@ -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); throw new Error(error);
} else if (!chatCompletion) { } else if (!chatCompletion) {
throw new Error('Chat completion failed'); throw new Error('Chat completion failed');
@ -829,7 +842,9 @@ ${convo}
return ''; return '';
} }
if ( 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('The server had an error processing your request') ||
err?.message?.includes('missing finish_reason') || err?.message?.includes('missing finish_reason') ||
(err instanceof OpenAI.OpenAIError && 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(); super();
// Initialize properties using helper function // Initialize properties using helper function
this.serviceEndpoint = this._initializeField(fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT, 'AZURE_AI_SEARCH_SERVICE_ENDPOINT'); this.serviceEndpoint = this._initializeField(
this.indexName = this._initializeField(fields.AZURE_AI_SEARCH_INDEX_NAME, 'AZURE_AI_SEARCH_INDEX_NAME'); 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.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.apiVersion = this._initializeField(
this.queryType = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE, 'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', AzureAISearch.DEFAULT_QUERY_TYPE); fields.AZURE_AI_SEARCH_API_VERSION,
this.top = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP, 'AZURE_AI_SEARCH_SEARCH_OPTION_TOP', AzureAISearch.DEFAULT_TOP); 'AZURE_AI_SEARCH_API_VERSION',
this.select = this._initializeField(fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT, 'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT'); 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 // Check for required fields
if (!this.serviceEndpoint || !this.indexName || !this.apiKey) { 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 // Create SearchClient
@ -35,7 +58,7 @@ class AzureAISearch extends StructuredTool {
this.serviceEndpoint, this.serviceEndpoint,
this.indexName, this.indexName,
new AzureKeyCredential(this.apiKey), new AzureKeyCredential(this.apiKey),
{ apiVersion: this.apiVersion } { apiVersion: this.apiVersion },
); );
// Define schema // Define schema

View file

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

View file

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

View file

@ -105,7 +105,7 @@ process.on('uncaughtException', (err) => {
return; return;
} }
if (err.message.includes('OpenAIError')) { if (err.message.includes('OpenAIError') || err.message.includes('ChatCompletionMessage')) {
console.error( 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.', '\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 addTitle = async (req, { text, response, client }) => {
const { TITLE_CONVO = 'true' } = process.env ?? {}; const { TITLE_CONVO = 'true' } = process.env ?? {};
@ -7,6 +7,11 @@ const addTitle = async (req, { text, response, client }) => {
return; 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 }); const title = await client.titleConvo({ text, responseText: response?.text });
await saveConvo(req.user.id, { await saveConvo(req.user.id, {
conversationId: response.conversationId, conversationId: response.conversationId,

9
package-lock.json generated
View file

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