From 89c733add3cc88aee1c89e44ea8876b0ec517860 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 25 Nov 2024 10:42:27 -0500 Subject: [PATCH 1/4] fix: azure validation/extraction types --- packages/data-provider/src/azure.ts | 68 +++++++++++++++------------- packages/data-provider/src/config.ts | 4 +- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/data-provider/src/azure.ts b/packages/data-provider/src/azure.ts index 79382eef7b1..58a109c9350 100644 --- a/packages/data-provider/src/azure.ts +++ b/packages/data-provider/src/azure.ts @@ -63,13 +63,13 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati const { group: groupName, apiKey, - instanceName, - deploymentName, - version, - baseURL, + instanceName = '', + deploymentName = '', + version = '', + baseURL = '', additionalHeaders, models, - serverless, + serverless = false, ...rest } = group; @@ -120,9 +120,11 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati continue; } + const groupDeploymentName = group.deploymentName ?? ''; + const groupVersion = group.version ?? ''; if (typeof model === 'boolean') { // For boolean models, check if group-level deploymentName and version are present. - if (!group.deploymentName || !group.version) { + if (!groupDeploymentName || !groupVersion) { errors.push( `Model "${modelName}" in group "${groupName}" is missing a deploymentName or version.`, ); @@ -133,11 +135,10 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati group: groupName, }; } else { + const modelDeploymentName = model.deploymentName ?? ''; + const modelVersion = model.version ?? ''; // For object models, check if deploymentName and version are required but missing. - if ( - (!model.deploymentName && !group.deploymentName) || - (!model.version && !group.version) - ) { + if ((!modelDeploymentName && !groupDeploymentName) || (!modelVersion && !groupVersion)) { errors.push( `Model "${modelName}" in group "${groupName}" is missing a required deploymentName or version.`, ); @@ -146,8 +147,8 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati modelGroupMap[modelName] = { group: groupName, - // deploymentName: model.deploymentName || group.deploymentName, - // version: model.version || group.version, + // deploymentName: modelDeploymentName || groupDeploymentName, + // version: modelVersion || groupVersion, }; } } @@ -190,26 +191,27 @@ export function mapModelToAzureConfig({ ); } - const instanceName = groupConfig.instanceName; + const instanceName = groupConfig.instanceName ?? ''; - if (!instanceName && !groupConfig.serverless) { + if (!instanceName && groupConfig.serverless !== true) { throw new Error( `Group "${modelConfig.group}" is missing an instanceName for non-serverless configuration.`, ); } - if (groupConfig.serverless && !groupConfig.baseURL) { + const baseURL = groupConfig.baseURL ?? ''; + if (groupConfig.serverless === true && !baseURL) { throw new Error( `Group "${modelConfig.group}" is missing the required base URL for serverless configuration.`, ); } - if (groupConfig.serverless) { + if (groupConfig.serverless === true) { const result: MappedAzureConfig = { azureOptions: { azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey), }, - baseURL: extractEnvVariable(groupConfig.baseURL as string), + baseURL: extractEnvVariable(baseURL), serverless: true, }; @@ -232,11 +234,11 @@ export function mapModelToAzureConfig({ } const modelDetails = groupConfig.models[modelName]; - const { deploymentName, version } = + const { deploymentName = '', version = '' } = typeof modelDetails === 'object' ? { - deploymentName: modelDetails.deploymentName || groupConfig.deploymentName, - version: modelDetails.version || groupConfig.version, + deploymentName: modelDetails.deploymentName ?? groupConfig.deploymentName, + version: modelDetails.version ?? groupConfig.version, } : { deploymentName: groupConfig.deploymentName, @@ -264,8 +266,8 @@ export function mapModelToAzureConfig({ const result: MappedAzureConfig = { azureOptions }; - if (groupConfig.baseURL) { - result.baseURL = extractEnvVariable(groupConfig.baseURL); + if (baseURL) { + result.baseURL = extractEnvVariable(baseURL); } if (groupConfig.additionalHeaders) { @@ -287,15 +289,17 @@ export function mapGroupToAzureConfig({ throw new Error(`Group named "${groupName}" not found in configuration.`); } - const instanceName = groupConfig.instanceName as string; + const instanceName = groupConfig.instanceName ?? ''; + const serverless = groupConfig.serverless ?? false; + const baseURL = groupConfig.baseURL ?? ''; - if (!instanceName && !groupConfig.serverless) { + if (!instanceName && !serverless) { throw new Error( `Group "${groupName}" is missing an instanceName for non-serverless configuration.`, ); } - if (groupConfig.serverless && !groupConfig.baseURL) { + if (serverless && !baseURL) { throw new Error( `Group "${groupName}" is missing the required base URL for serverless configuration.`, ); @@ -316,20 +320,20 @@ export function mapGroupToAzureConfig({ // DeploymentName and Version set below }; - if (groupConfig.serverless) { + if (serverless) { return { azureOptions, - baseURL: extractEnvVariable(groupConfig.baseURL ?? ''), + baseURL: extractEnvVariable(baseURL), serverless: true, ...(groupConfig.additionalHeaders && { headers: groupConfig.additionalHeaders }), }; } - const { deploymentName, version } = + const { deploymentName = '', version = '' } = typeof modelDetails === 'object' ? { - deploymentName: modelDetails.deploymentName || groupConfig.deploymentName, - version: modelDetails.version || groupConfig.version, + deploymentName: modelDetails.deploymentName ?? groupConfig.deploymentName, + version: modelDetails.version ?? groupConfig.version, } : { deploymentName: groupConfig.deploymentName, @@ -347,8 +351,8 @@ export function mapGroupToAzureConfig({ const result: MappedAzureConfig = { azureOptions }; - if (groupConfig.baseURL) { - result.baseURL = extractEnvVariable(groupConfig.baseURL); + if (baseURL) { + result.baseURL = extractEnvVariable(baseURL); } if (groupConfig.additionalHeaders) { diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 04f3faf0771..66394b61524 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -114,10 +114,10 @@ export type TAzureModelMapSchema = { group: string; }; -export type TAzureModelGroupMap = Record; +export type TAzureModelGroupMap = Record; export type TAzureGroupMap = Record< string, - TAzureBaseSchema & { models: Record } + (TAzureBaseSchema & { models: Record }) | undefined >; export type TValidatedAzureConfig = { From d90ee76298276b8e5e6def085d5c7dc358a8a1bb Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 25 Nov 2024 12:02:16 -0500 Subject: [PATCH 2/4] fix: typing, add optional chaining for modelGroup and groupMap properties; expect azureOpenAIApiVersion in serverless tests --- packages/data-provider/specs/azure.spec.ts | 13 ++++++++----- packages/data-provider/src/azure.ts | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/data-provider/specs/azure.spec.ts b/packages/data-provider/specs/azure.spec.ts index c73ca99e8e3..5628d3f24b6 100644 --- a/packages/data-provider/specs/azure.spec.ts +++ b/packages/data-provider/specs/azure.spec.ts @@ -94,8 +94,8 @@ describe('validateAzureGroups', () => { expect(isValid).toBe(true); const modelGroup = modelGroupMap['gpt-5-turbo']; expect(modelGroup).toBeDefined(); - expect(modelGroup.group).toBe('japan-east'); - expect(groupMap[modelGroup.group]).toBeDefined(); + expect(modelGroup?.group).toBe('japan-east'); + expect(groupMap[modelGroup?.group ?? '']).toBeDefined(); expect(modelNames).toContain('gpt-5-turbo'); const { azureOptions } = mapModelToAzureConfig({ modelName: 'gpt-5-turbo', @@ -323,6 +323,7 @@ describe('validateAzureGroups for Serverless Configurations', () => { expect(azureOptions).toEqual({ azureOpenAIApiKey: 'def456', + azureOpenAIApiVersion: '', }); expect(baseURL).toEqual('https://new-serverless.example.com/v1/completions'); expect(serverless).toBe(true); @@ -381,10 +382,10 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => { const { isValid, modelGroupMap, groupMap } = validateAzureGroups(validConfigs); expect(isValid).toBe(true); expect(modelGroupMap['gpt-4-turbo']).toBeDefined(); - expect(modelGroupMap['gpt-4-turbo'].group).toBe('us-east'); + expect(modelGroupMap['gpt-4-turbo']?.group).toBe('us-east'); expect(groupMap['us-east']).toBeDefined(); - expect(groupMap['us-east'].apiKey).toBe('prod-1234'); - expect(groupMap['us-east'].models['gpt-4-turbo']).toBeDefined(); + expect(groupMap['us-east']?.apiKey).toBe('prod-1234'); + expect(groupMap['us-east']?.models['gpt-4-turbo']).toBeDefined(); const { azureOptions, baseURL, headers } = mapModelToAzureConfig({ modelName: 'gpt-4-turbo', modelGroupMap, @@ -765,6 +766,7 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => { ); expect(azureOptions7).toEqual({ azureOpenAIApiKey: 'mistral-key', + azureOpenAIApiVersion: '', }); const { @@ -782,6 +784,7 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => { ); expect(azureOptions8).toEqual({ azureOpenAIApiKey: 'llama-key', + azureOpenAIApiVersion: '', }); }); }); diff --git a/packages/data-provider/src/azure.ts b/packages/data-provider/src/azure.ts index 58a109c9350..f5948820be7 100644 --- a/packages/data-provider/src/azure.ts +++ b/packages/data-provider/src/azure.ts @@ -209,6 +209,7 @@ export function mapModelToAzureConfig({ if (groupConfig.serverless === true) { const result: MappedAzureConfig = { azureOptions: { + azureOpenAIApiVersion: extractEnvVariable(groupConfig.version ?? ''), azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey), }, baseURL: extractEnvVariable(baseURL), @@ -315,6 +316,7 @@ export function mapGroupToAzureConfig({ const modelDetails = groupConfig.models[firstModelName]; const azureOptions: AzureOptions = { + azureOpenAIApiVersion: extractEnvVariable(groupConfig.version ?? ''), azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey), azureOpenAIApiInstanceName: extractEnvVariable(instanceName), // DeploymentName and Version set below From b16407c266ba0debcbdc85d64a75a0fdfc528bf2 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 25 Nov 2024 12:08:24 -0500 Subject: [PATCH 3/4] fix: add support for azureOpenAIApiVersion and api-key in serverless mode across clients --- api/app/clients/ChatGPTClient.js | 10 ++++++++++ api/app/clients/OpenAIClient.js | 16 ++++++++++++++++ .../Endpoints/azureAssistants/initialize.js | 6 ++++++ .../services/Endpoints/gptPlugins/initialize.js | 6 ++++++ .../services/Endpoints/openAI/initialize.js | 6 ++++++ api/server/services/Endpoints/openAI/llm.js | 5 +++++ 6 files changed, 49 insertions(+) diff --git a/api/app/clients/ChatGPTClient.js b/api/app/clients/ChatGPTClient.js index 22f7cf31385..6a7ba7b9896 100644 --- a/api/app/clients/ChatGPTClient.js +++ b/api/app/clients/ChatGPTClient.js @@ -227,6 +227,16 @@ class ChatGPTClient extends BaseClient { this.azure = !serverless && azureOptions; this.azureEndpoint = !serverless && genAzureChatCompletion(this.azure, modelOptions.model, this); + if (serverless === true) { + this.options.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + this.options.headers['api-key'] = this.apiKey; + } + } + + if (this.options.defaultQuery) { + opts.defaultQuery = this.options.defaultQuery; } if (this.options.headers) { diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index d06ddd91775..6a8377be6f0 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -838,6 +838,12 @@ class OpenAIClient extends BaseClient { this.options.dropParams = azureConfig.groupMap[groupName].dropParams; this.options.forcePrompt = azureConfig.groupMap[groupName].forcePrompt; this.azure = !serverless && azureOptions; + if (serverless === true) { + this.options.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + this.options.headers['api-key'] = this.apiKey; + } } const titleChatCompletion = async () => { @@ -1169,6 +1175,10 @@ ${convo} opts.defaultHeaders = { ...opts.defaultHeaders, ...this.options.headers }; } + if (this.options.defaultQuery) { + opts.defaultQuery = this.options.defaultQuery; + } + if (this.options.proxy) { opts.httpAgent = new HttpsProxyAgent(this.options.proxy); } @@ -1207,6 +1217,12 @@ ${convo} this.azure = !serverless && azureOptions; this.azureEndpoint = !serverless && genAzureChatCompletion(this.azure, modelOptions.model, this); + if (serverless === true) { + this.options.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + this.options.headers['api-key'] = this.apiKey; + } } if (this.azure || this.options.azure) { diff --git a/api/server/services/Endpoints/azureAssistants/initialize.js b/api/server/services/Endpoints/azureAssistants/initialize.js index 69a55c74bbb..fc8024af072 100644 --- a/api/server/services/Endpoints/azureAssistants/initialize.js +++ b/api/server/services/Endpoints/azureAssistants/initialize.js @@ -135,6 +135,12 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie clientOptions.reverseProxyUrl = baseURL ?? clientOptions.reverseProxyUrl; clientOptions.headers = opts.defaultHeaders; clientOptions.azure = !serverless && azureOptions; + if (serverless === true) { + clientOptions.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + clientOptions.headers['api-key'] = apiKey; + } } } diff --git a/api/server/services/Endpoints/gptPlugins/initialize.js b/api/server/services/Endpoints/gptPlugins/initialize.js index 7e79d425640..7bfb43f0041 100644 --- a/api/server/services/Endpoints/gptPlugins/initialize.js +++ b/api/server/services/Endpoints/gptPlugins/initialize.js @@ -96,6 +96,12 @@ const initializeClient = async ({ req, res, endpointOption }) => { apiKey = azureOptions.azureOpenAIApiKey; clientOptions.azure = !serverless && azureOptions; + if (serverless === true) { + clientOptions.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + clientOptions.headers['api-key'] = apiKey; + } } else if (useAzure || (apiKey && apiKey.includes('{"azure') && !clientOptions.azure)) { clientOptions.azure = userProvidesKey ? JSON.parse(userValues.apiKey) : getAzureCredentials(); apiKey = clientOptions.azure.azureOpenAIApiKey; diff --git a/api/server/services/Endpoints/openAI/initialize.js b/api/server/services/Endpoints/openAI/initialize.js index 215b9437309..a84be42b91c 100644 --- a/api/server/services/Endpoints/openAI/initialize.js +++ b/api/server/services/Endpoints/openAI/initialize.js @@ -97,6 +97,12 @@ const initializeClient = async ({ apiKey = azureOptions.azureOpenAIApiKey; clientOptions.azure = !serverless && azureOptions; + if (serverless === true) { + clientOptions.defaultQuery = azureOptions.azureOpenAIApiVersion + ? { 'api-version': azureOptions.azureOpenAIApiVersion } + : undefined; + clientOptions.headers['api-key'] = apiKey; + } } else if (isAzureOpenAI) { clientOptions.azure = userProvidesKey ? JSON.parse(userValues.apiKey) : getAzureCredentials(); apiKey = clientOptions.azure.azureOpenAIApiKey; diff --git a/api/server/services/Endpoints/openAI/llm.js b/api/server/services/Endpoints/openAI/llm.js index bd51679e1b6..e372c9d7945 100644 --- a/api/server/services/Endpoints/openAI/llm.js +++ b/api/server/services/Endpoints/openAI/llm.js @@ -29,6 +29,7 @@ function getLLMConfig(apiKey, options = {}) { modelOptions = {}, reverseProxyUrl, useOpenRouter, + defaultQuery, headers, proxy, azure, @@ -74,6 +75,10 @@ function getLLMConfig(apiKey, options = {}) { } } + if (defaultQuery) { + configOptions.baseOptions.defaultQuery = defaultQuery; + } + if (proxy) { const proxyAgent = new HttpsProxyAgent(proxy); Object.assign(configOptions, { From ebbe8981dee87e1bb4a9f9a8a184adf71a309488 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 25 Nov 2024 12:20:05 -0500 Subject: [PATCH 4/4] chore: update CONFIG_VERSION to 1.1.8, data-provider bump --- package-lock.json | 2 +- packages/data-provider/package.json | 2 +- packages/data-provider/src/config.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56937b6fff1..d292fff47e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36137,7 +36137,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.55", + "version": "0.7.56", "license": "ISC", "dependencies": { "@types/js-yaml": "^4.0.9", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index ec477129754..95fde7c0b0b 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.55", + "version": "0.7.56", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 66394b61524..306cc3d80e8 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -1080,7 +1080,7 @@ export enum Constants { /** Key for the app's version. */ VERSION = 'v0.7.5', /** Key for the Custom Config's version (librechat.yaml). */ - CONFIG_VERSION = '1.1.7', + CONFIG_VERSION = '1.1.8', /** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */ NO_PARENT = '00000000-0000-0000-0000-000000000000', /** Standard value for the initial conversationId before a request is sent */