🔄 refactor: Consolidate Ask/Edit Controllers (#1365)

* refactor(Ask/Edit): consolidate ask/edit controllers between the main modules and openAI controllers to reduce repetition of code and increase reusability

* fix(winston/logger): circular dependency issue

* fix(config/scripts): fix script imports

* refactor(indexSync): make not configured message an info log message

* chore: create a rollup script for api/server/index.js to check circular dependencies

* chore: bump @keyv/redis
This commit is contained in:
Danny Avila 2023-12-15 15:47:40 -05:00 committed by GitHub
parent 0958db3825
commit 509b1e5c63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 299 additions and 368 deletions

View file

@ -290,7 +290,7 @@ class AnthropicClient extends BaseClient {
top_p,
top_k,
};
logger.debug('[AnthropicClient]', { requestOptions });
logger.debug('[AnthropicClient]', { ...requestOptions });
const response = await client.completions.create(requestOptions);
signal.addEventListener('abort', () => {

View file

@ -2,7 +2,6 @@ const path = require('path');
const winston = require('winston');
require('winston-daily-rotate-file');
const { redact, deepObjectFormat } = require('./parsers');
const { isEnabled } = require('~/server/utils/handleText');
const logDir = path.join(__dirname, '..', 'logs');
@ -67,7 +66,10 @@ const transports = [
// );
// }
if (isEnabled && isEnabled(DEBUG_LOGGING)) {
if (
(typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING?.toLowerCase() === 'true') ||
DEBUG_LOGGING === true
) {
transports.push(
new winston.transports.DailyRotateFile({
level: 'debug',
@ -88,7 +90,10 @@ const consoleFormat = winston.format.combine(
winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`),
);
if (isEnabled && isEnabled(DEBUG_CONSOLE)) {
if (
(typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE?.toLowerCase() === 'true') ||
DEBUG_CONSOLE === true
) {
transports.push(
new winston.transports.Console({
level: 'debug',

View file

@ -65,6 +65,8 @@ async function indexSync(req, res, next) {
logger.error('[indexSync] Trouble creating indices, try restarting the server.', err);
}
}, 750);
} else if (err.message.includes('Meilisearch not configured')) {
logger.info('[indexSync] Meilisearch not configured, search will be disabled.');
} else {
logger.error('[indexSync] error', err);
// res.status(500).json({ error: 'Server error' });

View file

@ -30,7 +30,7 @@
"@anthropic-ai/sdk": "^0.5.4",
"@azure/search-documents": "^12.0.0",
"@keyv/mongo": "^2.1.8",
"@keyv/redis": "^2.8.0",
"@keyv/redis": "^2.8.1",
"@langchain/google-genai": "^0.0.2",
"axios": "^1.3.4",
"bcryptjs": "^2.4.3",

View file

@ -4,7 +4,7 @@ const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { createAbortController, handleAbortError } = require('~/server/middleware');
const { logger } = require('~/config');
const AskController = async (req, res, next, initializeClient) => {
const AskController = async (req, res, next, initializeClient, addTitle) => {
let {
text,
endpointOption,
@ -12,7 +12,9 @@ const AskController = async (req, res, next, initializeClient) => {
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
logger.debug('[AskController]', { text, conversationId, ...endpointOption });
let metadata;
let userMessage;
let promptTokens;
@ -21,8 +23,11 @@ const AskController = async (req, res, next, initializeClient) => {
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const newConvo = !conversationId;
const user = req.user.id;
const addMetadata = (data) => (metadata = data);
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
@ -50,6 +55,7 @@ const AskController = async (req, res, next, initializeClient) => {
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: true,
cancelled: false,
error: false,
@ -62,46 +68,50 @@ const AskController = async (req, res, next, initializeClient) => {
}
},
});
const getAbortData = () => ({
sender,
conversationId,
messageId: responseMessageId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
try {
const addMetadata = (data) => (metadata = data);
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
// debug: true,
const messageOptions = {
user,
conversationId,
parentMessageId,
conversationId,
overrideParentMessageId,
...endpointOption,
getReqData,
onStart,
addMetadata,
abortController,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
parentMessageId: overrideParentMessageId || userMessageId,
}),
onStart,
getReqData,
addMetadata,
abortController,
});
};
let response = await client.sendMessage(text, messageOptions);
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
if (metadata) {
response = { ...response, ...metadata };
}
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
if (client.options.attachments) {
userMessage.files = client.options.attachments;
delete userMessage.image_urls;
}
sendMessage(res, {
@ -116,7 +126,13 @@ const AskController = async (req, res, next, initializeClient) => {
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add title service
if (addTitle && parentMessageId === '00000000-0000-0000-0000-000000000000' && newConvo) {
addTitle(req, {
text,
response,
client,
});
}
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {

View file

@ -15,6 +15,7 @@ const EditController = async (req, res, next, initializeClient) => {
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
logger.debug('[EditController]', {
text,
generation,
@ -22,6 +23,7 @@ const EditController = async (req, res, next, initializeClient) => {
conversationId,
...endpointOption,
});
let metadata;
let userMessage;
let promptTokens;
@ -48,6 +50,7 @@ const EditController = async (req, res, next, initializeClient) => {
generation,
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
@ -56,6 +59,7 @@ const EditController = async (req, res, next, initializeClient) => {
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: true,
cancelled: false,
isEdited: true,
@ -69,19 +73,20 @@ const EditController = async (req, res, next, initializeClient) => {
}
},
});
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
try {
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
@ -93,25 +98,22 @@ const EditController = async (req, res, next, initializeClient) => {
parentMessageId,
responseMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
}),
getReqData,
onStart,
addMetadata,
abortController,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId,
}),
});
if (metadata) {
response = { ...response, ...metadata };
}
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
await saveMessage({ ...response, user });
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
@ -121,11 +123,6 @@ const EditController = async (req, res, next, initializeClient) => {
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add title service
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {

View file

@ -1,160 +1,19 @@
const express = require('express');
const router = express.Router();
const { sendMessage, createOnProgress } = require('~/server/utils');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { getResponseSender } = require('librechat-data-provider');
const AskController = require('~/server/controllers/AskController');
const { addTitle, initializeClient } = require('~/server/services/Endpoints/openAI');
const {
handleAbort,
createAbortController,
handleAbortError,
setHeaders,
validateEndpoint,
buildEndpointOption,
} = require('~/server/middleware');
const { logger } = require('~/config');
const router = express.Router();
router.post('/abort', handleAbort());
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => {
let {
text,
endpointOption,
conversationId,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
logger.debug('[/ask/openAI]', { text, conversationId, ...endpointOption });
let metadata;
let userMessage;
let promptTokens;
let userMessageId;
let responseMessageId;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const newConvo = !conversationId;
const user = req.user.id;
const addMetadata = (data) => (metadata = data);
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
userMessageId = data[key].messageId;
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
} else if (!conversationId && key === 'conversationId') {
conversationId = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: true,
cancelled: false,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
const getAbortData = () => ({
sender,
conversationId,
messageId: responseMessageId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
try {
const { client } = await initializeClient({ req, res, endpointOption });
const messageOptions = {
user,
parentMessageId,
conversationId,
overrideParentMessageId,
getReqData,
onStart,
addMetadata,
abortController,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId,
}),
};
let response = await client.sendMessage(text, messageOptions);
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
if (metadata) {
response = { ...response, ...metadata };
}
if (client.options.attachments) {
userMessage.files = client.options.attachments;
delete userMessage.image_urls;
}
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
if (parentMessageId === '00000000-0000-0000-0000-000000000000' && newConvo) {
addTitle(req, {
text,
response,
client,
});
}
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
await AskController(req, res, next, initializeClient, addTitle);
});
module.exports = router;

View file

@ -1,150 +1,19 @@
const express = require('express');
const router = express.Router();
const { getResponseSender } = require('librechat-data-provider');
const EditController = require('~/server/controllers/EditController');
const { initializeClient } = require('~/server/services/Endpoints/openAI');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { sendMessage, createOnProgress } = require('~/server/utils');
const {
handleAbort,
createAbortController,
handleAbortError,
setHeaders,
validateEndpoint,
buildEndpointOption,
} = require('~/server/middleware');
const { logger } = require('~/config');
const router = express.Router();
router.post('/abort', handleAbort());
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => {
let {
text,
generation,
endpointOption,
conversationId,
responseMessageId,
isContinued = false,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
logger.debug('[/edit/openAI]', {
text,
generation,
isContinued,
conversationId,
...endpointOption,
});
let metadata;
let userMessage;
let promptTokens;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const userMessageId = parentMessageId;
const user = req.user.id;
const addMetadata = (data) => (metadata = data);
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
generation,
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: true,
cancelled: false,
isEdited: true,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
const getAbortData = () => ({
sender,
conversationId,
messageId: responseMessageId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
try {
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
user,
generation,
isContinued,
isEdited: true,
conversationId,
parentMessageId,
responseMessageId,
overrideParentMessageId,
getReqData,
onStart,
addMetadata,
abortController,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId,
}),
});
if (metadata) {
response = { ...response, ...metadata };
}
await saveMessage({ ...response, user });
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
await EditController(req, res, next, initializeClient);
});
module.exports = router;