diff --git a/api/cache/getLogStores.js b/api/cache/getLogStores.js index 7f759e5f33..894edd2162 100644 --- a/api/cache/getLogStores.js +++ b/api/cache/getLogStores.js @@ -31,6 +31,10 @@ const genTitle = isEnabled(USE_REDIS) // ttl: 2 minutes ? new Keyv({ store: keyvRedis, ttl: 120000 }) : new Keyv({ namespace: CacheKeys.GEN_TITLE, ttl: 120000 }); +const modelQueries = isEnabled(process.env.USE_REDIS) + ? new Keyv({ store: keyvRedis }) + : new Keyv({ namespace: 'models' }); + const namespaces = { [CacheKeys.CONFIG_STORE]: config, pending_req, @@ -44,6 +48,7 @@ const namespaces = { logins: createViolationInstance('logins'), [CacheKeys.TOKEN_CONFIG]: tokenConfig, [CacheKeys.GEN_TITLE]: genTitle, + [CacheKeys.MODEL_QUERIES]: modelQueries, }; /** diff --git a/api/server/controllers/ModelController.js b/api/server/controllers/ModelController.js index 2d23961e15..0025ef910d 100644 --- a/api/server/controllers/ModelController.js +++ b/api/server/controllers/ModelController.js @@ -9,8 +9,8 @@ async function modelController(req, res) { res.send(cachedModelsConfig); return; } - const defaultModelsConfig = await loadDefaultModels(); - const customModelsConfig = await loadConfigModels(); + const defaultModelsConfig = await loadDefaultModels(req); + const customModelsConfig = await loadConfigModels(req); const modelConfig = { ...defaultModelsConfig, ...customModelsConfig }; diff --git a/api/server/services/AuthService.js b/api/server/services/AuthService.js index 62e3ee8396..0811b10fa2 100644 --- a/api/server/services/AuthService.js +++ b/api/server/services/AuthService.js @@ -1,7 +1,7 @@ const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const { registerSchema, errorsToString } = require('~/strategies/validators'); -const getCustomConfig = require('~/cache/getCustomConfig'); +const getCustomConfig = require('~/server/services/Config/getCustomConfig'); const Token = require('~/models/schema/tokenSchema'); const { sendEmail } = require('~/server/utils'); const Session = require('~/models/Session'); diff --git a/api/server/services/AuthService.spec.js b/api/server/services/AuthService.spec.js index 23aad21d3f..fb5d8e2533 100644 --- a/api/server/services/AuthService.spec.js +++ b/api/server/services/AuthService.spec.js @@ -1,7 +1,7 @@ -const getCustomConfig = require('~/cache/getCustomConfig'); +const getCustomConfig = require('~/server/services/Config/getCustomConfig'); const { isDomainAllowed } = require('./AuthService'); -jest.mock('~/cache/getCustomConfig', () => jest.fn()); +jest.mock('~/server/services/Config/getCustomConfig', () => jest.fn()); describe('isDomainAllowed', () => { it('should allow domain when customConfig is not available', async () => { diff --git a/api/cache/getCustomConfig.js b/api/server/services/Config/getCustomConfig.js similarity index 80% rename from api/cache/getCustomConfig.js rename to api/server/services/Config/getCustomConfig.js index 6d5cf8f275..a479ca37b7 100644 --- a/api/cache/getCustomConfig.js +++ b/api/server/services/Config/getCustomConfig.js @@ -1,6 +1,6 @@ const { CacheKeys } = require('librechat-data-provider'); -const loadCustomConfig = require('~/server/services/Config/loadCustomConfig'); -const getLogStores = require('./getLogStores'); +const loadCustomConfig = require('./loadCustomConfig'); +const getLogStores = require('~/cache/getLogStores'); /** * Retrieves the configuration object diff --git a/api/server/services/Config/index.js b/api/server/services/Config/index.js index 57a00bf515..2e8ccb1433 100644 --- a/api/server/services/Config/index.js +++ b/api/server/services/Config/index.js @@ -1,4 +1,5 @@ const { config } = require('./EndpointService'); +const getCustomConfig = require('./getCustomConfig'); const loadCustomConfig = require('./loadCustomConfig'); const loadConfigModels = require('./loadConfigModels'); const loadDefaultModels = require('./loadDefaultModels'); @@ -9,6 +10,7 @@ const loadDefaultEndpointsConfig = require('./loadDefaultEConfig'); module.exports = { config, + getCustomConfig, loadCustomConfig, loadConfigModels, loadDefaultModels, diff --git a/api/server/services/Config/loadConfigEndpoints.js b/api/server/services/Config/loadConfigEndpoints.js index 1b435e144e..156688b059 100644 --- a/api/server/services/Config/loadConfigEndpoints.js +++ b/api/server/services/Config/loadConfigEndpoints.js @@ -1,18 +1,12 @@ -const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); +const { EModelEndpoint } = require('librechat-data-provider'); const { isUserProvided, extractEnvVariable } = require('~/server/utils'); -const loadCustomConfig = require('./loadCustomConfig'); -const { getLogStores } = require('~/cache'); +const getCustomConfig = require('./getCustomConfig'); /** * Load config endpoints from the cached configuration object * @function loadConfigEndpoints */ async function loadConfigEndpoints() { - const cache = getLogStores(CacheKeys.CONFIG_STORE); - let customConfig = await cache.get(CacheKeys.CUSTOM_CONFIG); - - if (!customConfig) { - customConfig = await loadCustomConfig(); - } + const customConfig = await getCustomConfig(); if (!customConfig) { return {}; diff --git a/api/server/services/Config/loadConfigModels.js b/api/server/services/Config/loadConfigModels.js index aff8614934..d78ad5950b 100644 --- a/api/server/services/Config/loadConfigModels.js +++ b/api/server/services/Config/loadConfigModels.js @@ -1,19 +1,15 @@ -const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); +const { EModelEndpoint } = require('librechat-data-provider'); const { isUserProvided, extractEnvVariable } = require('~/server/utils'); const { fetchModels } = require('~/server/services/ModelService'); -const loadCustomConfig = require('./loadCustomConfig'); -const { getLogStores } = require('~/cache'); +const getCustomConfig = require('./getCustomConfig'); /** * Load config endpoints from the cached configuration object - * @function loadConfigModels */ -async function loadConfigModels() { - const cache = getLogStores(CacheKeys.CONFIG_STORE); - let customConfig = await cache.get(CacheKeys.CUSTOM_CONFIG); - - if (!customConfig) { - customConfig = await loadCustomConfig(); - } + * @function loadConfigModels + * @param {Express.Request} req - The Express request object. + */ +async function loadConfigModels(req) { + const customConfig = await getCustomConfig(); if (!customConfig) { return {}; @@ -49,7 +45,14 @@ async function loadConfigModels() { if (models.fetch && !isUserProvided(API_KEY) && !isUserProvided(BASE_URL)) { fetchPromisesMap[BASE_URL] = - fetchPromisesMap[BASE_URL] || fetchModels({ baseURL: BASE_URL, apiKey: API_KEY, name }); + fetchPromisesMap[BASE_URL] || + fetchModels({ + user: req.user.id, + baseURL: BASE_URL, + apiKey: API_KEY, + name, + userIdQuery: models.userIdQuery, + }); baseUrlToNameMap[BASE_URL] = baseUrlToNameMap[BASE_URL] || []; baseUrlToNameMap[BASE_URL].push(name); continue; diff --git a/api/server/services/Config/loadCustomConfig.js b/api/server/services/Config/loadCustomConfig.js index ab99f2aa72..a90083b2fc 100644 --- a/api/server/services/Config/loadCustomConfig.js +++ b/api/server/services/Config/loadCustomConfig.js @@ -17,6 +17,7 @@ const configPath = path.resolve(projectRoot, 'librechat.yaml'); async function loadCustomConfig() { const customConfig = loadYaml(configPath); if (!customConfig) { + logger.info('Custom config file missing or YAML format invalid.'); return null; } @@ -25,7 +26,7 @@ async function loadCustomConfig() { logger.error(`Invalid custom config file at ${configPath}`, result.error); return null; } else { - logger.info('Loaded custom config file:'); + logger.info('Custom config file loaded:'); logger.info(JSON.stringify(customConfig, null, 2)); } diff --git a/api/server/services/Config/loadDefaultModels.js b/api/server/services/Config/loadDefaultModels.js index 665aa71479..e22a9b2769 100644 --- a/api/server/services/Config/loadDefaultModels.js +++ b/api/server/services/Config/loadDefaultModels.js @@ -11,13 +11,23 @@ const fitlerAssistantModels = (str) => { return /gpt-4|gpt-3\\.5/i.test(str) && !/vision|instruct/i.test(str); }; -async function loadDefaultModels() { +/** + * Loads the default models for the application. + * @async + * @function + * @param {Express.Request} req - The Express request object. + */ +async function loadDefaultModels(req) { const google = getGoogleModels(); - const openAI = await getOpenAIModels(); + const openAI = await getOpenAIModels({ user: req.user.id }); const anthropic = getAnthropicModels(); const chatGPTBrowser = getChatGPTBrowserModels(); - const azureOpenAI = await getOpenAIModels({ azure: true }); - const gptPlugins = await getOpenAIModels({ azure: useAzurePlugins, plugins: true }); + const azureOpenAI = await getOpenAIModels({ user: req.user.id, azure: true }); + const gptPlugins = await getOpenAIModels({ + user: req.user.id, + azure: useAzurePlugins, + plugins: true, + }); return { [EModelEndpoint.openAI]: openAI, diff --git a/api/server/services/Endpoints/custom/initializeClient.js b/api/server/services/Endpoints/custom/initializeClient.js index e5c9a62e83..c308bd6a71 100644 --- a/api/server/services/Endpoints/custom/initializeClient.js +++ b/api/server/services/Endpoints/custom/initializeClient.js @@ -1,8 +1,8 @@ const { EModelEndpoint, CacheKeys } = require('librechat-data-provider'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); +const getCustomConfig = require('~/server/services/Config/getCustomConfig'); const { isUserProvided, extractEnvVariable } = require('~/server/utils'); const { fetchModels } = require('~/server/services/ModelService'); -const getCustomConfig = require('~/cache/getCustomConfig'); const getLogStores = require('~/cache/getLogStores'); const { OpenAIClient } = require('~/app'); diff --git a/api/server/services/ModelService.js b/api/server/services/ModelService.js index 9a9f5238f6..45ae11aa1a 100644 --- a/api/server/services/ModelService.js +++ b/api/server/services/ModelService.js @@ -1,47 +1,35 @@ -const Keyv = require('keyv'); const axios = require('axios'); -const HttpsProxyAgent = require('https-proxy-agent'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const { EModelEndpoint, defaultModels, CacheKeys } = require('librechat-data-provider'); const { extractBaseURL, inputSchema, processModelData } = require('~/utils'); const getLogStores = require('~/cache/getLogStores'); -const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); // const { getAzureCredentials, genAzureChatCompletion } = require('~/utils/'); const { openAIApiKey, userProvidedOpenAI } = require('./Config/EndpointService').config; -const modelsCache = isEnabled(process.env.USE_REDIS) - ? new Keyv({ store: keyvRedis }) - : new Keyv({ namespace: 'models' }); - -const { - OPENROUTER_API_KEY, - OPENAI_REVERSE_PROXY, - CHATGPT_MODELS, - ANTHROPIC_MODELS, - GOOGLE_MODELS, - PROXY, -} = process.env ?? {}; - /** * Fetches OpenAI models from the specified base API path or Azure, based on the provided configuration. * * @param {Object} params - The parameters for fetching the models. + * @param {Object} params.user - The user ID to send to the API. * @param {string} params.apiKey - The API key for authentication with the API. * @param {string} params.baseURL - The base path URL for the API. * @param {string} [params.name='OpenAI'] - The name of the API; defaults to 'OpenAI'. * @param {boolean} [params.azure=false] - Whether to fetch models from Azure. + * @param {boolean} [params.userIdQuery=false] - Whether to send the user ID as a query parameter. * @param {boolean} [params.createTokenConfig=true] - Whether to create a token configuration from the API response. * @returns {Promise} A promise that resolves to an array of model identifiers. * @async */ const fetchModels = async ({ + user, apiKey, baseURL, name = 'OpenAI', azure = false, + userIdQuery = false, createTokenConfig = true, }) => { let models = []; @@ -51,21 +39,26 @@ const fetchModels = async ({ } try { - const payload = { + const options = { headers: { Authorization: `Bearer ${apiKey}`, }, }; - if (PROXY) { - payload.httpsAgent = new HttpsProxyAgent(PROXY); + if (process.env.PROXY) { + options.httpsAgent = new HttpsProxyAgent(process.env.PROXY); } if (process.env.OPENAI_ORGANIZATION && baseURL.includes('openai')) { - payload.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION; + options.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION; } - const res = await axios.get(`${baseURL}${azure ? '' : '/models'}`, payload); + const url = new URL(`${baseURL}${azure ? '' : '/models'}`); + if (user && userIdQuery) { + url.searchParams.append('user', user); + } + const res = await axios.get(url.toString(), options); + /** @type {z.infer} */ const input = res.data; @@ -83,11 +76,22 @@ const fetchModels = async ({ return models; }; -const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _models = []) => { +/** + * Fetches models from the specified API path or Azure, based on the provided options. + * @async + * @function + * @param {object} opts - The options for fetching the models. + * @param {string} opts.user - The user ID to send to the API. + * @param {boolean} [opts.azure=false] - Whether to fetch models from Azure. + * @param {boolean} [opts.plugins=false] - Whether to fetch models from the plugins. + * @param {string[]} [_models=[]] - The models to use as a fallback. + */ +const fetchOpenAIModels = async (opts, _models = []) => { let models = _models.slice() ?? []; let apiKey = openAIApiKey; - let baseURL = 'https://api.openai.com/v1'; - let reverseProxyUrl = OPENAI_REVERSE_PROXY; + const openaiBaseURL = 'https://api.openai.com/v1'; + let baseURL = openaiBaseURL; + let reverseProxyUrl = process.env.OPENAI_REVERSE_PROXY; if (opts.azure) { return models; // const azure = getAzureCredentials(); @@ -95,15 +99,17 @@ const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _model // .split('/deployments')[0] // .concat(`/models?api-version=${azure.azureOpenAIApiVersion}`); // apiKey = azureOpenAIApiKey; - } else if (OPENROUTER_API_KEY) { + } else if (process.env.OPENROUTER_API_KEY) { reverseProxyUrl = 'https://openrouter.ai/api/v1'; - apiKey = OPENROUTER_API_KEY; + apiKey = process.env.OPENROUTER_API_KEY; } if (reverseProxyUrl) { baseURL = extractBaseURL(reverseProxyUrl); } + const modelsCache = getLogStores(CacheKeys.MODEL_QUERIES); + const cachedModels = await modelsCache.get(baseURL); if (cachedModels) { return cachedModels; @@ -114,10 +120,15 @@ const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _model apiKey, baseURL, azure: opts.azure, + user: opts.user, }); } - if (!reverseProxyUrl) { + if (models.length === 0) { + return _models; + } + + if (baseURL === openaiBaseURL) { const regex = /(text-davinci-003|gpt-)/; models = models.filter((model) => regex.test(model)); } @@ -126,18 +137,27 @@ const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _model return models; }; -const getOpenAIModels = async (opts = { azure: false, plugins: false }) => { - let models = [ - 'gpt-4', - 'gpt-4-0613', - 'gpt-3.5-turbo', - 'gpt-3.5-turbo-16k', - 'gpt-3.5-turbo-0613', - 'gpt-3.5-turbo-0301', - ]; +/** + * Loads the default models for the application. + * @async + * @function + * @param {object} opts - The options for fetching the models. + * @param {string} opts.user - The user ID to send to the API. + * @param {boolean} [opts.azure=false] - Whether to fetch models from Azure. + * @param {boolean} [opts.plugins=false] - Whether to fetch models from the plugins. + */ +const getOpenAIModels = async (opts) => { + let models = defaultModels.openAI; - if (!opts.plugins) { - models.push('text-davinci-003'); + if (opts.plugins) { + models = models.filter( + (model) => + !model.includes('text-davinci') && + !model.includes('instruct') && + !model.includes('0613') && + !model.includes('0314') && + !model.includes('0301'), + ); } let key; @@ -154,7 +174,7 @@ const getOpenAIModels = async (opts = { azure: false, plugins: false }) => { return models; } - if (userProvidedOpenAI && !OPENROUTER_API_KEY) { + if (userProvidedOpenAI && !process.env.OPENROUTER_API_KEY) { return models; } @@ -163,8 +183,8 @@ const getOpenAIModels = async (opts = { azure: false, plugins: false }) => { const getChatGPTBrowserModels = () => { let models = ['text-davinci-002-render-sha', 'gpt-4']; - if (CHATGPT_MODELS) { - models = String(CHATGPT_MODELS).split(','); + if (process.env.CHATGPT_MODELS) { + models = String(process.env.CHATGPT_MODELS).split(','); } return models; @@ -172,8 +192,8 @@ const getChatGPTBrowserModels = () => { const getAnthropicModels = () => { let models = defaultModels[EModelEndpoint.anthropic]; - if (ANTHROPIC_MODELS) { - models = String(ANTHROPIC_MODELS).split(','); + if (process.env.ANTHROPIC_MODELS) { + models = String(process.env.ANTHROPIC_MODELS).split(','); } return models; @@ -181,8 +201,8 @@ const getAnthropicModels = () => { const getGoogleModels = () => { let models = defaultModels[EModelEndpoint.google]; - if (GOOGLE_MODELS) { - models = String(GOOGLE_MODELS).split(','); + if (process.env.GOOGLE_MODELS) { + models = String(process.env.GOOGLE_MODELS).split(','); } return models; diff --git a/api/server/services/ModelService.spec.js b/api/server/services/ModelService.spec.js new file mode 100644 index 0000000000..19fa407ce8 --- /dev/null +++ b/api/server/services/ModelService.spec.js @@ -0,0 +1,212 @@ +const axios = require('axios'); + +const { fetchModels, getOpenAIModels } = require('./ModelService'); +jest.mock('~/utils', () => { + const originalUtils = jest.requireActual('~/utils'); + return { + ...originalUtils, + processModelData: jest.fn((...args) => { + return originalUtils.processModelData(...args); + }), + }; +}); + +jest.mock('axios'); +jest.mock('~/cache/getLogStores', () => + jest.fn().mockImplementation(() => ({ + get: jest.fn().mockResolvedValue(undefined), + set: jest.fn().mockResolvedValue(true), + })), +); +jest.mock('~/config', () => ({ + logger: { + error: jest.fn(), + }, +})); +jest.mock('./Config/EndpointService', () => ({ + config: { + openAIApiKey: 'mockedApiKey', + userProvidedOpenAI: false, + }, +})); + +axios.get.mockResolvedValue({ + data: { + data: [{ id: 'model-1' }, { id: 'model-2' }], + }, +}); + +describe('fetchModels', () => { + it('fetches models successfully from the API', async () => { + const models = await fetchModels({ + user: 'user123', + apiKey: 'testApiKey', + baseURL: 'https://api.test.com', + name: 'TestAPI', + }); + + expect(models).toEqual(['model-1', 'model-2']); + expect(axios.get).toHaveBeenCalledWith( + expect.stringContaining('https://api.test.com/models'), + expect.any(Object), + ); + }); + + it('adds the user ID to the models query when option and ID are passed', async () => { + const models = await fetchModels({ + user: 'user123', + apiKey: 'testApiKey', + baseURL: 'https://api.test.com', + userIdQuery: true, + name: 'TestAPI', + }); + + expect(models).toEqual(['model-1', 'model-2']); + expect(axios.get).toHaveBeenCalledWith( + expect.stringContaining('https://api.test.com/models?user=user123'), + expect.any(Object), + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); + +describe('fetchModels with createTokenConfig true', () => { + const data = { + data: [ + { + id: 'model-1', + pricing: { + prompt: '0.002', + completion: '0.001', + }, + context_length: 1024, + }, + { + id: 'model-2', + pricing: { + prompt: '0.003', + completion: '0.0015', + }, + context_length: 2048, + }, + ], + }; + + beforeEach(() => { + // Clears the mock's history before each test + const _utils = require('~/utils'); + axios.get.mockResolvedValue({ data }); + }); + + it('creates and stores token configuration if createTokenConfig is true', async () => { + await fetchModels({ + user: 'user123', + apiKey: 'testApiKey', + baseURL: 'https://api.test.com', + createTokenConfig: true, + }); + + const { processModelData } = require('~/utils'); + expect(processModelData).toHaveBeenCalled(); + expect(processModelData).toHaveBeenCalledWith(data); + }); +}); + +describe('getOpenAIModels', () => { + let originalEnv; + + beforeEach(() => { + originalEnv = { ...process.env }; + axios.get.mockRejectedValue(new Error('Network error')); + }); + + afterEach(() => { + process.env = originalEnv; + axios.get.mockReset(); + }); + + it('returns default models when no environment configurations are provided (and fetch fails)', async () => { + const models = await getOpenAIModels({ user: 'user456' }); + expect(models).toContain('gpt-4'); + }); + + it('returns `AZURE_OPENAI_MODELS` with `azure` flag (and fetch fails)', async () => { + process.env.AZURE_OPENAI_MODELS = 'azure-model,azure-model-2'; + const models = await getOpenAIModels({ azure: true }); + expect(models).toEqual(expect.arrayContaining(['azure-model', 'azure-model-2'])); + }); + + it('returns `PLUGIN_MODELS` with `plugins` flag (and fetch fails)', async () => { + process.env.PLUGIN_MODELS = 'plugins-model,plugins-model-2'; + const models = await getOpenAIModels({ plugins: true }); + expect(models).toEqual(expect.arrayContaining(['plugins-model', 'plugins-model-2'])); + }); + + it('returns `OPENAI_MODELS` with no flags (and fetch fails)', async () => { + process.env.OPENAI_MODELS = 'openai-model,openai-model-2'; + const models = await getOpenAIModels({}); + expect(models).toEqual(expect.arrayContaining(['openai-model', 'openai-model-2'])); + }); + + it('attempts to use OPENROUTER_API_KEY if set', async () => { + process.env.OPENROUTER_API_KEY = 'test-router-key'; + const expectedModels = ['model-router-1', 'model-router-2']; + + axios.get.mockResolvedValue({ + data: { + data: expectedModels.map((id) => ({ id })), + }, + }); + + const models = await getOpenAIModels({ user: 'user456' }); + + expect(models).toEqual(expect.arrayContaining(expectedModels)); + expect(axios.get).toHaveBeenCalled(); + }); + + it('utilizes proxy configuration when PROXY is set', async () => { + axios.get.mockResolvedValue({ + data: { + data: [], + }, + }); + process.env.PROXY = 'http://localhost:8888'; + await getOpenAIModels({ user: 'user456' }); + + expect(axios.get).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + httpsAgent: expect.anything(), + }), + ); + }); +}); + +describe('getOpenAIModels with mocked config', () => { + it('uses alternative behavior when userProvidedOpenAI is true', async () => { + jest.mock('./Config/EndpointService', () => ({ + config: { + openAIApiKey: 'mockedApiKey', + userProvidedOpenAI: true, + }, + })); + jest.mock('librechat-data-provider', () => { + const original = jest.requireActual('librechat-data-provider'); + return { + ...original, + defaultModels: { + [original.EModelEndpoint.openAI]: ['some-default-model'], + }, + }; + }); + + jest.resetModules(); + const { getOpenAIModels } = require('./ModelService'); + + const models = await getOpenAIModels({ user: 'user456' }); + expect(models).toContain('some-default-model'); + }); +}); diff --git a/bun.lockb b/bun.lockb index 9fd1d656d1..c97b397047 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/install/configuration/custom_config.md b/docs/install/configuration/custom_config.md index a45cd69a44..fde2cccc81 100644 --- a/docs/install/configuration/custom_config.md +++ b/docs/install/configuration/custom_config.md @@ -242,6 +242,9 @@ endpoints: - Type: Boolean - Example: `fetch: true` - **Note**: May cause slowdowns during initial use of the app if the response is delayed. Defaults to `false`. + - **userIdQuery**: When set to `true`, adds the LibreChat user ID as a query parameter to the API models request. + - Type: Boolean + - Example: `userIdQuery: true` ### **titleConvo**: diff --git a/package.json b/package.json index 526a6d289a..48209877b4 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "format": "prettier-eslint --write \"{,!(node_modules)/**/}*.{js,jsx,ts,tsx}\"", "b:api": "NODE_ENV=production bun run api/server/index.js", "b:api:dev": "NODE_ENV=production bun run --watch api/server/index.js", - "b:data-provider": "cd packages/data-provider && bun run b:build", - "b:client": "bun --bun run b:data-provider && cd client && bun --bun run b:build", + "b:data": "cd packages/data-provider && bun run b:build", + "b:client": "bun --bun run b:data && cd client && bun --bun run b:build", "b:client:dev": "cd client && bun run b:dev", "b:test:client": "cd client && bun run b:test", "b:test:api": "cd api && bun run b:test", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 44c0a260b4..753b35bb85 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -15,6 +15,7 @@ export const endpointSchema = z.object({ models: z.object({ default: z.array(z.string()).min(1), fetch: z.boolean().optional(), + userIdQuery: z.boolean().optional(), }), titleConvo: z.boolean().optional(), titleMethod: z.union([z.literal('completion'), z.literal('functions')]).optional(), @@ -40,7 +41,8 @@ export const configSchema = z.object({ .object({ custom: z.array(endpointSchema.partial()), }) - .strict(), + .strict() + .optional(), }); export type TCustomConfig = z.infer; @@ -177,6 +179,10 @@ export enum CacheKeys { * Key for the model config cache. */ MODELS_CONFIG = 'modelsConfig', + /** + * Key for the model queries cache. + */ + MODEL_QUERIES = 'modelQueries', /** * Key for the default endpoint config cache. */