mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
feat: allow any reverse proxy URLs, add proxy support to model fetching (#1192)
* feat: allow any reverse proxy URLs * feat: add proxy support to model fetching
This commit is contained in:
parent
bac1fb67d2
commit
c64970525b
6 changed files with 17 additions and 19 deletions
|
|
@ -145,10 +145,6 @@ class OpenAIClient extends BaseClient {
|
||||||
if (reverseProxy) {
|
if (reverseProxy) {
|
||||||
this.completionsUrl = reverseProxy;
|
this.completionsUrl = reverseProxy;
|
||||||
this.langchainProxy = extractBaseURL(reverseProxy);
|
this.langchainProxy = extractBaseURL(reverseProxy);
|
||||||
!this.langchainProxy &&
|
|
||||||
console.warn(`The reverse proxy URL ${reverseProxy} is not valid for Plugins.
|
|
||||||
The url must follow OpenAI specs, for example: https://localhost:8080/v1/chat/completions
|
|
||||||
If your reverse proxy is compatible to OpenAI specs in every other way, it may still work without plugins enabled.`);
|
|
||||||
} else if (isChatGptModel) {
|
} else if (isChatGptModel) {
|
||||||
this.completionsUrl = 'https://api.openai.com/v1/chat/completions';
|
this.completionsUrl = 'https://api.openai.com/v1/chat/completions';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@ class PluginsClient extends OpenAIClient {
|
||||||
|
|
||||||
if (this.options.reverseProxyUrl) {
|
if (this.options.reverseProxyUrl) {
|
||||||
this.langchainProxy = extractBaseURL(this.options.reverseProxyUrl);
|
this.langchainProxy = extractBaseURL(this.options.reverseProxyUrl);
|
||||||
!this.langchainProxy &&
|
|
||||||
console.warn(`The reverse proxy URL ${this.options.reverseProxyUrl} is not valid for Plugins.
|
|
||||||
The url must follow OpenAI specs, for example: https://localhost:8080/v1/chat/completions
|
|
||||||
If your reverse proxy is compatible to OpenAI specs in every other way, it may still work without plugins enabled.`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ describe('OpenAIClient', () => {
|
||||||
|
|
||||||
client.setOptions({ reverseProxyUrl: 'https://example.com/completions' });
|
client.setOptions({ reverseProxyUrl: 'https://example.com/completions' });
|
||||||
expect(client.completionsUrl).toBe('https://example.com/completions');
|
expect(client.completionsUrl).toBe('https://example.com/completions');
|
||||||
expect(client.langchainProxy).toBe(null);
|
expect(client.langchainProxy).toBe('https://example.com/completions');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const Keyv = require('keyv');
|
const HttpsProxyAgent = require('https-proxy-agent');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const Keyv = require('keyv');
|
||||||
const { isEnabled } = require('../utils');
|
const { isEnabled } = require('../utils');
|
||||||
const { extractBaseURL } = require('../../utils');
|
const { extractBaseURL } = require('../../utils');
|
||||||
const keyvRedis = require('../../cache/keyvRedis');
|
const keyvRedis = require('../../cache/keyvRedis');
|
||||||
|
|
@ -10,7 +11,7 @@ const modelsCache = isEnabled(process.env.USE_REDIS)
|
||||||
? new Keyv({ store: keyvRedis })
|
? new Keyv({ store: keyvRedis })
|
||||||
: new Keyv({ namespace: 'models' });
|
: new Keyv({ namespace: 'models' });
|
||||||
|
|
||||||
const { OPENROUTER_API_KEY, OPENAI_REVERSE_PROXY, CHATGPT_MODELS, ANTHROPIC_MODELS } =
|
const { OPENROUTER_API_KEY, OPENAI_REVERSE_PROXY, CHATGPT_MODELS, ANTHROPIC_MODELS, PROXY } =
|
||||||
process.env ?? {};
|
process.env ?? {};
|
||||||
|
|
||||||
const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _models = []) => {
|
const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _models = []) => {
|
||||||
|
|
@ -39,13 +40,18 @@ const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _model
|
||||||
return cachedModels;
|
return cachedModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (basePath?.includes('v1') || opts.azure) {
|
if (basePath || opts.azure) {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`${basePath}${opts.azure ? '' : '/models'}`, {
|
const payload = {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (PROXY) {
|
||||||
|
payload.httpsAgent = new HttpsProxyAgent(PROXY);
|
||||||
|
}
|
||||||
|
const res = await axios.get(`${basePath}${opts.azure ? '' : '/models'}`, payload);
|
||||||
|
|
||||||
models = res.data.data.map((item) => item.id);
|
models = res.data.data.map((item) => item.id);
|
||||||
// console.log(`Fetched ${models.length} models from ${opts.azure ? 'Azure ' : ''}OpenAI API`);
|
// console.log(`Fetched ${models.length} models from ${opts.azure ? 'Azure ' : ''}OpenAI API`);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Extracts a valid OpenAI baseURL from a given string, matching "url/v1," also an added suffix,
|
* Extracts a valid OpenAI baseURL from a given string, matching "url/v1," also an added suffix,
|
||||||
* ending with "/openai" (to allow the Cloudflare, LiteLLM pattern).
|
* ending with "/openai" (to allow the Cloudflare, LiteLLM pattern).
|
||||||
|
* Returns the original URL if no match is found.
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* - `https://open.ai/v1/chat` -> `https://open.ai/v1`
|
* - `https://open.ai/v1/chat` -> `https://open.ai/v1`
|
||||||
|
|
@ -9,12 +10,11 @@
|
||||||
* - `https://open.ai/v1/hi/openai` -> `https://open.ai/v1/hi/openai`
|
* - `https://open.ai/v1/hi/openai` -> `https://open.ai/v1/hi/openai`
|
||||||
*
|
*
|
||||||
* @param {string} url - The URL to be processed.
|
* @param {string} url - The URL to be processed.
|
||||||
* @returns {string|null} The matched pattern or null if no match is found.
|
* @returns {string} The matched pattern or input if no match is found.
|
||||||
*/
|
*/
|
||||||
function extractBaseURL(url) {
|
function extractBaseURL(url) {
|
||||||
// First, let's make sure the URL contains '/v1'.
|
|
||||||
if (!url.includes('/v1')) {
|
if (!url.includes('/v1')) {
|
||||||
return null;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the index of '/v1' to use it as a reference point.
|
// Find the index of '/v1' to use it as a reference point.
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ describe('extractBaseURL', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return null if the URL does not match the expected pattern', () => {
|
test('should return input if the URL does not match the expected pattern', () => {
|
||||||
const url = 'https://someotherdomain.com/notv1';
|
const url = 'https://someotherdomain.com/notv1';
|
||||||
expect(extractBaseURL(url)).toBeNull();
|
expect(extractBaseURL(url)).toBe(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test our JSDoc examples.
|
// Test our JSDoc examples.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue