LibreChat/api/server/middleware/abortMiddleware.js
Danny Avila ea1dd59ef4
refactor(api): Central Logging 📜 (#1348)
* WIP: initial logging changes
add several transports in ~/config/winston
omit messages in logs, truncate long strings
add short blurb in dotenv for debug logging
GoogleClient: using logger
OpenAIClient: using logger, handleOpenAIErrors
Adding typedef for payload message
bumped winston and using winston-daily-rotate-file
moved config for server paths to ~/config dir
Added `DEBUG_LOGGING=true` to .env.example

* WIP: Refactor logging statements in code

* WIP: Refactor logging statements and import configurations

* WIP: Refactor logging statements and import configurations

* refactor: broadcast Redis initialization message with `info` not `debug`

* refactor: complete Refactor logging statements and import configurations

* chore: delete unused tools

* fix: circular dependencies due to accessing logger

* refactor(handleText): handle booleans and write tests

* refactor: redact sensitive values, better formatting

* chore: improve log formatting, avoid passing strings to 2nd arg

* fix(ci): fix jest tests due to logger changes

* refactor(getAvailablePluginsController): cache plugins as they are static and avoids async addOpenAPISpecs call every time

* chore: update docs

* chore: update docs

* chore: create separate meiliSync logger, clean up logs to avoid being unnecessarily verbose

* chore: spread objects where they are commonly logged to allow string truncation

* chore: improve error log formatting
2023-12-14 07:49:27 -05:00

126 lines
3.7 KiB
JavaScript

const { sendMessage, sendError, countTokens, isEnabled } = require('~/server/utils');
const { saveMessage, getConvo, getConvoTitle } = require('~/models');
const clearPendingReq = require('~/cache/clearPendingReq');
const abortControllers = require('./abortControllers');
const spendTokens = require('~/models/spendTokens');
const { logger } = require('~/config');
async function abortMessage(req, res) {
const { abortKey } = req.body;
if (!abortControllers.has(abortKey) && !res.headersSent) {
return res.status(404).send({ message: 'Request not found' });
}
const { abortController } = abortControllers.get(abortKey);
const ret = await abortController.abortCompletion();
logger.debug('[abortMessage] Aborted request', { abortKey });
abortControllers.delete(abortKey);
res.send(JSON.stringify(ret));
}
const handleAbort = () => {
return async (req, res) => {
try {
if (isEnabled(process.env.LIMIT_CONCURRENT_MESSAGES)) {
await clearPendingReq({ userId: req.user.id });
}
return await abortMessage(req, res);
} catch (err) {
logger.error('[abortMessage] handleAbort error', err);
}
};
};
const createAbortController = (req, res, getAbortData) => {
const abortController = new AbortController();
const { endpointOption } = req.body;
const onStart = (userMessage) => {
sendMessage(res, { message: userMessage, created: true });
const abortKey = userMessage?.conversationId ?? req.user.id;
abortControllers.set(abortKey, { abortController, ...endpointOption });
res.on('finish', function () {
abortControllers.delete(abortKey);
});
};
abortController.abortCompletion = async function () {
abortController.abort();
const { conversationId, userMessage, promptTokens, ...responseData } = getAbortData();
const completionTokens = await countTokens(responseData?.text ?? '');
const user = req.user.id;
const responseMessage = {
...responseData,
conversationId,
finish_reason: 'incomplete',
model: endpointOption.modelOptions.model,
unfinished: false,
cancelled: true,
error: false,
isCreatedByUser: false,
tokenCount: completionTokens,
};
await spendTokens(
{ ...responseMessage, context: 'incomplete', user },
{ promptTokens, completionTokens },
);
saveMessage({ ...responseMessage, user });
return {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage,
};
};
return { abortController, onStart };
};
const handleAbortError = async (res, req, error, data) => {
logger.error('[handleAbortError] response error and aborting request', error);
const { sender, conversationId, messageId, parentMessageId, partialText } = data;
const respondWithError = async () => {
const options = {
sender,
messageId,
conversationId,
parentMessageId,
text: error.message,
shouldSaveMessage: true,
user: req.user.id,
};
const callback = async () => {
if (abortControllers.has(conversationId)) {
const { abortController } = abortControllers.get(conversationId);
abortController.abort();
abortControllers.delete(conversationId);
}
};
await sendError(res, options, callback);
};
if (partialText && partialText.length > 5) {
try {
return await abortMessage(req, res);
} catch (err) {
logger.error('[handleAbortError] error while trying to abort message', err);
return respondWithError();
}
} else {
return respondWithError();
}
};
module.exports = {
handleAbort,
createAbortController,
handleAbortError,
};