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.
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/package.json +1 -1
- package/specs/azure.spec.ts +221 -1
- package/src/azure.ts +69 -4
- package/src/config.ts +15 -1
- package/src/types.ts +1 -0
package/specs/azure.spec.ts
CHANGED
|
@@ -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
|
|
142
|
-
azureOpenAIApiDeploymentName
|
|
143
|
-
azureOpenAIApiVersion
|
|
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(
|
|
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
|
-
|
|
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[] = [
|