Skip to content

Commit 64bd373

Browse files
authored
🔧 fix: Keyv and Proxy Issues, and More Memory Optimizations (danny-avila#6867)
* chore: update @librechat/agents dependency to version 2.4.15 * refactor: Prevent memory leaks by nullifying boundModel.client in disposeClient function * fix: use of proxy, use undici * chore: update @librechat/agents dependency to version 2.4.16 * Revert "fix: use of proxy, use undici" This reverts commit 83153cd. * fix: ensure fetch is imported for HTTP requests * fix: replace direct OpenAI import with CustomOpenAIClient from @librechat/agents * fix: update keyv peer dependency to version 5.3.2 * fix: update keyv dependency to version 5.3.2 * refactor: replace KeyvMongo with custom implementation and update flow state manager usage * fix: update @librechat/agents dependency to version 2.4.17 * ci: update OpenAIClient tests to use CustomOpenAIClient from @librechat/agents * refactor: remove KeyvMongo mock and related dependencies
1 parent 339882e commit 64bd373

File tree

18 files changed

+375
-743
lines changed

18 files changed

+375
-743
lines changed

api/app/clients/OpenAIClient.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
const OpenAI = require('openai');
21
const { OllamaClient } = require('./OllamaClient');
32
const { HttpsProxyAgent } = require('https-proxy-agent');
4-
const { SplitStreamHandler } = require('@librechat/agents');
3+
const { SplitStreamHandler, CustomOpenAIClient: OpenAI } = require('@librechat/agents');
54
const {
65
Constants,
76
ImageDetail,

api/app/clients/generators.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const fetch = require('node-fetch');
12
const { GraphEvents } = require('@librechat/agents');
23
const { logger, sendEvent } = require('~/config');
34

api/app/clients/specs/BaseClient.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jest.mock('~/models', () => ({
3232

3333
const { getConvo, saveConvo } = require('~/models');
3434

35-
jest.mock('@langchain/openai', () => {
35+
jest.mock('@librechat/agents', () => {
3636
return {
3737
ChatOpenAI: jest.fn().mockImplementation(() => {
3838
return {};

api/app/clients/specs/OpenAIClient.test.js

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
jest.mock('~/cache/getLogStores');
22
require('dotenv').config();
3-
const OpenAI = require('openai');
4-
const getLogStores = require('~/cache/getLogStores');
53
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
6-
const { genAzureChatCompletion } = require('~/utils/azureUtils');
4+
const getLogStores = require('~/cache/getLogStores');
75
const OpenAIClient = require('../OpenAIClient');
86
jest.mock('meilisearch');
97

@@ -36,19 +34,21 @@ jest.mock('~/models', () => ({
3634
updateFileUsage: jest.fn(),
3735
}));
3836

39-
jest.mock('@langchain/openai', () => {
40-
return {
41-
ChatOpenAI: jest.fn().mockImplementation(() => {
42-
return {};
43-
}),
44-
};
45-
});
37+
// Import the actual module but mock specific parts
38+
const agents = jest.requireActual('@librechat/agents');
39+
const { CustomOpenAIClient } = agents;
4640

47-
jest.mock('openai');
41+
// Also mock ChatOpenAI to prevent real API calls
42+
agents.ChatOpenAI = jest.fn().mockImplementation(() => {
43+
return {};
44+
});
45+
agents.AzureChatOpenAI = jest.fn().mockImplementation(() => {
46+
return {};
47+
});
4848

49-
jest.spyOn(OpenAI, 'constructor').mockImplementation(function (...options) {
50-
// We can add additional logic here if needed
51-
return new OpenAI(...options);
49+
// Mock only the CustomOpenAIClient constructor
50+
jest.spyOn(CustomOpenAIClient, 'constructor').mockImplementation(function (...options) {
51+
return new CustomOpenAIClient(...options);
5252
});
5353

5454
const finalChatCompletion = jest.fn().mockResolvedValue({
@@ -120,7 +120,13 @@ const create = jest.fn().mockResolvedValue({
120120
],
121121
});
122122

123-
OpenAI.mockImplementation(() => ({
123+
// Mock the implementation of CustomOpenAIClient instances
124+
jest.spyOn(CustomOpenAIClient.prototype, 'constructor').mockImplementation(function () {
125+
return this;
126+
});
127+
128+
// Create a mock for the CustomOpenAIClient class
129+
const mockCustomOpenAIClient = jest.fn().mockImplementation(() => ({
124130
beta: {
125131
chat: {
126132
completions: {
@@ -135,6 +141,8 @@ OpenAI.mockImplementation(() => ({
135141
},
136142
}));
137143

144+
CustomOpenAIClient.mockImplementation = mockCustomOpenAIClient;
145+
138146
describe('OpenAIClient', () => {
139147
beforeEach(() => {
140148
const mockCache = {
@@ -559,41 +567,6 @@ describe('OpenAIClient', () => {
559567
expect(requestBody).toHaveProperty('model');
560568
expect(requestBody.model).toBe(model);
561569
});
562-
563-
it('[Azure OpenAI] should call chatCompletion and OpenAI.stream with correct args', async () => {
564-
// Set a default model
565-
process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt4-turbo';
566-
567-
const onProgress = jest.fn().mockImplementation(() => ({}));
568-
client.azure = defaultAzureOptions;
569-
const chatCompletion = jest.spyOn(client, 'chatCompletion');
570-
await client.sendMessage('Hi mom!', {
571-
replaceOptions: true,
572-
...defaultOptions,
573-
modelOptions: { model: 'gpt4-turbo', stream: true },
574-
onProgress,
575-
azure: defaultAzureOptions,
576-
});
577-
578-
expect(chatCompletion).toHaveBeenCalled();
579-
expect(chatCompletion.mock.calls.length).toBe(1);
580-
581-
const chatCompletionArgs = chatCompletion.mock.calls[0][0];
582-
const { payload } = chatCompletionArgs;
583-
584-
expect(payload[0].role).toBe('user');
585-
expect(payload[0].content).toBe('Hi mom!');
586-
587-
// Azure OpenAI does not use the model property, and will error if it's passed
588-
// This check ensures the model property is not present
589-
const streamArgs = stream.mock.calls[0][0];
590-
expect(streamArgs).not.toHaveProperty('model');
591-
592-
// Check if the baseURL is correct
593-
const constructorArgs = OpenAI.mock.calls[0][0];
594-
const expectedURL = genAzureChatCompletion(defaultAzureOptions).split('/chat')[0];
595-
expect(constructorArgs.baseURL).toBe(expectedURL);
596-
});
597570
});
598571

599572
describe('checkVisionRequest functionality', () => {

0 commit comments

Comments
 (0)