diff --git a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts index 2340d5a9cb..bbf82ccdf8 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts @@ -3,7 +3,7 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { useRecoilCallback, CallbackInterface } from 'recoil'; -import { ILuisConfig, IQnAConfig } from '@bfc/shared'; +import { ILuisConfig, IQnAConfig, IOrchestratorConfig } from '@bfc/shared'; import * as luUtil from '../../utils/luUtil'; import * as qnaUtil from '../../utils/qnaUtil'; @@ -23,7 +23,8 @@ export const builderDispatcher = () => { (callbackHelpers: CallbackInterface) => async ( projectId: string, luisConfig: ILuisConfig, - qnaConfig: IQnAConfig + qnaConfig: IQnAConfig, + orchestratorConfig: IOrchestratorConfig ) => { const { set, snapshot } = callbackHelpers; const dialogs = await snapshot.getPromise(dialogsWithLuProviderSelectorFamily(projectId)); @@ -53,6 +54,7 @@ export const builderDispatcher = () => { await httpClient.post(`/projects/${projectId}/build`, { luisConfig, qnaConfig, + orchestratorConfig, projectId, luFiles: referredLuFiles.map((file) => ({ id: file.id, isEmpty: file.empty })), qnaFiles: referredQnaFiles.map((file) => ({ id: file.id, isEmpty: file.empty })), diff --git a/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts b/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts index 31f0d36666..8c060d7881 100644 --- a/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts +++ b/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts @@ -46,6 +46,7 @@ export const buildEssentialsSelector = selectorFamily({ const configuration = { luis: settings.luis, qna: settings.qna, + orchestrator: settings.orchestrator, }; const dialogs = get(dialogsSelectorFamily(projectId)); const luFiles = get(luFilesSelectorFamily(projectId)); @@ -115,7 +116,7 @@ const botRuntimeAction = (dispatcher: Dispatcher) => { if (config) { await dispatcher.downloadLanguageModels(projectId); dispatcher.setBotStatus(projectId, BotStatus.publishing); - await dispatcher.build(projectId, config.luis, config.qna); + await dispatcher.build(projectId, config.luis, config.qna, config.orchestrator); } }, startBot: async (projectId: string, sensitiveSettings) => { diff --git a/Composer/packages/server/src/controllers/project.ts b/Composer/packages/server/src/controllers/project.ts index 252bae111d..8fe0bf604c 100644 --- a/Composer/packages/server/src/controllers/project.ts +++ b/Composer/packages/server/src/controllers/project.ts @@ -404,10 +404,11 @@ async function build(req: Request, res: Response) { const currentProject = await BotProjectService.getProjectById(projectId, user); if (currentProject !== undefined) { try { - const { luisConfig, qnaConfig, luFiles, qnaFiles } = req.body; + const { luisConfig, qnaConfig, orchestratorConfig, luFiles, qnaFiles } = req.body; const files = await currentProject.buildFiles({ luisConfig, qnaConfig, + orchestratorConfig, luResource: luFiles, qnaResource: qnaFiles, }); diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index c368c49115..acb6c5a19d 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -477,7 +477,13 @@ export class BotProject implements IBotProject { return createdFiles; }; - public buildFiles = async ({ luisConfig, qnaConfig, luResource = [], qnaResource = [] }: IBuildConfig) => { + public buildFiles = async ({ + luisConfig, + qnaConfig, + orchestratorConfig, + luResource = [], + qnaResource = [], + }: IBuildConfig) => { if (this.settings) { const luFiles: FileInfo[] = []; const emptyFiles = {}; @@ -501,7 +507,12 @@ export class BotProject implements IBotProject { this.builder.rootDir = this.dir; this.builder.setBuildConfig( - { ...luisConfig, subscriptionKey: qnaConfig.subscriptionKey ?? '', qnaRegion: qnaConfig.qnaRegion ?? '' }, + { + ...luisConfig, + subscriptionKey: qnaConfig.subscriptionKey ?? '', + qnaRegion: qnaConfig.qnaRegion ?? '', + ...orchestratorConfig, + }, this.settings.downsampling ); await this.builder.build( diff --git a/Composer/packages/server/src/models/bot/builder.ts b/Composer/packages/server/src/models/bot/builder.ts index e606fbdbd0..7971cd85da 100644 --- a/Composer/packages/server/src/models/bot/builder.ts +++ b/Composer/packages/server/src/models/bot/builder.ts @@ -177,8 +177,12 @@ export class Builder { const nlrList = await this.runOrchestratorNlrList(); const modelDatas = [ - { model: nlrList?.defaults?.en_intent, lang: 'en', luFiles: enLuFiles }, - { model: nlrList?.defaults?.multilingual_intent, lang: 'multilang', luFiles: multiLangLuFiles }, + { model: this.config?.model?.en_intent ?? nlrList?.defaults?.en_intent, lang: 'en', luFiles: enLuFiles }, + { + model: this.config?.model?.multilingual_intent ?? nlrList?.defaults?.multilingual_intent, + lang: 'multilang', + luFiles: multiLangLuFiles, + }, ]; for (const modelData of modelDatas) { @@ -187,13 +191,13 @@ export class Builder { throw new Error('Model not set'); } const modelPath = Path.resolve(await this.getModelPathAsync(), modelData.model.replace('.onnx', '')); - await this.runOrchestratorNlrGet(modelPath, modelData.model); + if (!(await pathExists(modelPath))) { + throw new Error('Orchestrator Model missing: ' + modelPath); + } const snapshotData = await this.buildOrchestratorSnapshots(modelPath, modelData.luFiles, emptyFiles); this.orchestratorSettings.orchestrator.models[modelData.lang] = modelPath; - for (const snap in snapshotData) { - this.orchestratorSettings.orchestrator.snapshots[snap] = snapshotData[snap]; - } + this.orchestratorSettings.orchestrator.snapshots = snapshotData; } } @@ -215,7 +219,7 @@ export class Builder { luFiles: FileInfo[], emptyFiles: { [key: string]: boolean } ) => { - if (!luFiles.filter((file) => !emptyFiles[file.name]).length) return; + if (!luFiles.filter((file) => !emptyFiles[file.name]).length) return {}; // build snapshots from LU files return await orchestratorBuilder.build(this.botDir, luFiles, modelPath, this.generatedFolderPath); }; diff --git a/Composer/packages/server/src/models/bot/interface.ts b/Composer/packages/server/src/models/bot/interface.ts index e2e6495474..4c9e3b5c59 100644 --- a/Composer/packages/server/src/models/bot/interface.ts +++ b/Composer/packages/server/src/models/bot/interface.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { BaseSchema, ILuisConfig, IQnAConfig } from '@bfc/shared'; +import { BaseSchema, ILuisConfig, IQnAConfig, IOrchestratorConfig } from '@bfc/shared'; export type Resource = { id: string; isEmpty: boolean }; @@ -13,6 +13,7 @@ export interface LocationRef { export interface IBuildConfig { luisConfig: ILuisConfig; qnaConfig: IQnAConfig; + orchestratorConfig?: IOrchestratorConfig; luResource: Resource[]; qnaResource: Resource[]; } diff --git a/Composer/packages/server/src/models/bot/process/orchestratorWorker.ts b/Composer/packages/server/src/models/bot/process/orchestratorWorker.ts index e667725b33..74962706de 100644 --- a/Composer/packages/server/src/models/bot/process/orchestratorWorker.ts +++ b/Composer/packages/server/src/models/bot/process/orchestratorWorker.ts @@ -129,12 +129,29 @@ export async function orchestratorBuilder( ): Promise { const orchestratorLabelResolvers = cache.get(projectId); + //if user has changed language model settings, invalidate cached embeddings for that dialog + const keysToInvalidate: string[] = []; + + for (const [key, labelResolver] of orchestratorLabelResolvers.entries()) { + //JSON.parse can throw - this is expected to be caught in the process message handler below. + const modelName: string | undefined = JSON.parse(LabelResolver.getConfigJson(labelResolver))?.Name; + + if (modelName && modelName !== Path.basename(modelPath) + '.onnx') { + keysToInvalidate.push(key); + } + } + + for (const key of keysToInvalidate) { + orchestratorLabelResolvers.delete(key); + } + const luObjects = files .filter((fi) => fi.name.endsWith('.lu') && fi.content) .map((fi) => ({ id: fi.name, content: fi.content, })); + const result = await Orchestrator.buildAsync( modelPath, luObjects, diff --git a/Composer/packages/types/src/publish.ts b/Composer/packages/types/src/publish.ts index e61f08a999..9261661978 100644 --- a/Composer/packages/types/src/publish.ts +++ b/Composer/packages/types/src/publish.ts @@ -5,7 +5,7 @@ import type { JSONSchema7 } from 'json-schema'; import type { IBotProject } from './server'; import type { UserIdentity } from './user'; -import type { ILuisConfig, IQnAConfig } from './settings'; +import type { ILuisConfig, IOrchestratorConfig, IQnAConfig } from './settings'; import { AuthParameters } from './auth'; export type PublishResult = { @@ -133,6 +133,7 @@ export type PublishPlugin = { export type IPublishConfig = { luis: ILuisConfig; qna: IQnAConfig; + orchestrator?: IOrchestratorConfig; }; export type PublishTarget = { diff --git a/Composer/packages/types/src/settings.ts b/Composer/packages/types/src/settings.ts index 18230e82df..2e9febee7d 100644 --- a/Composer/packages/types/src/settings.ts +++ b/Composer/packages/types/src/settings.ts @@ -47,6 +47,7 @@ export type DialogSetting = { MicrosoftAppId?: string; MicrosoftAppPassword?: string; luis: ILuisConfig; + orchestrator?: IOrchestratorConfig; luFeatures: ILUFeaturesConfig; qna: IQnAConfig; publishTargets?: PublishTarget[]; @@ -99,11 +100,16 @@ export type IQnAConfig = { hostname?: string; }; -export type IConfig = ILuisConfig & { - subscriptionKey: string; - qnaRegion: string | 'westus'; +export type IOrchestratorConfig = { + model?: Record<'en_intent' | 'multilingual_intent', string>; }; +export type IConfig = ILuisConfig & + IOrchestratorConfig & { + subscriptionKey: string; + qnaRegion: string | 'westus'; + }; + export type LgOptions = { customFunctions: string[]; };