botbuilder-ai
Advanced tools
Comparing version 4.6.2 to 4.7.0
@@ -28,2 +28,3 @@ /** | ||
/** | ||
* | ||
* Options per LUIS prediction. | ||
@@ -93,2 +94,90 @@ */ | ||
} | ||
export interface LuisRecognizerOptions { | ||
/** | ||
* (Optional) Telemetry Client. | ||
*/ | ||
telemetryClient?: BotTelemetryClient; | ||
/** | ||
* (Optional) Designates whether personal information should be logged in telemetry. | ||
*/ | ||
logPersonalInformation?: boolean; | ||
/** | ||
* (Optional) Force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false` | ||
*/ | ||
includeAPIResults?: boolean; | ||
} | ||
export interface LuisRecognizerOptionsV3 extends LuisRecognizerOptions { | ||
/** | ||
* (Optional) Luis Api endpoint version. | ||
*/ | ||
apiVersion: "v3"; | ||
/** | ||
* (Optional) Determine if all intents come back or only the top one. | ||
*/ | ||
includeAllIntents?: boolean; | ||
/** | ||
* (Optional) A value indicating whether or not instance data should be included in response. | ||
*/ | ||
includeInstanceData?: boolean; | ||
/** | ||
* (Optional) If queries should be logged in LUIS. | ||
*/ | ||
log?: boolean; | ||
/** | ||
* (Optional) Dynamic lists of things like contact names to recognize at query time.. | ||
*/ | ||
dynamicLists?: Array<any>; | ||
/** | ||
* (Optional) External entities recognized in query. | ||
*/ | ||
externalEntities?: Array<any>; | ||
/** | ||
* (Optional) Boolean for if external entities should be preferred to the results from LUIS models. | ||
*/ | ||
preferExternalEntities?: boolean; | ||
/** | ||
* (Optional) By default this uses the production slot. You can find other standard slots in <see cref="LuisSlot"/>. | ||
* If you specify a Version, then a private version of the application is used instead of a slot. | ||
*/ | ||
slot?: 'production' | 'staging'; | ||
/** | ||
* (Optional) LUIS supports versions and this is the version to use instead of a slot. | ||
* If this is specified, then the <see cref="Slot"/> is ignored.. | ||
*/ | ||
version?: string; | ||
} | ||
export interface LuisRecognizerOptionsV2 extends LuisRecognizerOptions { | ||
/** | ||
* Luis Api endpoint version. | ||
*/ | ||
apiVersion: "v2"; | ||
/** | ||
* (Optional) Bing Spell Check subscription key. | ||
*/ | ||
bingSpellCheckSubscriptionKey?: string; | ||
/** | ||
* (Optional) Determine if all intents come back or only the top one. | ||
*/ | ||
includeAllIntents?: boolean; | ||
/** | ||
* (Optional) A value indicating whether or not instance data should be included in response. | ||
*/ | ||
includeInstanceData?: boolean; | ||
/** | ||
* (Optional) If queries should be logged in LUIS. | ||
*/ | ||
log?: boolean; | ||
/** | ||
* (Optional) Whether to spell check query. | ||
*/ | ||
spellCheck?: boolean; | ||
/** | ||
* (Optional) Whether to use the staging endpoint. | ||
*/ | ||
staging?: boolean; | ||
/** | ||
* (Optional) The time zone offset for resolving datetimes. | ||
*/ | ||
timezoneOffset?: number; | ||
} | ||
/** | ||
@@ -111,10 +200,12 @@ * Recognize intents in a user utterance using a configured LUIS model. | ||
private cacheKey; | ||
private luisRecognizerInternal; | ||
/** | ||
* Creates a new LuisRecognizer instance. | ||
* @param application An object conforming to the [LuisApplication](#luisapplication) definition or a string representing a LUIS application endpoint, usually retrieved from https://luis.ai. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisPrectionOptions](#luispredictionoptions) definition. | ||
* @param includeApiResults (Optional) flag that if set to `true` will force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false`. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisRecognizerOptions](#luisrecognizeroptions) definition. | ||
* @param includeApiResults (Deprecated) flag that if set to `true` will force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false`. | ||
*/ | ||
constructor(application: string, options?: LuisPredictionOptions, includeApiResults?: boolean); | ||
constructor(application: LuisApplication, options?: LuisPredictionOptions, includeApiResults?: boolean); | ||
constructor(application: LuisApplication | string, options?: LuisRecognizerOptionsV3 | LuisRecognizerOptionsV2); | ||
/** | ||
@@ -164,3 +255,3 @@ * Gets a value indicating whether determines whether to log personal information that came from the user. | ||
* @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisPrectionOptions](#luispredictionoptions) definition. | ||
* @param options (Optional) options object used to override control predictions. Should conform to the [LuisRecognizerOptionsV2] or [LuisRecognizerOptionsV3] definition. | ||
*/ | ||
@@ -171,3 +262,3 @@ recognize(context: TurnContext, telemetryProperties?: { | ||
[key: string]: number; | ||
}, options?: LuisPredictionOptions): Promise<RecognizerResult>; | ||
}, options?: LuisRecognizerOptionsV2 | LuisRecognizerOptionsV3 | LuisPredictionOptions): Promise<RecognizerResult>; | ||
/** | ||
@@ -198,21 +289,4 @@ * Invoked prior to a LuisResult Event being logged. | ||
}>; | ||
private getUserAgent; | ||
private emitTraceInfo; | ||
private prepareErrorMessage; | ||
private normalizeName; | ||
private getIntents; | ||
private getEntitiesAndMetadata; | ||
private getEntityValue; | ||
private getEntityMetadata; | ||
private getNormalizedEntityName; | ||
private populateCompositeEntity; | ||
/** | ||
* If a property doesn't exist add it to a new array, otherwise append it to the existing array | ||
* @param obj Object on which the property is to be set | ||
* @param key Property Key | ||
* @param value Property Value | ||
*/ | ||
private addProperty; | ||
private getSentiment; | ||
/** | ||
* Merges the default options set by the Recognizer contructor with the 'user' options passed into the 'recognize' method | ||
@@ -227,2 +301,6 @@ */ | ||
private validateLuisApplication; | ||
/** | ||
* Builds a LuisRecognizer Strategy depending on the options passed | ||
*/ | ||
private buildRecognizer; | ||
} |
@@ -11,19 +11,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* @module botbuilder-ai | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const cognitiveservices_luis_runtime_1 = require("@azure/cognitiveservices-luis-runtime"); | ||
const msRest = require("@azure/ms-rest-js"); | ||
const botbuilder_core_1 = require("botbuilder-core"); | ||
const os = require("os"); | ||
const Url = require("url-parse"); | ||
const luisTelemetryConstants_1 = require("./luisTelemetryConstants"); | ||
const pjson = require('../package.json'); | ||
const LUIS_TRACE_TYPE = 'https://www.luis.ai/schemas/trace'; | ||
const LUIS_TRACE_NAME = 'LuisRecognizer'; | ||
const LUIS_TRACE_LABEL = 'Luis Trace'; | ||
const luisRecognizerOptionsV2_1 = require("./luisRecognizerOptionsV2"); | ||
const luisRecognizerOptionsV3_1 = require("./luisRecognizerOptionsV3"); | ||
/** | ||
@@ -61,13 +49,18 @@ * Recognize intents in a user utterance using a configured LUIS model. | ||
this.validateLuisApplication(); | ||
this.options = Object.assign({ includeAllIntents: false, includeInstanceData: true, log: true, spellCheck: false, staging: false }, options); | ||
this.includeApiResults = !!includeApiResults; | ||
// Create client | ||
// - We have to cast "creds as any" to avoid a build break relating to different versions | ||
// of autorest being used by our various components. This is just a build issue and | ||
// shouldn't effect production bots. | ||
const creds = new msRest.TokenCredentials(this.application.endpointKey); | ||
const baseUri = this.application.endpoint || 'https://westus.api.cognitive.microsoft.com'; | ||
this.luisClient = new cognitiveservices_luis_runtime_1.LUISRuntimeClient(creds, baseUri); | ||
this._telemetryClient = this.options.telemetryClient || new botbuilder_core_1.NullTelemetryClient(); | ||
this._logPersonalInformation = this.options.logPersonalInformation || false; | ||
this._telemetryClient = (options && options.telemetryClient) || new botbuilder_core_1.NullTelemetryClient(); | ||
this._logPersonalInformation = (options && options.logPersonalInformation) || false; | ||
if (!options) { | ||
this.luisRecognizerInternal = new luisRecognizerOptionsV2_1.LuisRecognizerV2(this.application); | ||
} | ||
else if (luisRecognizerOptionsV3_1.isLuisRecognizerOptionsV3(options)) { | ||
this.luisRecognizerInternal = new luisRecognizerOptionsV3_1.LuisRecognizerV3(this.application, options); | ||
} | ||
else if (luisRecognizerOptionsV2_1.isLuisRecognizerOptionsV2(options)) { | ||
this.luisRecognizerInternal = new luisRecognizerOptionsV2_1.LuisRecognizerV2(this.application, options); | ||
} | ||
else { | ||
this.options = Object.assign({}, options); | ||
let recOptions = Object.assign({ includeAPIResults: !!includeApiResults }, options, { apiVersion: 'v2' }); | ||
this.luisRecognizerInternal = new luisRecognizerOptionsV2_1.LuisRecognizerV2(this.application, recOptions); | ||
} | ||
} | ||
@@ -132,7 +125,7 @@ /** | ||
* @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisPrectionOptions](#luispredictionoptions) definition. | ||
* @param options (Optional) options object used to override control predictions. Should conform to the [LuisRecognizerOptionsV2] or [LuisRecognizerOptionsV3] definition. | ||
*/ | ||
recognize(context, telemetryProperties, telemetryMetrics, options) { | ||
const cached = context.turnState.get(this.cacheKey); | ||
const luisPredictionOptions = options ? this.setLuisPredictionOptions(this.options, options) : this.options; | ||
const luisRecognizer = options ? this.buildRecognizer(options) : this.luisRecognizerInternal; | ||
if (!cached) { | ||
@@ -150,15 +143,3 @@ const utterance = context.activity.text || ''; | ||
else { | ||
recognizerPromise = this.luisClient.prediction.resolve(this.application.applicationId, utterance, Object.assign({ verbose: luisPredictionOptions.includeAllIntents, customHeaders: { | ||
'Ocp-Apim-Subscription-Key': this.application.endpointKey, | ||
'User-Agent': this.getUserAgent() | ||
} }, luisPredictionOptions)) | ||
// Map results | ||
.then((luisResult) => ({ | ||
text: luisResult.query, | ||
alteredText: luisResult.alteredQuery, | ||
intents: this.getIntents(luisResult), | ||
entities: this.getEntitiesAndMetadata(luisResult.entities, luisResult.compositeEntities, luisPredictionOptions.includeInstanceData === undefined || luisPredictionOptions.includeInstanceData), | ||
sentiment: this.getSentiment(luisResult), | ||
luisResult: (this.includeApiResults ? luisResult : null) | ||
})); | ||
recognizerPromise = luisRecognizer.recognizeInternalAsync(context); | ||
} | ||
@@ -171,5 +152,3 @@ return recognizerPromise | ||
this.onRecognizerResults(recognizerResult, context, telemetryProperties, telemetryMetrics); | ||
return this.emitTraceInfo(context, recognizerResult.luisResult || null, recognizerResult).then(() => { | ||
return recognizerResult; | ||
}); | ||
return recognizerResult; | ||
}) | ||
@@ -247,29 +226,2 @@ .catch((error) => { | ||
} | ||
getUserAgent() { | ||
// Note when the ms-rest dependency the LuisClient uses has been updated | ||
// this code should be modified to use the client's addUserAgentInfo() function. | ||
const packageUserAgent = `${pjson.name}/${pjson.version}`; | ||
const platformUserAgent = `(${os.arch()}-${os.type()}-${os.release()}; Node.js,Version=${process.version})`; | ||
const userAgent = `${packageUserAgent} ${platformUserAgent}`; | ||
return userAgent; | ||
} | ||
emitTraceInfo(context, luisResult, recognizerResult) { | ||
const traceInfo = { | ||
recognizerResult: recognizerResult, | ||
luisResult: luisResult, | ||
luisOptions: { | ||
Staging: this.options.staging | ||
}, | ||
luisModel: { | ||
ModelID: this.application.applicationId | ||
} | ||
}; | ||
return context.sendActivity({ | ||
type: 'trace', | ||
valueType: LUIS_TRACE_TYPE, | ||
name: LUIS_TRACE_NAME, | ||
label: LUIS_TRACE_LABEL, | ||
value: traceInfo | ||
}); | ||
} | ||
prepareErrorMessage(error) { | ||
@@ -313,216 +265,3 @@ // If the `error` received is a azure-cognitiveservices-luis-runtime error, | ||
} | ||
normalizeName(name) { | ||
return name.replace(/\.| /g, '_'); | ||
} | ||
getIntents(luisResult) { | ||
const intents = {}; | ||
if (luisResult.intents) { | ||
luisResult.intents.reduce((prev, curr) => { | ||
prev[this.normalizeName(curr.intent)] = { score: curr.score }; | ||
return prev; | ||
}, intents); | ||
} | ||
else { | ||
const topScoringIntent = luisResult.topScoringIntent; | ||
intents[this.normalizeName((topScoringIntent).intent)] = { score: topScoringIntent.score }; | ||
} | ||
return intents; | ||
} | ||
getEntitiesAndMetadata(entities, compositeEntities, verbose) { | ||
const entitiesAndMetadata = verbose ? { $instance: {} } : {}; | ||
let compositeEntityTypes = []; | ||
// We start by populating composite entities so that entities covered by them are removed from the entities list | ||
if (compositeEntities) { | ||
compositeEntityTypes = compositeEntities.map((compositeEntity) => compositeEntity.parentType); | ||
compositeEntities.forEach((compositeEntity) => { | ||
entities = this.populateCompositeEntity(compositeEntity, entities, entitiesAndMetadata, verbose); | ||
}); | ||
} | ||
entities.forEach((entity) => { | ||
// we'll address composite entities separately | ||
if (compositeEntityTypes.indexOf(entity.type) > -1) { | ||
return; | ||
} | ||
let val = this.getEntityValue(entity); | ||
if (val != null) { | ||
this.addProperty(entitiesAndMetadata, this.getNormalizedEntityName(entity), val); | ||
if (verbose) { | ||
this.addProperty(entitiesAndMetadata.$instance, this.getNormalizedEntityName(entity), this.getEntityMetadata(entity)); | ||
} | ||
} | ||
}); | ||
return entitiesAndMetadata; | ||
} | ||
getEntityValue(entity) { | ||
if (entity.type.startsWith("builtin.geographyV2.")) { | ||
return { | ||
"type": entity.type.substring(20), | ||
"location": entity.entity | ||
}; | ||
} | ||
if (entity.type.startsWith('builtin.ordinalV2')) { | ||
return { | ||
"relativeTo": entity.resolution.relativeTo, | ||
"offset": Number(entity.resolution.offset) | ||
}; | ||
} | ||
if (!entity.resolution) { | ||
return entity.entity; | ||
} | ||
if (entity.type.startsWith('builtin.datetimeV2.')) { | ||
if (!entity.resolution.values || !entity.resolution.values.length) { | ||
return entity.resolution; | ||
} | ||
const vals = entity.resolution.values; | ||
const type = vals[0].type; | ||
const timexes = vals.map((t) => t.timex); | ||
const distinct = timexes.filter((v, i, a) => a.indexOf(v) === i); | ||
return { type: type, timex: distinct }; | ||
} | ||
else { | ||
const res = entity.resolution; | ||
switch (entity.type) { | ||
case 'builtin.number': | ||
case 'builtin.ordinal': return Number(res.value); | ||
case 'builtin.percentage': | ||
{ | ||
let svalue = res.value; | ||
if (svalue.endsWith('%')) { | ||
svalue = svalue.substring(0, svalue.length - 1); | ||
} | ||
return Number(svalue); | ||
} | ||
case 'builtin.age': | ||
case 'builtin.dimension': | ||
case 'builtin.currency': | ||
case 'builtin.temperature': | ||
{ | ||
const val = res.value; | ||
const obj = {}; | ||
if (val) { | ||
obj.number = Number(val); | ||
} | ||
obj.units = res.unit; | ||
return obj; | ||
} | ||
default: | ||
// This will return null if there is no value/values which can happen when a new prebuilt is introduced | ||
return entity.resolution.value ? | ||
entity.resolution.value : | ||
entity.resolution.values; | ||
} | ||
} | ||
} | ||
getEntityMetadata(entity) { | ||
const res = { | ||
startIndex: entity.startIndex, | ||
endIndex: entity.endIndex + 1, | ||
score: entity.score, | ||
text: entity.entity, | ||
type: entity.type | ||
}; | ||
if (entity.resolution && entity.resolution.subtype) { | ||
res.subtype = entity.resolution.subtype; | ||
} | ||
return res; | ||
} | ||
getNormalizedEntityName(entity) { | ||
// Type::Role -> Role | ||
let type = entity.type.split(':').pop(); | ||
if (type.startsWith('builtin.datetimeV2.')) { | ||
type = 'datetime'; | ||
} | ||
else if (type.startsWith('builtin.currency')) { | ||
type = 'money'; | ||
} | ||
else if (type.startsWith('builtin.geographyV2')) { | ||
type = 'geographyV2'; | ||
} | ||
else if (type.startsWith('builtin.ordinalV2')) { | ||
type = 'ordinalV2'; | ||
} | ||
else if (type.startsWith('builtin.')) { | ||
type = type.substring(8); | ||
} | ||
if (entity.role !== null && entity.role !== '' && entity.role !== undefined) { | ||
type = entity.role; | ||
} | ||
return type.replace(/\.|\s/g, '_'); | ||
} | ||
populateCompositeEntity(compositeEntity, entities, entitiesAndMetadata, verbose) { | ||
const childrenEntites = verbose ? { $instance: {} } : {}; | ||
let childrenEntitiesMetadata = {}; | ||
// This is now implemented as O(n^2) search and can be reduced to O(2n) using a map as an optimization if n grows | ||
const compositeEntityMetadata = entities.find((entity) => { | ||
// For now we are matching by value, which can be ambiguous if the same composite entity shows up with the same text | ||
// multiple times within an utterance, but this is just a stop gap solution till the indices are included in composite entities | ||
return entity.type === compositeEntity.parentType && entity.entity === compositeEntity.value; | ||
}); | ||
const filteredEntities = []; | ||
if (verbose) { | ||
childrenEntitiesMetadata = this.getEntityMetadata(compositeEntityMetadata); | ||
} | ||
// This is now implemented as O(n*k) search and can be reduced to O(n + k) using a map as an optimization if n or k grow | ||
const coveredSet = new Set(); | ||
compositeEntity.children.forEach((childEntity) => { | ||
for (let i = 0; i < entities.length; i++) { | ||
const entity = entities[i]; | ||
if (!coveredSet.has(i) && | ||
childEntity.type === entity.type && | ||
compositeEntityMetadata && | ||
entity.startIndex !== undefined && | ||
compositeEntityMetadata.startIndex !== undefined && | ||
entity.startIndex >= compositeEntityMetadata.startIndex && | ||
entity.endIndex !== undefined && | ||
compositeEntityMetadata.endIndex !== undefined && | ||
entity.endIndex <= compositeEntityMetadata.endIndex) { | ||
// Add to the set to ensure that we don't consider the same child entity more than once per composite | ||
coveredSet.add(i); | ||
let val = this.getEntityValue(entity); | ||
if (val != null) { | ||
this.addProperty(childrenEntites, this.getNormalizedEntityName(entity), val); | ||
if (verbose) { | ||
this.addProperty(childrenEntites.$instance, this.getNormalizedEntityName(entity), this.getEntityMetadata(entity)); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
// filter entities that were covered by this composite entity | ||
for (let i = 0; i < entities.length; i++) { | ||
if (!coveredSet.has(i)) { | ||
filteredEntities.push(entities[i]); | ||
} | ||
} | ||
this.addProperty(entitiesAndMetadata, this.getNormalizedEntityName(compositeEntityMetadata), childrenEntites); | ||
if (verbose) { | ||
this.addProperty(entitiesAndMetadata.$instance, this.getNormalizedEntityName(compositeEntityMetadata), childrenEntitiesMetadata); | ||
} | ||
return filteredEntities; | ||
} | ||
/** | ||
* If a property doesn't exist add it to a new array, otherwise append it to the existing array | ||
* @param obj Object on which the property is to be set | ||
* @param key Property Key | ||
* @param value Property Value | ||
*/ | ||
addProperty(obj, key, value) { | ||
if (key in obj) { | ||
obj[key] = obj[key].concat(value); | ||
} | ||
else { | ||
obj[key] = [value]; | ||
} | ||
} | ||
getSentiment(luis) { | ||
let result; | ||
if (luis.sentimentAnalysis) { | ||
result = { | ||
label: luis.sentimentAnalysis.label, | ||
score: luis.sentimentAnalysis.score | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Merges the default options set by the Recognizer contructor with the 'user' options passed into the 'recognize' method | ||
@@ -546,4 +285,23 @@ */ | ||
} | ||
/** | ||
* Builds a LuisRecognizer Strategy depending on the options passed | ||
*/ | ||
buildRecognizer(userOptions) { | ||
if (luisRecognizerOptionsV3_1.isLuisRecognizerOptionsV3(userOptions)) { | ||
return new luisRecognizerOptionsV3_1.LuisRecognizerV3(this.application, userOptions); | ||
} | ||
else if (luisRecognizerOptionsV2_1.isLuisRecognizerOptionsV2(userOptions)) { | ||
return new luisRecognizerOptionsV2_1.LuisRecognizerV2(this.application, userOptions); | ||
} | ||
else { | ||
if (!this.options) { | ||
this.options = {}; | ||
} | ||
const merge = Object.assign(this.options, userOptions); | ||
let recOptions = Object.assign({}, merge, { apiVersion: 'v2' }); | ||
return new luisRecognizerOptionsV2_1.LuisRecognizerV2(this.application, recOptions); | ||
} | ||
} | ||
} | ||
exports.LuisRecognizer = LuisRecognizer; | ||
//# sourceMappingURL=luisRecognizer.js.map |
@@ -49,2 +49,10 @@ /** | ||
qnaId?: number; | ||
/** | ||
* A value indicating whether to call test or prod environment of knowledgebase. | ||
*/ | ||
isTest?: boolean; | ||
/** | ||
* Ranker types. | ||
*/ | ||
rankerType?: string; | ||
} |
@@ -20,2 +20,3 @@ "use strict"; | ||
const __1 = require(".."); | ||
const rankerTypes_1 = require("../qnamaker-interfaces/rankerTypes"); | ||
/** | ||
@@ -60,3 +61,4 @@ * Generate Answer api utils class. | ||
const url = `${endpoint.host}/knowledgebases/${endpoint.knowledgeBaseId}/generateanswer`; | ||
const queryOptions = Object.assign({}, this._options, options); | ||
var queryOptions = Object.assign({}, this._options, options); | ||
queryOptions.rankerType = !queryOptions.rankerType ? rankerTypes_1.RankerTypes.default : queryOptions.rankerType; | ||
this.validateOptions(queryOptions); | ||
@@ -105,3 +107,3 @@ var payloadBody = JSON.stringify(Object.assign({ question: question }, queryOptions)); | ||
validateOptions(options) { | ||
const { scoreThreshold, top } = options; | ||
const { scoreThreshold, top, rankerType } = options; | ||
if (scoreThreshold) { | ||
@@ -108,0 +110,0 @@ this.validateScoreThreshold(scoreThreshold); |
@@ -5,3 +5,3 @@ { | ||
"description": "Cognitive services extensions for Microsoft BotBuilder.", | ||
"version": "4.6.2", | ||
"version": "4.7.0", | ||
"license": "MIT", | ||
@@ -28,3 +28,3 @@ "keywords": [ | ||
"@types/node": "^10.12.18", | ||
"botbuilder-core": "4.6.2", | ||
"botbuilder-core": "4.7.0", | ||
"moment": "^2.20.1", | ||
@@ -51,3 +51,3 @@ "node-fetch": "^2.3.0", | ||
"clean": "erase /q /s .\\lib", | ||
"set-version": "npm version --allow-same-version 4.6.2" | ||
"set-version": "npm version --allow-same-version 4.7.0" | ||
}, | ||
@@ -54,0 +54,0 @@ "files": [ |
@@ -9,15 +9,8 @@ /** | ||
import { LUISRuntimeClient as LuisClient, LUISRuntimeModels as LuisModels } from '@azure/cognitiveservices-luis-runtime'; | ||
import * as msRest from '@azure/ms-rest-js'; | ||
import { BotTelemetryClient, NullTelemetryClient, RecognizerResult, TurnContext } from 'botbuilder-core'; | ||
import * as os from 'os'; | ||
import * as Url from 'url-parse'; | ||
import { LuisTelemetryConstants } from './luisTelemetryConstants'; | ||
import { isLuisRecognizerOptionsV2, LuisRecognizerV2 } from './luisRecognizerOptionsV2'; | ||
import { isLuisRecognizerOptionsV3, LuisRecognizerV3 } from './luisRecognizerOptionsV3'; | ||
const pjson = require('../package.json'); | ||
const LUIS_TRACE_TYPE = 'https://www.luis.ai/schemas/trace'; | ||
const LUIS_TRACE_NAME = 'LuisRecognizer'; | ||
const LUIS_TRACE_LABEL = 'Luis Trace'; | ||
/** | ||
@@ -68,2 +61,3 @@ * @private | ||
/** | ||
* | ||
* Options per LUIS prediction. | ||
@@ -141,3 +135,109 @@ */ | ||
export interface LuisRecognizerOptions { | ||
/** | ||
* (Optional) Telemetry Client. | ||
*/ | ||
telemetryClient?: BotTelemetryClient; | ||
/** | ||
* (Optional) Designates whether personal information should be logged in telemetry. | ||
*/ | ||
logPersonalInformation?: boolean; | ||
/** | ||
* (Optional) Force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false` | ||
*/ | ||
includeAPIResults?: boolean; | ||
} | ||
export interface LuisRecognizerOptionsV3 extends LuisRecognizerOptions { | ||
/** | ||
* (Optional) Luis Api endpoint version. | ||
*/ | ||
apiVersion: "v3"; | ||
/** | ||
* (Optional) Determine if all intents come back or only the top one. | ||
*/ | ||
includeAllIntents?: boolean; | ||
/** | ||
* (Optional) A value indicating whether or not instance data should be included in response. | ||
*/ | ||
includeInstanceData?: boolean; | ||
/** | ||
* (Optional) If queries should be logged in LUIS. | ||
*/ | ||
log?: boolean; | ||
/** | ||
* (Optional) Dynamic lists of things like contact names to recognize at query time.. | ||
*/ | ||
dynamicLists?: Array<any>; | ||
/** | ||
* (Optional) External entities recognized in query. | ||
*/ | ||
externalEntities?: Array<any>; | ||
/** | ||
* (Optional) Boolean for if external entities should be preferred to the results from LUIS models. | ||
*/ | ||
preferExternalEntities?: boolean; | ||
/** | ||
* (Optional) By default this uses the production slot. You can find other standard slots in <see cref="LuisSlot"/>. | ||
* If you specify a Version, then a private version of the application is used instead of a slot. | ||
*/ | ||
slot?: 'production' | 'staging'; | ||
/** | ||
* (Optional) LUIS supports versions and this is the version to use instead of a slot. | ||
* If this is specified, then the <see cref="Slot"/> is ignored.. | ||
*/ | ||
version?: string; | ||
} | ||
export interface LuisRecognizerOptionsV2 extends LuisRecognizerOptions { | ||
/** | ||
* Luis Api endpoint version. | ||
*/ | ||
apiVersion: "v2"; | ||
/** | ||
* (Optional) Bing Spell Check subscription key. | ||
*/ | ||
bingSpellCheckSubscriptionKey?: string; | ||
/** | ||
* (Optional) Determine if all intents come back or only the top one. | ||
*/ | ||
includeAllIntents?: boolean; | ||
/** | ||
* (Optional) A value indicating whether or not instance data should be included in response. | ||
*/ | ||
includeInstanceData?: boolean; | ||
/** | ||
* (Optional) If queries should be logged in LUIS. | ||
*/ | ||
log?: boolean; | ||
/** | ||
* (Optional) Whether to spell check query. | ||
*/ | ||
spellCheck?: boolean; | ||
/** | ||
* (Optional) Whether to use the staging endpoint. | ||
*/ | ||
staging?: boolean; | ||
/** | ||
* (Optional) The time zone offset for resolving datetimes. | ||
*/ | ||
timezoneOffset?: number; | ||
} | ||
/** | ||
@@ -162,2 +262,3 @@ * Recognize intents in a user utterance using a configured LUIS model. | ||
private cacheKey: symbol = Symbol('results'); | ||
private luisRecognizerInternal: LuisRecognizerV2 | LuisRecognizerV3; | ||
@@ -167,8 +268,9 @@ /** | ||
* @param application An object conforming to the [LuisApplication](#luisapplication) definition or a string representing a LUIS application endpoint, usually retrieved from https://luis.ai. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisPrectionOptions](#luispredictionoptions) definition. | ||
* @param includeApiResults (Optional) flag that if set to `true` will force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false`. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisRecognizerOptions](#luisrecognizeroptions) definition. | ||
* @param includeApiResults (Deprecated) flag that if set to `true` will force the inclusion of LUIS Api call in results returned by [recognize()](#recognize). Defaults to a value of `false`. | ||
*/ | ||
constructor(application: string, options?: LuisPredictionOptions, includeApiResults?: boolean); | ||
constructor(application: LuisApplication, options?: LuisPredictionOptions, includeApiResults?: boolean); | ||
constructor(application: LuisApplication | string, options?: LuisPredictionOptions, includeApiResults?: boolean) { | ||
constructor(application: LuisApplication | string, options?: LuisRecognizerOptionsV3 | LuisRecognizerOptionsV2); | ||
constructor(application: LuisApplication | string, options?: LuisRecognizerOptionsV3 | LuisRecognizerOptionsV2 | LuisPredictionOptions, includeApiResults?: boolean) { | ||
if (typeof application === 'string') { | ||
@@ -194,22 +296,25 @@ const parsedEndpoint: Url = Url(application); | ||
this.options = { | ||
includeAllIntents: false, | ||
includeInstanceData: true, | ||
log: true, | ||
spellCheck: false, | ||
staging: false, | ||
...options | ||
}; | ||
this.includeApiResults = !!includeApiResults; | ||
this._telemetryClient = (options && options.telemetryClient) || new NullTelemetryClient(); | ||
this._logPersonalInformation = (options && options.logPersonalInformation) || false; | ||
// Create client | ||
// - We have to cast "creds as any" to avoid a build break relating to different versions | ||
// of autorest being used by our various components. This is just a build issue and | ||
// shouldn't effect production bots. | ||
const creds: msRest.TokenCredentials = new msRest.TokenCredentials(this.application.endpointKey); | ||
const baseUri: string = this.application.endpoint || 'https://westus.api.cognitive.microsoft.com'; | ||
this.luisClient = new LuisClient(creds as any, baseUri); | ||
if(!options) { | ||
this.luisRecognizerInternal = new LuisRecognizerV2(this.application); | ||
} else if (isLuisRecognizerOptionsV3(options)) { | ||
this.luisRecognizerInternal = new LuisRecognizerV3(this.application, options); | ||
} else if (isLuisRecognizerOptionsV2(options)) { | ||
this.luisRecognizerInternal = new LuisRecognizerV2(this.application, options); | ||
} else { | ||
this._telemetryClient = this.options.telemetryClient || new NullTelemetryClient(); | ||
this._logPersonalInformation = this.options.logPersonalInformation || false; | ||
this.options = { | ||
...options, | ||
} | ||
let recOptions: LuisRecognizerOptionsV2 = { | ||
includeAPIResults: !!includeApiResults, | ||
...options, | ||
apiVersion: 'v2' | ||
}; | ||
this.luisRecognizerInternal = new LuisRecognizerV2(this.application, recOptions); | ||
} | ||
} | ||
@@ -279,7 +384,7 @@ | ||
* @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. | ||
* @param options (Optional) options object used to control predictions. Should conform to the [LuisPrectionOptions](#luispredictionoptions) definition. | ||
* @param options (Optional) options object used to override control predictions. Should conform to the [LuisRecognizerOptionsV2] or [LuisRecognizerOptionsV3] definition. | ||
*/ | ||
public recognize(context: TurnContext, telemetryProperties?: { [key: string]: string }, telemetryMetrics?: { [key: string]: number }, options?: LuisPredictionOptions): Promise<RecognizerResult> { | ||
public recognize(context: TurnContext, telemetryProperties?: { [key: string]: string }, telemetryMetrics?: { [key: string]: number }, options?: LuisRecognizerOptionsV2 | LuisRecognizerOptionsV3 | LuisPredictionOptions): Promise<RecognizerResult> { | ||
const cached: any = context.turnState.get(this.cacheKey); | ||
const luisPredictionOptions = options ? this.setLuisPredictionOptions(this.options, options) : this.options; | ||
const luisRecognizer = options ? this.buildRecognizer(options) : this.luisRecognizerInternal; | ||
if (!cached) { | ||
@@ -297,25 +402,3 @@ const utterance: string = context.activity.text || ''; | ||
} else { | ||
recognizerPromise = this.luisClient.prediction.resolve( | ||
this.application.applicationId, utterance, | ||
{ | ||
verbose: luisPredictionOptions.includeAllIntents, | ||
customHeaders: { | ||
'Ocp-Apim-Subscription-Key': this.application.endpointKey, | ||
'User-Agent': this.getUserAgent() | ||
}, | ||
...luisPredictionOptions | ||
}) | ||
// Map results | ||
.then((luisResult: LuisModels.LuisResult) => ({ | ||
text: luisResult.query, | ||
alteredText: luisResult.alteredQuery, | ||
intents: this.getIntents(luisResult), | ||
entities: this.getEntitiesAndMetadata( | ||
luisResult.entities, | ||
luisResult.compositeEntities, | ||
luisPredictionOptions.includeInstanceData === undefined || luisPredictionOptions.includeInstanceData | ||
), | ||
sentiment: this.getSentiment(luisResult), | ||
luisResult: (this.includeApiResults ? luisResult : null) | ||
})); | ||
recognizerPromise = luisRecognizer.recognizeInternalAsync(context); | ||
} | ||
@@ -331,5 +414,3 @@ | ||
return this.emitTraceInfo(context, recognizerResult.luisResult || null, recognizerResult).then(() => { | ||
return recognizerResult; | ||
}); | ||
return recognizerResult; | ||
}) | ||
@@ -414,36 +495,2 @@ .catch((error: any) => { | ||
private getUserAgent(): string { | ||
// Note when the ms-rest dependency the LuisClient uses has been updated | ||
// this code should be modified to use the client's addUserAgentInfo() function. | ||
const packageUserAgent = `${pjson.name}/${pjson.version}`; | ||
const platformUserAgent = `(${os.arch()}-${os.type()}-${os.release()}; Node.js,Version=${process.version})`; | ||
const userAgent = `${packageUserAgent} ${platformUserAgent}`; | ||
return userAgent; | ||
} | ||
private emitTraceInfo(context: TurnContext, luisResult: LuisModels.LuisResult, recognizerResult: RecognizerResult): Promise<any> { | ||
const traceInfo: LuisTraceInfo = { | ||
recognizerResult: recognizerResult, | ||
luisResult: luisResult, | ||
luisOptions: { | ||
Staging: this.options.staging | ||
}, | ||
luisModel: { | ||
ModelID: this.application.applicationId | ||
} | ||
}; | ||
return context.sendActivity({ | ||
type: 'trace', | ||
valueType: LUIS_TRACE_TYPE, | ||
name: LUIS_TRACE_NAME, | ||
label: LUIS_TRACE_LABEL, | ||
value: traceInfo | ||
}); | ||
} | ||
private prepareErrorMessage(error: Error): void { | ||
@@ -488,259 +535,3 @@ // If the `error` received is a azure-cognitiveservices-luis-runtime error, | ||
private normalizeName(name: string): string { | ||
return name.replace(/\.| /g, '_'); | ||
} | ||
private getIntents(luisResult: LuisModels.LuisResult): any { | ||
const intents: { [name: string]: { score: number } } = {}; | ||
if (luisResult.intents) { | ||
luisResult.intents.reduce( | ||
(prev: any, curr: LuisModels.IntentModel) => { | ||
prev[this.normalizeName(curr.intent)] = { score: curr.score }; | ||
return prev; | ||
}, | ||
intents | ||
); | ||
} else { | ||
const topScoringIntent: LuisModels.IntentModel = luisResult.topScoringIntent; | ||
intents[this.normalizeName((topScoringIntent).intent)] = { score: topScoringIntent.score }; | ||
} | ||
return intents; | ||
} | ||
private getEntitiesAndMetadata( | ||
entities: LuisModels.EntityModel[], | ||
compositeEntities: LuisModels.CompositeEntityModel[] | undefined, | ||
verbose: boolean | ||
): any { | ||
const entitiesAndMetadata: any = verbose ? { $instance: {} } : {}; | ||
let compositeEntityTypes: string[] = []; | ||
// We start by populating composite entities so that entities covered by them are removed from the entities list | ||
if (compositeEntities) { | ||
compositeEntityTypes = compositeEntities.map((compositeEntity: LuisModels.CompositeEntityModel) => compositeEntity.parentType); | ||
compositeEntities.forEach((compositeEntity: LuisModels.CompositeEntityModel) => { | ||
entities = this.populateCompositeEntity(compositeEntity, entities, entitiesAndMetadata, verbose); | ||
}); | ||
} | ||
entities.forEach((entity: LuisModels.EntityModel) => { | ||
// we'll address composite entities separately | ||
if (compositeEntityTypes.indexOf(entity.type) > -1) { | ||
return; | ||
} | ||
let val = this.getEntityValue(entity); | ||
if (val != null) { | ||
this.addProperty(entitiesAndMetadata, this.getNormalizedEntityName(entity), val); | ||
if (verbose) { | ||
this.addProperty(entitiesAndMetadata.$instance, this.getNormalizedEntityName(entity), this.getEntityMetadata(entity)); | ||
} | ||
} | ||
}); | ||
return entitiesAndMetadata; | ||
} | ||
private getEntityValue(entity: LuisModels.EntityModel): any { | ||
if (entity.type.startsWith("builtin.geographyV2.")) { | ||
return { | ||
"type": entity.type.substring(20), | ||
"location": entity.entity | ||
}; | ||
} | ||
if (entity.type.startsWith('builtin.ordinalV2')) { | ||
return { | ||
"relativeTo": entity.resolution.relativeTo, | ||
"offset": Number(entity.resolution.offset) | ||
} | ||
} | ||
if (!entity.resolution) { | ||
return entity.entity; | ||
} | ||
if (entity.type.startsWith('builtin.datetimeV2.')) { | ||
if (!entity.resolution.values || !entity.resolution.values.length) { | ||
return entity.resolution; | ||
} | ||
const vals: any = entity.resolution.values; | ||
const type: any = vals[0].type; | ||
const timexes: any[] = vals.map((t: any) => t.timex); | ||
const distinct: any = timexes.filter((v: any, i: number, a: any[]) => a.indexOf(v) === i); | ||
return { type: type, timex: distinct }; | ||
} else { | ||
const res: any = entity.resolution; | ||
switch (entity.type) { | ||
case 'builtin.number': | ||
case 'builtin.ordinal': return Number(res.value); | ||
case 'builtin.percentage': | ||
{ | ||
let svalue: string = res.value; | ||
if (svalue.endsWith('%')) { | ||
svalue = svalue.substring(0, svalue.length - 1); | ||
} | ||
return Number(svalue); | ||
} | ||
case 'builtin.age': | ||
case 'builtin.dimension': | ||
case 'builtin.currency': | ||
case 'builtin.temperature': | ||
{ | ||
const val: any = res.value; | ||
const obj: any = {}; | ||
if (val) { | ||
obj.number = Number(val); | ||
} | ||
obj.units = res.unit; | ||
return obj; | ||
} | ||
default: | ||
// This will return null if there is no value/values which can happen when a new prebuilt is introduced | ||
return entity.resolution.value ? | ||
entity.resolution.value : | ||
entity.resolution.values; | ||
} | ||
} | ||
} | ||
private getEntityMetadata(entity: LuisModels.EntityModel): any { | ||
const res: any = { | ||
startIndex: entity.startIndex, | ||
endIndex: entity.endIndex + 1, | ||
score: entity.score, | ||
text: entity.entity, | ||
type: entity.type | ||
}; | ||
if (entity.resolution && entity.resolution.subtype) { | ||
res.subtype = entity.resolution.subtype; | ||
} | ||
return res; | ||
} | ||
private getNormalizedEntityName(entity: LuisModels.EntityModel): string { | ||
// Type::Role -> Role | ||
let type: string = entity.type.split(':').pop(); | ||
if (type.startsWith('builtin.datetimeV2.')) { | ||
type = 'datetime'; | ||
} | ||
else if (type.startsWith('builtin.currency')) { | ||
type = 'money'; | ||
} | ||
else if (type.startsWith('builtin.geographyV2')) { | ||
type = 'geographyV2'; | ||
} | ||
else if (type.startsWith('builtin.ordinalV2')) { | ||
type = 'ordinalV2'; | ||
} | ||
else if (type.startsWith('builtin.')) { | ||
type = type.substring(8); | ||
} | ||
if (entity.role !== null && entity.role !== '' && entity.role !== undefined) { | ||
type = entity.role; | ||
} | ||
return type.replace(/\.|\s/g, '_'); | ||
} | ||
private populateCompositeEntity( | ||
compositeEntity: LuisModels.CompositeEntityModel, | ||
entities: LuisModels.EntityModel[], | ||
entitiesAndMetadata: any, | ||
verbose: boolean | ||
): LuisModels.EntityModel[] { | ||
const childrenEntites: any = verbose ? { $instance: {} } : {}; | ||
let childrenEntitiesMetadata: any = {}; | ||
// This is now implemented as O(n^2) search and can be reduced to O(2n) using a map as an optimization if n grows | ||
const compositeEntityMetadata: LuisModels.EntityModel | undefined = entities.find((entity: LuisModels.EntityModel) => { | ||
// For now we are matching by value, which can be ambiguous if the same composite entity shows up with the same text | ||
// multiple times within an utterance, but this is just a stop gap solution till the indices are included in composite entities | ||
return entity.type === compositeEntity.parentType && entity.entity === compositeEntity.value; | ||
}); | ||
const filteredEntities: LuisModels.EntityModel[] = []; | ||
if (verbose) { | ||
childrenEntitiesMetadata = this.getEntityMetadata(compositeEntityMetadata); | ||
} | ||
// This is now implemented as O(n*k) search and can be reduced to O(n + k) using a map as an optimization if n or k grow | ||
const coveredSet: Set<any> = new Set(); | ||
compositeEntity.children.forEach((childEntity: LuisModels.CompositeChildModel) => { | ||
for (let i = 0; i < entities.length; i++) { | ||
const entity: LuisModels.EntityModel = entities[i]; | ||
if (!coveredSet.has(i) && | ||
childEntity.type === entity.type && | ||
compositeEntityMetadata && | ||
entity.startIndex !== undefined && | ||
compositeEntityMetadata.startIndex !== undefined && | ||
entity.startIndex >= compositeEntityMetadata.startIndex && | ||
entity.endIndex !== undefined && | ||
compositeEntityMetadata.endIndex !== undefined && | ||
entity.endIndex <= compositeEntityMetadata.endIndex | ||
) { | ||
// Add to the set to ensure that we don't consider the same child entity more than once per composite | ||
coveredSet.add(i); | ||
let val = this.getEntityValue(entity); | ||
if (val != null) { | ||
this.addProperty(childrenEntites, this.getNormalizedEntityName(entity), val); | ||
if (verbose) { | ||
this.addProperty(childrenEntites.$instance, this.getNormalizedEntityName(entity), this.getEntityMetadata(entity)); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
// filter entities that were covered by this composite entity | ||
for (let i = 0; i < entities.length; i++) { | ||
if (!coveredSet.has(i)) { | ||
filteredEntities.push(entities[i]); | ||
} | ||
} | ||
this.addProperty(entitiesAndMetadata, this.getNormalizedEntityName(compositeEntityMetadata), childrenEntites); | ||
if (verbose) { | ||
this.addProperty(entitiesAndMetadata.$instance, this.getNormalizedEntityName(compositeEntityMetadata), childrenEntitiesMetadata); | ||
} | ||
return filteredEntities; | ||
} | ||
/** | ||
* If a property doesn't exist add it to a new array, otherwise append it to the existing array | ||
* @param obj Object on which the property is to be set | ||
* @param key Property Key | ||
* @param value Property Value | ||
*/ | ||
private addProperty(obj: any, key: string, value: any): void { | ||
if (key in obj) { | ||
obj[key] = obj[key].concat(value); | ||
} else { | ||
obj[key] = [value]; | ||
} | ||
} | ||
private getSentiment(luis: LuisModels.LuisResult): any { | ||
let result: any; | ||
if (luis.sentimentAnalysis) { | ||
result = { | ||
label: luis.sentimentAnalysis.label, | ||
score: luis.sentimentAnalysis.score | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Merges the default options set by the Recognizer contructor with the 'user' options passed into the 'recognize' method | ||
@@ -765,2 +556,25 @@ */ | ||
} | ||
/** | ||
* Builds a LuisRecognizer Strategy depending on the options passed | ||
*/ | ||
private buildRecognizer(userOptions: LuisRecognizerOptionsV2 | LuisRecognizerOptionsV3 | LuisPredictionOptions): LuisRecognizerV3 | LuisRecognizerV2 { | ||
if (isLuisRecognizerOptionsV3(userOptions)) { | ||
return new LuisRecognizerV3(this.application, userOptions); | ||
} else if (isLuisRecognizerOptionsV2(userOptions)) { | ||
return new LuisRecognizerV2(this.application, userOptions); | ||
} else { | ||
if (!this.options) { | ||
this.options = {}; | ||
} | ||
const merge = Object.assign(this.options, userOptions); | ||
let recOptions: LuisRecognizerOptionsV2 = { | ||
... merge, | ||
apiVersion: 'v2' | ||
}; | ||
return new LuisRecognizerV2(this.application, recOptions); | ||
} | ||
} | ||
} |
@@ -57,2 +57,12 @@ /** | ||
qnaId?: number; | ||
/** | ||
* A value indicating whether to call test or prod environment of knowledgebase. | ||
*/ | ||
isTest?: boolean; | ||
/** | ||
* Ranker types. | ||
*/ | ||
rankerType?: string; | ||
} |
@@ -20,2 +20,3 @@ /** | ||
import { QNAMAKER_TRACE_TYPE, QNAMAKER_TRACE_LABEL, QNAMAKER_TRACE_NAME } from '..'; | ||
import { RankerTypes } from '../qnamaker-interfaces/rankerTypes'; | ||
@@ -62,4 +63,5 @@ /** | ||
const url: string = `${ endpoint.host }/knowledgebases/${ endpoint.knowledgeBaseId }/generateanswer`; | ||
const queryOptions: QnAMakerOptions = { ...this._options, ...options } as QnAMakerOptions; | ||
var queryOptions: QnAMakerOptions = { ...this._options, ...options } as QnAMakerOptions; | ||
queryOptions.rankerType = !queryOptions.rankerType ? RankerTypes.default : queryOptions.rankerType; | ||
this.validateOptions(queryOptions); | ||
@@ -114,4 +116,4 @@ | ||
*/ | ||
public validateOptions(options: QnAMakerOptions): void { | ||
const { scoreThreshold, top } = options; | ||
public validateOptions(options: QnAMakerOptions) { | ||
const { scoreThreshold, top, rankerType } = options; | ||
@@ -118,0 +120,0 @@ if (scoreThreshold) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
267371
127
5444
2
+ Addedbotbuilder-core@4.7.0(transitive)
+ Addedbotframework-schema@4.7.0(transitive)
- Removedbotbuilder-core@4.6.2(transitive)
- Removedbotframework-schema@4.6.2(transitive)
Updatedbotbuilder-core@4.7.0