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

@ -464,7 +464,9 @@ class AgentClient extends BaseClient {
/** @type {Agent} */
let prelimAgent;
const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
const allowedProviders = new Set(
appConfig?.endpoints?.[EModelEndpoint.agents]?.allowedProviders,
);
try {
if (memoryConfig.agent?.id != null && memoryConfig.agent.id !== this.options.agent.id) {
prelimAgent = await loadAgent({
@ -770,8 +772,8 @@ class AgentClient extends BaseClient {
}
const appConfig = await getAppConfig({ role: this.options.req.user?.role });
/** @type {TCustomConfig['endpoints']['agents']} */
const agentsEConfig = appConfig[EModelEndpoint.agents];
/** @type {AppConfig['endpoints']['agents']} */
const agentsEConfig = appConfig.endpoints?.[EModelEndpoint.agents];
config = {
configurable: {
@ -1104,7 +1106,9 @@ class AgentClient extends BaseClient {
/** @type {TEndpoint | undefined} */
const endpointConfig =
appConfig.all ?? appConfig[endpoint] ?? titleProviderConfig.customEndpointConfig;
appConfig.endpoints?.all ??
appConfig.endpoints?.[endpoint] ??
titleProviderConfig.customEndpointConfig;
if (!endpointConfig) {
logger.warn(
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',

View file

@ -46,12 +46,14 @@ describe('AgentClient - titleConvo', () => {
// Mock getAppConfig to return endpoint configurations
getAppConfig.mockResolvedValue({
[EModelEndpoint.openAI]: {
// Match the agent endpoint
titleModel: 'gpt-3.5-turbo',
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
endpoints: {
[EModelEndpoint.openAI]: {
// Match the agent endpoint
titleModel: 'gpt-3.5-turbo',
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
},
},
});
@ -148,7 +150,7 @@ describe('AgentClient - titleConvo', () => {
it('should handle missing endpoint config gracefully', async () => {
// Remove endpoint config
getAppConfig.mockResolvedValue({});
getAppConfig.mockResolvedValue({ endpoints: {} });
const text = 'Test conversation text';
const abortController = new AbortController();
@ -167,11 +169,13 @@ describe('AgentClient - titleConvo', () => {
it('should use agent model when titleModel is not provided', async () => {
// Remove titleModel from config
getAppConfig.mockResolvedValue({
[EModelEndpoint.openAI]: {
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
// titleModel is omitted
endpoints: {
[EModelEndpoint.openAI]: {
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
// titleModel is omitted
},
},
});
@ -186,11 +190,13 @@ describe('AgentClient - titleConvo', () => {
it('should not use titleModel when it equals CURRENT_MODEL constant', async () => {
getAppConfig.mockResolvedValue({
[EModelEndpoint.openAI]: {
titleModel: Constants.CURRENT_MODEL,
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
endpoints: {
[EModelEndpoint.openAI]: {
titleModel: Constants.CURRENT_MODEL,
titlePrompt: 'Custom title prompt',
titleMethod: 'structured',
titlePromptTemplate: 'Template: {{content}}',
},
},
});
@ -265,12 +271,14 @@ describe('AgentClient - titleConvo', () => {
// Add titleEndpoint to the config
getAppConfig.mockResolvedValue({
[EModelEndpoint.openAI]: {
titleModel: 'gpt-3.5-turbo',
titleEndpoint: EModelEndpoint.anthropic,
titleMethod: 'structured',
titlePrompt: 'Custom title prompt',
titlePromptTemplate: 'Custom template',
endpoints: {
[EModelEndpoint.openAI]: {
titleModel: 'gpt-3.5-turbo',
titleEndpoint: EModelEndpoint.anthropic,
titleMethod: 'structured',
titlePrompt: 'Custom title prompt',
titlePromptTemplate: 'Custom template',
},
},
});
@ -300,11 +308,13 @@ describe('AgentClient - titleConvo', () => {
it('should use all config when endpoint config is missing', async () => {
// Set 'all' config without endpoint-specific config
getAppConfig.mockResolvedValue({
all: {
titleModel: 'gpt-4o-mini',
titlePrompt: 'All config title prompt',
titleMethod: 'completion',
titlePromptTemplate: 'All config template: {{content}}',
endpoints: {
all: {
titleModel: 'gpt-4o-mini',
titlePrompt: 'All config title prompt',
titleMethod: 'completion',
titlePromptTemplate: 'All config template: {{content}}',
},
},
});
@ -330,17 +340,19 @@ describe('AgentClient - titleConvo', () => {
it('should prioritize all config over endpoint config for title settings', async () => {
// Set both endpoint and 'all' config
getAppConfig.mockResolvedValue({
[EModelEndpoint.openAI]: {
titleModel: 'gpt-3.5-turbo',
titlePrompt: 'Endpoint title prompt',
titleMethod: 'structured',
// titlePromptTemplate is omitted to test fallback
},
all: {
titleModel: 'gpt-4o-mini',
titlePrompt: 'All config title prompt',
titleMethod: 'completion',
titlePromptTemplate: 'All config template',
endpoints: {
[EModelEndpoint.openAI]: {
titleModel: 'gpt-3.5-turbo',
titlePrompt: 'Endpoint title prompt',
titleMethod: 'structured',
// titlePromptTemplate is omitted to test fallback
},
all: {
titleModel: 'gpt-4o-mini',
titlePrompt: 'All config title prompt',
titleMethod: 'completion',
titlePromptTemplate: 'All config template',
},
},
});
@ -370,13 +382,15 @@ describe('AgentClient - titleConvo', () => {
// Set comprehensive 'all' config with all new title options
getAppConfig.mockResolvedValue({
all: {
titleConvo: true,
titleModel: 'claude-3-haiku-20240307',
titleMethod: 'completion', // Testing the new default method
titlePrompt: 'Generate a concise, descriptive title for this conversation',
titlePromptTemplate: 'Conversation summary: {{content}}',
titleEndpoint: EModelEndpoint.anthropic, // Should switch provider to Anthropic
endpoints: {
all: {
titleConvo: true,
titleModel: 'claude-3-haiku-20240307',
titleMethod: 'completion', // Testing the new default method
titlePrompt: 'Generate a concise, descriptive title for this conversation',
titlePromptTemplate: 'Conversation summary: {{content}}',
titleEndpoint: EModelEndpoint.anthropic, // Should switch provider to Anthropic
},
},
});
@ -425,11 +439,13 @@ describe('AgentClient - titleConvo', () => {
// Set 'all' config with specific titleMethod
getAppConfig.mockResolvedValue({
all: {
titleModel: 'gpt-4o-mini',
titleMethod: method,
titlePrompt: `Testing ${method} method`,
titlePromptTemplate: `Template for ${method}: {{content}}`,
endpoints: {
all: {
titleModel: 'gpt-4o-mini',
titleMethod: method,
titlePrompt: `Testing ${method} method`,
titlePromptTemplate: `Template for ${method}: {{content}}`,
},
},
});
@ -476,27 +492,29 @@ describe('AgentClient - titleConvo', () => {
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
mockAgent.provider = EModelEndpoint.azureOpenAI;
getAppConfig.mockResolvedValue({
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'grok-3',
titleMethod: 'completion',
titlePrompt: 'Azure serverless title prompt',
streamRate: 35,
modelGroupMap: {
'grok-3': {
group: 'Azure AI Foundry',
deploymentName: 'grok-3',
endpoints: {
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'grok-3',
titleMethod: 'completion',
titlePrompt: 'Azure serverless title prompt',
streamRate: 35,
modelGroupMap: {
'grok-3': {
group: 'Azure AI Foundry',
deploymentName: 'grok-3',
},
},
},
groupMap: {
'Azure AI Foundry': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://test.services.ai.azure.com/models',
version: '2024-05-01-preview',
serverless: true,
models: {
'grok-3': {
deploymentName: 'grok-3',
groupMap: {
'Azure AI Foundry': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://test.services.ai.azure.com/models',
version: '2024-05-01-preview',
serverless: true,
models: {
'grok-3': {
deploymentName: 'grok-3',
},
},
},
},
@ -526,26 +544,28 @@ describe('AgentClient - titleConvo', () => {
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
mockAgent.provider = EModelEndpoint.azureOpenAI;
getAppConfig.mockResolvedValue({
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'gpt-4o',
titleMethod: 'structured',
titlePrompt: 'Azure instance title prompt',
streamRate: 35,
modelGroupMap: {
'gpt-4o': {
group: 'eastus',
deploymentName: 'gpt-4o',
endpoints: {
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'gpt-4o',
titleMethod: 'structured',
titlePrompt: 'Azure instance title prompt',
streamRate: 35,
modelGroupMap: {
'gpt-4o': {
group: 'eastus',
deploymentName: 'gpt-4o',
},
},
},
groupMap: {
eastus: {
apiKey: '${EASTUS_API_KEY}',
instanceName: 'region-instance',
version: '2024-02-15-preview',
models: {
'gpt-4o': {
deploymentName: 'gpt-4o',
groupMap: {
eastus: {
apiKey: '${EASTUS_API_KEY}',
instanceName: 'region-instance',
version: '2024-02-15-preview',
models: {
'gpt-4o': {
deploymentName: 'gpt-4o',
},
},
},
},
@ -576,27 +596,29 @@ describe('AgentClient - titleConvo', () => {
mockAgent.provider = EModelEndpoint.azureOpenAI;
mockAgent.model_parameters.model = 'gpt-4o-latest';
getAppConfig.mockResolvedValue({
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: Constants.CURRENT_MODEL,
titleMethod: 'functions',
streamRate: 35,
modelGroupMap: {
'gpt-4o-latest': {
group: 'region-eastus',
deploymentName: 'gpt-4o-mini',
version: '2024-02-15-preview',
endpoints: {
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: Constants.CURRENT_MODEL,
titleMethod: 'functions',
streamRate: 35,
modelGroupMap: {
'gpt-4o-latest': {
group: 'region-eastus',
deploymentName: 'gpt-4o-mini',
version: '2024-02-15-preview',
},
},
},
groupMap: {
'region-eastus': {
apiKey: '${EASTUS2_API_KEY}',
instanceName: 'test-instance',
version: '2024-12-01-preview',
models: {
'gpt-4o-latest': {
deploymentName: 'gpt-4o-mini',
version: '2024-02-15-preview',
groupMap: {
'region-eastus': {
apiKey: '${EASTUS2_API_KEY}',
instanceName: 'test-instance',
version: '2024-12-01-preview',
models: {
'gpt-4o-latest': {
deploymentName: 'gpt-4o-mini',
version: '2024-02-15-preview',
},
},
},
},
@ -625,54 +647,56 @@ describe('AgentClient - titleConvo', () => {
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
mockAgent.provider = EModelEndpoint.azureOpenAI;
getAppConfig.mockResolvedValue({
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'o1-mini',
titleMethod: 'completion',
streamRate: 35,
modelGroupMap: {
'gpt-4o': {
group: 'eastus',
deploymentName: 'gpt-4o',
},
'o1-mini': {
group: 'region-eastus',
deploymentName: 'o1-mini',
},
'codex-mini': {
group: 'codex-mini',
deploymentName: 'codex-mini',
},
},
groupMap: {
eastus: {
apiKey: '${EASTUS_API_KEY}',
instanceName: 'region-eastus',
version: '2024-02-15-preview',
models: {
'gpt-4o': {
deploymentName: 'gpt-4o',
},
endpoints: {
[EModelEndpoint.azureOpenAI]: {
titleConvo: true,
titleModel: 'o1-mini',
titleMethod: 'completion',
streamRate: 35,
modelGroupMap: {
'gpt-4o': {
group: 'eastus',
deploymentName: 'gpt-4o',
},
'o1-mini': {
group: 'region-eastus',
deploymentName: 'o1-mini',
},
'codex-mini': {
group: 'codex-mini',
deploymentName: 'codex-mini',
},
},
'region-eastus': {
apiKey: '${EASTUS2_API_KEY}',
instanceName: 'region-eastus2',
version: '2024-12-01-preview',
models: {
'o1-mini': {
deploymentName: 'o1-mini',
groupMap: {
eastus: {
apiKey: '${EASTUS_API_KEY}',
instanceName: 'region-eastus',
version: '2024-02-15-preview',
models: {
'gpt-4o': {
deploymentName: 'gpt-4o',
},
},
},
},
'codex-mini': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://example.cognitiveservices.azure.com/openai/',
version: '2025-04-01-preview',
serverless: true,
models: {
'codex-mini': {
deploymentName: 'codex-mini',
'region-eastus': {
apiKey: '${EASTUS2_API_KEY}',
instanceName: 'region-eastus2',
version: '2024-12-01-preview',
models: {
'o1-mini': {
deploymentName: 'o1-mini',
},
},
},
'codex-mini': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://example.cognitiveservices.azure.com/openai/',
version: '2025-04-01-preview',
serverless: true,
models: {
'codex-mini': {
deploymentName: 'codex-mini',
},
},
},
},
@ -709,27 +733,29 @@ describe('AgentClient - titleConvo', () => {
// Set 'all' config as fallback with a serverless Azure config
getAppConfig.mockResolvedValue({
all: {
titleConvo: true,
titleModel: 'gpt-4',
titleMethod: 'structured',
titlePrompt: 'Fallback title prompt from all config',
titlePromptTemplate: 'Template: {{content}}',
modelGroupMap: {
'gpt-4': {
group: 'default-group',
deploymentName: 'gpt-4',
endpoints: {
all: {
titleConvo: true,
titleModel: 'gpt-4',
titleMethod: 'structured',
titlePrompt: 'Fallback title prompt from all config',
titlePromptTemplate: 'Template: {{content}}',
modelGroupMap: {
'gpt-4': {
group: 'default-group',
deploymentName: 'gpt-4',
},
},
},
groupMap: {
'default-group': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://default.openai.azure.com/',
version: '2024-02-15-preview',
serverless: true,
models: {
'gpt-4': {
deploymentName: 'gpt-4',
groupMap: {
'default-group': {
apiKey: '${AZURE_API_KEY}',
baseURL: 'https://default.openai.azure.com/',
version: '2024-02-15-preview',
serverless: true,
models: {
'gpt-4': {
deploymentName: 'gpt-4',
},
},
},
},

View file

@ -376,9 +376,9 @@ const chatV2 = async (req, res) => {
};
/** @type {undefined | TAssistantEndpoint} */
const config = appConfig[endpoint] ?? {};
const config = appConfig.endpoints?.[endpoint] ?? {};
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
const streamRunManager = new StreamRunManager({
req,

View file

@ -231,20 +231,20 @@ const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
if (endpoint === EModelEndpoint.assistants) {
({ body } = await listAllAssistants({ req, res, version, query }));
} else if (endpoint === EModelEndpoint.azureAssistants) {
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
const azureConfig = appConfig.endpoints?.[EModelEndpoint.azureOpenAI];
body = await listAssistantsForAzure({ req, res, version, azureConfig, query });
}
if (req.user.role === SystemRoles.ADMIN) {
return body;
} else if (!appConfig[endpoint]) {
} else if (!appConfig.endpoints?.[endpoint]) {
return body;
}
body.data = filterAssistants({
userId: req.user.id,
assistants: body.data,
assistantsConfig: appConfig[endpoint],
assistantsConfig: appConfig.endpoints?.[endpoint],
});
return body;
};

View file

@ -260,7 +260,7 @@ const getAssistantDocuments = async (req, res) => {
try {
const appConfig = await getAppConfig({ role: req.user?.role });
const endpoint = req.query;
const assistantsConfig = appConfig[endpoint];
const assistantsConfig = appConfig.endpoints?.[endpoint];
const documents = await getAssistants(
{},
{

View file

@ -15,7 +15,7 @@ const validateAssistant = async (req, res, next) => {
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {Partial<TAssistantEndpoint>} */
const assistantsConfig = appConfig?.[endpoint];
const assistantsConfig = appConfig.endpoints?.[endpoint];
if (!assistantsConfig) {
return next();
}

View file

@ -23,7 +23,7 @@ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistant
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {Partial<TAssistantEndpoint>} */
const assistantsConfig = appConfig?.[endpoint];
const assistantsConfig = appConfig.endpoints?.[endpoint];
if (!assistantsConfig) {
return;
}

View file

@ -130,7 +130,7 @@ router.post('/:assistant_id', async (req, res) => {
}
/* Map Azure OpenAI model to the assistant as defined by config */
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
updatedAssistant = {
...updatedAssistant,
model: req.body.model,

View file

@ -111,7 +111,9 @@ const AppService = async () => {
if (!Object.keys(config).length) {
const appConfig = {
...defaultConfig,
[EModelEndpoint.agents]: agentsDefaults,
endpoints: {
[EModelEndpoint.agents]: agentsDefaults,
},
};
await setAppConfig(appConfig);
return;
@ -126,7 +128,7 @@ const AppService = async () => {
fileConfig: config?.fileConfig,
secureImageLinks: config?.secureImageLinks,
modelSpecs: processModelSpecs(config?.endpoints, config.modelSpecs, interfaceConfig),
...loadedEndpoints,
endpoints: loadedEndpoints,
};
await setAppConfig(appConfig);

View file

@ -172,12 +172,14 @@ describe('AppService', () => {
searxngInstanceUrl: '${SEARXNG_INSTANCE_URL}',
}),
memory: undefined,
agents: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
maxCitations: 30,
maxCitationsPerFile: 7,
minRelevanceScore: 0.45,
endpoints: expect.objectContaining({
agents: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
maxCitations: 30,
maxCitationsPerFile: 7,
minRelevanceScore: 0.45,
}),
}),
}),
);
@ -328,12 +330,14 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.assistants]: expect.objectContaining({
disableBuilder: true,
pollIntervalMs: 5000,
timeoutMs: 30000,
supportedIds: expect.arrayContaining(['id1', 'id2']),
privateAssistants: false,
endpoints: expect.objectContaining({
[EModelEndpoint.assistants]: expect.objectContaining({
disableBuilder: true,
pollIntervalMs: 5000,
timeoutMs: 30000,
supportedIds: expect.arrayContaining(['id1', 'id2']),
privateAssistants: false,
}),
}),
}),
);
@ -358,15 +362,17 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: true,
recursionLimit: 10,
maxRecursionLimit: 20,
allowedProviders: expect.arrayContaining(['openai', 'anthropic']),
capabilities: expect.arrayContaining([
AgentCapabilities.tools,
AgentCapabilities.actions,
]),
endpoints: expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: true,
recursionLimit: 10,
maxRecursionLimit: 20,
allowedProviders: expect.arrayContaining(['openai', 'anthropic']),
capabilities: expect.arrayContaining([
AgentCapabilities.tools,
AgentCapabilities.actions,
]),
}),
}),
}),
);
@ -379,9 +385,11 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
endpoints: expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
}),
}),
}),
);
@ -402,12 +410,14 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
}),
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
endpoints: expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
}),
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
}),
}),
}),
);
@ -432,12 +442,14 @@ describe('AppService', () => {
await AppService();
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.azureAssistants]: expect.objectContaining({
capabilities: expect.arrayContaining([
expect.any(String),
expect.any(String),
expect.any(String),
]),
endpoints: expect.objectContaining({
[EModelEndpoint.azureAssistants]: expect.objectContaining({
capabilities: expect.arrayContaining([
expect.any(String),
expect.any(String),
expect.any(String),
]),
}),
}),
}),
);
@ -462,10 +474,12 @@ describe('AppService', () => {
const { modelNames, modelGroupMap, groupMap } = validateAzureGroups(azureGroups);
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.azureOpenAI]: expect.objectContaining({
modelNames,
modelGroupMap,
groupMap,
endpoints: expect.objectContaining({
[EModelEndpoint.azureOpenAI]: expect.objectContaining({
modelNames,
modelGroupMap,
groupMap,
}),
}),
}),
);
@ -619,27 +633,29 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
// Check OpenAI endpoint configuration
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
titleMethod: 'structured',
titlePrompt: 'Custom title prompt for conversation',
titlePromptTemplate: 'Summarize this conversation: {{conversation}}',
}),
// Check Assistants endpoint configuration
[EModelEndpoint.assistants]: expect.objectContaining({
titleMethod: 'functions',
titlePrompt: 'Generate a title for this assistant conversation',
titlePromptTemplate: 'Assistant conversation template: {{messages}}',
}),
// Check Azure OpenAI endpoint configuration
[EModelEndpoint.azureOpenAI]: expect.objectContaining({
titleConvo: true,
titleMethod: 'completion',
titleModel: 'gpt-4',
titlePrompt: 'Azure title prompt',
titlePromptTemplate: 'Azure conversation: {{context}}',
endpoints: expect.objectContaining({
// Check OpenAI endpoint configuration
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
titleMethod: 'structured',
titlePrompt: 'Custom title prompt for conversation',
titlePromptTemplate: 'Summarize this conversation: {{conversation}}',
}),
// Check Assistants endpoint configuration
[EModelEndpoint.assistants]: expect.objectContaining({
titleMethod: 'functions',
titlePrompt: 'Generate a title for this assistant conversation',
titlePromptTemplate: 'Assistant conversation template: {{messages}}',
}),
// Check Azure OpenAI endpoint configuration
[EModelEndpoint.azureOpenAI]: expect.objectContaining({
titleConvo: true,
titleMethod: 'completion',
titleModel: 'gpt-4',
titlePrompt: 'Azure title prompt',
titlePromptTemplate: 'Azure conversation: {{context}}',
}),
}),
}),
);
@ -667,18 +683,20 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
titleConvo: true,
titleModel: 'gpt-4',
titleMethod: 'structured',
titlePrompt: 'Generate a descriptive title for this agent conversation',
titlePromptTemplate: 'Agent conversation summary: {{content}}',
recursionLimit: 15,
capabilities: expect.arrayContaining([
AgentCapabilities.tools,
AgentCapabilities.actions,
]),
endpoints: expect.objectContaining({
[EModelEndpoint.agents]: expect.objectContaining({
disableBuilder: false,
titleConvo: true,
titleModel: 'gpt-4',
titleMethod: 'structured',
titlePrompt: 'Generate a descriptive title for this agent conversation',
titlePromptTemplate: 'Agent conversation summary: {{content}}',
recursionLimit: 15,
capabilities: expect.arrayContaining([
AgentCapabilities.tools,
AgentCapabilities.actions,
]),
}),
}),
}),
);
@ -700,17 +718,19 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
endpoints: expect.objectContaining({
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
}),
}),
}),
);
// Verify that optional fields are not set when not provided
const initCall = setAppConfig.mock.calls[0][0];
expect(initCall[EModelEndpoint.openAI].titlePrompt).toBeUndefined();
expect(initCall[EModelEndpoint.openAI].titlePromptTemplate).toBeUndefined();
expect(initCall[EModelEndpoint.openAI].titleMethod).toBeUndefined();
expect(initCall.endpoints[EModelEndpoint.openAI].titlePrompt).toBeUndefined();
expect(initCall.endpoints[EModelEndpoint.openAI].titlePromptTemplate).toBeUndefined();
expect(initCall.endpoints[EModelEndpoint.openAI].titleMethod).toBeUndefined();
});
it('should correctly configure titleEndpoint when specified', async () => {
@ -735,17 +755,19 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
// Check OpenAI endpoint has titleEndpoint
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
titleEndpoint: EModelEndpoint.anthropic,
titlePrompt: 'Generate a concise title',
}),
// Check Agents endpoint has titleEndpoint
[EModelEndpoint.agents]: expect.objectContaining({
titleEndpoint: 'custom-provider',
titleMethod: 'structured',
endpoints: expect.objectContaining({
// Check OpenAI endpoint has titleEndpoint
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
titleEndpoint: EModelEndpoint.anthropic,
titlePrompt: 'Generate a concise title',
}),
// Check Agents endpoint has titleEndpoint
[EModelEndpoint.agents]: expect.objectContaining({
titleEndpoint: 'custom-provider',
titleMethod: 'structured',
}),
}),
}),
);
@ -777,19 +799,21 @@ describe('AppService', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
// Check that 'all' endpoint config is loaded
all: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-4o-mini',
titleMethod: 'structured',
titlePrompt: 'Default title prompt for all endpoints',
titlePromptTemplate: 'Default template: {{conversation}}',
titleEndpoint: EModelEndpoint.anthropic,
streamRate: 50,
}),
// Check that OpenAI endpoint has its own config
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
endpoints: expect.objectContaining({
all: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-4o-mini',
titleMethod: 'structured',
titlePrompt: 'Default title prompt for all endpoints',
titlePromptTemplate: 'Default template: {{conversation}}',
titleEndpoint: EModelEndpoint.anthropic,
streamRate: 50,
}),
// Check that OpenAI endpoint has its own config
[EModelEndpoint.openAI]: expect.objectContaining({
titleConvo: true,
titleModel: 'gpt-3.5-turbo',
}),
}),
}),
);
@ -883,18 +907,20 @@ describe('AppService updating app config and issuing warnings', () => {
expect(setAppConfig).toHaveBeenCalledWith(
expect.objectContaining({
assistants: expect.objectContaining({
disableBuilder: true,
pollIntervalMs: 5000,
timeoutMs: 30000,
supportedIds: ['id1', 'id2'],
endpoints: expect.objectContaining({
assistants: expect.objectContaining({
disableBuilder: true,
pollIntervalMs: 5000,
timeoutMs: 30000,
supportedIds: ['id1', 'id2'],
}),
}),
}),
);
// Verify excludedIds is undefined when not provided
const initCall = setAppConfig.mock.calls[0][0];
expect(initCall.assistants.excludedIds).toBeUndefined();
expect(initCall.endpoints.assistants.excludedIds).toBeUndefined();
});
it('should log a warning when both supportedIds and excludedIds are provided', async () => {

View file

@ -398,8 +398,8 @@ async function runAssistant({
});
const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body;
/** @type {TCustomConfig.endpoints.assistants} */
const assistantsEndpointConfig = appConfig?.[endpoint] ?? {};
/** @type {AppConfig['endpoints']['assistants']} */
const assistantsEndpointConfig = appConfig.endpoints?.[endpoint] ?? {};
const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig;
const run = await waitForRun({

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);

View file

@ -90,8 +90,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
}
const agentConfigs = new Map();
/** @type {Set<string>} */
const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
const allowedProviders = new Set(appConfig?.endpoints?.[EModelEndpoint.agents]?.allowedProviders);
const loadTools = createToolLoader();
/** @type {Array<MongoFile>} */
@ -145,7 +144,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
}
}
let endpointConfig = appConfig[primaryConfig.endpoint];
let endpointConfig = appConfig.endpoints?.[primaryConfig.endpoint];
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
try {
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);

View file

@ -25,15 +25,14 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
let clientOptions = {};
/** @type {undefined | TBaseEndpoint} */
const anthropicConfig = appConfig[EModelEndpoint.anthropic];
const anthropicConfig = appConfig.endpoints?.[EModelEndpoint.anthropic];
if (anthropicConfig) {
clientOptions.streamRate = anthropicConfig.streamRate;
clientOptions.titleModel = anthropicConfig.titleModel;
}
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
if (allConfig) {
clientOptions.streamRate = allConfig.streamRate;
}

View file

@ -83,7 +83,7 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
};
/** @type {TAzureConfig | undefined} */
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
const azureConfig = appConfig.endpoints?.[EModelEndpoint.azureOpenAI];
/** @type {AzureOptions | undefined} */
let azureOptions;

View file

@ -52,14 +52,13 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
let streamRate = Constants.DEFAULT_STREAM_RATE;
/** @type {undefined | TBaseEndpoint} */
const bedrockConfig = appConfig[EModelEndpoint.bedrock];
const bedrockConfig = appConfig.endpoints?.[EModelEndpoint.bedrock];
if (bedrockConfig && bedrockConfig.streamRate) {
streamRate = bedrockConfig.streamRate;
}
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
if (allConfig && allConfig.streamRate) {
streamRate = allConfig.streamRate;
}

View file

@ -118,8 +118,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
endpointTokenConfig,
};
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
if (allConfig) {
customOptions.streamRate = allConfig.streamRate;
}

View file

@ -49,9 +49,9 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
/** @type {undefined | TBaseEndpoint} */
const googleConfig = appConfig[EModelEndpoint.google];
const googleConfig = appConfig.endpoints?.[EModelEndpoint.google];
if (googleConfig) {
clientOptions.streamRate = googleConfig.streamRate;

View file

@ -16,7 +16,7 @@ const addTitle = async (req, { text, response, client }) => {
}
const { GOOGLE_TITLE_MODEL } = process.env ?? {};
const appConfig = await getAppConfig({ role: req.user?.role });
const providerConfig = appConfig[EModelEndpoint.google];
const providerConfig = appConfig.endpoints?.[EModelEndpoint.google];
let model =
providerConfig?.titleModel ??
GOOGLE_TITLE_MODEL ??

View file

@ -66,7 +66,7 @@ const initializeClient = async ({
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
/** @type {false | TAzureConfig} */
const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
const azureConfig = isAzureOpenAI && appConfig.endpoints?.[EModelEndpoint.azureOpenAI];
let serverless = false;
if (isAzureOpenAI && azureConfig) {
const { modelGroupMap, groupMap } = azureConfig;
@ -115,15 +115,14 @@ const initializeClient = async ({
}
/** @type {undefined | TBaseEndpoint} */
const openAIConfig = appConfig[EModelEndpoint.openAI];
const openAIConfig = appConfig.endpoints?.[EModelEndpoint.openAI];
if (!isAzureOpenAI && openAIConfig) {
clientOptions.streamRate = openAIConfig.streamRate;
clientOptions.titleModel = openAIConfig.titleModel;
}
/** @type {undefined | TBaseEndpoint} */
const allConfig = appConfig.all;
const allConfig = appConfig.endpoints?.all;
if (allConfig) {
clientOptions.streamRate = allConfig.streamRate;
}

View file

@ -22,28 +22,30 @@ jest.mock('~/server/services/UserService', () => ({
jest.mock('~/server/services/Config', () => ({
getAppConfig: jest.fn().mockResolvedValue({
openAI: {
apiKey: 'test-key',
},
azureOpenAI: {
apiKey: 'test-azure-key',
modelNames: ['gpt-4-vision-preview', 'gpt-3.5-turbo', 'gpt-4'],
modelGroupMap: {
'gpt-4-vision-preview': {
group: 'librechat-westus',
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
endpoints: {
openAI: {
apiKey: 'test-key',
},
groupMap: {
'librechat-westus': {
apiKey: 'WESTUS_API_KEY',
instanceName: 'librechat-westus',
version: '2023-12-01-preview',
models: {
'gpt-4-vision-preview': {
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
azureOpenAI: {
apiKey: 'test-azure-key',
modelNames: ['gpt-4-vision-preview', 'gpt-3.5-turbo', 'gpt-4'],
modelGroupMap: {
'gpt-4-vision-preview': {
group: 'librechat-westus',
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
},
groupMap: {
'librechat-westus': {
apiKey: 'WESTUS_API_KEY',
instanceName: 'librechat-westus',
version: '2023-12-01-preview',
models: {
'gpt-4-vision-preview': {
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
},
},
},

View file

@ -51,9 +51,11 @@ async function processFileCitations({ user, toolArtifact, toolCallId, metadata }
}
const appConfig = await getAppConfig({ role: user?.role });
const maxCitations = appConfig?.[EModelEndpoint.agents]?.maxCitations ?? 30;
const maxCitationsPerFile = appConfig?.[EModelEndpoint.agents]?.maxCitationsPerFile ?? 5;
const minRelevanceScore = appConfig?.[EModelEndpoint.agents]?.minRelevanceScore ?? 0.45;
const maxCitations = appConfig.endpoints?.[EModelEndpoint.agents]?.maxCitations ?? 30;
const maxCitationsPerFile =
appConfig.endpoints?.[EModelEndpoint.agents]?.maxCitationsPerFile ?? 5;
const minRelevanceScore =
appConfig.endpoints?.[EModelEndpoint.agents]?.minRelevanceScore ?? 0.45;
const sources = toolArtifact[Tools.file_search].sources || [];
const filteredSources = sources.filter((source) => source.relevance >= minRelevanceScore);

View file

@ -165,7 +165,7 @@ const processDeleteRequest = async ({ req, files }) => {
/** @type {Record<string, OpenAI | undefined>} */
const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined };
const initializeClients = async () => {
if (appConfig[EModelEndpoint.assistants]) {
if (appConfig.endpoints?.[EModelEndpoint.assistants]) {
const openAIClient = await getOpenAIClient({
req,
overrideEndpoint: EModelEndpoint.assistants,
@ -173,7 +173,7 @@ const processDeleteRequest = async ({ req, files }) => {
client[FileSources.openai] = openAIClient.openai;
}
if (!appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
if (!appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
return;
}

View file

@ -33,7 +33,7 @@ async function retrieveRun({ thread_id, run_id, timeout, openai }) {
}
/** @type {TAzureConfig | undefined} */
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
const azureConfig = appConfig.endpoints?.[EModelEndpoint.azureOpenAI];
if (azureConfig && azureConfig.assistants) {
delete headers.Authorization;

View file

@ -504,7 +504,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
enabledCapabilities = new Set(
appConfig?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
);
}
const checkCapability = (capability) => {