UNPKG

botbuilder-dialogs-adaptive

Version:

Rule system for the Microsoft BotBuilder dialog system.

190 lines (165 loc) • 7.04 kB
/** * @module botbuilder-dialogs-adaptive */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { Culture } from '@microsoft/recognizers-text-suite'; import { Activity, Entity, RecognizerResult } from 'botbuilder'; import { Converter, ConverterFactory, DialogContext } from 'botbuilder-dialogs'; import { IntentPattern } from './intentPattern'; import { EntityRecognizer, TextEntity, EntityRecognizerSet } from './entityRecognizers'; import { RecognizerSetConfiguration } from './recognizerSet'; import { AdaptiveRecognizer } from './adaptiveRecognizer'; import { TelemetryLoggerConstants } from '../telemetryLoggerConstants'; type IntentPatternInput = { intent: string; pattern: string; }; class IntentPatternsConverter implements Converter<IntentPatternInput[], IntentPattern[]> { convert(items: IntentPatternInput[] | IntentPattern[]): IntentPattern[] { const results: IntentPattern[] = []; items.forEach((item) => { results.push(item instanceof IntentPattern ? item : new IntentPattern(item.intent, item.pattern)); }); return results; } } export interface RegexRecognizerConfiguration extends RecognizerSetConfiguration { intents?: IntentPatternInput[] | IntentPattern[]; } /** * Recognizer implementation which uses regex expressions to identify intents. */ export class RegexRecognizer extends AdaptiveRecognizer implements RegexRecognizerConfiguration { static $kind = 'Microsoft.RegexRecognizer'; /** * Array of patterns -> intent names. */ intents: IntentPattern[] = []; /** * The entity recognizers. */ entities: EntityRecognizer[] = []; /** * @param property The key of the conditional selector configuration. * @returns The converter for the selector configuration. */ getConverter(property: keyof RegexRecognizerConfiguration): Converter | ConverterFactory { switch (property) { case 'intents': return new IntentPatternsConverter(); default: return super.getConverter(property); } } /** * Runs current DialogContext.TurnContext.Activity through a recognizer and returns a [RecognizerResult](xref:botbuilder-core.RecognizerResult). * * @param dialogContext The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation. * @param activity [Activity](xref:botframework-schema.Activity) to recognize. * @param telemetryProperties Optional, additional properties to be logged to telemetry with the LuisResult event. * @param telemetryMetrics Optional, additional metrics to be logged to telemetry with the LuisResult event. * @returns {Promise<RecognizerResult>} Analysis of utterance. */ async recognize( dialogContext: DialogContext, activity: Activity, telemetryProperties?: { [key: string]: string }, telemetryMetrics?: { [key: string]: number } ): Promise<RecognizerResult> { const text = activity.text ?? ''; const locale = activity.locale ?? Culture.English; const recognizerResult: RecognizerResult = { text: text, intents: {}, entities: {}, }; if (!text) { // nothing to recognize, return empty result return recognizerResult; } const textEntity: Entity = new TextEntity(text); textEntity.start = 0; textEntity.end = text.length; textEntity.score = 1.0; const entityPool: Entity[] = [textEntity]; for (const intentPattern of this.intents) { const matches: RegExpExecArray[] = []; let matched: RegExpExecArray; const regexp = intentPattern.regex; while ((matched = regexp.exec(text))) { matches.push(matched); if (regexp.lastIndex == text.length || !regexp.lastIndex) { break; // to avoid infinite loop } } if (matches.length === 0) { continue; } // TODO length weighted match and multiple intents const intentKey = intentPattern.intent.split(' ').join('_'); recognizerResult.intents[`${intentKey}`] ??= { score: 1.0, pattern: intentPattern.pattern, }; matches.forEach((match: RegExpExecArray) => { if (match.groups) { Object.entries(match.groups) .filter(([_name, text]) => text !== undefined) .forEach(([name, text]) => { const entity: Entity = { type: name, text, start: match.index, end: match.index + text.length, }; entityPool.push(entity); }); } }); } if (this.entities) { const entitySet = new EntityRecognizerSet(...this.entities); const newEntities = await entitySet.recognizeEntities(dialogContext, text, locale, entityPool); entityPool.push(...newEntities); } entityPool .filter((e) => e !== textEntity) .forEach((entityResult) => { const { type: entityType, text: entityText } = entityResult; recognizerResult.entities[`${entityType}`] ??= []; recognizerResult.entities[`${entityType}`].push(entityText); recognizerResult.entities['$instance'] ??= {}; const instanceRoot = recognizerResult.entities['$instance']; instanceRoot[`${entityType}`] ??= []; const instanceData = instanceRoot[`${entityType}`]; const instance = { startIndex: entityResult.start, endIndex: entityResult.end, score: 1.0, text: entityText, type: entityType, resolution: entityResult.resolution, }; instanceData.push(instance); }); if (Object.entries(recognizerResult.intents).length == 0) { recognizerResult.intents.None = { score: 1.0 }; } await dialogContext.context.sendTraceActivity( 'RegexRecognizer', recognizerResult, 'RecognizerResult', 'Regex RecognizerResult' ); this.trackRecognizerResult( dialogContext, TelemetryLoggerConstants.RegexRecognizerResultEvent, this.fillRecognizerResultTelemetryProperties(recognizerResult, telemetryProperties, dialogContext), telemetryMetrics ); return recognizerResult; } }