UNPKG

botbuilder-dialogs-adaptive

Version:

Rule system for the Microsoft BotBuilder dialog system.

194 lines • 9.16 kB
"use strict"; /** * @module botbuilder-dialogs-adaptive */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CrossTrainedRecognizerSet = void 0; const merge_1 = __importDefault(require("lodash/merge")); const pickBy_1 = __importDefault(require("lodash/pickBy")); const botbuilder_1 = require("botbuilder"); const converters_1 = require("../converters"); const adaptiveRecognizer_1 = require("./adaptiveRecognizer"); const telemetryLoggerConstants_1 = require("../telemetryLoggerConstants"); /** * Standard cross trained intent name prefix. */ const deferPrefix = 'DeferToRecognizer_'; /** * Recognizer for selecting between cross trained recognizers. */ class CrossTrainedRecognizerSet extends adaptiveRecognizer_1.AdaptiveRecognizer { constructor() { super(...arguments); /** * Gets or sets the input recognizers. */ this.recognizers = []; } /** * @param property The key of the conditional selector configuration. * @returns The converter for the selector configuration. */ getConverter(property) { switch (property) { case 'recognizers': return converters_1.RecognizerListConverter; default: return super.getConverter(property); } } /** * To recognize intents and entities in a users utterance. * * @param {DialogContext} dialogContext The dialog context. * @param {Activity} activity The activity. * @param {object} telemetryProperties Optional. Additional properties to be logged to telemetry with the recognizer result event. * @param {object} telemetryMetrics Optional. Additional metrics to be logged to telemetry with the recognizer result event. * @returns {Promise<RecognizerResult>} Promise of the intent recognized by the recognizer in the form of a RecognizerResult. */ recognize(dialogContext, activity, telemetryProperties, telemetryMetrics) { return __awaiter(this, void 0, void 0, function* () { if (!this.recognizers.length) { return { text: '', intents: { None: { score: 1.0 } }, }; } for (let i = 0; i < this.recognizers.length; i++) { if (!this.recognizers[i].id) { throw new Error('This recognizer requires that each recognizer in the set have an id.'); } } const results = yield Promise.all(this.recognizers.map((recognizer) => __awaiter(this, void 0, void 0, function* () { const result = yield recognizer.recognize(dialogContext, activity, telemetryProperties, telemetryMetrics); result['id'] = recognizer.id; return result; }))); const result = this.processResults(results); this.trackRecognizerResult(dialogContext, telemetryLoggerConstants_1.TelemetryLoggerConstants.CrossTrainedRecognizerSetResultEvent, this.fillRecognizerResultTelemetryProperties(result, telemetryProperties, dialogContext), telemetryMetrics); return result; }); } /** * Process a list of raw results from recognizers. * If there is consensus among the cross trained recognizers, the recognizerResult structure from * the consensus recognizer is returned. * * @param {RecognizerResult[]} results A list of recognizer results to be processed. * @returns {RecognizerResult} The the result cross-trained by the multiple results of the cross-training recognizers. */ processResults(results) { const recognizerResults = {}; const intents = {}; let text = ''; for (let i = 0; i < results.length; i++) { const result = results[i]; const recognizer = this.recognizers[i]; recognizerResults[recognizer.id] = result; const { intent } = (0, botbuilder_1.getTopScoringIntent)(result); intents[recognizer.id] = intent; text = result.text || ''; } let consensusRecognizedId; for (let i = 0; i < this.recognizers.length; i++) { const recognizer = this.recognizers[i]; let recognizerId = recognizer.id; let intent = intents[recognizer.id]; if (this.isRedirect(intent)) { // follow redirect and see where it takes us recognizerId = this.getRedirectId(intent); intent = intents[recognizerId]; while (recognizerId != recognizer.id && this.isRedirect(intent)) { recognizerId = this.getRedirectId(intent); intent = intents[recognizerId]; } // if we ended up back at the recognizer.id and we have no consensus then it's a none intent if (recognizerId === recognizer.id && !consensusRecognizedId) { const recognizerResult = { text: recognizerResults[recognizer.id].text, intents: { None: { score: 1.0 } }, sentiment: recognizerResults[recognizer.id].sentiment, }; return recognizerResult; } } // we have a real intent and it's the first one we found if (!consensusRecognizedId) { if (intent && intent !== 'None') { consensusRecognizedId = recognizerId; } } else { // we have a second recognizer result which is either none or real // if one of them is None intent, then go with the other one if (!intent || intent === 'None') { // then we are fine with the one we have, just ignore this one continue; } else if (recognizerId === consensusRecognizedId) { // this is more consensus for this recognizer continue; } else { // ambiguous because we have 2 or more real intents, so return `ChooseIntent`, // filter out redirect results and return `ChooseIntent`. const recognizersWithRealIntents = (0, pickBy_1.default)(recognizerResults, (value) => !this.isRedirect((0, botbuilder_1.getTopScoringIntent)(value).intent)); return this.createChooseIntentResult(recognizersWithRealIntents); } } } // we have consensus for consensusRecognizer, return the results of that recognizer as the result if (consensusRecognizedId) { return recognizerResults[consensusRecognizedId]; } //find if matched entities found when hits the none intent const mergedEntities = results.reduce((acc, curr) => (0, merge_1.default)(acc, curr.entities), {}); const sentiment = results.reduce((acc, curr) => (0, merge_1.default)(acc, curr.sentiment), {}); // return none const recognizerResult = { text, intents: { None: { score: 1.0 } }, entities: mergedEntities, sentiment: sentiment, }; return recognizerResult; } /** * Check if an intent is triggering redirects. * * @param {string} intent The intent. * @returns {boolean} Boolean result of whether or not an intent begins with the `DeferToRecognizer_` prefix. */ isRedirect(intent) { var _a; return (_a = intent === null || intent === void 0 ? void 0 : intent.startsWith(deferPrefix)) !== null && _a !== void 0 ? _a : false; } /** * Extracts the redirect ID from an intent. * * @param {string} intent Intent string contains redirect id. * @returns {string} The redirect ID. */ getRedirectId(intent) { return intent.substr(deferPrefix.length); } } exports.CrossTrainedRecognizerSet = CrossTrainedRecognizerSet; CrossTrainedRecognizerSet.$kind = 'Microsoft.CrossTrainedRecognizerSet'; //# sourceMappingURL=crossTrainedRecognizerSet.js.map