| jest.mock('~/cache/getLogStores', () => ({ |
| getLogStores: jest.fn().mockReturnValue({ |
| get: jest.fn().mockResolvedValue({ |
| openAI: { apiKey: 'test-key' }, |
| }), |
| set: jest.fn(), |
| delete: jest.fn(), |
| }), |
| })); |
|
|
| const { EModelEndpoint, ErrorTypes, validateAzureGroups } = require('librechat-data-provider'); |
| const { getUserKey, getUserKeyValues } = require('~/server/services/UserService'); |
| const initializeClient = require('./initialize'); |
| const { OpenAIClient } = require('~/app'); |
|
|
| |
| jest.mock('~/server/services/UserService', () => ({ |
| getUserKey: jest.fn(), |
| getUserKeyValues: jest.fn(), |
| checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry, |
| })); |
|
|
| const mockAppConfig = { |
| endpoints: { |
| openAI: { |
| apiKey: 'test-key', |
| }, |
| azureOpenAI: { |
| apiKey: 'test-azure-key', |
| modelNames: ['gpt-4-vision-preview', 'gpt-3.5-turbo', 'gpt-4'], |
| modelGroupMap: { |
| 'gpt-4-vision-preview': { |
| group: 'librechat-westus', |
| deploymentName: 'gpt-4-vision-preview', |
| version: '2024-02-15-preview', |
| }, |
| }, |
| groupMap: { |
| 'librechat-westus': { |
| apiKey: 'WESTUS_API_KEY', |
| instanceName: 'librechat-westus', |
| version: '2023-12-01-preview', |
| models: { |
| 'gpt-4-vision-preview': { |
| deploymentName: 'gpt-4-vision-preview', |
| version: '2024-02-15-preview', |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }; |
|
|
| describe('initializeClient', () => { |
| |
| const originalEnvironment = process.env; |
| const app = { |
| locals: {}, |
| }; |
|
|
| const validAzureConfigs = [ |
| { |
| group: 'librechat-westus', |
| apiKey: 'WESTUS_API_KEY', |
| instanceName: 'librechat-westus', |
| version: '2023-12-01-preview', |
| models: { |
| 'gpt-4-vision-preview': { |
| deploymentName: 'gpt-4-vision-preview', |
| version: '2024-02-15-preview', |
| }, |
| 'gpt-3.5-turbo': { |
| deploymentName: 'gpt-35-turbo', |
| }, |
| 'gpt-3.5-turbo-1106': { |
| deploymentName: 'gpt-35-turbo-1106', |
| }, |
| 'gpt-4': { |
| deploymentName: 'gpt-4', |
| }, |
| 'gpt-4-1106-preview': { |
| deploymentName: 'gpt-4-1106-preview', |
| }, |
| }, |
| }, |
| { |
| group: 'librechat-eastus', |
| apiKey: 'EASTUS_API_KEY', |
| instanceName: 'librechat-eastus', |
| deploymentName: 'gpt-4-turbo', |
| version: '2024-02-15-preview', |
| models: { |
| 'gpt-4-turbo': true, |
| }, |
| baseURL: 'https://eastus.example.com', |
| additionalHeaders: { |
| 'x-api-key': 'x-api-key-value', |
| }, |
| }, |
| { |
| group: 'mistral-inference', |
| apiKey: 'AZURE_MISTRAL_API_KEY', |
| baseURL: |
| 'https://Mistral-large-vnpet-serverless.region.inference.ai.azure.com/v1/chat/completions', |
| serverless: true, |
| models: { |
| 'mistral-large': true, |
| }, |
| }, |
| { |
| group: 'llama-70b-chat', |
| apiKey: 'AZURE_LLAMA2_70B_API_KEY', |
| baseURL: |
| 'https://Llama-2-70b-chat-qmvyb-serverless.region.inference.ai.azure.com/v1/chat/completions', |
| serverless: true, |
| models: { |
| 'llama-70b-chat': true, |
| }, |
| }, |
| ]; |
|
|
| const { modelNames } = validateAzureGroups(validAzureConfigs); |
|
|
| beforeEach(() => { |
| jest.resetModules(); |
| process.env = { ...originalEnvironment }; |
| }); |
|
|
| afterAll(() => { |
| process.env = originalEnvironment; |
| }); |
|
|
| test('should initialize client with OpenAI API key and default options', async () => { |
| process.env.OPENAI_API_KEY = 'test-openai-api-key'; |
| process.env.DEBUG_OPENAI = 'false'; |
| process.env.OPENAI_SUMMARIZE = 'false'; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const result = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(result.openAIApiKey).toBe('test-openai-api-key'); |
| expect(result.client).toBeInstanceOf(OpenAIClient); |
| }); |
|
|
| test('should initialize client with Azure credentials when endpoint is azureOpenAI', async () => { |
| process.env.AZURE_API_KEY = 'test-azure-api-key'; |
| (process.env.AZURE_OPENAI_API_INSTANCE_NAME = 'some-value'), |
| (process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME = 'some-value'), |
| (process.env.AZURE_OPENAI_API_VERSION = 'some-value'), |
| (process.env.AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME = 'some-value'), |
| (process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME = 'some-value'), |
| (process.env.OPENAI_API_KEY = 'test-openai-api-key'); |
| process.env.DEBUG_OPENAI = 'false'; |
| process.env.OPENAI_SUMMARIZE = 'false'; |
|
|
| const req = { |
| body: { |
| key: null, |
| endpoint: 'azureOpenAI', |
| model: 'gpt-4-vision-preview', |
| }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(client.openAIApiKey).toBe('WESTUS_API_KEY'); |
| expect(client.client).toBeInstanceOf(OpenAIClient); |
| }); |
|
|
| test('should use the debug option when DEBUG_OPENAI is enabled', async () => { |
| process.env.OPENAI_API_KEY = 'test-openai-api-key'; |
| process.env.DEBUG_OPENAI = 'true'; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(client.client.options.debug).toBe(true); |
| }); |
|
|
| test('should set contextStrategy to summarize when OPENAI_SUMMARIZE is enabled', async () => { |
| process.env.OPENAI_API_KEY = 'test-openai-api-key'; |
| process.env.OPENAI_SUMMARIZE = 'true'; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(client.client.options.contextStrategy).toBe('summarize'); |
| }); |
|
|
| test('should set reverseProxyUrl and proxy when they are provided in the environment', async () => { |
| process.env.OPENAI_API_KEY = 'test-openai-api-key'; |
| process.env.OPENAI_REVERSE_PROXY = 'http://reverse.proxy'; |
| process.env.PROXY = 'http://proxy'; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(client.client.options.reverseProxyUrl).toBe('http://reverse.proxy'); |
| expect(client.client.options.proxy).toBe('http://proxy'); |
| }); |
|
|
| test('should throw an error if the user-provided key has expired', async () => { |
| process.env.OPENAI_API_KEY = 'user_provided'; |
| process.env.AZURE_API_KEY = 'user_provided'; |
| process.env.DEBUG_OPENAI = 'false'; |
| process.env.OPENAI_SUMMARIZE = 'false'; |
|
|
| const expiresAt = new Date(Date.now() - 10000).toISOString(); |
| const req = { |
| body: { key: expiresAt, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow( |
| /expired_user_key/, |
| ); |
| }); |
|
|
| test('should throw an error if no API keys are provided in the environment', async () => { |
| |
| delete process.env.OPENAI_API_KEY; |
| delete process.env.AZURE_API_KEY; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow( |
| `${EModelEndpoint.openAI} API Key not provided.`, |
| ); |
| }); |
|
|
| it('should handle user-provided keys and check expiry', async () => { |
| |
| const req = { |
| body: { |
| key: new Date(Date.now() + 10000).toISOString(), |
| endpoint: EModelEndpoint.openAI, |
| }, |
| user: { |
| id: '123', |
| }, |
| app, |
| config: mockAppConfig, |
| }; |
|
|
| const res = {}; |
| const endpointOption = {}; |
|
|
| |
| process.env.OPENAI_API_KEY = 'user_provided'; |
|
|
| |
| getUserKeyValues.mockResolvedValue({ apiKey: 'test-user-provided-openai-api-key' }); |
|
|
| |
| const result = await initializeClient({ req, res, endpointOption }); |
|
|
| |
| expect(result.openAIApiKey).toBe('test-user-provided-openai-api-key'); |
| }); |
|
|
| test('should throw an error if the user-provided key is invalid', async () => { |
| const invalidKey = new Date(Date.now() - 100000).toISOString(); |
| const req = { |
| body: { key: invalidKey, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| |
| process.env.OPENAI_API_KEY = 'user_provided'; |
|
|
| |
| getUserKey.mockResolvedValue(invalidKey); |
|
|
| await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow( |
| /expired_user_key/, |
| ); |
| }); |
|
|
| test('should throw an error when user-provided values are not valid JSON', async () => { |
| process.env.OPENAI_API_KEY = 'user_provided'; |
| const req = { |
| body: { key: new Date(Date.now() + 10000).toISOString(), endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| |
| getUserKey.mockResolvedValue('not-a-json'); |
| getUserKeyValues.mockImplementation(() => { |
| let userValues = getUserKey(); |
| try { |
| userValues = JSON.parse(userValues); |
| } catch { |
| throw new Error( |
| JSON.stringify({ |
| type: ErrorTypes.INVALID_USER_KEY, |
| }), |
| ); |
| } |
| return userValues; |
| }); |
|
|
| await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow( |
| /invalid_user_key/, |
| ); |
| }); |
|
|
| test('should initialize client correctly for Azure OpenAI with valid configuration', async () => { |
| |
| process.env.WESTUS_API_KEY = 'test-westus-key'; |
|
|
| const req = { |
| body: { |
| key: null, |
| endpoint: EModelEndpoint.azureOpenAI, |
| model: modelNames[0], |
| }, |
| user: { id: '123' }, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
| expect(client.client.options.azure).toBeDefined(); |
| }); |
|
|
| test('should initialize client with default options when certain env vars are not set', async () => { |
| delete process.env.DEBUG_OPENAI; |
| delete process.env.OPENAI_SUMMARIZE; |
| process.env.OPENAI_API_KEY = 'some-api-key'; |
|
|
| const req = { |
| body: { key: null, endpoint: EModelEndpoint.openAI }, |
| user: { id: '123' }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| const client = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(client.client.options.debug).toBe(false); |
| expect(client.client.options.contextStrategy).toBe(null); |
| }); |
|
|
| test('should correctly use user-provided apiKey and baseURL when provided', async () => { |
| process.env.OPENAI_API_KEY = 'user_provided'; |
| process.env.OPENAI_REVERSE_PROXY = 'user_provided'; |
| const req = { |
| body: { |
| key: new Date(Date.now() + 10000).toISOString(), |
| endpoint: EModelEndpoint.openAI, |
| }, |
| user: { |
| id: '123', |
| }, |
| app, |
| config: mockAppConfig, |
| }; |
| const res = {}; |
| const endpointOption = {}; |
|
|
| getUserKeyValues.mockResolvedValue({ |
| apiKey: 'test', |
| baseURL: 'https://user-provided-url.com', |
| }); |
|
|
| const result = await initializeClient({ req, res, endpointOption }); |
|
|
| expect(result.openAIApiKey).toBe('test'); |
| expect(result.client.options.reverseProxyUrl).toBe('https://user-provided-url.com'); |
| }); |
| }); |
|
|