refactor: update appConfig access to use endpoints structure across various services

This commit is contained in:
Danny Avila 2025-08-18 15:20:58 -04:00
parent 89fb9c7e1c
commit 240e3bd59e
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
36 changed files with 591 additions and 510 deletions

View file

@ -36,7 +36,7 @@ const getCustomEndpointConfig = async (endpoint) => {
throw new Error(`Config not found for the ${endpoint} custom endpoint.`);
}
const customEndpoints = appConfig[EModelEndpoint.custom] ?? [];
const customEndpoints = appConfig.endpoints?.[EModelEndpoint.custom] ?? [];
return customEndpoints.find(
(endpointConfig) => normalizeEndpointName(endpointConfig.name) === endpoint,
);

View file

@ -28,9 +28,12 @@ async function getEndpointsConfig(req) {
/** @type {TEndpointsConfig} */
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
if (mergedConfig[EModelEndpoint.assistants] && appConfig?.[EModelEndpoint.assistants]) {
if (
mergedConfig[EModelEndpoint.assistants] &&
appConfig?.endpoints?.[EModelEndpoint.assistants]
) {
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
appConfig[EModelEndpoint.assistants];
appConfig.endpoints[EModelEndpoint.assistants];
mergedConfig[EModelEndpoint.assistants] = {
...mergedConfig[EModelEndpoint.assistants],
@ -40,9 +43,9 @@ async function getEndpointsConfig(req) {
capabilities,
};
}
if (mergedConfig[EModelEndpoint.agents] && appConfig?.[EModelEndpoint.agents]) {
if (mergedConfig[EModelEndpoint.agents] && appConfig?.endpoints?.[EModelEndpoint.agents]) {
const { disableBuilder, capabilities, allowedProviders, ..._rest } =
appConfig[EModelEndpoint.agents];
appConfig.endpoints[EModelEndpoint.agents];
mergedConfig[EModelEndpoint.agents] = {
...mergedConfig[EModelEndpoint.agents],
@ -52,9 +55,12 @@ async function getEndpointsConfig(req) {
};
}
if (mergedConfig[EModelEndpoint.azureAssistants] && appConfig?.[EModelEndpoint.azureAssistants]) {
if (
mergedConfig[EModelEndpoint.azureAssistants] &&
appConfig?.endpoints?.[EModelEndpoint.azureAssistants]
) {
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
appConfig[EModelEndpoint.azureAssistants];
appConfig.endpoints[EModelEndpoint.azureAssistants];
mergedConfig[EModelEndpoint.azureAssistants] = {
...mergedConfig[EModelEndpoint.azureAssistants],
@ -65,8 +71,8 @@ async function getEndpointsConfig(req) {
};
}
if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.[EModelEndpoint.bedrock]) {
const { availableRegions } = appConfig[EModelEndpoint.bedrock];
if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.endpoints?.[EModelEndpoint.bedrock]) {
const { availableRegions } = appConfig.endpoints[EModelEndpoint.bedrock];
mergedConfig[EModelEndpoint.bedrock] = {
...mergedConfig[EModelEndpoint.bedrock],
availableRegions,

View file

@ -1,6 +1,7 @@
const appConfig = require('./app');
const { config } = require('./EndpointService');
const getCachedTools = require('./getCachedTools');
const getCustomConfig = require('./getCustomConfig');
const loadCustomConfig = require('./loadCustomConfig');
const loadConfigModels = require('./loadConfigModels');
const loadDefaultModels = require('./loadDefaultModels');
@ -17,5 +18,6 @@ module.exports = {
loadAsyncEndpoints,
...appConfig,
...getCachedTools,
...getCustomConfig,
...getEndpointsConfig,
};

View file

@ -36,7 +36,7 @@ async function loadAsyncEndpoints(req) {
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
const useAzure = appConfig[EModelEndpoint.azureOpenAI]?.plugins;
const useAzure = appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.plugins;
const gptPlugins =
useAzure || openAIApiKey || azureOpenAIApiKey
? {

View file

@ -15,8 +15,8 @@ async function loadConfigEndpoints(req) {
const endpointsConfig = {};
if (Array.isArray(appConfig[EModelEndpoint.custom])) {
const customEndpoints = appConfig[EModelEndpoint.custom].filter(
if (Array.isArray(appConfig.endpoints?.[EModelEndpoint.custom])) {
const customEndpoints = appConfig.endpoints[EModelEndpoint.custom].filter(
(endpoint) =>
endpoint.baseURL &&
endpoint.apiKey &&
@ -51,14 +51,14 @@ async function loadConfigEndpoints(req) {
}
}
if (appConfig[EModelEndpoint.azureOpenAI]) {
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]) {
/** @type {Omit<TConfig, 'order'>} */
endpointsConfig[EModelEndpoint.azureOpenAI] = {
userProvide: false,
};
}
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
/** @type {Omit<TConfig, 'order'>} */
endpointsConfig[EModelEndpoint.azureAssistants] = {
userProvide: false,

View file

@ -14,7 +14,7 @@ async function loadConfigModels(req) {
return {};
}
const modelsConfig = {};
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
const azureConfig = appConfig.endpoints?.[EModelEndpoint.azureOpenAI];
const { modelNames } = azureConfig ?? {};
if (modelNames && azureConfig) {
@ -29,11 +29,11 @@ async function loadConfigModels(req) {
modelsConfig[EModelEndpoint.azureAssistants] = azureConfig.assistantModels;
}
if (!Array.isArray(appConfig[EModelEndpoint.custom])) {
if (!Array.isArray(appConfig.endpoints?.[EModelEndpoint.custom])) {
return modelsConfig;
}
const customEndpoints = appConfig[EModelEndpoint.custom].filter(
const customEndpoints = appConfig.endpoints[EModelEndpoint.custom].filter(
(endpoint) =>
endpoint.baseURL &&
endpoint.apiKey &&

View file

@ -6,55 +6,57 @@ jest.mock('~/server/services/ModelService');
jest.mock('./app');
const exampleConfig = {
custom: [
{
name: 'Mistral',
apiKey: '${MY_PRECIOUS_MISTRAL_KEY}',
baseURL: 'https://api.mistral.ai/v1',
models: {
default: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest'],
fetch: true,
endpoints: {
custom: [
{
name: 'Mistral',
apiKey: '${MY_PRECIOUS_MISTRAL_KEY}',
baseURL: 'https://api.mistral.ai/v1',
models: {
default: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest'],
fetch: true,
},
dropParams: ['stop', 'user', 'frequency_penalty', 'presence_penalty'],
},
dropParams: ['stop', 'user', 'frequency_penalty', 'presence_penalty'],
},
{
name: 'OpenRouter',
apiKey: '${MY_OPENROUTER_API_KEY}',
baseURL: 'https://openrouter.ai/api/v1',
models: {
default: ['gpt-3.5-turbo'],
fetch: true,
{
name: 'OpenRouter',
apiKey: '${MY_OPENROUTER_API_KEY}',
baseURL: 'https://openrouter.ai/api/v1',
models: {
default: ['gpt-3.5-turbo'],
fetch: true,
},
dropParams: ['stop'],
},
dropParams: ['stop'],
},
{
name: 'groq',
apiKey: 'user_provided',
baseURL: 'https://api.groq.com/openai/v1/',
models: {
default: ['llama2-70b-4096', 'mixtral-8x7b-32768'],
fetch: false,
{
name: 'groq',
apiKey: 'user_provided',
baseURL: 'https://api.groq.com/openai/v1/',
models: {
default: ['llama2-70b-4096', 'mixtral-8x7b-32768'],
fetch: false,
},
},
},
{
name: 'Ollama',
apiKey: 'user_provided',
baseURL: 'http://localhost:11434/v1/',
models: {
default: ['mistral', 'llama2:13b'],
fetch: false,
{
name: 'Ollama',
apiKey: 'user_provided',
baseURL: 'http://localhost:11434/v1/',
models: {
default: ['mistral', 'llama2:13b'],
fetch: false,
},
},
},
{
name: 'MLX',
apiKey: 'user_provided',
baseURL: 'http://localhost:8080/v1/',
models: {
default: ['Meta-Llama-3-8B-Instruct-4bit'],
fetch: false,
{
name: 'MLX',
apiKey: 'user_provided',
baseURL: 'http://localhost:8080/v1/',
models: {
default: ['Meta-Llama-3-8B-Instruct-4bit'],
fetch: false,
},
},
},
],
],
},
};
describe('loadConfigModels', () => {
@ -83,7 +85,9 @@ describe('loadConfigModels', () => {
it('handles azure models and endpoint correctly', async () => {
getAppConfig.mockResolvedValue({
azureOpenAI: { modelNames: ['model1', 'model2'] },
endpoints: {
azureOpenAI: { modelNames: ['model1', 'model2'] },
},
});
const result = await loadConfigModels(mockRequest);
@ -102,7 +106,7 @@ describe('loadConfigModels', () => {
},
];
getAppConfig.mockResolvedValue({ custom: customEndpoints });
getAppConfig.mockResolvedValue({ endpoints: { custom: customEndpoints } });
fetchModels.mockResolvedValue(['customModel1', 'customModel2']);
const result = await loadConfigModels(mockRequest);
@ -112,20 +116,22 @@ describe('loadConfigModels', () => {
it('correctly associates models to names using unique keys', async () => {
getAppConfig.mockResolvedValue({
custom: [
{
baseURL: 'http://example.com',
apiKey: 'API_KEY1',
name: 'Model1',
models: { fetch: true },
},
{
baseURL: 'http://example.com',
apiKey: 'API_KEY2',
name: 'Model2',
models: { fetch: true },
},
],
endpoints: {
custom: [
{
baseURL: 'http://example.com',
apiKey: 'API_KEY1',
name: 'Model1',
models: { fetch: true },
},
{
baseURL: 'http://example.com',
apiKey: 'API_KEY2',
name: 'Model2',
models: { fetch: true },
},
],
},
});
fetchModels.mockImplementation(({ apiKey }) =>
Promise.resolve(apiKey === 'API_KEY1' ? ['model1Data'] : ['model2Data']),
@ -139,26 +145,28 @@ describe('loadConfigModels', () => {
it('correctly handles multiple endpoints with the same baseURL but different apiKeys', async () => {
// Mock the custom configuration to simulate the user's scenario
getAppConfig.mockResolvedValue({
custom: [
{
name: 'LiteLLM',
apiKey: '${LITELLM_ALL_MODELS}',
baseURL: '${LITELLM_HOST}',
models: { fetch: true },
},
{
name: 'OpenAI',
apiKey: '${LITELLM_OPENAI_MODELS}',
baseURL: '${LITELLM_SECOND_HOST}',
models: { fetch: true },
},
{
name: 'Google',
apiKey: '${LITELLM_GOOGLE_MODELS}',
baseURL: '${LITELLM_SECOND_HOST}',
models: { fetch: true },
},
],
endpoints: {
custom: [
{
name: 'LiteLLM',
apiKey: '${LITELLM_ALL_MODELS}',
baseURL: '${LITELLM_HOST}',
models: { fetch: true },
},
{
name: 'OpenAI',
apiKey: '${LITELLM_OPENAI_MODELS}',
baseURL: '${LITELLM_SECOND_HOST}',
models: { fetch: true },
},
{
name: 'Google',
apiKey: '${LITELLM_GOOGLE_MODELS}',
baseURL: '${LITELLM_SECOND_HOST}',
models: { fetch: true },
},
],
},
});
// Mock `fetchModels` to return different models based on the apiKey
@ -246,8 +254,8 @@ describe('loadConfigModels', () => {
// For groq and ollama, since the apiKey is "user_provided", models should not be fetched
// Depending on your implementation's behavior regarding "default" models without fetching,
// you may need to adjust the following assertions:
expect(result.groq).toBe(exampleConfig.custom[2].models.default);
expect(result.ollama).toBe(exampleConfig.custom[3].models.default);
expect(result.groq).toBe(exampleConfig.endpoints.custom[2].models.default);
expect(result.ollama).toBe(exampleConfig.endpoints.custom[3].models.default);
// Verifying fetchModels was not called for groq and ollama
expect(fetchModels).not.toHaveBeenCalledWith(
@ -264,26 +272,28 @@ describe('loadConfigModels', () => {
it('falls back to default models if fetching returns an empty array', async () => {
getAppConfig.mockResolvedValue({
custom: [
{
name: 'EndpointWithSameFetchKey',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1'],
endpoints: {
custom: [
{
name: 'EndpointWithSameFetchKey',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1'],
},
},
},
{
name: 'EmptyFetchModel',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1', 'defaultModel2'],
{
name: 'EmptyFetchModel',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1', 'defaultModel2'],
},
},
},
],
],
},
});
fetchModels.mockResolvedValue([]);
@ -295,17 +305,19 @@ describe('loadConfigModels', () => {
it('falls back to default models if fetching returns a falsy value', async () => {
getAppConfig.mockResolvedValue({
custom: [
{
name: 'FalsyFetchModel',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1', 'defaultModel2'],
endpoints: {
custom: [
{
name: 'FalsyFetchModel',
apiKey: 'API_KEY',
baseURL: 'http://example.com',
models: {
fetch: true,
default: ['defaultModel1', 'defaultModel2'],
},
},
},
],
],
},
});
fetchModels.mockResolvedValue(false);
@ -354,7 +366,9 @@ describe('loadConfigModels', () => {
];
getAppConfig.mockResolvedValue({
custom: testCases,
endpoints: {
custom: testCases,
},
});
const result = await loadConfigModels(mockRequest);