/** * @module botbuilder-dialogs-adaptive */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { Activity, BotTelemetryClient, BotTelemetryClientKey, getTopScoringIntent, NullTelemetryClient, RecognizerResult, } from 'botbuilder-core'; import { Configurable } from './configurable'; import { DialogContext } from './dialogContext'; import { DialogTurnStateConstants } from './dialogTurnStateConstants'; import omit from 'lodash/omit'; export interface RecognizerConfiguration { id?: string; telemetryClient?: BotTelemetryClient; } /** * Recognizer base class. */ export class Recognizer extends Configurable implements RecognizerConfiguration { /** * Recognizers unique ID. */ id: string; /** * The telemetry client for logging events. * Default this to the NullTelemetryClient, which does nothing. */ telemetryClient: BotTelemetryClient = new NullTelemetryClient(); /** * To recognize intents and entities in a users utterance. * * @param {DialogContext} _dialogContext Dialog Context. * @param {Partial} _activity Activity. * @param {Record} _telemetryProperties Additional properties to be logged to telemetry with event. * @param {Record} _telemetryMetrics Additional metrics to be logged to telemetry with event. */ recognize( _dialogContext: DialogContext, _activity: Partial, _telemetryProperties?: Record, _telemetryMetrics?: Record, ): Promise { throw new Error('Please implement recognize function.'); } /** * Creates choose intent result in the case that there are conflicting or ambiguous signals from the recognizers. * * @param {Record} recognizerResults A group of recognizer results. * @returns {RecognizerResult} Recognizer result which is ChooseIntent. */ protected createChooseIntentResult(recognizerResults: Record): RecognizerResult { let text: string; let sentiment: Record = null; type candidateType = { id: string; intent: string; score: number; result: RecognizerResult }; const candidates = Object.entries(recognizerResults).reduce((candidates: candidateType[], [key, result]) => { text = result.text; sentiment = result.sentiment; const { intent, score } = getTopScoringIntent(result); if (intent !== 'None') { candidates.push({ id: key, intent, score, result, }); } return candidates; }, []); if (candidates.length) { // return `ChooseIntent` with candidates array. const recognizerResult: RecognizerResult = { text, intents: { ChooseIntent: { score: 1.0 } }, candidates, entities: {}, }; return recognizerResult; } // just return a `None` intent. const recognizerResult: RecognizerResult = { text, intents: { None: { score: 1.0 } }, entities: {}, sentiment: sentiment, }; return recognizerResult; } /** * Uses the RecognizerResult to create a list of properties to be included when tracking the result in telemetry. * * @param {RecognizerResult} recognizerResult Recognizer Result. * @param {Record} telemetryProperties A list of properties to append or override the properties created using the RecognizerResult. * @param {DialogContext} _dialogContext Dialog Context. * @returns {Record} A collection of properties that can be included when calling the TrackEvent method on the TelemetryClient. */ protected fillRecognizerResultTelemetryProperties( recognizerResult: RecognizerResult, telemetryProperties: Record, _dialogContext?: DialogContext, ): Record { const { intent, score } = getTopScoringIntent(recognizerResult); const intents = Object.entries(recognizerResult.intents); const properties: Record = { Text: recognizerResult.text, AlteredText: recognizerResult.alteredText, TopIntent: intents.length > 0 ? intent : undefined, TopIntentScore: intents.length > 0 ? score.toString() : undefined, Intents: intents.length > 0 ? JSON.stringify(recognizerResult.intents) : undefined, Entities: recognizerResult.entities ? JSON.stringify(recognizerResult.entities) : undefined, AdditionalProperties: JSON.stringify( omit(recognizerResult, ['text', 'alteredText', 'intents', 'entities']), ), }; // Additional Properties can override "stock" properties. if (telemetryProperties) { return Object.assign({}, properties, telemetryProperties); } return properties; } protected stringifyAdditionalPropertiesOfRecognizerResult(recognizerResult: RecognizerResult): string { const generalProperties = new Set(['text', 'alteredText', 'intents', 'entities']); const additionalProperties: { [key: string]: string } = {}; for (const key in recognizerResult) { if (!generalProperties.has(key)) { additionalProperties[key] = recognizerResult[key]; } } return Object.keys(additionalProperties).length > 0 ? JSON.stringify(additionalProperties) : undefined; } /** * Tracks an event with the event name provided using the TelemetryClient attaching the properties/metrics. * * @param {DialogContext} dialogContext Dialog context. * @param {string} eventName The name of the event to track. * @param {Record} telemetryProperties The properties to be included as part of the event tracking. * @param {Record} telemetryMetrics The metrics to be included as part of the event tracking. */ protected trackRecognizerResult( dialogContext: DialogContext, eventName: string, telemetryProperties?: Record, telemetryMetrics?: Record, ): void { if (this.telemetryClient instanceof NullTelemetryClient) { const turnStateTelemetryClient = dialogContext.context.turnState.get(DialogTurnStateConstants.telemetryClient) ?? dialogContext.context.turnState.get(BotTelemetryClientKey); this.telemetryClient = turnStateTelemetryClient ?? this.telemetryClient; } this.telemetryClient.trackEvent({ name: eventName, properties: telemetryProperties, metrics: telemetryMetrics, }); } }