mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🔧 fix: Update Proxy Config for OpenAI Image Tools (#8712)
- Replaced HttpsProxyAgent with ProxyAgent from undici for improved proxy handling in DALLE3.js and OpenAIImageTools.js. - Updated fetchOptions to use dispatcher for proxy configuration. - Added new test suite for DALLE3 to verify proxy configuration behavior based on environment variables.
This commit is contained in:
parent
ec3cbca6e3
commit
8e6eef04ab
3 changed files with 123 additions and 6 deletions
|
|
@ -3,8 +3,8 @@ const path = require('path');
|
||||||
const OpenAI = require('openai');
|
const OpenAI = require('openai');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
const { ProxyAgent } = require('undici');
|
||||||
const { Tool } = require('@langchain/core/tools');
|
const { Tool } = require('@langchain/core/tools');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
||||||
const { FileContext, ContentTypes } = require('librechat-data-provider');
|
const { FileContext, ContentTypes } = require('librechat-data-provider');
|
||||||
const { getImageBasename } = require('~/server/services/Files/images');
|
const { getImageBasename } = require('~/server/services/Files/images');
|
||||||
const extractBaseURL = require('~/utils/extractBaseURL');
|
const extractBaseURL = require('~/utils/extractBaseURL');
|
||||||
|
|
@ -46,7 +46,10 @@ class DALLE3 extends Tool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PROXY) {
|
if (process.env.PROXY) {
|
||||||
config.httpAgent = new HttpsProxyAgent(process.env.PROXY);
|
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||||
|
config.fetchOptions = {
|
||||||
|
dispatcher: proxyAgent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {OpenAI} */
|
/** @type {OpenAI} */
|
||||||
|
|
@ -163,7 +166,8 @@ Error Message: ${error.message}`);
|
||||||
if (this.isAgent) {
|
if (this.isAgent) {
|
||||||
let fetchOptions = {};
|
let fetchOptions = {};
|
||||||
if (process.env.PROXY) {
|
if (process.env.PROXY) {
|
||||||
fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
|
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||||
|
fetchOptions.dispatcher = proxyAgent;
|
||||||
}
|
}
|
||||||
const imageResponse = await fetch(theImageUrl, fetchOptions);
|
const imageResponse = await fetch(theImageUrl, fetchOptions);
|
||||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
const arrayBuffer = await imageResponse.arrayBuffer();
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ const axios = require('axios');
|
||||||
const { v4 } = require('uuid');
|
const { v4 } = require('uuid');
|
||||||
const OpenAI = require('openai');
|
const OpenAI = require('openai');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
|
const { ProxyAgent } = require('undici');
|
||||||
const { tool } = require('@langchain/core/tools');
|
const { tool } = require('@langchain/core/tools');
|
||||||
const { logAxiosError } = require('@librechat/api');
|
const { logAxiosError } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
||||||
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
|
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { extractBaseURL } = require('~/utils');
|
const { extractBaseURL } = require('~/utils');
|
||||||
|
|
@ -189,7 +189,10 @@ function createOpenAIImageTools(fields = {}) {
|
||||||
}
|
}
|
||||||
const clientConfig = { ...closureConfig };
|
const clientConfig = { ...closureConfig };
|
||||||
if (process.env.PROXY) {
|
if (process.env.PROXY) {
|
||||||
clientConfig.httpAgent = new HttpsProxyAgent(process.env.PROXY);
|
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||||
|
clientConfig.fetchOptions = {
|
||||||
|
dispatcher: proxyAgent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {OpenAI} */
|
/** @type {OpenAI} */
|
||||||
|
|
@ -335,7 +338,10 @@ Error Message: ${error.message}`);
|
||||||
|
|
||||||
const clientConfig = { ...closureConfig };
|
const clientConfig = { ...closureConfig };
|
||||||
if (process.env.PROXY) {
|
if (process.env.PROXY) {
|
||||||
clientConfig.httpAgent = new HttpsProxyAgent(process.env.PROXY);
|
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||||
|
clientConfig.fetchOptions = {
|
||||||
|
dispatcher: proxyAgent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
@ -447,6 +453,19 @@ Error Message: ${error.message}`);
|
||||||
baseURL,
|
baseURL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
try {
|
||||||
|
const url = new URL(process.env.PROXY);
|
||||||
|
axiosConfig.proxy = {
|
||||||
|
host: url.hostname.replace(/^\[|\]$/g, ''),
|
||||||
|
port: url.port ? parseInt(url.port, 10) : undefined,
|
||||||
|
protocol: url.protocol.replace(':', ''),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error parsing proxy URL:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.IMAGE_GEN_OAI_AZURE_API_VERSION && process.env.IMAGE_GEN_OAI_BASEURL) {
|
if (process.env.IMAGE_GEN_OAI_AZURE_API_VERSION && process.env.IMAGE_GEN_OAI_BASEURL) {
|
||||||
axiosConfig.params = {
|
axiosConfig.params = {
|
||||||
'api-version': process.env.IMAGE_GEN_OAI_AZURE_API_VERSION,
|
'api-version': process.env.IMAGE_GEN_OAI_AZURE_API_VERSION,
|
||||||
|
|
|
||||||
94
api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js
Normal file
94
api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
const DALLE3 = require('../DALLE3');
|
||||||
|
const { ProxyAgent } = require('undici');
|
||||||
|
|
||||||
|
const processFileURL = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('~/server/services/Files/images', () => ({
|
||||||
|
getImageBasename: jest.fn().mockImplementation((url) => {
|
||||||
|
const parts = url.split('/');
|
||||||
|
const lastPart = parts.pop();
|
||||||
|
const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|tiff|svg)$/i;
|
||||||
|
if (imageExtensionRegex.test(lastPart)) {
|
||||||
|
return lastPart;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('fs', () => {
|
||||||
|
return {
|
||||||
|
existsSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn(),
|
||||||
|
promises: {
|
||||||
|
writeFile: jest.fn(),
|
||||||
|
readFile: jest.fn(),
|
||||||
|
unlink: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('path', () => {
|
||||||
|
return {
|
||||||
|
resolve: jest.fn(),
|
||||||
|
join: jest.fn(),
|
||||||
|
relative: jest.fn(),
|
||||||
|
extname: jest.fn().mockImplementation((filename) => {
|
||||||
|
return filename.slice(filename.lastIndexOf('.'));
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DALLE3 Proxy Configuration', () => {
|
||||||
|
let originalEnv;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
originalEnv = { ...process.env };
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
process.env = { ...originalEnv };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = originalEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure ProxyAgent in fetchOptions.dispatcher when PROXY env is set', () => {
|
||||||
|
// Set proxy environment variable
|
||||||
|
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||||
|
process.env.DALLE_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
// Create instance
|
||||||
|
const dalleWithProxy = new DALLE3({ processFileURL });
|
||||||
|
|
||||||
|
// Check that the openai client exists
|
||||||
|
expect(dalleWithProxy.openai).toBeDefined();
|
||||||
|
|
||||||
|
// Check that _options exists and has fetchOptions with a dispatcher
|
||||||
|
expect(dalleWithProxy.openai._options).toBeDefined();
|
||||||
|
expect(dalleWithProxy.openai._options.fetchOptions).toBeDefined();
|
||||||
|
expect(dalleWithProxy.openai._options.fetchOptions.dispatcher).toBeDefined();
|
||||||
|
expect(dalleWithProxy.openai._options.fetchOptions.dispatcher).toBeInstanceOf(ProxyAgent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not configure ProxyAgent when PROXY env is not set', () => {
|
||||||
|
// Ensure PROXY is not set
|
||||||
|
delete process.env.PROXY;
|
||||||
|
process.env.DALLE_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
// Create instance
|
||||||
|
const dalleWithoutProxy = new DALLE3({ processFileURL });
|
||||||
|
|
||||||
|
// Check that the openai client exists
|
||||||
|
expect(dalleWithoutProxy.openai).toBeDefined();
|
||||||
|
|
||||||
|
// Check that _options exists but fetchOptions either doesn't exist or doesn't have a dispatcher
|
||||||
|
expect(dalleWithoutProxy.openai._options).toBeDefined();
|
||||||
|
|
||||||
|
// fetchOptions should either not exist or not have a dispatcher
|
||||||
|
if (dalleWithoutProxy.openai._options.fetchOptions) {
|
||||||
|
expect(dalleWithoutProxy.openai._options.fetchOptions.dispatcher).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue