* initial commit

* add orchestrator pkg to updateDep script, minor cleanup

* updates to samples

* updates

* update

* removing sample

* updates

* updates

* update exports

* Dispatch sample

* add adaptive sample

* Adaptive recognizer with sample

* add timing and update samples

* updates.

* made some changes to make declarative sample work

* updates

* updates to recognizer

* tests

* removing samples

* updates to lerna

* fixing build issues

* cleanup in orchestrator lib

* sort ai-orchestrator index.ts

* adding tests.

* remove `dom` from bb-ai-orchestrator

* revert Orchestrator require()

* updating orchestrator-core pkg.

* updates to package.json

* rev orchestrator-core pkg

* revert readme.md

* revert readme.md

* update package.json to match bf-cli

Co-authored-by: stevengum <14935595+stevengum@users.noreply.github.com>
Co-authored-by: Zichuan Ma <zim@microsoft.com>
This commit is contained in:
Vishwac Sena Kannan 2020-08-12 19:30:38 -07:00 коммит произвёл GitHub
Родитель fa3e1c970f
Коммит 01cd644408
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 713 добавлений и 1 удалений

Просмотреть файл

@ -16,6 +16,7 @@
"packages": [
"libraries/botbuilder",
"libraries/botbuilder-ai",
"libraries/botbuilder-ai-orchestrator",
"libraries/botbuilder-applicationinsights",
"libraries/botbuilder-azure",
"libraries/botbuilder-core",

7
libraries/botbuilder-ai-orchestrator/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
/**/node_modules
/**/.vscode
/**/lib/*
coverage
.nyc_output
/**/.antlr
/**/*.interp

Просмотреть файл

@ -0,0 +1,19 @@
{
"extension": [
".js"
],
"include": [
"lib/**/*.js"
],
"exclude": [
"**/node_modules/**",
"**/tests/**",
"**/coverage/**",
"**/*.d.ts"
],
"reporter": [
"html"
],
"all": true,
"cache": true
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1,54 @@
{
"name": "botbuilder-ai-orchestrator",
"author": "Microsoft Corp.",
"description": "Orchestrator recognizer",
"version": "4.1.6",
"license": "MIT",
"keywords": [
"botbuilder",
"botframework",
"orchestrator"
],
"bugs": {
"url": "https://github.com/Microsoft/botbuilder-js/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/botbuilder-js.git"
},
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"dependencies": {
"orchestrator-core": "beta",
"adaptive-expressions": "4.1.6",
"botbuilder-core": "4.1.6",
"botbuilder-dialogs": "4.1.6",
"botbuilder-dialogs-adaptive": "4.1.6",
"botbuilder-dialogs-declarative": "4.1.6",
"read-text-file": "1.1.0"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.7.12",
"@types/mocha": "^2.2.47",
"@types/node": "^12.6.9",
"mocha": "^5.2.0",
"nyc": "^15.0.0",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0",
"typescript": "3.5.3"
},
"scripts": {
"build": "tsc",
"build-docs": "typedoc --theme markdown --entryPoint botbuilder-ai-orchestrator --excludePrivate --includeDeclarations --ignoreCompilerErrors --module amd --out ..\\..\\doc\\botbuilder-ai-orchestrator .\\lib\\index.d.ts --hideGenerator --name \"Bot Builder SDK - Orchestrator\" --readme none",
"build:rollup": "npm run clean && npm run build && api-extractor run --verbose --local",
"clean": "erase /q /s .\\lib",
"set-version": "npm version --allow-same-version ${Version}",
"test": "tsc && nyc mocha tests/",
"test:compat": "api-extractor run --verbose"
},
"files": [
"/lib",
"/schema",
"/src"
]
}

Просмотреть файл

@ -0,0 +1,70 @@
{
"$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema",
"$role": "implements(Microsoft.IRecognizer)",
"title": "QnAMaker Recognizer",
"description": "Recognizer for generating QnAMatch intents from a KB.",
"type": "object",
"properties": {
"id": {
"type": "string",
"title": "Id",
"description": "Optional unique id using with RecognizerSet."
},
"modelPath": {
"$ref": "schema:#/definitions/stringExpression",
"title": "Model",
"description": "NLR model file path.",
"default": "settings.orchestrator.modelpath"
},
"snapshotPath": {
"$ref": "schema:#/definitions/stringExpression",
"title": "Endpoint Key",
"description": "SnapShot file path.",
"default": "settings.orchestrator.shapshotpath"
},
"useCompactEmbeddings": {
"$ref": "schema:#/definitions/booleanExpression",
"title": "Use compact embeddings",
"description": "If true, compact embeddings will be used.",
"default": "true",
"examples": [
true,
"=turn.useCompactEmbeddings"
]
},
"entityRecognizers": {
"type": "array",
"items": {
"$kind": "Microsoft.IEntityRecognizer"
},
"title": "Entity recognizers",
"description": "Collection of entity recognizers to use."
},
"disambiguationScoreThreshold": {
"$ref": "schema:#/definitions/numberExpression",
"title": "Threshold",
"description": "Recognizer returns ChooseIntent (disambiguation) if other intents are classified within this score of the top scoring intent.",
"default": 0.05,
"examples": [
true,
"=turn.scoreThreshold",
"=settings.orchestrator.disambigscorethreshold"
]
},
"detectAmbiguousIntents": {
"$ref": "schema:#/definitions/booleanExpression",
"title": "Threshold",
"description": "If true, recognizer will look for ambiguous intents (intents with close recognition scores from top scoring intent).",
"default": false,
"examples": [
true,
"=turn.detectAmbiguousIntents",
"=settings.orchestrator.detectambiguousintents"
]
}
},
"required": [
"model",
"shapShot"
]
}

Просмотреть файл

@ -0,0 +1,11 @@
/**
* @module botbuilder-ai-orchestrator
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export { OrchestratorAdaptiveRecognizer } from './orchestratorAdaptiveRecognizer';
export { OrchestratorComponentRegistration } from './orchestratorComponentRegistration';
export { OrchestratorRecognizer } from './orchestratorRecognizer';

Просмотреть файл

@ -0,0 +1,282 @@
/**
* @module botbuilder-ai-orchestrator
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { existsSync } from 'fs';
import { resolve } from 'path';
import { TextEncoder } from 'util';
import { BoolExpression, NumberExpression, StringExpression } from 'adaptive-expressions';
import { Activity, Entity, RecognizerResult } from 'botbuilder-core';
import { DialogContext } from 'botbuilder-dialogs';
import { createRecognizerResult, EntityRecognizer, EntityRecognizerSet, Recognizer, TextEntity } from 'botbuilder-dialogs-adaptive';
const oc: any = require('orchestrator-core/orchestrator-core.node');
const ReadText: any = require('read-text-file');
export class OrchestratorAdaptiveRecognizer extends Recognizer {
/**
* Recognizers unique ID.
*/
public id: string;
/**
* Path to the model to load.
*/
public modelPath: StringExpression = new StringExpression('');
/**
* Path to the snapshot (.blu file) to load.
*/
public snapshotPath: StringExpression = new StringExpression('');
/**
* Threshold value to use for ambiguous intent detection.
* Any intents that are classified with a score that is within this value from the top scoring intent is determined to be ambiguous.
*/
public disambiguationScoreThreshold: NumberExpression = new NumberExpression(0.05);
/**
* Enable ambiguous intent detection.
*/
public detectAmbiguousIntents: BoolExpression = new BoolExpression(false);
/**
* The entity recognizers.
*/
public entityRecognizers: EntityRecognizer[] = [];
/**
* Intent name if ambiguous intents are detected.
*/
public readonly chooseIntent: string = 'ChooseIntent';
/**
* Property under which ambiguous intents are returned.
*/
public readonly candidatesCollection: string = 'candidates';
/**
* Intent name when no intent matches.
*/
public readonly noneIntent: string = 'None';
/**
* Full recognition results are available under this property
*/
public readonly resultProperty: string = 'result';
private readonly unknownIntentFilterScore = 0.4;
private static orchestrator: any = null;
private resolver: any = null;
private _modelPath: string = null;
private _snapshotPath: string = null;
/**
* Returns an OrchestratorAdaptiveRecognizer instance.
* @param modelPath Path to NLR model.
* @param snapshoPath Path to snapshot.
* @param resolver Orchestrator resolver to use.
*/
constructor(modelPath?: string, snapshoPath?: string, resolver?: any)
{
super()
this._modelPath = modelPath !== undefined ? modelPath : null;
this._snapshotPath = snapshoPath !== undefined ? snapshoPath : null;
this.resolver = resolver !== undefined ? resolver : null;
}
/**
* Returns a new OrchestratorAdaptiveRecognizer instance.
* @param dialogContext Context for the current dialog.
* @param activity Current activity sent from user.
*/
public async recognize(dialogContext: DialogContext, activity: Activity): Promise<RecognizerResult> {
if (this.modelPath === null) {
throw new Error(`Missing "ModelPath" information.`);
}
if (this.snapshotPath === null) {
throw new Error(`Missing "SnapshotPath" information.`);
}
const text = activity.text || '';
this._modelPath = this.modelPath.getValue(dialogContext.state);
this._snapshotPath = this.snapshotPath.getValue(dialogContext.state);
const detectAmbiguity = this.detectAmbiguousIntents.getValue(dialogContext.state);
const disambiguationScoreThreshold = this.disambiguationScoreThreshold.getValue(dialogContext.state);
const recognizerResult: RecognizerResult = createRecognizerResult(text, {}, {});
if (text === '') {
return recognizerResult;
}
if (OrchestratorAdaptiveRecognizer.orchestrator === null || this.resolver === null) {
this.Initialize();
}
// recognize text
let result = await this.resolver.score(text);
if (Object.entries(result).length !== 0) {
this.AddTopScoringIntent(result, recognizerResult);
}
// run entity recognizers
await this.recognizeEntities(dialogContext, text, activity.locale || '', recognizerResult);
// Add full recognition result as a 'result' property
recognizerResult[this.resultProperty] = result;
// disambiguate
if (detectAmbiguity) {
const topScoringIntent = result[0].score;
const scoreDelta = topScoringIntent - disambiguationScoreThreshold;
const ambiguousIntents = result.filter(x => x.score >= scoreDelta);
if (ambiguousIntents.length > 1) {
recognizerResult.intents = {};
recognizerResult.intents[this.chooseIntent] = { score: 1.0 };
recognizerResult[this.candidatesCollection] = [];
ambiguousIntents.forEach(item => {
let itemRecoResult = {
text: text,
intents: {},
entities: {},
score: item.score
};
itemRecoResult.intents[item.label.name] = {
score: item.score
};
itemRecoResult.entities = recognizerResult.entities;
recognizerResult[this.candidatesCollection].push({
intent: item.label.name,
score: item.score,
closestText: item.closest_text,
result: itemRecoResult
});
})
}
}
if (Object.entries(recognizerResult.intents).length == 0) {
recognizerResult.intents[this.noneIntent] = { score: 1.0 };
}
await dialogContext.context.sendTraceActivity('OrchestratorRecognizer', recognizerResult, 'RecognizerResult', 'Orchestrator recognizer RecognizerResult');
return recognizerResult;
}
private AddTopScoringIntent(result: any, recognizerResult: RecognizerResult): void {
const topScoringIntent = result[0].label.name;
const topScore = result[0].score;
// if top scoring intent is less than threshold, return None
if (topScore < this.unknownIntentFilterScore) {
recognizerResult.intents['None'] = { score: 1.0 };
} else if (!recognizerResult.intents[topScoringIntent]) {
recognizerResult.intents[topScoringIntent] = {
score: topScore
};
}
}
private async recognizeEntities(dialogContext: DialogContext, text: string, locale: string, recognizerResult: RecognizerResult) {
const entityPool: Entity[] = [];
const textEntity = new TextEntity(text);
textEntity['start'] = 0;
textEntity['end'] = text.length;
textEntity['score'] = 1.0;
entityPool.push(textEntity);
if (this.entityRecognizers) {
const entitySet = new EntityRecognizerSet();
entitySet.push(...this.entityRecognizers);
const newEntities = await entitySet.recognizeEntities(dialogContext, text, locale, entityPool);
if (newEntities.length > 0) {
entityPool.push(...newEntities);
}
}
for (let i = 0; i < entityPool.length; i++) {
const entityResult = entityPool[i];
let values = [];
if (!recognizerResult.entities.hasOwnProperty(entityResult.type)) {
recognizerResult.entities[entityResult.type] = values;
} else {
values = recognizerResult.entities[entityResult.type];
}
values.push(entityResult['text']);
let instanceRoot = {};
if (!recognizerResult.entities.hasOwnProperty('$instance')) {
recognizerResult.entities['$instance'] = instanceRoot;
} else {
instanceRoot = recognizerResult.entities['$instance'];
}
let instanceData = [];
if (!instanceRoot.hasOwnProperty(entityResult.type)) {
instanceRoot[entityResult.type] = instanceData;
} else {
instanceData = instanceRoot[entityResult.type];
}
const instance = {
startIndex: entityResult['start'],
endIndex: entityResult['end'],
score: 1.0,
text: entityResult['text'],
type: entityResult['type'],
resolution: entityResult['resolution']
};
instanceData.push(instance);
}
}
private Initialize() {
if (OrchestratorAdaptiveRecognizer.orchestrator == null && this.resolver == null)
{
if (this._modelPath == null) {
throw new Error(`Missing "ModelPath" information.`);
}
if (this._snapshotPath == null) {
throw new Error(`Missing "ShapshotPath" information.`);
}
const fullModelPath = resolve(this._modelPath);
const fullSnapshotPath = resolve(this._snapshotPath);
if (!existsSync(fullModelPath)) {
throw new Error(`Model folder does not exist at ${fullModelPath}.`);
}
if (!existsSync(fullSnapshotPath)) {
throw new Error(`Snapshot file does not exist at ${fullSnapshotPath}.`);
}
if (OrchestratorAdaptiveRecognizer.orchestrator == null) {
console.time("Model load");
// Create orchestrator core
OrchestratorAdaptiveRecognizer.orchestrator = new oc.Orchestrator();
if (OrchestratorAdaptiveRecognizer.orchestrator.load(fullModelPath) === false) {
throw new Error(`Model load failed.`);
}
console.timeEnd("Model load");
}
if (this.resolver == null) {
// Load the snapshot
const encoder: any = new TextEncoder();
const fileContent: string = ReadText.readSync(fullSnapshotPath)
const snapshot: Uint8Array = encoder.encode(fileContent);
// Load snapshot and create resolver
this.resolver = OrchestratorAdaptiveRecognizer.orchestrator.createLabelResolver(snapshot);
}
}
}
};

Просмотреть файл

@ -0,0 +1,35 @@
/**
* @module botbuilder-ai-orchestrator
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { BoolExpressionConverter, NumberExpressionConverter, StringExpressionConverter } from 'adaptive-expressions';
import { AdaptiveTypeBuilder } from 'botbuilder-dialogs-adaptive';
import { BuilderRegistration, ComponentRegistration, ResourceExplorer } from 'botbuilder-dialogs-declarative';
import { OrchestratorAdaptiveRecognizer } from './orchestratorAdaptiveRecognizer';
export class OrchestratorComponentRegistration implements ComponentRegistration {
private readonly _builderRegistrations: BuilderRegistration[] = [];
private _resourceExplorer: ResourceExplorer;
public getTypeBuilders(): BuilderRegistration[] {
return this._builderRegistrations;
}
public constructor(resourceExplorer: ResourceExplorer) {
this._resourceExplorer = resourceExplorer;
this._builderRegistrations.push(
new BuilderRegistration('Microsoft.OrchestratorRecognizer', new AdaptiveTypeBuilder(OrchestratorAdaptiveRecognizer, this._resourceExplorer, {
modelPath: new StringExpressionConverter(),
snapshotPath: new StringExpressionConverter(),
disambiguationScoreThreshold: new NumberExpressionConverter(),
detectAmbiguousIntents: new BoolExpressionConverter(),
}))
);
}
};

Просмотреть файл

@ -0,0 +1,70 @@
/**
* @module botbuilder-ai-orchestrator
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { BoolExpression, NumberExpression, StringExpression } from 'adaptive-expressions';
import { RecognizerResult, TurnContext } from 'botbuilder-core';
import { Configurable, DialogContext, DialogSet } from 'botbuilder-dialogs';
import { EntityRecognizer } from 'botbuilder-dialogs-adaptive';
import { OrchestratorAdaptiveRecognizer } from './orchestratorAdaptiveRecognizer';
export class OrchestratorRecognizer extends Configurable {
/**
* Full recognition results are available under this property
*/
public readonly resultProperty: string = 'result';
/**
* Recognizers unique ID.
*/
public id: string;
/**
* Path to the model to load.
*/
public modelPath: string = null;
/**
* Path to the snapshot (.blu file) to load.
*/
public snapshotPath: string = null;
/**
* The entity recognizers.
*/
public entityRecognizers: EntityRecognizer[] = [];
/**
* Threshold value to use for ambiguous intent detection. Defaults to 0.05.
* Any intents that are classified with a score that is within this value from the top
* scoring intent is determined to be ambiguous.
*/
public disambiguationScoreThreshold: number = 0.05;
/**
* Enable ambiguous intent detection. Defaults to false.
*/
public detectAmbiguousIntents: boolean = false;
/**
* Returns recognition result. Also sends trace activity with recognition result.
* @param context Context for the current turn of conversation with the use.
*/
public async recognize(context: TurnContext): Promise<RecognizerResult> {
const rec = new OrchestratorAdaptiveRecognizer();
rec.id = this.id;
rec.modelPath = new StringExpression(this.modelPath);
rec.snapshotPath = new StringExpression(this.snapshotPath);
rec.entityRecognizers = this.entityRecognizers;
rec.disambiguationScoreThreshold = new NumberExpression(this.disambiguationScoreThreshold);
rec.detectAmbiguousIntents = new BoolExpression(this.detectAmbiguousIntents);
const dc = new DialogContext(new DialogSet(), context, { dialogStack:[] });
return await rec.recognize(dc, context.activity);
}
};

Просмотреть файл

@ -0,0 +1,4 @@
--require ts-node/register
--require source-map-support/register
--recursive
**/*.js

Просмотреть файл

@ -0,0 +1,24 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
class MockResolver {
constructor(score)
{
this._score = score;
}
score(text)
{
return this._score;
}
}
class TestAdapterSettings {
constructor(appId, appPassword) {
this.appId = appId;
this.appPassword = appPassword;
}
}
module.exports = {MockResolver, TestAdapterSettings};

Просмотреть файл

@ -0,0 +1,116 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const { MockResolver, TestAdapterSettings } = require('./mockResolver');
const assert = require('assert');
const { OrchestratorAdaptiveRecognizer } = require('../lib');
const { DialogContext, DialogSet } = require('botbuilder-dialogs');
const { TurnContext, MessageFactory } = require('botbuilder-core');
const { BotFrameworkAdapter } = require('../../botbuilder/lib');
const { StringExpression, BoolExpression, NumberExpression } = require('adaptive-expressions');
const { NumberEntityRecognizer } = require('botbuilder-dialogs-adaptive');
describe('OrchestratorAdpativeRecognizer tests', function() {
it('Test intent recognition', (done) => {
let result = [
{
score : 0.9,
label : {
name : "mockLabel"
}
}
];
let mockResolver = new MockResolver(result);
let testPaths = "test";
let rec = new OrchestratorAdaptiveRecognizer(testPaths, testPaths, mockResolver);
rec.modelPath = new StringExpression(testPaths);
rec.snapshotPath = new StringExpression(testPaths);
let {dc, activity} = createTestDcAndActivity("hello")
rec.recognize(dc, activity)
.then(res => {
assert(res.text, "hello");
assert(res.intents.mockLabel.score, 0.9);
done();
})
.catch(err => done(err))
})
it('Test entity recognition', (done) => {
let result = [
{
score : 0.9,
label : {
name : "mockLabel"
}
}
];
let mockResolver = new MockResolver(result);
let testPaths = "test";
let rec = new OrchestratorAdaptiveRecognizer(testPaths, testPaths, mockResolver);
rec.modelPath = new StringExpression(testPaths);
rec.snapshotPath = new StringExpression(testPaths);
rec.entityRecognizers = [
new NumberEntityRecognizer()
];
let {dc, activity} = createTestDcAndActivity("hello 123")
rec.recognize(dc, activity)
.then(res => {
assert(res.text, "hello 123");
assert(res.intents.mockLabel.score, 0.9);
assert(res.entities.number[0], "123");
done();
})
.catch(err => done(err))
})
it('Test ambiguous intent recognition', (done) => {
let result = [
{
score : 0.9,
label : {
name : "mockLabel1"
}
},
{
score : 0.85,
label : {
name : "mockLabel2"
}
},
{
score : 0.79,
label : {
name : "mockLabel3"
}
}
];
let mockResolver = new MockResolver(result);
let testPaths = "test";
let rec = new OrchestratorAdaptiveRecognizer(testPaths, testPaths, mockResolver);
rec.modelPath = new StringExpression(testPaths);
rec.snapshotPath = new StringExpression(testPaths);
rec.detectAmbiguousIntents = new BoolExpression(true);
rec.disambiguationScoreThreshold = new NumberExpression(0.1);
let {dc, activity} = createTestDcAndActivity("hello")
rec.recognize(dc, activity)
.then(res => {
assert(res.intents.ChooseIntent.score, 1);
assert(res.candidates[0].intent, "mockLabel1");
assert(res.candidates[1].intent, "mockLabel2");
done();
})
.catch(err => done(err))
})
})
const createTestDcAndActivity = function (message) {
let settings = new TestAdapterSettings('appId', 'password');
let adapter = new BotFrameworkAdapter(settings);
let activity = MessageFactory.text(message);
let turnContext = new TurnContext(adapter, activity);
turnContext.sendTraceActivity = function() {}
let state = [{dialogStack: []}];
let dc = new DialogContext(new DialogSet(), turnContext, state);
return {dc, activity};
}

Просмотреть файл

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es6",
"lib": ["es2015"],
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"downlevelIteration": true,
"sourceMap": true,
"esModuleInterop": true,
"outDir": "./lib",
"rootDir": "./src",
"types" : ["node"]
},
"include": [
"src/**/*"
]
}

Просмотреть файл

@ -13,8 +13,9 @@ if(previewVersion === 'adaptive-expressions') {
previewVersion = undefined;
}
const previewPackages = {
'botbuilder-ai-orchestrator': true,
'botbuilder-dialogs-adaptive': true,
'botbuilder-dialogs-adaptive-tests': true,
'botbuilder-dialogs-adaptive-testing': true,
'botbuilder-dialogs-declarative': true
}
var dependencies = myArgs.slice(previewVersion ? 3 : 2);