🧼 refactor(AppService): Consolidate Logic & Issue more Warnings (#2468)

* chore: bump example config version

* refactor(AppService): issue warnings from separate modules where possible

* refactor(AppService): consolidate AppService logic to separate modules as much as possible

* chore: bump data-provider

* chore: remove unn. variable definition

* chore: warning wording
This commit is contained in:
Danny Avila 2024-04-19 12:05:39 -04:00 committed by GitHub
parent de3987cbaf
commit 3d1dec62a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 231 additions and 149 deletions

View file

@ -1,29 +1,17 @@
const { const {
Constants,
FileSources, FileSources,
Capabilities,
EModelEndpoint, EModelEndpoint,
EImageOutputType, EImageOutputType,
defaultSocialLogins, defaultSocialLogins,
validateAzureGroups,
mapModelToAzureConfig,
assistantEndpointSchema,
deprecatedAzureVariables,
conflictingAzureVariables,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { checkVariables, checkHealth, checkConfig, checkAzureVariables } = require('./start/checks');
const { initializeFirebase } = require('./Files/Firebase/initialize'); const { initializeFirebase } = require('./Files/Firebase/initialize');
const { assistantsConfigSetup } = require('./start/assistants');
const loadCustomConfig = require('./Config/loadCustomConfig'); const loadCustomConfig = require('./Config/loadCustomConfig');
const handleRateLimits = require('./Config/handleRateLimits'); const handleRateLimits = require('./Config/handleRateLimits');
const { azureConfigSetup } = require('./start/azureOpenAI');
const { loadAndFormatTools } = require('./ToolService'); const { loadAndFormatTools } = require('./ToolService');
const paths = require('~/config/paths'); const paths = require('~/config/paths');
const { logger } = require('~/config');
const secretDefaults = {
CREDS_KEY: 'f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0',
CREDS_IV: 'e2341419ec3dd3d19b13a1a87fafcbfb',
JWT_SECRET: '16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef',
JWT_REFRESH_SECRET: 'eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418',
};
/** /**
* *
@ -39,6 +27,9 @@ const AppService = async (app) => {
const imageOutputType = config?.imageOutputType ?? EImageOutputType.PNG; const imageOutputType = config?.imageOutputType ?? EImageOutputType.PNG;
process.env.CDN_PROVIDER = fileStrategy; process.env.CDN_PROVIDER = fileStrategy;
checkVariables();
await checkHealth();
if (fileStrategy === FileSources.firebase) { if (fileStrategy === FileSources.firebase) {
initializeFirebase(); initializeFirebase();
} }
@ -59,161 +50,41 @@ const AppService = async (app) => {
if (!Object.keys(config).length) { if (!Object.keys(config).length) {
app.locals = { app.locals = {
paths,
fileStrategy, fileStrategy,
socialLogins, socialLogins,
availableTools, availableTools,
imageOutputType, imageOutputType,
paths,
}; };
return; return;
} }
if (config.version !== Constants.CONFIG_VERSION) { checkConfig(config);
logger.info(
`\nOutdated Config version: ${config.version}. Current version: ${Constants.CONFIG_VERSION}\n\nCheck out the latest config file guide for new options and features.\nhttps://docs.librechat.ai/install/configuration/custom_config.html\n\n`,
);
}
handleRateLimits(config?.rateLimits); handleRateLimits(config?.rateLimits);
const endpointLocals = {}; const endpointLocals = {};
if (config?.endpoints?.[EModelEndpoint.azureOpenAI]) { if (config?.endpoints?.[EModelEndpoint.azureOpenAI]) {
const { groups, ...azureConfiguration } = config.endpoints[EModelEndpoint.azureOpenAI]; endpointLocals[EModelEndpoint.azureOpenAI] = azureConfigSetup(config);
const { isValid, modelNames, modelGroupMap, groupMap, errors } = validateAzureGroups(groups); checkAzureVariables();
if (!isValid) {
const errorString = errors.join('\n');
const errorMessage = 'Invalid Azure OpenAI configuration:\n' + errorString;
logger.error(errorMessage);
throw new Error(errorMessage);
}
const assistantModels = [];
const assistantGroups = new Set();
for (const modelName of modelNames) {
mapModelToAzureConfig({ modelName, modelGroupMap, groupMap });
const groupName = modelGroupMap?.[modelName]?.group;
const modelGroup = groupMap?.[groupName];
let supportsAssistants = modelGroup?.assistants || modelGroup?.[modelName]?.assistants;
if (supportsAssistants) {
assistantModels.push(modelName);
!assistantGroups.has(groupName) && assistantGroups.add(groupName);
}
}
if (azureConfiguration.assistants && assistantModels.length === 0) {
throw new Error(
'No Azure models are configured to support assistants. Please remove the `assistants` field or configure at least one model to support assistants.',
);
}
endpointLocals[EModelEndpoint.azureOpenAI] = {
modelNames,
modelGroupMap,
groupMap,
assistantModels,
assistantGroups: Array.from(assistantGroups),
...azureConfiguration,
};
deprecatedAzureVariables.forEach(({ key, description }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable (related to ${description}) should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you will experience conflicts and errors.`,
);
}
});
conflictingAzureVariables.forEach(({ key }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you may experience with the defined placeholders for mapping to the current model grouping using the same name.`,
);
}
});
if (azureConfiguration.assistants) {
endpointLocals[EModelEndpoint.assistants] = {
// Note: may need to add retrieval models here in the future
capabilities: [Capabilities.tools, Capabilities.actions, Capabilities.code_interpreter],
};
}
} }
if (config?.endpoints?.[EModelEndpoint.assistants]) { if (config?.endpoints?.[EModelEndpoint.assistants]) {
const assistantsConfig = config.endpoints[EModelEndpoint.assistants]; endpointLocals[EModelEndpoint.assistants] = assistantsConfigSetup(config);
const parsedConfig = assistantEndpointSchema.parse(assistantsConfig);
if (assistantsConfig.supportedIds?.length && assistantsConfig.excludedIds?.length) {
logger.warn(
`Both \`supportedIds\` and \`excludedIds\` are defined for the ${EModelEndpoint.assistants} endpoint; \`excludedIds\` field will be ignored.`,
);
}
const prevConfig = endpointLocals[EModelEndpoint.assistants] ?? {};
/** @type {Partial<TAssistantEndpoint>} */
endpointLocals[EModelEndpoint.assistants] = {
...prevConfig,
retrievalModels: parsedConfig.retrievalModels,
disableBuilder: parsedConfig.disableBuilder,
pollIntervalMs: parsedConfig.pollIntervalMs,
supportedIds: parsedConfig.supportedIds,
capabilities: parsedConfig.capabilities,
excludedIds: parsedConfig.excludedIds,
timeoutMs: parsedConfig.timeoutMs,
};
}
try {
const response = await fetch(`${process.env.RAG_API_URL}/health`);
if (response?.ok && response?.status === 200) {
logger.info(`RAG API is running and reachable at ${process.env.RAG_API_URL}.`);
}
} catch (error) {
logger.warn(
`RAG API is either not running or not reachable at ${process.env.RAG_API_URL}, you may experience errors with file uploads.`,
);
} }
app.locals = { app.locals = {
paths,
socialLogins, socialLogins,
fileStrategy, fileStrategy,
availableTools, availableTools,
imageOutputType, imageOutputType,
fileConfig: config?.fileConfig,
interface: config?.interface, interface: config?.interface,
fileConfig: config?.fileConfig,
secureImageLinks: config?.secureImageLinks, secureImageLinks: config?.secureImageLinks,
paths,
...endpointLocals, ...endpointLocals,
}; };
let hasDefaultSecrets = false;
for (const [key, value] of Object.entries(secretDefaults)) {
if (process.env[key] === value) {
logger.warn(`Default value for ${key} is being used.`);
!hasDefaultSecrets && (hasDefaultSecrets = true);
}
}
if (hasDefaultSecrets) {
logger.info(
`Please replace any default secret values.
For your conveninence, fork & run this replit to generate your own secret values:
https://replit.com/@daavila/crypto#index.js
`,
);
}
if (process.env.GOOGLE_API_KEY) {
logger.warn(
'The `GOOGLE_API_KEY` environment variable is deprecated.\nPlease use the `GOOGLE_SEARCH_API_KEY` environment variable instead.',
);
}
}; };
module.exports = AppService; module.exports = AppService;

View file

@ -0,0 +1,40 @@
const {
Capabilities,
EModelEndpoint,
assistantEndpointSchema,
} = require('librechat-data-provider');
const { logger } = require('~/config');
/**
* Sets up the Assistants configuration from the config (`librechat.yaml`) file.
* @param {TCustomConfig} config - The loaded custom configuration.
* @returns {Partial<TAssistantEndpoint>} The Assistants endpoint configuration.
*/
function assistantsConfigSetup(config) {
const assistantsConfig = config.endpoints[EModelEndpoint.assistants];
const parsedConfig = assistantEndpointSchema.parse(assistantsConfig);
if (assistantsConfig.supportedIds?.length && assistantsConfig.excludedIds?.length) {
logger.warn(
`Both \`supportedIds\` and \`excludedIds\` are defined for the ${EModelEndpoint.assistants} endpoint; \`excludedIds\` field will be ignored.`,
);
}
const prevConfig = config.endpoints[EModelEndpoint.azureOpenAI]?.assistants
? {
capabilities: [Capabilities.tools, Capabilities.actions, Capabilities.code_interpreter],
}
: {};
return {
...prevConfig,
retrievalModels: parsedConfig.retrievalModels,
disableBuilder: parsedConfig.disableBuilder,
pollIntervalMs: parsedConfig.pollIntervalMs,
supportedIds: parsedConfig.supportedIds,
capabilities: parsedConfig.capabilities,
excludedIds: parsedConfig.excludedIds,
timeoutMs: parsedConfig.timeoutMs,
};
}
module.exports = { assistantsConfigSetup };

View file

@ -0,0 +1,54 @@
const {
EModelEndpoint,
validateAzureGroups,
mapModelToAzureConfig,
} = require('librechat-data-provider');
const { logger } = require('~/config');
/**
* Sets up the Azure OpenAI configuration from the config (`librechat.yaml`) file.
* @param {TCustomConfig} config - The loaded custom configuration.
* @returns {TAzureConfig} The Azure OpenAI configuration.
*/
function azureConfigSetup(config) {
const { groups, ...azureConfiguration } = config.endpoints[EModelEndpoint.azureOpenAI];
/** @type {TAzureConfigValidationResult} */
const { isValid, modelNames, modelGroupMap, groupMap, errors } = validateAzureGroups(groups);
if (!isValid) {
const errorString = errors.join('\n');
const errorMessage = 'Invalid Azure OpenAI configuration:\n' + errorString;
logger.error(errorMessage);
throw new Error(errorMessage);
}
const assistantModels = [];
const assistantGroups = new Set();
for (const modelName of modelNames) {
mapModelToAzureConfig({ modelName, modelGroupMap, groupMap });
const groupName = modelGroupMap?.[modelName]?.group;
const modelGroup = groupMap?.[groupName];
let supportsAssistants = modelGroup?.assistants || modelGroup?.[modelName]?.assistants;
if (supportsAssistants) {
assistantModels.push(modelName);
!assistantGroups.has(groupName) && assistantGroups.add(groupName);
}
}
if (azureConfiguration.assistants && assistantModels.length === 0) {
throw new Error(
'No Azure models are configured to support assistants. Please remove the `assistants` field or configure at least one model to support assistants.',
);
}
return {
modelNames,
modelGroupMap,
groupMap,
assistantModels,
assistantGroups: Array.from(assistantGroups),
...azureConfiguration,
};
}
module.exports = { azureConfigSetup };

View file

@ -0,0 +1,107 @@
const {
Constants,
deprecatedAzureVariables,
conflictingAzureVariables,
} = require('librechat-data-provider');
const { logger } = require('~/config');
const secretDefaults = {
CREDS_KEY: 'f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0',
CREDS_IV: 'e2341419ec3dd3d19b13a1a87fafcbfb',
JWT_SECRET: '16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef',
JWT_REFRESH_SECRET: 'eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418',
};
/**
* Checks environment variables for default secrets and deprecated variables.
* Logs warnings for any default secret values being used and for usage of deprecated `GOOGLE_API_KEY`.
* Advises on replacing default secrets and updating deprecated variables.
*/
function checkVariables() {
let hasDefaultSecrets = false;
for (const [key, value] of Object.entries(secretDefaults)) {
if (process.env[key] === value) {
logger.warn(`Default value for ${key} is being used.`);
!hasDefaultSecrets && (hasDefaultSecrets = true);
}
}
if (hasDefaultSecrets) {
logger.info(
`Please replace any default secret values.
For your conveninence, fork & run this replit to generate your own secret values:
https://replit.com/@daavila/crypto#index.js
`,
);
}
if (process.env.GOOGLE_API_KEY) {
logger.warn(
'The `GOOGLE_API_KEY` environment variable is deprecated.\nPlease use the `GOOGLE_SEARCH_API_KEY` environment variable instead.',
);
}
if (process.env.OPENROUTER_API_KEY) {
logger.warn(
`The \`OPENROUTER_API_KEY\` environment variable is deprecated and its functionality will be removed soon.
Use of this environment variable is highly discouraged as it can lead to unexpected errors when using custom endpoints.
Please use the config (\`librechat.yaml\`) file for setting up OpenRouter, and use \`OPENROUTER_KEY\` or another environment variable instead.`,
);
}
}
/**
* Checks the health of auxiliary API's by attempting a fetch request to their respective `/health` endpoints.
* Logs information or warning based on the API's availability and response.
*/
async function checkHealth() {
try {
const response = await fetch(`${process.env.RAG_API_URL}/health`);
if (response?.ok && response?.status === 200) {
logger.info(`RAG API is running and reachable at ${process.env.RAG_API_URL}.`);
}
} catch (error) {
logger.warn(
`RAG API is either not running or not reachable at ${process.env.RAG_API_URL}, you may experience errors with file uploads.`,
);
}
}
/**
* Checks for the usage of deprecated and conflicting Azure variables.
* Logs warnings for any deprecated or conflicting environment variables found, indicating potential issues with `azureOpenAI` endpoint configuration.
*/
function checkAzureVariables() {
deprecatedAzureVariables.forEach(({ key, description }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable (related to ${description}) should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you will experience conflicts and errors.`,
);
}
});
conflictingAzureVariables.forEach(({ key }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you may experience with the defined placeholders for mapping to the current model grouping using the same name.`,
);
}
});
}
/**
* Performs basic checks on the loaded config object.
* @param {TCustomConfig} config - The loaded custom configuration.
*/
function checkConfig(config) {
if (config.version !== Constants.CONFIG_VERSION) {
logger.info(
`\nOutdated Config version: ${config.version}. Current version: ${Constants.CONFIG_VERSION}\n\nCheck out the latest config file guide for new options and features.\nhttps://docs.librechat.ai/install/configuration/custom_config.html\n\n`,
);
}
}
module.exports = { checkVariables, checkHealth, checkConfig, checkAzureVariables };

View file

@ -301,6 +301,12 @@
* @memberof typedefs * @memberof typedefs
*/ */
/**
* @exports TAzureConfigValidationResult
* @typedef {import('librechat-data-provider').TAzureConfigValidationResult} TAzureConfigValidationResult
* @memberof typedefs
*/
/** /**
* @exports EImageOutputType * @exports EImageOutputType
* @typedef {import('librechat-data-provider').EImageOutputType} EImageOutputType * @typedef {import('librechat-data-provider').EImageOutputType} EImageOutputType

View file

@ -2,7 +2,7 @@
# https://docs.librechat.ai/install/configuration/custom_config.html # https://docs.librechat.ai/install/configuration/custom_config.html
# Configuration version (required) # Configuration version (required)
version: 1.0.5 version: 1.0.6
# Cache settings: Set to true to enable caching # Cache settings: Set to true to enable caching
cache: true cache: true

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.5.4", "version": "0.5.5",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",

View file

@ -4,6 +4,7 @@ import type {
TAzureGroupMap, TAzureGroupMap,
TAzureModelGroupMap, TAzureModelGroupMap,
TValidatedAzureConfig, TValidatedAzureConfig,
TAzureConfigValidationResult,
} from '../src/config'; } from '../src/config';
import { errorsToString, extractEnvVariable, envVarRegex } from '../src/parsers'; import { errorsToString, extractEnvVariable, envVarRegex } from '../src/parsers';
import { azureGroupConfigsSchema } from '../src/config'; import { azureGroupConfigsSchema } from '../src/config';
@ -46,10 +47,7 @@ export const conflictingAzureVariables = [
}, },
]; ];
export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfig & { export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidationResult {
isValid: boolean;
errors: (ZodError | string)[];
} {
let isValid = true; let isValid = true;
const modelNames: string[] = []; const modelNames: string[] = [];
const modelGroupMap: TAzureModelGroupMap = {}; const modelGroupMap: TAzureModelGroupMap = {};

View file

@ -1,5 +1,6 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
import { z } from 'zod'; import { z } from 'zod';
import type { ZodError } from 'zod';
import { EModelEndpoint, eModelEndpointSchema } from './schemas'; import { EModelEndpoint, eModelEndpointSchema } from './schemas';
import { fileConfigSchema } from './file-config'; import { fileConfigSchema } from './file-config';
import { FileSources } from './types/files'; import { FileSources } from './types/files';
@ -81,6 +82,11 @@ export type TValidatedAzureConfig = {
groupMap: TAzureGroupMap; groupMap: TAzureGroupMap;
}; };
export type TAzureConfigValidationResult = TValidatedAzureConfig & {
isValid: boolean;
errors: (ZodError | string)[];
};
export enum Capabilities { export enum Capabilities {
code_interpreter = 'code_interpreter', code_interpreter = 'code_interpreter',
image_vision = 'image_vision', image_vision = 'image_vision',
@ -173,7 +179,7 @@ export const azureEndpointSchema = z
); );
export type TAzureConfig = Omit<z.infer<typeof azureEndpointSchema>, 'groups'> & export type TAzureConfig = Omit<z.infer<typeof azureEndpointSchema>, 'groups'> &
TValidatedAzureConfig; TAzureConfigValidationResult;
export const rateLimitSchema = z.object({ export const rateLimitSchema = z.object({
fileUploads: z fileUploads: z