mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
🅰️ feat: Azure Config to Allow Different Deployments per Model (#1863)
* wip: first pass for azure endpoint schema * refactor: azure config to return groupMap and modelConfigMap * wip: naming and schema changes * refactor(errorsToString): move to data-provider * feat: rename to azureGroups, add additional tests, tests all expected outcomes, return errors * feat(AppService): load Azure groups * refactor(azure): use imported types, write `mapModelToAzureConfig` * refactor: move `extractEnvVariable` to data-provider * refactor(validateAzureGroups): throw on duplicate groups or models; feat(mapModelToAzureConfig): throw if env vars not present, add tests * refactor(AppService): ensure each model is properly configured on startup * refactor: deprecate azureOpenAI environment variables in favor of librechat.yaml config * feat: use helper functions to handle and order enabled/default endpoints; initialize azureOpenAI from config file * refactor: redefine types as well as load azureOpenAI models from config file * chore(ci): fix test description naming * feat(azureOpenAI): use validated model grouping for request authentication * chore: bump data-provider following rebase * chore: bump config file version noting significant changes * feat: add title options and switch azure configs for titling and vision requests * feat: enable azure plugins from config file * fix(ci): pass tests * chore(.env.example): mark `PLUGINS_USE_AZURE` as deprecated * fix(fetchModels): early return if apiKey not passed * chore: fix azure config typing * refactor(mapModelToAzureConfig): return baseURL and headers as well as azureOptions * feat(createLLM): use `azureOpenAIBasePath` * feat(parsers): resolveHeaders * refactor(extractBaseURL): handle invalid input * feat(OpenAIClient): handle headers and baseURL for azureConfig * fix(ci): pass `OpenAIClient` tests * chore: extract env var for azureOpenAI group config, baseURL * docs: azureOpenAI config setup docs * feat: safe check of potential conflicting env vars that map to unique placeholders * fix: reset apiKey when model switches from originally requested model (vision or title) * chore: linting * docs: CONFIG_PATH notes in custom_config.md
This commit is contained in:
parent
7a55132e42
commit
097a978e5b
37 changed files with 2066 additions and 394 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import type { ZodIssue } from 'zod';
|
||||
import type { TConversation, TPreset } from './schemas';
|
||||
import type { TEndpointOption } from './types';
|
||||
import type { TConfig, TEndpointOption, TEndpointsConfig } from './types';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
openAISchema,
|
||||
|
|
@ -42,6 +43,101 @@ const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
|
|||
// [EModelEndpoint.google]: createGoogleSchema,
|
||||
// };
|
||||
|
||||
/** Get the enabled endpoints from the `ENDPOINTS` environment variable */
|
||||
export function getEnabledEndpoints() {
|
||||
const defaultEndpoints: string[] = [
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.bingAI,
|
||||
EModelEndpoint.chatGPTBrowser,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.anthropic,
|
||||
];
|
||||
|
||||
const endpointsEnv = process.env.ENDPOINTS || '';
|
||||
let enabledEndpoints = defaultEndpoints;
|
||||
if (endpointsEnv) {
|
||||
enabledEndpoints = endpointsEnv
|
||||
.split(',')
|
||||
.filter((endpoint) => endpoint?.trim())
|
||||
.map((endpoint) => endpoint.trim());
|
||||
}
|
||||
return enabledEndpoints;
|
||||
}
|
||||
|
||||
/** Orders an existing EndpointsConfig object based on enabled endpoint/custom ordering */
|
||||
export function orderEndpointsConfig(endpointsConfig: TEndpointsConfig) {
|
||||
if (!endpointsConfig) {
|
||||
return {};
|
||||
}
|
||||
const enabledEndpoints = getEnabledEndpoints();
|
||||
const endpointKeys = Object.keys(endpointsConfig);
|
||||
const defaultCustomIndex = enabledEndpoints.indexOf(EModelEndpoint.custom);
|
||||
return endpointKeys.reduce(
|
||||
(accumulatedConfig: Record<string, TConfig | null | undefined>, currentEndpointKey) => {
|
||||
const isCustom = !(currentEndpointKey in EModelEndpoint);
|
||||
const isEnabled = enabledEndpoints.includes(currentEndpointKey);
|
||||
if (!isEnabled && !isCustom) {
|
||||
return accumulatedConfig;
|
||||
}
|
||||
|
||||
const index = enabledEndpoints.indexOf(currentEndpointKey);
|
||||
|
||||
if (isCustom) {
|
||||
accumulatedConfig[currentEndpointKey] = {
|
||||
order: defaultCustomIndex >= 0 ? defaultCustomIndex : 9999,
|
||||
...(endpointsConfig[currentEndpointKey] as Omit<TConfig, 'order'> & { order?: number }),
|
||||
};
|
||||
} else if (endpointsConfig[currentEndpointKey]) {
|
||||
accumulatedConfig[currentEndpointKey] = {
|
||||
...endpointsConfig[currentEndpointKey],
|
||||
order: index,
|
||||
};
|
||||
}
|
||||
return accumulatedConfig;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
/** Converts an array of Zod issues into a string. */
|
||||
export function errorsToString(errors: ZodIssue[]) {
|
||||
return errors
|
||||
.map((error) => {
|
||||
const field = error.path.join('.');
|
||||
const message = error.message;
|
||||
|
||||
return `${field}: ${message}`;
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export const envVarRegex = /^\${(.+)}$/;
|
||||
|
||||
/** Extracts the value of an environment variable from a string. */
|
||||
export function extractEnvVariable(value: string) {
|
||||
const envVarMatch = value.match(envVarRegex);
|
||||
if (envVarMatch) {
|
||||
return process.env[envVarMatch[1]] || value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Resolves header values to env variables if detected */
|
||||
export function resolveHeaders(headers: Record<string, string> | undefined) {
|
||||
const resolvedHeaders = { ...(headers ?? {}) };
|
||||
|
||||
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
|
||||
Object.keys(headers).forEach((key) => {
|
||||
resolvedHeaders[key] = extractEnvVariable(headers[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return resolvedHeaders;
|
||||
}
|
||||
|
||||
export function getFirstDefinedValue(possibleValues: string[]) {
|
||||
let returnValue;
|
||||
for (const value of possibleValues) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue