mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 11:20:15 +01:00
refactor: implement custom endpoints configuration and streamline endpoint loading logic
This commit is contained in:
parent
240e3bd59e
commit
5eef6ea9e8
12 changed files with 100 additions and 88 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
const { loadCustomEndpointsConfig } = require('@librechat/api');
|
||||||
const {
|
const {
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
|
|
@ -6,7 +7,6 @@ const {
|
||||||
defaultAgentCapabilities,
|
defaultAgentCapabilities,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
|
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
|
||||||
const loadConfigEndpoints = require('./loadConfigEndpoints');
|
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const { getAppConfig } = require('./app');
|
const { getAppConfig } = require('./app');
|
||||||
|
|
||||||
|
|
@ -22,12 +22,30 @@ async function getEndpointsConfig(req) {
|
||||||
return cachedEndpointsConfig;
|
return cachedEndpointsConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
|
|
||||||
const customConfigEndpoints = await loadConfigEndpoints(req);
|
|
||||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(appConfig);
|
||||||
|
const customEndpointsConfig = loadCustomEndpointsConfig(appConfig?.endpoints?.custom);
|
||||||
|
|
||||||
/** @type {TEndpointsConfig} */
|
/** @type {TEndpointsConfig} */
|
||||||
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
|
const mergedConfig = {
|
||||||
|
...defaultEndpointsConfig,
|
||||||
|
...customEndpointsConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]) {
|
||||||
|
/** @type {Omit<TConfig, 'order'>} */
|
||||||
|
mergedConfig[EModelEndpoint.azureOpenAI] = {
|
||||||
|
userProvide: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
|
/** @type {Omit<TConfig, 'order'>} */
|
||||||
|
mergedConfig[EModelEndpoint.azureAssistants] = {
|
||||||
|
userProvide: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
mergedConfig[EModelEndpoint.assistants] &&
|
mergedConfig[EModelEndpoint.assistants] &&
|
||||||
appConfig?.endpoints?.[EModelEndpoint.assistants]
|
appConfig?.endpoints?.[EModelEndpoint.assistants]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { loadServiceKey, isUserProvided } = require('@librechat/api');
|
|
||||||
const { EModelEndpoint } = require('librechat-data-provider');
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
|
const { loadServiceKey, isUserProvided } = require('@librechat/api');
|
||||||
const { config } = require('./EndpointService');
|
const { config } = require('./EndpointService');
|
||||||
const { getAppConfig } = require('./app');
|
|
||||||
|
|
||||||
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
|
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load async endpoints and return a configuration object
|
* Load async endpoints and return a configuration object
|
||||||
* @param {Express.Request} req - The request object
|
* @param {AppConfig} [appConfig] - The app configuration object
|
||||||
*/
|
*/
|
||||||
async function loadAsyncEndpoints(req) {
|
async function loadAsyncEndpoints(appConfig) {
|
||||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
|
||||||
let serviceKey, googleUserProvides;
|
let serviceKey, googleUserProvides;
|
||||||
|
|
||||||
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
|
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
|
||||||
|
|
@ -36,7 +34,7 @@ async function loadAsyncEndpoints(req) {
|
||||||
|
|
||||||
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
|
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
|
||||||
|
|
||||||
const useAzure = appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.plugins;
|
const useAzure = !!appConfig?.endpoints?.[EModelEndpoint.azureOpenAI]?.plugins;
|
||||||
const gptPlugins =
|
const gptPlugins =
|
||||||
useAzure || openAIApiKey || azureOpenAIApiKey
|
useAzure || openAIApiKey || azureOpenAIApiKey
|
||||||
? {
|
? {
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
const { isUserProvided, normalizeEndpointName } = require('@librechat/api');
|
|
||||||
const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider');
|
|
||||||
const { getAppConfig } = require('./app');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load config endpoints from the cached configuration object
|
|
||||||
* @param {Express.Request} req - The request object
|
|
||||||
* @returns {Promise<TEndpointsConfig>} A promise that resolves to an object containing the endpoints configuration
|
|
||||||
*/
|
|
||||||
async function loadConfigEndpoints(req) {
|
|
||||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
|
||||||
if (!appConfig) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpointsConfig = {};
|
|
||||||
|
|
||||||
if (Array.isArray(appConfig.endpoints?.[EModelEndpoint.custom])) {
|
|
||||||
const customEndpoints = appConfig.endpoints[EModelEndpoint.custom].filter(
|
|
||||||
(endpoint) =>
|
|
||||||
endpoint.baseURL &&
|
|
||||||
endpoint.apiKey &&
|
|
||||||
endpoint.name &&
|
|
||||||
endpoint.models &&
|
|
||||||
(endpoint.models.fetch || endpoint.models.default),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < customEndpoints.length; i++) {
|
|
||||||
const endpoint = customEndpoints[i];
|
|
||||||
const {
|
|
||||||
baseURL,
|
|
||||||
apiKey,
|
|
||||||
name: configName,
|
|
||||||
iconURL,
|
|
||||||
modelDisplayLabel,
|
|
||||||
customParams,
|
|
||||||
} = endpoint;
|
|
||||||
const name = normalizeEndpointName(configName);
|
|
||||||
|
|
||||||
const resolvedApiKey = extractEnvVariable(apiKey);
|
|
||||||
const resolvedBaseURL = extractEnvVariable(baseURL);
|
|
||||||
|
|
||||||
endpointsConfig[name] = {
|
|
||||||
type: EModelEndpoint.custom,
|
|
||||||
userProvide: isUserProvided(resolvedApiKey),
|
|
||||||
userProvideURL: isUserProvided(resolvedBaseURL),
|
|
||||||
modelDisplayLabel,
|
|
||||||
iconURL,
|
|
||||||
customParams,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]) {
|
|
||||||
/** @type {Omit<TConfig, 'order'>} */
|
|
||||||
endpointsConfig[EModelEndpoint.azureOpenAI] = {
|
|
||||||
userProvide: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
|
|
||||||
/** @type {Omit<TConfig, 'order'>} */
|
|
||||||
endpointsConfig[EModelEndpoint.azureAssistants] = {
|
|
||||||
userProvide: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpointsConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = loadConfigEndpoints;
|
|
||||||
|
|
@ -4,11 +4,11 @@ const { config } = require('./EndpointService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load async endpoints and return a configuration object
|
* Load async endpoints and return a configuration object
|
||||||
* @param {Express.Request} req - The request object
|
* @param {AppConfig} appConfig - The app configuration object
|
||||||
* @returns {Promise<Object.<string, EndpointWithOrder>>} An object whose keys are endpoint names and values are objects that contain the endpoint configuration and an order.
|
* @returns {Promise<Object.<string, EndpointWithOrder>>} An object whose keys are endpoint names and values are objects that contain the endpoint configuration and an order.
|
||||||
*/
|
*/
|
||||||
async function loadDefaultEndpointsConfig(req) {
|
async function loadDefaultEndpointsConfig(appConfig) {
|
||||||
const { google, gptPlugins } = await loadAsyncEndpoints(req);
|
const { google, gptPlugins } = await loadAsyncEndpoints(appConfig);
|
||||||
const { assistants, azureAssistants, azureOpenAI, chatGPTBrowser } = config;
|
const { assistants, azureAssistants, azureOpenAI, chatGPTBrowser } = config;
|
||||||
|
|
||||||
const enabledEndpoints = getEnabledEndpoints();
|
const enabledEndpoints = getEnabledEndpoints();
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ const loadEndpoints = (config, agentsDefaults) => {
|
||||||
const endpointKeys = [
|
const endpointKeys = [
|
||||||
EModelEndpoint.openAI,
|
EModelEndpoint.openAI,
|
||||||
EModelEndpoint.google,
|
EModelEndpoint.google,
|
||||||
|
EModelEndpoint.custom,
|
||||||
EModelEndpoint.bedrock,
|
EModelEndpoint.bedrock,
|
||||||
EModelEndpoint.anthropic,
|
EModelEndpoint.anthropic,
|
||||||
EModelEndpoint.gptPlugins,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
endpointKeys.forEach((key) => {
|
endpointKeys.forEach((key) => {
|
||||||
|
|
|
||||||
56
packages/api/src/endpoints/custom/config.ts
Normal file
56
packages/api/src/endpoints/custom/config.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { EModelEndpoint, extractEnvVariable } from 'librechat-data-provider';
|
||||||
|
import type { TCustomEndpoints, TEndpoint, TConfig } from 'librechat-data-provider';
|
||||||
|
import type { TCustomEndpointsConfig } from '~/types/endpoints';
|
||||||
|
import { isUserProvided, normalizeEndpointName } from '~/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config endpoints from the cached configuration object
|
||||||
|
* @param customEndpointsConfig - The configuration object
|
||||||
|
*/
|
||||||
|
export function loadCustomEndpointsConfig(
|
||||||
|
customEndpoints?: TCustomEndpoints,
|
||||||
|
): TCustomEndpointsConfig | undefined {
|
||||||
|
if (!customEndpoints) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customEndpointsConfig: TCustomEndpointsConfig = {};
|
||||||
|
|
||||||
|
if (Array.isArray(customEndpoints)) {
|
||||||
|
const filteredEndpoints = customEndpoints.filter(
|
||||||
|
(endpoint) =>
|
||||||
|
endpoint.baseURL &&
|
||||||
|
endpoint.apiKey &&
|
||||||
|
endpoint.name &&
|
||||||
|
endpoint.models &&
|
||||||
|
(endpoint.models.fetch || endpoint.models.default),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < filteredEndpoints.length; i++) {
|
||||||
|
const endpoint = filteredEndpoints[i] as TEndpoint;
|
||||||
|
const {
|
||||||
|
baseURL,
|
||||||
|
apiKey,
|
||||||
|
name: configName,
|
||||||
|
iconURL,
|
||||||
|
modelDisplayLabel,
|
||||||
|
customParams,
|
||||||
|
} = endpoint;
|
||||||
|
const name = normalizeEndpointName(configName);
|
||||||
|
|
||||||
|
const resolvedApiKey = extractEnvVariable(apiKey ?? '');
|
||||||
|
const resolvedBaseURL = extractEnvVariable(baseURL ?? '');
|
||||||
|
|
||||||
|
customEndpointsConfig[name] = {
|
||||||
|
type: EModelEndpoint.custom,
|
||||||
|
userProvide: isUserProvided(resolvedApiKey),
|
||||||
|
userProvideURL: isUserProvided(resolvedBaseURL),
|
||||||
|
customParams: customParams as TConfig['customParams'],
|
||||||
|
modelDisplayLabel,
|
||||||
|
iconURL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return customEndpointsConfig;
|
||||||
|
}
|
||||||
1
packages/api/src/endpoints/custom/index.ts
Normal file
1
packages/api/src/endpoints/custom/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './config';
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
export * from './custom';
|
||||||
export * from './google';
|
export * from './google';
|
||||||
export * from './openai';
|
export * from './openai';
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type {
|
||||||
TMemoryConfig,
|
TMemoryConfig,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
TAgentsEndpoint,
|
TAgentsEndpoint,
|
||||||
|
TCustomEndpoints,
|
||||||
TAssistantEndpoint,
|
TAssistantEndpoint,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
|
|
||||||
|
|
@ -78,9 +79,9 @@ export interface AppConfig {
|
||||||
azureAssistants?: TAssistantEndpoint;
|
azureAssistants?: TAssistantEndpoint;
|
||||||
/** Agents endpoint configuration */
|
/** Agents endpoint configuration */
|
||||||
[EModelEndpoint.agents]?: TAgentsEndpoint;
|
[EModelEndpoint.agents]?: TAgentsEndpoint;
|
||||||
|
/** Custom endpoints configuration */
|
||||||
|
[EModelEndpoint.custom]?: TCustomEndpoints;
|
||||||
/** Global endpoint configuration */
|
/** Global endpoint configuration */
|
||||||
all?: TEndpoint;
|
all?: TEndpoint;
|
||||||
/** Any additional endpoint configurations */
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
packages/api/src/types/endpoints.ts
Normal file
3
packages/api/src/types/endpoints.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import type { TConfig } from 'librechat-data-provider';
|
||||||
|
|
||||||
|
export type TCustomEndpointsConfig = Partial<{ [key: string]: Omit<TConfig, 'order'> }>;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './config';
|
export * from './config';
|
||||||
export * from './azure';
|
export * from './azure';
|
||||||
export * from './balance';
|
export * from './balance';
|
||||||
|
export * from './endpoints';
|
||||||
export * from './events';
|
export * from './events';
|
||||||
export * from './error';
|
export * from './error';
|
||||||
export * from './google';
|
export * from './google';
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,7 @@ export const endpointSchema = baseEndpointSchema.merge(
|
||||||
}),
|
}),
|
||||||
summarize: z.boolean().optional(),
|
summarize: z.boolean().optional(),
|
||||||
summaryModel: z.string().optional(),
|
summaryModel: z.string().optional(),
|
||||||
|
iconURL: z.string().optional(),
|
||||||
forcePrompt: z.boolean().optional(),
|
forcePrompt: z.boolean().optional(),
|
||||||
modelDisplayLabel: z.string().optional(),
|
modelDisplayLabel: z.string().optional(),
|
||||||
headers: z.record(z.any()).optional(),
|
headers: z.record(z.any()).optional(),
|
||||||
|
|
@ -789,6 +790,8 @@ export const memorySchema = z.object({
|
||||||
|
|
||||||
export type TMemoryConfig = z.infer<typeof memorySchema>;
|
export type TMemoryConfig = z.infer<typeof memorySchema>;
|
||||||
|
|
||||||
|
const customEndpointsSchema = z.array(endpointSchema.partial()).optional();
|
||||||
|
|
||||||
export const configSchema = z.object({
|
export const configSchema = z.object({
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
cache: z.boolean().default(true),
|
cache: z.boolean().default(true),
|
||||||
|
|
@ -837,7 +840,7 @@ export const configSchema = z.object({
|
||||||
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
|
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
|
||||||
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
|
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
|
||||||
[EModelEndpoint.agents]: agentsEndpointSchema.optional(),
|
[EModelEndpoint.agents]: agentsEndpointSchema.optional(),
|
||||||
[EModelEndpoint.custom]: z.array(endpointSchema.partial()).optional(),
|
[EModelEndpoint.custom]: customEndpointsSchema.optional(),
|
||||||
[EModelEndpoint.bedrock]: baseEndpointSchema.optional(),
|
[EModelEndpoint.bedrock]: baseEndpointSchema.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
@ -850,6 +853,7 @@ export const configSchema = z.object({
|
||||||
export const getConfigDefaults = () => getSchemaDefaults(configSchema);
|
export const getConfigDefaults = () => getSchemaDefaults(configSchema);
|
||||||
|
|
||||||
export type TCustomConfig = z.infer<typeof configSchema>;
|
export type TCustomConfig = z.infer<typeof configSchema>;
|
||||||
|
export type TCustomEndpoints = z.infer<typeof customEndpointsSchema>;
|
||||||
|
|
||||||
export type TProviderSchema =
|
export type TProviderSchema =
|
||||||
| z.infer<typeof ttsOpenaiSchema>
|
| z.infer<typeof ttsOpenaiSchema>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue