botbuilder-dialogs-adaptive
Version:
Rule system for the Microsoft BotBuilder dialog system.
194 lines • 9.16 kB
JavaScript
;
/**
* @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