Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🅰️ feat: Azure Config to Allow Different Deployments per Model #1863

Merged
merged 35 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
aea07aa
wip: first pass for azure endpoint schema
danny-avila Feb 19, 2024
83dc2f7
refactor: azure config to return groupMap and modelConfigMap
danny-avila Feb 19, 2024
d06dec9
wip: naming and schema changes
danny-avila Feb 20, 2024
79091a3
refactor(errorsToString): move to data-provider
danny-avila Feb 21, 2024
00d4372
feat: rename to azureGroups, add additional tests, tests all expected…
danny-avila Feb 21, 2024
dea6984
feat(AppService): load Azure groups
danny-avila Feb 21, 2024
d5cdf63
refactor(azure): use imported types, write `mapModelToAzureConfig`
danny-avila Feb 21, 2024
a0345fd
refactor: move `extractEnvVariable` to data-provider
danny-avila Feb 21, 2024
de913a3
refactor(validateAzureGroups): throw on duplicate groups or models; f…
danny-avila Feb 21, 2024
b19300a
refactor(AppService): ensure each model is properly configured on sta…
danny-avila Feb 21, 2024
a7b9d6a
refactor: deprecate azureOpenAI environment variables in favor of lib…
danny-avila Feb 21, 2024
6d9f393
feat: use helper functions to handle and order enabled/default endpoi…
danny-avila Feb 21, 2024
9bf2ef0
refactor: redefine types as well as load azureOpenAI models from conf…
danny-avila Feb 21, 2024
0d2f7b4
chore(ci): fix test description naming
danny-avila Feb 21, 2024
03ab698
feat(azureOpenAI): use validated model grouping for request authentic…
danny-avila Feb 21, 2024
a00558b
chore: bump data-provider following rebase
danny-avila Feb 21, 2024
7aea82a
chore: bump config file version noting significant changes
danny-avila Feb 21, 2024
2bec71a
feat: add title options and switch azure configs for titling and visi…
danny-avila Feb 21, 2024
958dfcf
feat: enable azure plugins from config file
danny-avila Feb 23, 2024
425c76c
fix(ci): pass tests
danny-avila Feb 23, 2024
8d22017
chore(.env.example): mark `PLUGINS_USE_AZURE` as deprecated
danny-avila Feb 23, 2024
c2f37b2
fix(fetchModels): early return if apiKey not passed
danny-avila Feb 23, 2024
4afed05
chore: fix azure config typing
danny-avila Feb 23, 2024
d94e5a1
refactor(mapModelToAzureConfig): return baseURL and headers as well a…
danny-avila Feb 23, 2024
e53fdf6
feat(createLLM): use `azureOpenAIBasePath`
danny-avila Feb 23, 2024
3b472ca
feat(parsers): resolveHeaders
danny-avila Feb 23, 2024
1e9772a
refactor(extractBaseURL): handle invalid input
danny-avila Feb 23, 2024
98b74de
feat(OpenAIClient): handle headers and baseURL for azureConfig
danny-avila Feb 23, 2024
a84df63
fix(ci): pass `OpenAIClient` tests
danny-avila Feb 23, 2024
403a16f
chore: extract env var for azureOpenAI group config, baseURL
danny-avila Feb 26, 2024
b0b5aa8
docs: azureOpenAI config setup docs
danny-avila Feb 26, 2024
6c4d24b
feat: safe check of potential conflicting env vars that map to unique…
danny-avila Feb 26, 2024
e6c2bd4
fix: reset apiKey when model switches from originally requested model…
danny-avila Feb 26, 2024
7141c5e
chore: linting
danny-avila Feb 26, 2024
ff90508
docs: CONFIG_PATH notes in custom_config.md
danny-avila Feb 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,21 @@ ANTHROPIC_MODELS=claude-1,claude-instant-1,claude-2
# Azure #
#============#

# AZURE_API_KEY=
AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4
# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo
# PLUGINS_USE_AZURE="true"

AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE

# AZURE_OPENAI_API_INSTANCE_NAME=
# AZURE_OPENAI_API_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_VERSION=
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME=

# Note: these variables are DEPRECATED
# Use the `librechat.yaml` configuration for `azureOpenAI` instead
# You may also continue to use them if you opt out of using the `librechat.yaml` configuration

# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # Deprecated
# AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4 # Deprecated
# AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE # Deprecated
# AZURE_API_KEY= # Deprecated
# AZURE_OPENAI_API_INSTANCE_NAME= # Deprecated
# AZURE_OPENAI_API_DEPLOYMENT_NAME= # Deprecated
# AZURE_OPENAI_API_VERSION= # Deprecated
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Deprecated
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Deprecated
# PLUGINS_USE_AZURE="true" # Deprecated

#============#
# BingAI #
Expand Down
66 changes: 64 additions & 2 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const OpenAI = require('openai');
const { HttpsProxyAgent } = require('https-proxy-agent');
const {
ImageDetail,
EModelEndpoint,
resolveHeaders,
ImageDetailCost,
getResponseSender,
validateVisionModel,
ImageDetailCost,
ImageDetail,
mapModelToAzureConfig,
} = require('librechat-data-provider');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const {
Expand Down Expand Up @@ -665,6 +668,16 @@ class OpenAIClient extends BaseClient {
};
}

const { headers } = this.options;
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
configOptions.baseOptions = {
headers: resolveHeaders({
...headers,
...configOptions?.baseOptions?.headers,
}),
};
}

if (this.options.proxy) {
configOptions.httpAgent = new HttpsProxyAgent(this.options.proxy);
configOptions.httpsAgent = new HttpsProxyAgent(this.options.proxy);
Expand Down Expand Up @@ -725,6 +738,26 @@ class OpenAIClient extends BaseClient {
max_tokens: 16,
};

/** @type {TAzureConfig | undefined} */
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
if (this.azure && azureConfig) {
const { modelGroupMap, groupMap } = azureConfig;
const {
azureOptions,
baseURL,
headers = {},
} = mapModelToAzureConfig({
modelName: modelOptions.model,
modelGroupMap,
groupMap,
});
this.azure = azureOptions;
this.options.headers = resolveHeaders(headers);
this.options.reverseProxyUrl = baseURL ?? null;
this.langchainProxy = extractBaseURL(this.options.reverseProxyUrl);
this.apiKey = azureOptions.azureOpenAIApiKey;
}

const titleChatCompletion = async () => {
modelOptions.model = model;

Expand Down Expand Up @@ -975,6 +1008,27 @@ ${convo}
modelOptions.max_tokens = 4000;
}

/** @type {TAzureConfig | undefined} */
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];

if (this.azure && this.isVisionModel && azureConfig) {
const { modelGroupMap, groupMap } = azureConfig;
const {
azureOptions,
baseURL,
headers = {},
} = mapModelToAzureConfig({
modelName: modelOptions.model,
modelGroupMap,
groupMap,
});
this.azure = azureOptions;
this.azureEndpoint = genAzureChatCompletion(this.azure, modelOptions.model, this);
opts.defaultHeaders = resolveHeaders(headers);
this.langchainProxy = extractBaseURL(baseURL);
this.apiKey = azureOptions.azureOpenAIApiKey;
}

if (this.azure || this.options.azure) {
// Azure does not accept `model` in the body, so we need to remove it.
delete modelOptions.model;
Expand Down Expand Up @@ -1026,12 +1080,20 @@ ${convo}
...modelOptions,
...this.options.addParams,
};
logger.debug('[OpenAIClient] chatCompletion: added params', {
addParams: this.options.addParams,
modelOptions,
});
}

if (this.options.dropParams && Array.isArray(this.options.dropParams)) {
this.options.dropParams.forEach((param) => {
delete modelOptions[param];
});
logger.debug('[OpenAIClient] chatCompletion: dropped params', {
dropParams: this.options.dropParams,
modelOptions,
});
}

let UnexpectedRoleError = false;
Expand Down
5 changes: 4 additions & 1 deletion api/app/clients/llm/createLLM.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ function createLLM({
}

if (azure && configOptions.basePath) {
configOptions.basePath = constructAzureURL({
const azureURL = constructAzureURL({
baseURL: configOptions.basePath,
azure: azureOptions,
});
azureOptions.azureOpenAIBasePath = azureURL.split(
`/${azureOptions.azureOpenAIApiDeploymentName}`,
)[0];
}

return new ChatOpenAI(
Expand Down
15 changes: 9 additions & 6 deletions api/server/controllers/EndpointController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { CacheKeys, EModelEndpoint } = require('librechat-data-provider');
const { CacheKeys, EModelEndpoint, orderEndpointsConfig } = require('librechat-data-provider');
const { loadDefaultEndpointsConfig, loadConfigEndpoints } = require('~/server/services/Config');
const { getLogStores } = require('~/cache');

Expand All @@ -10,15 +10,18 @@ async function endpointController(req, res) {
return;
}

const defaultEndpointsConfig = await loadDefaultEndpointsConfig();
const customConfigEndpoints = await loadConfigEndpoints();
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
const customConfigEndpoints = await loadConfigEndpoints(req);

const endpointsConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
if (endpointsConfig[EModelEndpoint.assistants] && req.app.locals?.[EModelEndpoint.assistants]) {
endpointsConfig[EModelEndpoint.assistants].disableBuilder =
/** @type {TEndpointsConfig} */
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
if (mergedConfig[EModelEndpoint.assistants] && req.app.locals?.[EModelEndpoint.assistants]) {
mergedConfig[EModelEndpoint.assistants].disableBuilder =
req.app.locals[EModelEndpoint.assistants].disableBuilder;
}

const endpointsConfig = orderEndpointsConfig(mergedConfig);

await cache.set(CacheKeys.ENDPOINT_CONFIG, endpointsConfig);
res.send(JSON.stringify(endpointsConfig));
}
Expand Down
50 changes: 49 additions & 1 deletion api/server/services/AppService.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
const {
Constants,
FileSources,
EModelEndpoint,
Constants,
defaultSocialLogins,
validateAzureGroups,
mapModelToAzureConfig,
deprecatedAzureVariables,
conflictingAzureVariables,
} = require('librechat-data-provider');
const { initializeFirebase } = require('./Files/Firebase/initialize');
const loadCustomConfig = require('./Config/loadCustomConfig');
Expand Down Expand Up @@ -62,6 +66,50 @@ const AppService = async (app) => {
handleRateLimits(config?.rateLimits);

const endpointLocals = {};

if (config?.endpoints?.[EModelEndpoint.azureOpenAI]) {
const { groups, titleModel, titleConvo, titleMethod, plugins } =
config.endpoints[EModelEndpoint.azureOpenAI];
const { isValid, modelNames, modelGroupMap, groupMap, errors } = validateAzureGroups(groups);

if (!isValid) {
const errorString = errors.join('\n');
const errorMessage = 'Invalid Azure OpenAI configuration:\n' + errorString;
logger.error(errorMessage);
throw new Error(errorMessage);
}

for (const modelName of modelNames) {
mapModelToAzureConfig({ modelName, modelGroupMap, groupMap });
}

endpointLocals[EModelEndpoint.azureOpenAI] = {
modelNames,
modelGroupMap,
groupMap,
titleConvo,
titleMethod,
titleModel,
plugins,
};

deprecatedAzureVariables.forEach(({ key, description }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable (related to ${description}) should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you will experience conflicts and errors.`,
);
}
});

conflictingAzureVariables.forEach(({ key }) => {
if (process.env[key]) {
logger.warn(
`The \`${key}\` environment variable should not be used in combination with the \`azureOpenAI\` endpoint configuration, as you may experience with the defined placeholders for mapping to the current model grouping using the same name.`,
);
}
});
}

if (config?.endpoints?.[EModelEndpoint.assistants]) {
const { disableBuilder, pollIntervalMs, timeoutMs, supportedIds, excludedIds } =
config.endpoints[EModelEndpoint.assistants];
Expand Down
Loading
Loading