mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🦙 fix: Ollama Provider Handling (#10711)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* 🔧 fix: Correct URL Construction in fetchModels Function Updated the URL construction in the fetchModels function to ensure proper formatting by removing trailing slashes from the base URL. This change prevents potential issues with API endpoint calls. * 🔧 fix: Remove OLLAMA from Known Custom Providers Updated the isKnownCustomProvider function and providerConfigMap to exclude OLLAMA as a known custom provider, streamlining the provider checks and configurations. * 🔧 test: Enhance fetchModels Tests for URL Construction Added new test cases to validate the URL construction in the fetchModels function, ensuring it handles trailing slashes correctly and appends query parameters as expected. This improves the robustness of the API endpoint calls. * chore: remove ollama provider-specific handling * chore: Refactor imports to use isUserProvided from @librechat/api
This commit is contained in:
parent
872dbb4151
commit
801c95a829
9 changed files with 317 additions and 152 deletions
|
|
@ -47,7 +47,7 @@
|
||||||
"@langchain/google-genai": "^0.2.13",
|
"@langchain/google-genai": "^0.2.13",
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^3.0.33",
|
"@librechat/agents": "^3.0.34",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const OpenAI = require('openai');
|
const OpenAI = require('openai');
|
||||||
const { ProxyAgent } = require('undici');
|
const { ProxyAgent } = require('undici');
|
||||||
|
const { isUserProvided } = require('@librechat/api');
|
||||||
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
|
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getUserKeyValues,
|
getUserKeyValues,
|
||||||
|
|
@ -7,7 +8,6 @@ const {
|
||||||
checkUserKeyExpiry,
|
checkUserKeyExpiry,
|
||||||
} = require('~/server/services/UserService');
|
} = require('~/server/services/UserService');
|
||||||
const OAIClient = require('~/app/clients/OpenAIClient');
|
const OAIClient = require('~/app/clients/OpenAIClient');
|
||||||
const { isUserProvided } = require('~/server/utils');
|
|
||||||
|
|
||||||
const initializeClient = async ({ req, res, endpointOption, version, initAppClient = false }) => {
|
const initializeClient = async ({ req, res, endpointOption, version, initAppClient = false }) => {
|
||||||
const { PROXY, OPENAI_ORGANIZATION, ASSISTANTS_API_KEY, ASSISTANTS_BASE_URL } = process.env;
|
const { PROXY, OPENAI_ORGANIZATION, ASSISTANTS_API_KEY, ASSISTANTS_BASE_URL } = process.env;
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,13 @@ const initGoogle = require('~/server/services/Endpoints/google/initialize');
|
||||||
* @returns {boolean} - True if the provider is a known custom provider, false otherwise
|
* @returns {boolean} - True if the provider is a known custom provider, false otherwise
|
||||||
*/
|
*/
|
||||||
function isKnownCustomProvider(provider) {
|
function isKnownCustomProvider(provider) {
|
||||||
return [Providers.XAI, Providers.OLLAMA, Providers.DEEPSEEK, Providers.OPENROUTER].includes(
|
return [Providers.XAI, Providers.DEEPSEEK, Providers.OPENROUTER].includes(
|
||||||
provider?.toLowerCase() || '',
|
provider?.toLowerCase() || '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerConfigMap = {
|
const providerConfigMap = {
|
||||||
[Providers.XAI]: initCustom,
|
[Providers.XAI]: initCustom,
|
||||||
[Providers.OLLAMA]: initCustom,
|
|
||||||
[Providers.DEEPSEEK]: initCustom,
|
[Providers.DEEPSEEK]: initCustom,
|
||||||
[Providers.OPENROUTER]: initCustom,
|
[Providers.OPENROUTER]: initCustom,
|
||||||
[EModelEndpoint.openAI]: initOpenAI,
|
[EModelEndpoint.openAI]: initOpenAI,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { Providers } = require('@librechat/agents');
|
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { logAxiosError, inputSchema, processModelData } = require('@librechat/api');
|
const { logAxiosError, inputSchema, processModelData, isUserProvided } = require('@librechat/api');
|
||||||
const { EModelEndpoint, defaultModels, CacheKeys } = require('librechat-data-provider');
|
const {
|
||||||
|
CacheKeys,
|
||||||
|
defaultModels,
|
||||||
|
KnownEndpoints,
|
||||||
|
EModelEndpoint,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
const { OllamaClient } = require('~/app/clients/OllamaClient');
|
const { OllamaClient } = require('~/app/clients/OllamaClient');
|
||||||
const { isUserProvided } = require('~/server/utils');
|
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const { extractBaseURL } = require('~/utils');
|
const { extractBaseURL } = require('~/utils');
|
||||||
|
|
||||||
|
|
@ -68,7 +71,7 @@ const fetchModels = async ({
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name && name.toLowerCase().startsWith(Providers.OLLAMA)) {
|
if (name && name.toLowerCase().startsWith(KnownEndpoints.ollama)) {
|
||||||
try {
|
try {
|
||||||
return await OllamaClient.fetchModels(baseURL, { headers, user: userObject });
|
return await OllamaClient.fetchModels(baseURL, { headers, user: userObject });
|
||||||
} catch (ollamaError) {
|
} catch (ollamaError) {
|
||||||
|
|
@ -103,7 +106,7 @@ const fetchModels = async ({
|
||||||
options.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION;
|
options.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(`${baseURL}${azure ? '' : '/models'}`);
|
const url = new URL(`${baseURL.replace(/\/+$/, '')}${azure ? '' : '/models'}`);
|
||||||
if (user && userIdQuery) {
|
if (user && userIdQuery) {
|
||||||
url.searchParams.append('user', user);
|
url.searchParams.append('user', user);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,68 @@ describe('fetchModels with Ollama specific logic', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('fetchModels URL construction with trailing slashes', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
data: [{ id: 'model-1' }, { id: 'model-2' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create double slashes when baseURL has a trailing slash', async () => {
|
||||||
|
await fetchModels({
|
||||||
|
user: 'user123',
|
||||||
|
apiKey: 'testApiKey',
|
||||||
|
baseURL: 'https://api.test.com/v1/',
|
||||||
|
name: 'TestAPI',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('https://api.test.com/v1/models', expect.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle baseURL without trailing slash normally', async () => {
|
||||||
|
await fetchModels({
|
||||||
|
user: 'user123',
|
||||||
|
apiKey: 'testApiKey',
|
||||||
|
baseURL: 'https://api.test.com/v1',
|
||||||
|
name: 'TestAPI',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('https://api.test.com/v1/models', expect.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle baseURL with multiple trailing slashes', async () => {
|
||||||
|
await fetchModels({
|
||||||
|
user: 'user123',
|
||||||
|
apiKey: 'testApiKey',
|
||||||
|
baseURL: 'https://api.test.com/v1///',
|
||||||
|
name: 'TestAPI',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('https://api.test.com/v1/models', expect.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly append query params after stripping trailing slashes', async () => {
|
||||||
|
await fetchModels({
|
||||||
|
user: 'user123',
|
||||||
|
apiKey: 'testApiKey',
|
||||||
|
baseURL: 'https://api.test.com/v1/',
|
||||||
|
name: 'TestAPI',
|
||||||
|
userIdQuery: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(
|
||||||
|
'https://api.test.com/v1/models?user=user123',
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('splitAndTrim', () => {
|
describe('splitAndTrim', () => {
|
||||||
it('should split a string by commas and trim each value', () => {
|
it('should split a string by commas and trim each value', () => {
|
||||||
const input = ' model1, model2 , model3,model4 ';
|
const input = ' model1, model2 , model3,model4 ';
|
||||||
|
|
|
||||||
378
package-lock.json
generated
378
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -84,7 +84,7 @@
|
||||||
"@azure/storage-blob": "^12.27.0",
|
"@azure/storage-blob": "^12.27.0",
|
||||||
"@keyv/redis": "^4.3.3",
|
"@keyv/redis": "^4.3.3",
|
||||||
"@langchain/core": "^0.3.79",
|
"@langchain/core": "^0.3.79",
|
||||||
"@librechat/agents": "^3.0.33",
|
"@librechat/agents": "^3.0.34",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.21.0",
|
"@modelcontextprotocol/sdk": "^1.21.0",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ import { resolveHeaders, createSafeUser } from '~/utils/env';
|
||||||
|
|
||||||
const customProviders = new Set([
|
const customProviders = new Set([
|
||||||
Providers.XAI,
|
Providers.XAI,
|
||||||
Providers.OLLAMA,
|
|
||||||
Providers.DEEPSEEK,
|
Providers.DEEPSEEK,
|
||||||
Providers.OPENROUTER,
|
Providers.OPENROUTER,
|
||||||
|
KnownEndpoints.ollama,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getReasoningKey(
|
export function getReasoningKey(
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ export enum Providers {
|
||||||
BEDROCK = 'bedrock',
|
BEDROCK = 'bedrock',
|
||||||
MISTRALAI = 'mistralai',
|
MISTRALAI = 'mistralai',
|
||||||
MISTRAL = 'mistral',
|
MISTRAL = 'mistral',
|
||||||
OLLAMA = 'ollama',
|
|
||||||
DEEPSEEK = 'deepseek',
|
DEEPSEEK = 'deepseek',
|
||||||
OPENROUTER = 'openrouter',
|
OPENROUTER = 'openrouter',
|
||||||
XAI = 'xai',
|
XAI = 'xai',
|
||||||
|
|
@ -59,7 +58,6 @@ export const documentSupportedProviders = new Set<string>([
|
||||||
Providers.VERTEXAI,
|
Providers.VERTEXAI,
|
||||||
Providers.MISTRALAI,
|
Providers.MISTRALAI,
|
||||||
Providers.MISTRAL,
|
Providers.MISTRAL,
|
||||||
Providers.OLLAMA,
|
|
||||||
Providers.DEEPSEEK,
|
Providers.DEEPSEEK,
|
||||||
Providers.OPENROUTER,
|
Providers.OPENROUTER,
|
||||||
Providers.XAI,
|
Providers.XAI,
|
||||||
|
|
@ -71,7 +69,6 @@ const openAILikeProviders = new Set<string>([
|
||||||
EModelEndpoint.custom,
|
EModelEndpoint.custom,
|
||||||
Providers.MISTRALAI,
|
Providers.MISTRALAI,
|
||||||
Providers.MISTRAL,
|
Providers.MISTRAL,
|
||||||
Providers.OLLAMA,
|
|
||||||
Providers.DEEPSEEK,
|
Providers.DEEPSEEK,
|
||||||
Providers.OPENROUTER,
|
Providers.OPENROUTER,
|
||||||
Providers.XAI,
|
Providers.XAI,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue