librechat-data-provider 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,13 +188,147 @@ describe('validateAzureGroups', () => {
188
188
  },
189
189
  },
190
190
  ];
191
- // @ts-expect-error This error is expected because the 'instanceName' property is intentionally left out.
192
191
  const { isValid, errors } = validateAzureGroups(configs);
193
192
  expect(isValid).toBe(false);
194
193
  expect(errors.length).toBe(1);
195
194
  });
196
195
  });
197
196
 
197
+ describe('validateAzureGroups for Serverless Configurations', () => {
198
+ const originalEnv = process.env;
199
+
200
+ beforeEach(() => {
201
+ jest.resetModules();
202
+ process.env = { ...originalEnv };
203
+ });
204
+
205
+ afterAll(() => {
206
+ process.env = originalEnv;
207
+ });
208
+
209
+ it('should validate a correct serverless configuration', () => {
210
+ const configs = [
211
+ {
212
+ group: 'serverless-group',
213
+ apiKey: '${SERVERLESS_API_KEY}',
214
+ baseURL: 'https://serverless.example.com/v1/completions',
215
+ serverless: true,
216
+ models: {
217
+ 'model-serverless': true,
218
+ },
219
+ },
220
+ ];
221
+
222
+ const { isValid, errors } = validateAzureGroups(configs);
223
+
224
+ expect(isValid).toBe(true);
225
+ expect(errors.length).toBe(0);
226
+ });
227
+
228
+ it('should return invalid for a serverless configuration missing baseURL', () => {
229
+ const configs = [
230
+ {
231
+ group: 'serverless-group',
232
+ apiKey: '${SERVERLESS_API_KEY}',
233
+ serverless: true,
234
+ models: {
235
+ 'model-serverless': true,
236
+ },
237
+ },
238
+ ];
239
+
240
+ const { isValid, errors } = validateAzureGroups(configs);
241
+ expect(isValid).toBe(false);
242
+ expect(errors).toEqual(
243
+ expect.arrayContaining([
244
+ expect.stringContaining(
245
+ 'Group "serverless-group" is serverless but missing mandatory "baseURL."',
246
+ ),
247
+ ]),
248
+ );
249
+ });
250
+
251
+ it('should throw an error when environment variable for apiKey is not set', () => {
252
+ process.env.SERVERLESS_API_KEY = '';
253
+
254
+ expect(() => {
255
+ mapModelToAzureConfig({
256
+ modelName: 'model-serverless',
257
+ modelGroupMap: {
258
+ 'model-serverless': {
259
+ group: 'serverless-group',
260
+ },
261
+ },
262
+ groupMap: {
263
+ 'serverless-group': {
264
+ apiKey: '${SERVERLESS_API_KEY}',
265
+ baseURL: 'https://serverless.example.com/v1/completions',
266
+ serverless: true,
267
+ models: { 'model-serverless': true },
268
+ },
269
+ },
270
+ });
271
+ }).toThrow('Azure configuration environment variable "${SERVERLESS_API_KEY}" was not found.');
272
+ });
273
+
274
+ it('should correctly extract environment variables and prepare serverless config', () => {
275
+ process.env.SERVERLESS_API_KEY = 'abc123';
276
+
277
+ const { azureOptions, baseURL, serverless } = mapModelToAzureConfig({
278
+ modelName: 'model-serverless',
279
+ modelGroupMap: {
280
+ 'model-serverless': {
281
+ group: 'serverless-group',
282
+ },
283
+ },
284
+ groupMap: {
285
+ 'serverless-group': {
286
+ apiKey: '${SERVERLESS_API_KEY}',
287
+ baseURL: 'https://serverless.example.com/v1/completions',
288
+ serverless: true,
289
+ models: { 'model-serverless': true },
290
+ },
291
+ },
292
+ });
293
+
294
+ expect(azureOptions.azureOpenAIApiKey).toEqual('abc123');
295
+ expect(baseURL).toEqual('https://serverless.example.com/v1/completions');
296
+ expect(serverless).toBe(true);
297
+ });
298
+
299
+ it('should ensure serverless flag triggers appropriate validations and mappings', () => {
300
+ const configs = [
301
+ {
302
+ group: 'serverless-group-2',
303
+ apiKey: '${NEW_SERVERLESS_API_KEY}',
304
+ baseURL: 'https://new-serverless.example.com/v1/completions',
305
+ serverless: true,
306
+ models: {
307
+ 'new-model-serverless': true,
308
+ },
309
+ },
310
+ ];
311
+
312
+ process.env.NEW_SERVERLESS_API_KEY = 'def456';
313
+
314
+ const { isValid, errors, modelGroupMap, groupMap } = validateAzureGroups(configs);
315
+ expect(isValid).toBe(true);
316
+ expect(errors.length).toBe(0);
317
+
318
+ const { azureOptions, baseURL, serverless } = mapModelToAzureConfig({
319
+ modelName: 'new-model-serverless',
320
+ modelGroupMap,
321
+ groupMap,
322
+ });
323
+
324
+ expect(azureOptions).toEqual({
325
+ azureOpenAIApiKey: 'def456',
326
+ });
327
+ expect(baseURL).toEqual('https://new-serverless.example.com/v1/completions');
328
+ expect(serverless).toBe(true);
329
+ });
330
+ });
331
+
198
332
  describe('validateAzureGroups with modelGroupMap and groupMap', () => {
199
333
  const originalEnv = process.env;
200
334
 
@@ -396,6 +530,8 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
396
530
  it('should list all expected models in both modelGroupMap and groupMap', () => {
397
531
  process.env.WESTUS_API_KEY = 'westus-key';
398
532
  process.env.EASTUS_API_KEY = 'eastus-key';
533
+ process.env.AZURE_MISTRAL_API_KEY = 'mistral-key';
534
+ process.env.AZURE_LLAMA2_70B_API_KEY = 'llama-key';
399
535
 
400
536
  const validConfigs: TAzureGroups = [
401
537
  {
@@ -436,6 +572,26 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
436
572
  'x-api-key': 'x-api-key-value',
437
573
  },
438
574
  },
575
+ {
576
+ group: 'mistral-inference',
577
+ apiKey: '${AZURE_MISTRAL_API_KEY}',
578
+ baseURL:
579
+ 'https://Mistral-large-vnpet-serverless.region.inference.ai.azure.com/v1/chat/completions',
580
+ serverless: true,
581
+ models: {
582
+ 'mistral-large': true,
583
+ },
584
+ },
585
+ {
586
+ group: 'llama-70b-chat',
587
+ apiKey: '${AZURE_LLAMA2_70B_API_KEY}',
588
+ baseURL:
589
+ 'https://Llama-2-70b-chat-qmvyb-serverless.region.inference.ai.azure.com/v1/chat/completions',
590
+ serverless: true,
591
+ models: {
592
+ 'llama-70b-chat': true,
593
+ },
594
+ },
439
595
  ];
440
596
  const { isValid, modelGroupMap, groupMap, modelNames } = validateAzureGroups(validConfigs);
441
597
  expect(isValid).toBe(true);
@@ -446,6 +602,8 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
446
602
  'gpt-4',
447
603
  'gpt-4-1106-preview',
448
604
  'gpt-4-turbo',
605
+ 'mistral-large',
606
+ 'llama-70b-chat',
449
607
  ]);
450
608
 
451
609
  // Check modelGroupMap
@@ -484,6 +642,34 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
484
642
  }),
485
643
  );
486
644
 
645
+ // Check groupMap for 'mistral-inference'
646
+ expect(groupMap).toHaveProperty('mistral-inference');
647
+ expect(groupMap['mistral-inference']).toEqual(
648
+ expect.objectContaining({
649
+ apiKey: '${AZURE_MISTRAL_API_KEY}',
650
+ baseURL:
651
+ 'https://Mistral-large-vnpet-serverless.region.inference.ai.azure.com/v1/chat/completions',
652
+ serverless: true,
653
+ models: expect.objectContaining({
654
+ 'mistral-large': true,
655
+ }),
656
+ }),
657
+ );
658
+
659
+ // Check groupMap for 'llama-70b-chat'
660
+ expect(groupMap).toHaveProperty('llama-70b-chat');
661
+ expect(groupMap['llama-70b-chat']).toEqual(
662
+ expect.objectContaining({
663
+ apiKey: '${AZURE_LLAMA2_70B_API_KEY}',
664
+ baseURL:
665
+ 'https://Llama-2-70b-chat-qmvyb-serverless.region.inference.ai.azure.com/v1/chat/completions',
666
+ serverless: true,
667
+ models: expect.objectContaining({
668
+ 'llama-70b-chat': true,
669
+ }),
670
+ }),
671
+ );
672
+
487
673
  const { azureOptions: azureOptions1 } = mapModelToAzureConfig({
488
674
  modelName: 'gpt-4-vision-preview',
489
675
  modelGroupMap,
@@ -563,5 +749,39 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
563
749
  azureOpenAIApiDeploymentName: 'gpt-4-1106-preview',
564
750
  azureOpenAIApiVersion: '2023-12-01-preview',
565
751
  });
752
+
753
+ const {
754
+ azureOptions: azureOptions7,
755
+ serverless: serverlessMistral,
756
+ baseURL: mistralEndpoint,
757
+ } = mapModelToAzureConfig({
758
+ modelName: 'mistral-large',
759
+ modelGroupMap,
760
+ groupMap,
761
+ });
762
+ expect(serverlessMistral).toBe(true);
763
+ expect(mistralEndpoint).toBe(
764
+ 'https://Mistral-large-vnpet-serverless.region.inference.ai.azure.com/v1/chat/completions',
765
+ );
766
+ expect(azureOptions7).toEqual({
767
+ azureOpenAIApiKey: 'mistral-key',
768
+ });
769
+
770
+ const {
771
+ azureOptions: azureOptions8,
772
+ serverless: serverlessLlama,
773
+ baseURL: llamaEndpoint,
774
+ } = mapModelToAzureConfig({
775
+ modelName: 'llama-70b-chat',
776
+ modelGroupMap,
777
+ groupMap,
778
+ });
779
+ expect(serverlessLlama).toBe(true);
780
+ expect(llamaEndpoint).toBe(
781
+ 'https://Llama-2-70b-chat-qmvyb-serverless.region.inference.ai.azure.com/v1/chat/completions',
782
+ );
783
+ expect(azureOptions8).toEqual({
784
+ azureOpenAIApiKey: 'llama-key',
785
+ });
566
786
  });
567
787
  });
package/src/azure.ts CHANGED
@@ -71,6 +71,8 @@ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfi
71
71
  baseURL,
72
72
  additionalHeaders,
73
73
  models,
74
+ serverless,
75
+ ...rest
74
76
  } = group;
75
77
 
76
78
  if (groupMap[groupName]) {
@@ -78,6 +80,18 @@ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfi
78
80
  return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
79
81
  }
80
82
 
83
+ if (serverless && !baseURL) {
84
+ errors.push(`Group "${groupName}" is serverless but missing mandatory "baseURL."`);
85
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
86
+ }
87
+
88
+ if (!instanceName && !serverless) {
89
+ errors.push(
90
+ `Group "${groupName}" is missing an "instanceName" for non-serverless configuration.`,
91
+ );
92
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
93
+ }
94
+
81
95
  groupMap[groupName] = {
82
96
  apiKey,
83
97
  instanceName,
@@ -86,6 +100,8 @@ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfi
86
100
  baseURL,
87
101
  additionalHeaders,
88
102
  models,
103
+ serverless,
104
+ ...rest,
89
105
  };
90
106
 
91
107
  for (const modelName in group.models) {
@@ -99,6 +115,13 @@ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfi
99
115
  return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
100
116
  }
101
117
 
118
+ if (serverless) {
119
+ modelGroupMap[modelName] = {
120
+ group: groupName,
121
+ };
122
+ continue;
123
+ }
124
+
102
125
  if (typeof model === 'boolean') {
103
126
  // For boolean models, check if group-level deploymentName and version are present.
104
127
  if (!group.deploymentName || !group.version) {
@@ -138,15 +161,16 @@ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfi
138
161
 
139
162
  type AzureOptions = {
140
163
  azureOpenAIApiKey: string;
141
- azureOpenAIApiInstanceName: string;
142
- azureOpenAIApiDeploymentName: string;
143
- azureOpenAIApiVersion: string;
164
+ azureOpenAIApiInstanceName?: string;
165
+ azureOpenAIApiDeploymentName?: string;
166
+ azureOpenAIApiVersion?: string;
144
167
  };
145
168
 
146
169
  type MappedAzureConfig = {
147
170
  azureOptions: AzureOptions;
148
171
  baseURL?: string;
149
172
  headers?: Record<string, string>;
173
+ serverless?: boolean;
150
174
  };
151
175
 
152
176
  export function mapModelToAzureConfig({
@@ -168,6 +192,47 @@ export function mapModelToAzureConfig({
168
192
  );
169
193
  }
170
194
 
195
+ const instanceName = groupConfig.instanceName;
196
+
197
+ if (!instanceName && !groupConfig.serverless) {
198
+ throw new Error(
199
+ `Group "${modelConfig.group}" is missing an instanceName for non-serverless configuration.`,
200
+ );
201
+ }
202
+
203
+ if (groupConfig.serverless && !groupConfig.baseURL) {
204
+ throw new Error(
205
+ `Group "${modelConfig.group}" is missing the required base URL for serverless configuration.`,
206
+ );
207
+ }
208
+
209
+ if (groupConfig.serverless) {
210
+ const result: MappedAzureConfig = {
211
+ azureOptions: {
212
+ azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey),
213
+ },
214
+ baseURL: extractEnvVariable(groupConfig.baseURL as string),
215
+ serverless: true,
216
+ };
217
+
218
+ const apiKeyValue = result.azureOptions.azureOpenAIApiKey;
219
+ if (typeof apiKeyValue === 'string' && envVarRegex.test(apiKeyValue)) {
220
+ throw new Error(`Azure configuration environment variable "${apiKeyValue}" was not found.`);
221
+ }
222
+
223
+ if (groupConfig.additionalHeaders) {
224
+ result.headers = groupConfig.additionalHeaders;
225
+ }
226
+
227
+ return result;
228
+ }
229
+
230
+ if (!instanceName) {
231
+ throw new Error(
232
+ `Group "${modelConfig.group}" is missing an instanceName for non-serverless configuration.`,
233
+ );
234
+ }
235
+
171
236
  const modelDetails = groupConfig.models[modelName];
172
237
  const deploymentName =
173
238
  typeof modelDetails === 'object'
@@ -186,7 +251,7 @@ export function mapModelToAzureConfig({
186
251
 
187
252
  const azureOptions: AzureOptions = {
188
253
  azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey),
189
- azureOpenAIApiInstanceName: extractEnvVariable(groupConfig.instanceName),
254
+ azureOpenAIApiInstanceName: extractEnvVariable(instanceName),
190
255
  azureOpenAIApiDeploymentName: extractEnvVariable(deploymentName),
191
256
  azureOpenAIApiVersion: extractEnvVariable(version),
192
257
  };
package/src/config.ts CHANGED
@@ -19,8 +19,12 @@ export type TAzureModelConfig = z.infer<typeof modelConfigSchema>;
19
19
 
20
20
  export const azureBaseSchema = z.object({
21
21
  apiKey: z.string(),
22
- instanceName: z.string(),
22
+ serverless: z.boolean().optional(),
23
+ instanceName: z.string().optional(),
23
24
  deploymentName: z.string().optional(),
25
+ addParams: z.record(z.any()).optional(),
26
+ dropParams: z.array(z.string()).optional(),
27
+ forcePrompt: z.boolean().optional(),
24
28
  version: z.string().optional(),
25
29
  baseURL: z.string().optional(),
26
30
  additionalHeaders: z.record(z.any()).optional(),
@@ -170,6 +174,16 @@ export type TCustomConfig = z.infer<typeof configSchema>;
170
174
  export enum KnownEndpoints {
171
175
  mistral = 'mistral',
172
176
  openrouter = 'openrouter',
177
+ groq = 'groq',
178
+ anyscale = 'anyscale',
179
+ fireworks = 'fireworks',
180
+ ollama = 'ollama',
181
+ perplexity = 'perplexity',
182
+ 'together.ai' = 'together.ai',
183
+ }
184
+
185
+ export enum FetchTokenConfig {
186
+ openrouter = KnownEndpoints.openrouter,
173
187
  }
174
188
 
175
189
  export const defaultEndpoints: EModelEndpoint[] = [
package/src/types.ts CHANGED
@@ -210,6 +210,7 @@ export type TStartupConfig = {
210
210
  emailEnabled: boolean;
211
211
  checkBalance: boolean;
212
212
  showBirthdayIcon: boolean;
213
+ helpAndFaqURL: string;
213
214
  customFooter?: string;
214
215
  };
215
216