Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@telefonica/luis-cli

Package Overview
Dependencies
Maintainers
11
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@telefonica/luis-cli - npm Package Compare versions

Comparing version 4.0.0 to 5.0.0

189

lib/luis-api-client.d.ts
/// <reference types="node" />
import { EventEmitter } from 'events';
declare module 'request' {
interface RequestResponse {
body?: any;
}
}
export declare namespace LuisApi {
interface RecognizedEntity {
entity: string;
type: string;
startIndex: number;
endIndex: number;
}
interface RecognitionResult {
sentence: string;
intent: string;
entities: RecognizedEntity[];
}
interface AppInfo {
interface AppInfoGET {
id: string;

@@ -25,59 +9,99 @@ name: string;

culture: string;
active: boolean;
numberOfIntents: number;
numberOfEntities: number;
isTrained: boolean;
}
interface IntentClassifier {
interface IntentGET {
id: string;
name: string;
typeId: number;
readableType: string;
}
interface EntityExtractor {
interface IntentPOST {
name: string;
}
interface IntentDELETE {
id: string;
}
interface EntityGET {
id: string;
name: string;
typeId: number;
readableType: string;
}
enum PhraseListModes {
Exchangeable = 0,
NonExchangeable = 1,
interface EntityPOST {
name: string;
}
interface PhraseList {
id?: string;
interface EntityDELETE {
id: string;
}
interface PhraseListGET {
id: number;
name: string;
mode: PhraseListModes;
isActive?: boolean;
editable?: boolean;
isActive: boolean;
isExchangeable: boolean;
phrases: string;
}
interface LabeledEntity {
interface PhraseListPOST {
name: string;
startToken: number;
endToken: number;
word: string;
isBuiltInExtractor: boolean;
phrases: string;
isExchangeable: boolean;
}
interface LabeledUtterance {
interface PhraseListDELETE {
id: string;
utteranceText: string;
}
interface EntityLabelExampleGET {
entityName: string;
startTokenIndex: number;
endTokenIndex: number;
}
interface EntityLabelExamplePOST {
entityName: string;
startCharIndex: number;
endCharIndex: number;
}
interface IntentPrediction {
name: string;
score: number;
}
interface EntityPrediction {
entityName: string;
startIndex: number;
endIndex: number;
phrase: string;
}
interface ExampleGET {
id: number;
text: string;
tokenizedText: string[];
intentLabel: string;
entityLabels: EntityLabelExampleGET[];
intentPredictions: IntentPrediction[];
entityPredictions: EntityPrediction[];
}
interface ExamplePOST {
text: string;
intentName: string;
entityLabels: EntityLabelExamplePOST[];
}
interface ExampleDELETE {
id: number;
}
interface RecognizedIntent {
intent: string;
predictedIntents: [{
name: string;
score: number;
}];
entities: LabeledEntity[];
predictedEntities: LabeledEntity[];
score: number;
}
interface Entity {
entityType: string;
startToken: number;
endToken: number;
interface RecognizedEntity {
entity: string;
type: string;
startIndex: number;
endIndex: number;
score: number;
}
interface Example {
exampleText: string;
selectedIntentName: string;
entityLabels?: Entity[];
interface RecognitionResult {
query: string;
topScoringIntent: RecognizedIntent;
intents: RecognizedIntent[];
entities: RecognizedEntity[];
}
enum TrainingStatuses {
Success = 0,
Failed = 1,
Fail = 1,
UpToDate = 2,

@@ -94,5 +118,6 @@ InProgress = 3,

interface PublishResult {
url: string;
endpointUrl: string;
subscriptionKey: string;
publishDate: Date;
endpointRegion: string;
isStaging: boolean;
}

@@ -113,31 +138,31 @@ }

private retryRequest(opts, expectedStatusCode);
private throttler(fn, items);
private throttler(fn, appVersion, items);
recognizeSentence(sentence: string): Promise<LuisApi.RecognitionResult>;
recognizeSentences(sentences: string[]): Promise<LuisApi.RecognitionResult[]>;
getApp(): Promise<LuisApi.AppInfo>;
getIntents(): Promise<LuisApi.IntentClassifier[]>;
createIntent(intentName: string): Promise<string>;
createIntents(intentNames: string[]): Promise<string[]>;
deleteIntent(intentId: string): Promise<void>;
deleteIntents(intentIds: string[]): Promise<void>;
getEntities(): Promise<LuisApi.EntityExtractor[]>;
createEntity(entityName: string): Promise<string>;
createEntities(entityNames: string[]): Promise<string[]>;
deleteEntity(entityId: string): Promise<void>;
deleteEntities(entityIds: string[]): Promise<void>;
getPhraseLists(): Promise<LuisApi.PhraseList[]>;
createPhraseList(phraseList: LuisApi.PhraseList): Promise<string>;
createPhraseLists(phraseLists: LuisApi.PhraseList[]): Promise<string[]>;
deletePhraseList(phraseListId: string): Promise<void>;
deletePhraseLists(phraseListIds: string[]): Promise<void>;
getExamples(skip: number, count: number): Promise<LuisApi.LabeledUtterance[]>;
getAllExamples(): Promise<LuisApi.LabeledUtterance[]>;
createExamples(examples: LuisApi.Example[]): Promise<string[]>;
deleteExample(exampleId: string): Promise<void>;
deleteExamples(exampleIds: string[]): Promise<void>;
recognizeSentences(queries: string[]): Promise<LuisApi.RecognitionResult[]>;
getApp(): Promise<LuisApi.AppInfoGET>;
getIntents(appVersion: string): Promise<LuisApi.IntentGET[]>;
createIntent(appVersion: string, intent: LuisApi.IntentPOST): Promise<string>;
createIntents(appVersion: string, intents: LuisApi.IntentPOST[]): Promise<string[]>;
deleteIntent(appVersion: string, intent: LuisApi.IntentDELETE): Promise<void>;
deleteIntents(appVersion: string, intents: LuisApi.IntentDELETE[]): Promise<void>;
getEntities(appVersion: string): Promise<LuisApi.EntityGET[]>;
createEntity(appVersion: string, entity: LuisApi.EntityPOST): Promise<string>;
createEntities(appVersion: string, entities: LuisApi.EntityPOST[]): Promise<string[]>;
deleteEntity(appVersion: string, entity: LuisApi.EntityDELETE): Promise<void>;
deleteEntities(appVersion: string, entities: LuisApi.EntityDELETE[]): Promise<void>;
getPhraseLists(appVersion: string): Promise<LuisApi.PhraseListGET[]>;
createPhraseList(appVersion: string, phraseList: LuisApi.PhraseListPOST): Promise<string>;
createPhraseLists(appVersion: string, phraseLists: LuisApi.PhraseListPOST[]): Promise<string[]>;
deletePhraseList(appVersion: string, phraseList: LuisApi.PhraseListDELETE): Promise<void>;
deletePhraseLists(appVersion: string, phraseLists: LuisApi.PhraseListDELETE[]): Promise<void>;
getExamples(appVersion: string, skip: number, count: number): Promise<LuisApi.ExampleGET[]>;
getAllExamples(appVersion: string): Promise<LuisApi.ExampleGET[]>;
createExamples(appVersion: string, examples: LuisApi.ExamplePOST[]): Promise<string[]>;
deleteExample(appVersion: string, example: LuisApi.ExampleDELETE): Promise<void>;
deleteExamples(appVersion: string, examples: LuisApi.ExampleDELETE[]): Promise<void>;
private convertTrainingStatus(apiTrainingStatus);
startTraining(): Promise<LuisApi.TrainingStatus>;
getTrainingStatus(): Promise<LuisApi.TrainingStatus>;
publish(): Promise<LuisApi.PublishResult>;
export(): Promise<any>;
startTraining(appVersion: string): Promise<LuisApi.TrainingStatus>;
getTrainingStatus(appVersion: string): Promise<LuisApi.TrainingStatus>;
publish(appVersion: string): Promise<LuisApi.PublishResult>;
export(appVersion: string): Promise<any>;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const request = require("request-promise-native");

@@ -9,11 +10,6 @@ const _ = require("lodash");

(function (LuisApi) {
var PhraseListModes;
(function (PhraseListModes) {
PhraseListModes[PhraseListModes["Exchangeable"] = 0] = "Exchangeable";
PhraseListModes[PhraseListModes["NonExchangeable"] = 1] = "NonExchangeable";
})(PhraseListModes = LuisApi.PhraseListModes || (LuisApi.PhraseListModes = {}));
var TrainingStatuses;
let TrainingStatuses;
(function (TrainingStatuses) {
TrainingStatuses[TrainingStatuses["Success"] = 0] = "Success";
TrainingStatuses[TrainingStatuses["Failed"] = 1] = "Failed";
TrainingStatuses[TrainingStatuses["Fail"] = 1] = "Fail";
TrainingStatuses[TrainingStatuses["UpToDate"] = 2] = "UpToDate";

@@ -24,3 +20,2 @@ TrainingStatuses[TrainingStatuses["InProgress"] = 3] = "InProgress";

const LUIS_API_BASE_URL = 'https://westus.api.cognitive.microsoft.com';
const LUIS_SERVICE_API_BASE_URL = 'http://luis-europe-west-endpoint.cloudapp.net';
const RETRY_OPTS = {

@@ -43,7 +38,5 @@ retries: 10,

this.serviceReq = request.defaults({
baseUrl: `${LUIS_SERVICE_API_BASE_URL}/api/v2.0/apps/${this.applicationId}`,
qs: {
'subscription-key': config.subscriptionKey,
verbose: false,
spellCheck: false
baseUrl: `${baseUrl}/luis/v2.0/apps/`,
headers: {
'Ocp-Apim-Subscription-Key': config.subscriptionKey
},

@@ -55,3 +48,3 @@ json: true,

this.provisionReq = request.defaults({
baseUrl: `${baseUrl}/luis/v1.0/prog/apps`,
baseUrl: `${baseUrl}/luis/api/v2.0/apps/`,
headers: {

@@ -85,4 +78,4 @@ 'Ocp-Apim-Subscription-Key': config.subscriptionKey

}
throttler(fn, items) {
let promises = items.map(item => this.promiseThrottle.add(fn.bind(this, item)));
throttler(fn, appVersion, items) {
let promises = items.map(item => this.promiseThrottle.add(fn.bind(this, appVersion, item)));
return Promise.all(promises);

@@ -93,3 +86,3 @@ }

method: 'GET',
uri: '',
uri: `/${this.applicationId}`,
qs: { q: sentence }

@@ -105,4 +98,5 @@ };

return {
sentence: res.body.query,
intent: res.body.topScoringIntent.intent,
query: res.body.query,
topScoringIntent: res.body.topScoringIntent,
intents: res.body.intents,
entities: res.body.entities.map((entity) => {

@@ -113,3 +107,4 @@ return {

startIndex: entity.startIndex,
endIndex: entity.endIndex
endIndex: entity.endIndex,
score: entity.score
};

@@ -120,3 +115,3 @@ })

}
recognizeSentences(sentences) {
recognizeSentences(queries) {
let promiseThrottle = new PromiseThrottle({

@@ -126,3 +121,3 @@ requestsPerSecond: SERVICE_API_REQUESTS_PER_SECOND,

});
let promises = sentences.map(sentence => promiseThrottle.add(this.recognizeSentence.bind(this, sentence)));
let promises = queries.map(query => promiseThrottle.add(this.recognizeSentence.bind(this, query)));
return Promise.all(promises);

@@ -133,3 +128,3 @@ }

method: 'GET',
uri: this.applicationId
uri: `/${this.applicationId}`
};

@@ -140,181 +135,134 @@ return this.retryRequest(opts, 200)

return {
id: body.ID,
name: body.Name,
description: body.Description,
culture: body.Culture,
active: body.Active,
numberOfIntents: body.NumberOfIntents,
numberOfEntities: body.NumberOfEntities,
isTrained: body.IsTrained
id: body.id,
name: body.name,
description: body.description,
culture: body.culture
};
});
}
getIntents() {
getIntents(appVersion) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/intents`
uri: `/${this.applicationId}/versions/${appVersion}/intents`
};
return this.retryRequest(opts, 200)
.then((res) => res.body.Result)
.then((intents) => intents.map((intent) => {
return {
id: intent.id,
name: intent.name
};
}));
.then((res) => res.body);
}
createIntent(intentName) {
createIntent(appVersion, intent) {
let opts = {
method: 'POST',
uri: `${this.applicationId}/intents`,
body: { name: intentName }
uri: `/${this.applicationId}/versions/${appVersion}/intents`,
body: intent
};
return this.retryRequest(opts, 201)
.then((res) => res.body)
.then(intentId => {
.then((res) => {
this.emit('createIntent', {
id: intentId,
name: intentName
id: res.body,
name: intent.name
});
return intentId;
return res.body;
});
}
createIntents(intentNames) {
return this.throttler(this.createIntent, intentNames);
createIntents(appVersion, intents) {
return this.throttler(this.createIntent, appVersion, intents);
}
deleteIntent(intentId) {
deleteIntent(appVersion, intent) {
let opts = {
method: 'DELETE',
uri: `${this.applicationId}/intents/${intentId}`
uri: `/${this.applicationId}/versions/${appVersion}/intents/${intent.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteIntent', intentId);
this.emit('deleteIntent', intent.id);
});
}
deleteIntents(intentIds) {
return this.throttler(this.deleteIntent, intentIds)
deleteIntents(appVersion, intents) {
return this.throttler(this.deleteIntent, appVersion, intents)
.then(() => Promise.resolve());
}
getEntities() {
getEntities(appVersion) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/entities`
uri: `/${this.applicationId}/versions/${appVersion}/entities`
};
return this.retryRequest(opts, 200)
.then((res) => res.body.Result)
.then((entities) => entities.map((entity) => {
return {
id: entity.id,
name: entity.name
};
}));
.then((res) => res.body);
}
createEntity(entityName) {
createEntity(appVersion, entity) {
let opts = {
method: 'POST',
uri: `${this.applicationId}/entities`,
body: { name: entityName }
uri: `/${this.applicationId}/versions/${appVersion}/entities`,
body: entity
};
return this.retryRequest(opts, 201)
.then((res) => res.body)
.then(entityId => {
.then((res) => {
this.emit('createEntity', {
id: entityId,
name: entityName
id: res.body,
name: entity.name
});
return entityId;
});
return res.body;
})
.catch(err => console.log(JSON.stringify(err)));
}
createEntities(entityNames) {
return this.throttler(this.createEntity, entityNames);
createEntities(appVersion, entities) {
return this.throttler(this.createEntity, appVersion, entities);
}
deleteEntity(entityId) {
deleteEntity(appVersion, entity) {
let opts = {
method: 'DELETE',
uri: `${this.applicationId}/entities/${entityId}`
uri: `/${this.applicationId}/versions/${appVersion}/entities/${entity.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteEntity', entityId);
this.emit('deleteEntity', entity.id);
});
}
deleteEntities(entityIds) {
return this.throttler(this.deleteEntity, entityIds)
deleteEntities(appVersion, entities) {
return this.throttler(this.deleteEntity, appVersion, entities)
.then(() => Promise.resolve());
}
getPhraseLists() {
getPhraseLists(appVersion) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/phraselists`
uri: `/${this.applicationId}/versions/${appVersion}/phraselists`
};
return this.retryRequest(opts, 200)
.then((res) => res.body)
.then((phraseLists) => phraseLists.map((phraseList) => {
return {
id: phraseList.Id.toString(),
name: phraseList.Name,
mode: phraseList.Mode.toLowerCase() === 'non-exchangeable' ?
LuisApi.PhraseListModes.NonExchangeable : LuisApi.PhraseListModes.Exchangeable,
isActive: phraseList.IsActive,
editable: phraseList.Editable,
phrases: phraseList.Phrases
};
}));
.then((res) => res.body);
}
createPhraseList(phraseList) {
createPhraseList(appVersion, phraseList) {
let opts = {
method: 'POST',
uri: `${this.applicationId}/phraselists`,
body: {
Name: phraseList.name,
Mode: phraseList.mode === LuisApi.PhraseListModes.NonExchangeable ?
'Non-exchangeable' : 'Exchangeable',
IsActive: phraseList.isActive !== undefined ? phraseList.isActive : true,
Editable: phraseList.editable !== undefined ? phraseList.editable : true,
Phrases: phraseList.phrases
}
uri: `/${this.applicationId}/versions/${appVersion}/phraselists`,
body: phraseList
};
return this.retryRequest(opts, 201)
.then((res) => res.body)
.then(phraseListId => {
.then((res) => {
this.emit('createPhraseList', {
id: phraseListId,
id: res.body,
name: phraseList.name
});
return phraseListId;
return res.body;
});
}
createPhraseLists(phraseLists) {
return this.throttler(this.createPhraseList, phraseLists);
createPhraseLists(appVersion, phraseLists) {
return this.throttler(this.createPhraseList, appVersion, phraseLists);
}
deletePhraseList(phraseListId) {
deletePhraseList(appVersion, phraseList) {
let opts = {
method: 'DELETE',
uri: `${this.applicationId}/phraselists/${phraseListId}`
uri: `/${this.applicationId}/versions/${appVersion}/phraselists/${phraseList.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deletePhraseList', phraseListId);
this.emit('deletePhraseList', phraseList.id);
});
}
deletePhraseLists(phraseListIds) {
return this.throttler(this.deletePhraseList, phraseListIds)
deletePhraseLists(appVersion, phraseLists) {
return this.throttler(this.deletePhraseList, appVersion, phraseLists)
.then(() => Promise.resolve());
}
getExamples(skip, count) {
function mapEntities(entities) {
return entities.map((entity) => {
return {
name: entity.name,
startToken: entity.indeces.startToken,
endToken: entity.indeces.endToken,
word: entity.word,
isBuiltInExtractor: entity.isBuiltInExtractor
};
});
}
getExamples(appVersion, skip, count) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/examples`,
uri: `/${this.applicationId}/versions/${appVersion}/examples`,
qs: { skip, count }

@@ -324,26 +272,9 @@ };

.then((res) => {
let examples = res.body;
if (examples.length) {
this.emit('getExamples', skip, skip + examples.length - 1);
if (res.body.length) {
this.emit('getExamples', skip, skip + res.body.length - 1);
}
return examples;
})
.then(examples => examples.map((example) => {
return {
id: example.exampleId,
utteranceText: example.utteranceText,
tokenizedText: example.tokenizedText,
intent: example.IntentsResults.Name,
predictedIntents: example.PredictedIntentResults.map((intent) => {
return {
name: intent.Name,
score: intent.score
};
}),
entities: mapEntities(example.EntitiesResults),
predictedEntities: mapEntities(example.PredictedEntitiesResults)
};
}));
return res.body;
});
}
getAllExamples() {
getAllExamples(appVersion) {
let examplesBunches = [];

@@ -353,3 +284,3 @@ const getExamplesBunch = (skip = 0) => {

.map((e, i) => skip + i * MAX_EXAMPLES_COUNT);
let promises = skipList.map(skip => this.promiseThrottle.add(this.getExamples.bind(this, skip, MAX_EXAMPLES_COUNT)));
let promises = skipList.map(skip => this.promiseThrottle.add(this.getExamples.bind(this, appVersion, skip, MAX_EXAMPLES_COUNT)));
return Promise.all(promises)

@@ -370,13 +301,12 @@ .then((examplesBunch) => {

}
createExamples(examples) {
let createLimitedExamples = (examples) => {
createExamples(appVersion, examples) {
let createLimitedExamples = (appVersion, examples) => {
let opts = {
method: 'POST',
uri: `${this.applicationId}/examples`,
uri: `/${this.applicationId}/versions/${appVersion}/examples`,
body: examples
};
return this.retryRequest(opts, 201)
.then((res) => res.body)
.then((body) => {
let errors = body.filter(r => r.hasError);
.then((res) => {
let errors = res.body.filter((r) => r.hasError);
if (errors.length) {

@@ -387,21 +317,21 @@ return Promise.reject(new Error('LuisApiClient: The following examples have errors:\n'

this.emit('createExampleBunch', examples.length);
return body.map(r => r.value.ExampleId.toString());
return res.body.map((r) => r.value.ExampleId.toString());
});
};
let examplesBunches = _.chunk(examples, MAX_EXAMPLES_UPLOAD);
return this.throttler(createLimitedExamples, examplesBunches)
return this.throttler(createLimitedExamples, appVersion, examplesBunches)
.then(_.flatten);
}
deleteExample(exampleId) {
deleteExample(appVersion, example) {
let opts = {
method: 'DELETE',
uri: `${this.applicationId}/examples/${exampleId}`
uri: `/${this.applicationId}/versions/${appVersion}/examples/${example.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteExample', exampleId);
this.emit('deleteExample', example.id);
});
}
deleteExamples(exampleIds) {
return this.throttler(this.deleteExample, exampleIds)
deleteExamples(appVersion, examples) {
return this.throttler(this.deleteExample, appVersion, examples)
.then(() => Promise.resolve());

@@ -412,8 +342,8 @@ }

let modelTrainingStatus = {
modelId: modelStatus.ModelId,
status: modelStatus.Details.StatusId,
exampleCount: modelStatus.Details.ExampleCount
modelId: modelStatus.modelId,
status: modelStatus.details.statusId,
exampleCount: modelStatus.details.exampleCount
};
if (modelTrainingStatus.status === LuisApi.TrainingStatuses.Failed) {
modelTrainingStatus.failureReason = modelStatus.Details.FailureReason;
if (modelTrainingStatus.status === LuisApi.TrainingStatuses.Fail) {
modelTrainingStatus.failureReason = modelStatus.details.failureReason;
}

@@ -423,15 +353,14 @@ return modelTrainingStatus;

}
startTraining() {
startTraining(appVersion) {
let opts = {
method: 'POST',
uri: `${this.applicationId}/train`
uri: `/${this.applicationId}/versions/${appVersion}/train`
};
return this.retryRequest(opts, 202)
.then((res) => res.body)
.then(this.convertTrainingStatus);
.then((res) => res.body);
}
getTrainingStatus() {
getTrainingStatus(appVersion) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/train`
uri: `/${this.applicationId}/versions/${appVersion}/train`
};

@@ -442,11 +371,10 @@ return this.retryRequest(opts, 200)

}
publish() {
publish(appVersion) {
let opts = {
method: 'POST',
uri: `${this.applicationId}/publish`,
uri: `/${this.applicationId}/publish`,
body: {
BotFramework: { Enabled: false, AppId: '', SubscriptionKey: '', Endpoint: '' },
Slack: { Enabled: false, ClientId: '', ClientSecret: '', RedirectUri: '' },
PrivacyStatement: '',
TermsOfUse: ''
versionId: appVersion,
isStaging: false,
region: 'westus'
}

@@ -458,12 +386,13 @@ };

return {
url: body.URL,
subscriptionKey: body.SubscriptionKey,
publishDate: new Date(body.PublishDate)
endpointUrl: body.endpointUrl,
subscriptionKey: body['subscription-key'],
endpointRegion: body.endpointRegion,
isStaging: body.isStaging
};
});
}
export() {
export(appVersion) {
let opts = {
method: 'GET',
uri: `${this.applicationId}/export`
uri: `/${this.applicationId}/versions/${appVersion}/export`
};

@@ -470,0 +399,0 @@ return this.retryRequest(opts, 200)

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");

@@ -15,4 +16,2 @@ const commander = require("commander");

})(Commands || (Commands = {}));
;
let command;
let runner = null;

@@ -28,2 +27,3 @@ const program = commander

.option('-m, --model <filename>', 'JSON file containing the model to upload to the LUIS application')
.option('-v, --app-version <app-version>', 'The version of the application to update')
.action(options => selectRunner(Commands.Update, options))

@@ -70,3 +70,4 @@ .on('--help', function () {

console.log();
console.log(` Check whether a set of examples are correctly recognized by an application in terms of intent and entities and save differences to 'errors.json':`);
console.log(' Check whether a set of examples are correctly recognized by an application in terms of intent and entities ' +
'and save differences to \'errors.json\':');
console.log(' $ luis-cli check -m model.json -a XXX -f errors.json -s YYY');

@@ -104,18 +105,27 @@ });

case Commands.Update:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.model) {
printError('missing JSON file from which the model will be read. Provide one through the `-m, --model` option.');
}
runner = updateApp(luisTrainer, applicationId, options.model);
runner = updateApp(luisTrainer, applicationId, options.appVersion, options.model);
break;
case Commands.Export:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.model) {
printError('missing JSON file to which the application will be exported. Provide one through the `-m, --model` option.');
}
runner = exportApp(luisTrainer, applicationId, options.model);
runner = exportApp(luisTrainer, applicationId, options.appVersion, options.model);
break;
case Commands.CheckPredictions:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.errors) {
printError('missing JSON file to which the differences will be saved. Provide one through the `-r, --errors` option.');
}
runner = checkPrediction(luisTrainer, applicationId, options.errors);
runner = checkPrediction(luisTrainer, applicationId, options.appVersion, options.errors);
break;

@@ -133,3 +143,3 @@ case Commands.TestExamples:

}
function updateApp(luisTrainer, applicationId, modelFilename) {
function updateApp(luisTrainer, applicationId, appVersion, modelFilename) {
let model;

@@ -147,3 +157,3 @@ try {

}
console.log(`Updating the application ${applicationId} with the model from "${modelFilename}"...`);
console.log(`Updating the version ${appVersion} of the application ${applicationId} with the model from "${modelFilename}"...`);
console.log();

@@ -224,3 +234,3 @@ luisTrainer.on('startUpdateIntents', (stats) => {

});
return luisTrainer.update(model)
return luisTrainer.update(appVersion, model)
.then(() => {

@@ -232,5 +242,5 @@ console.log('The application has been successfully updated');

}
function exportApp(luisTrainer, applicationId, modelFilename) {
function exportApp(luisTrainer, applicationId, appVersion, modelFilename) {
console.log(`Exporting the application ${applicationId} to "${modelFilename}"...`);
return luisTrainer.export()
return luisTrainer.export(appVersion)
.then(model => {

@@ -243,3 +253,3 @@ fs.writeFileSync(modelFilename, JSON.stringify(model, null, 2));

}
function checkPrediction(luisTrainer, applicationId, errorsFilename) {
function checkPrediction(luisTrainer, applicationId, appVersion, errorsFilename) {
console.log(`Checking predictions for the application ${applicationId}...`);

@@ -253,3 +263,3 @@ luisTrainer.on('startGetAllExamples', () => {

});
return luisTrainer.checkPredictions()
return luisTrainer.checkPredictions(appVersion)
.then(predictionResult => {

@@ -304,4 +314,4 @@ if (predictionResult.errors.length) {

function processPredictionResult(predictionResult, errorsFilename) {
let intentErrors = predictionResult.errors.filter(error => error.predictedIntents && !error.ambiguousPredictedIntent).length;
let ambiguousIntentErrors = predictionResult.errors.filter(error => error.predictedIntents && error.ambiguousPredictedIntent).length;
let intentErrors = predictionResult.errors.filter(error => error.intentPredictions && !error.ambiguousPredictedIntent).length;
let ambiguousIntentErrors = predictionResult.errors.filter(error => error.intentPredictions && error.ambiguousPredictedIntent).length;
let entityErrors = predictionResult.errors.filter(error => error.predictedEntities).length;

@@ -308,0 +318,0 @@ let tokenizationErrors = predictionResult.errors.filter(error => error.tokenizedText).length;

@@ -18,6 +18,6 @@ /// <reference types="node" />

intent: string;
predictedIntents?: string[];
intentPredictions?: LuisApi.IntentPrediction[];
ambiguousPredictedIntent?: boolean;
entities?: LuisApi.LabeledEntity[];
predictedEntities?: LuisApi.LabeledEntity[];
entities?: (LuisApi.EntityLabelExampleGET | LuisApi.EntityLabelExamplePOST)[];
predictedEntities?: LuisApi.EntityLabelExamplePOST[];
}

@@ -34,14 +34,14 @@ export interface PredictionResult {

constructor(config: LuisTrainerConfig);
export(): Promise<LuisModel.Model>;
update(model: LuisModel.Model): Promise<void>;
checkPredictions(): Promise<PredictionResult>;
export(appVersion: string): Promise<LuisModel.Model>;
update(appVersion: string, model: LuisModel.Model): Promise<void>;
checkPredictions(appVersion: string): Promise<PredictionResult>;
testExamples(examples: LuisModel.Utterance[]): Promise<PredictionResult>;
private static wrapError(error, message);
private checkCulture(culture);
private updateIntents(newIntents);
private updateEntities(newEntities);
private updatePhraseLists(newModelPhraseLists);
private updateExamples(newExamples);
private train();
private publish();
private checkCulture(appVersion, culture);
private updateIntents(appVersion, intents);
private updateEntities(appVersion, entities);
private updatePhraseLists(appVersion, modelPhraseLists);
private updateExamples(appVersion, modelExamples);
private train(appVersion);
private publish(appVersion);
private static splitSentenceByTokens(sentence);

@@ -48,0 +48,0 @@ private static tokenizeSentence(sentence);

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash");

@@ -11,4 +12,4 @@ const events_1 = require("events");

}
export() {
return this.luisApiClient.export()
export(appVersion) {
return this.luisApiClient.export(appVersion)
.catch((reason) => {

@@ -20,28 +21,41 @@ let err = new Error('Error trying to export the app');

}
update(model) {
return this.checkCulture(model.culture)
.then(() => this.updateIntents(model.intents.map(intent => intent.name)))
.then(() => this.updateEntities(model.entities.map(entity => entity.name)))
.then(() => this.updatePhraseLists(model.model_features))
.then(() => this.updateExamples(model.utterances))
.then(() => this.train())
.then(() => this.publish())
update(appVersion, model) {
return this.checkCulture(appVersion, model.culture)
.then(() => this.updateIntents(appVersion, model.intents.map(intent => {
return {
name: intent.name
};
})))
.then(() => this.updateEntities(appVersion, model.entities.map(entity => {
return {
name: entity.name
};
})))
.then(() => this.updatePhraseLists(appVersion, model.model_features))
.then(() => this.updateExamples(appVersion, model.utterances))
.then(() => this.train(appVersion))
.then(() => this.publish(appVersion))
.then(() => Promise.resolve());
}
checkPredictions() {
checkPredictions(appVersion) {
function getTopPredictedIntents(example) {
return example.predictedIntents
return example.intentPredictions
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
.filter((predictedIntent, i, sortedPredictedIntents) => predictedIntent.score === sortedPredictedIntents[0].score)
.map(predictedIntent => predictedIntent.name);
.map(predictedIntent => {
return {
name: predictedIntent.name,
score: predictedIntent.score
};
});
}
function matchPredictedEntities(example) {
return _.differenceWith(example.entities, example.predictedEntities, _.isEqual).length === 0;
return _.differenceWith(example.entityLabels, example.entityPredictions, _.isEqual).length === 0;
}
function matchTokenizedText(example) {
return _.isEqual(example.tokenizedText, LuisTrainer.tokenizeSentence(example.utteranceText));
return _.isEqual(example.tokenizedText, LuisTrainer.tokenizeSentence(example.text));
}
this.emit('startGetAllExamples');
this.luisApiClient.on('getExamples', (first, last) => this.emit('getExamples', first, last));
return this.luisApiClient.getAllExamples()
return this.luisApiClient.getAllExamples(appVersion)
.then(examples => {

@@ -52,13 +66,13 @@ this.emit('endGetAllExamples', examples.length);

let error = {
text: example.utteranceText,
intent: example.intent
text: example.text,
intent: example.intentLabel
};
let topPredictedIntents = getTopPredictedIntents(example);
if (topPredictedIntents.indexOf(example.intent) === -1) {
if (topPredictedIntents.findIndex(element => element.name === example.intentLabel) === -1) {
error.ambiguousPredictedIntent = false;
error.predictedIntents = topPredictedIntents;
error.intentPredictions = topPredictedIntents;
}
else if (topPredictedIntents.length > 1) {
error.ambiguousPredictedIntent = true;
error.predictedIntents = topPredictedIntents;
error.intentPredictions = topPredictedIntents;
}

@@ -68,4 +82,10 @@ else {

if (!matchPredictedEntities(example)) {
error.entities = example.entities;
error.predictedEntities = example.predictedEntities;
error.entities = example.entityLabels;
error.predictedEntities = example.entityPredictions.map(entityPrediction => {
return {
entityName: entityPrediction.entityName,
startCharIndex: entityPrediction.startIndex,
endCharIndex: entityPrediction.endIndex
};
});
}

@@ -75,3 +95,3 @@ if (!matchTokenizedText(example)) {

}
if (error.predictedIntents || error.predictedEntities || error.tokenizedText) {
if (error.intentPredictions || error.predictedEntities || error.tokenizedText) {
return error;

@@ -84,5 +104,5 @@ }

.filter(example => example !== null);
let exampleCounter = _.countBy(examples, example => example.intent);
let intentErrorCounter = _.countBy(errors.filter(error => error.predictedIntents && !error.ambiguousPredictedIntent), error => error.intent);
let ambiguousIntentCounter = _.countBy(errors.filter(error => error.predictedIntents && error.ambiguousPredictedIntent), error => error.intent);
let exampleCounter = _.countBy(examples, example => example.intentLabel);
let intentErrorCounter = _.countBy(errors.filter(error => error.intentPredictions && !error.ambiguousPredictedIntent), error => error.intent);
let ambiguousIntentCounter = _.countBy(errors.filter(error => error.intentPredictions && error.ambiguousPredictedIntent), error => error.intent);
let stats = new Map();

@@ -104,6 +124,5 @@ _.forEach(exampleCounter, (counter, intent) => {

function matchRecognizedEntities(labeledEntities, recognizedEntities) {
return _.differenceWith(labeledEntities, recognizedEntities, (labeled, recognized) => labeled.name === recognized.type &&
labeled.word === recognized.entity &&
labeled.startToken === recognized.startIndex &&
labeled.endToken === recognized.endIndex).length === 0;
return _.differenceWith(labeledEntities, recognizedEntities, (labeled, recognized) => labeled.entityName === recognized.entity &&
labeled.startCharIndex === recognized.startIndex &&
labeled.endCharIndex === recognized.endIndex).length === 0;
}

@@ -120,5 +139,10 @@ this.luisApiClient.on('recognizeSentence', (sentence) => this.emit('recognizeSentence', sentence));

};
if (example.intent !== recognitionResult.intent) {
if (example.intent !== recognitionResult.topScoringIntent.intent) {
error.ambiguousPredictedIntent = false;
error.predictedIntents = [recognitionResult.intent];
error.intentPredictions = [
{
name: recognitionResult.topScoringIntent.intent,
score: recognitionResult.topScoringIntent.score
}
];
}

@@ -128,7 +152,5 @@ let labeledEntities = example.entities.map(entity => {

return {
name: entity.entity,
startToken: foundEntity.startChar,
endToken: foundEntity.endChar,
word: foundEntity.word,
isBuiltInExtractor: false
entityName: foundEntity.word,
startCharIndex: foundEntity.startChar,
endCharIndex: foundEntity.endChar
};

@@ -140,11 +162,9 @@ });

return {
name: entity.type,
startToken: entity.startIndex,
endToken: entity.endIndex,
word: entity.entity,
isBuiltInExtractor: false
entityName: entity.entity,
startCharIndex: entity.startIndex,
endCharIndex: entity.endIndex
};
});
}
if (error.predictedIntents || error.predictedEntities) {
if (error.intentPredictions || error.predictedEntities) {
return error;

@@ -158,3 +178,3 @@ }

let exampleCounter = _.countBy(examples, example => example.intent);
let intentErrorCounter = _.countBy(errors.filter(error => error.predictedIntents), error => error.intent);
let intentErrorCounter = _.countBy(errors.filter(error => error.intentPredictions), error => error.intent);
let stats = new Map();

@@ -182,3 +202,3 @@ _.forEach(exampleCounter, (counter, intent) => {

}
checkCulture(culture) {
checkCulture(appVersion, culture) {
return this.luisApiClient.getApp()

@@ -198,14 +218,14 @@ .then(appInfo => {

}
updateIntents(newIntents) {
return this.luisApiClient.getIntents()
updateIntents(appVersion, intents) {
return this.luisApiClient.getIntents(appVersion)
.then(oldIntents => {
let intentIdsToBeDeleted = _.differenceWith(oldIntents, newIntents, (a, b) => a.name === b).map(intent => intent.id);
let intentsToBeCreated = _.differenceWith(newIntents, oldIntents, (a, b) => a === b.name);
let intentsToBeDeleted = _.differenceWith(oldIntents, intents, (a, b) => a.name === b.name);
let intentsToBeCreated = _.differenceWith(intents, oldIntents, (a, b) => a.name === b.name);
let stats = {
create: intentsToBeCreated.length,
delete: intentIdsToBeDeleted.length
delete: intentsToBeDeleted.length
};
this.emit('startUpdateIntents', stats);
return this.luisApiClient.deleteIntents(intentIdsToBeDeleted)
.then(() => this.luisApiClient.createIntents(intentsToBeCreated))
return this.luisApiClient.deleteIntents(appVersion, intentsToBeDeleted)
.then(() => this.luisApiClient.createIntents(appVersion, intentsToBeCreated))
.then(() => {

@@ -217,14 +237,14 @@ this.emit('endUpdateIntents', stats);

}
updateEntities(newEntities) {
return this.luisApiClient.getEntities()
updateEntities(appVersion, entities) {
return this.luisApiClient.getEntities(appVersion)
.then(oldEntities => {
let entityIdsToBeDeleted = _.differenceWith(oldEntities, newEntities, (a, b) => a.name === b).map(entity => entity.id);
let entitiesToBeCreated = _.differenceWith(newEntities, oldEntities, (a, b) => a === b.name);
let entitiesToBeDeleted = _.differenceWith(oldEntities, entities, (a, b) => a.name === b.name);
let entitiesToBeCreated = _.differenceWith(entities, oldEntities, (a, b) => a.name === b.name);
let stats = {
create: entitiesToBeCreated.length,
delete: entityIdsToBeDeleted.length
delete: entitiesToBeDeleted.length
};
this.emit('startUpdateEntities', stats);
return this.luisApiClient.deleteEntities(entityIdsToBeDeleted)
.then(() => this.luisApiClient.createEntities(entitiesToBeCreated))
return this.luisApiClient.deleteEntities(appVersion, entitiesToBeDeleted)
.then(() => this.luisApiClient.createEntities(appVersion, entitiesToBeCreated))
.then(() => {

@@ -236,30 +256,28 @@ this.emit('endUpdateEntities', stats);

}
updatePhraseLists(newModelPhraseLists) {
updatePhraseLists(appVersion, modelPhraseLists) {
const comparePhraseLists = (a, b) => {
return a.name === b.name &&
a.mode === b.mode &&
a.isActive === b.isActive &&
a.isExchangeable === b.isExchangeable &&
a.phrases === b.phrases;
};
return this.luisApiClient.getPhraseLists()
return this.luisApiClient.getPhraseLists(appVersion)
.then(oldPhraseLists => {
let newPhraseLists = newModelPhraseLists.map((phraseList) => {
let phraseLists = modelPhraseLists.map((phraseList) => {
return {
name: phraseList.name,
mode: phraseList.mode === false ?
luis_api_client_1.LuisApi.PhraseListModes.NonExchangeable : luis_api_client_1.LuisApi.PhraseListModes.Exchangeable,
isActive: phraseList.activated,
isExchangeable: true,
phrases: phraseList.words
};
});
let phraseListIdsToBeDeleted = _.differenceWith(oldPhraseLists, newPhraseLists, comparePhraseLists)
.map(phraseList => phraseList.id);
let phraseListToBeCreated = _.differenceWith(newPhraseLists, oldPhraseLists, comparePhraseLists);
let phraseListsToBeDeleted = _.differenceWith(oldPhraseLists, phraseLists, comparePhraseLists);
let phraseListToBeCreated = _.differenceWith(phraseLists, oldPhraseLists, comparePhraseLists);
let stats = {
create: phraseListToBeCreated.length,
delete: phraseListIdsToBeDeleted.length
delete: phraseListsToBeDeleted.length
};
this.emit('startUpdatePhraseLists', stats);
return this.luisApiClient.deletePhraseLists(phraseListIdsToBeDeleted)
.then(() => this.luisApiClient.createPhraseLists(phraseListToBeCreated))
return this.luisApiClient.deletePhraseLists(appVersion, phraseListsToBeDeleted)
.then(() => this.luisApiClient.createPhraseLists(appVersion, phraseListToBeCreated))
.then(() => {

@@ -271,28 +289,30 @@ this.emit('endUpdatePhraseLists', stats);

}
updateExamples(newExamples) {
updateExamples(appVersion, modelExamples) {
this.emit('startGetAllExamples');
this.luisApiClient.on('getExamples', (first, last) => this.emit('getExamples', first, last));
return this.luisApiClient.getAllExamples()
return this.luisApiClient.getAllExamples(appVersion)
.then(oldExamples => {
this.emit('endGetAllExamples', oldExamples.length);
let exampleIdsToBeDeleted = _.differenceWith(oldExamples, newExamples, (a, b) => a.utteranceText === b.text).map((example) => example.id);
let examplesToBeCreated = _.differenceWith(newExamples, oldExamples, (a, b) => {
let eq = a.text === b.utteranceText &&
a.intent === b.intent &&
a.entities.length === b.entities.length &&
a.entities.length === _.intersectionWith(a.entities, b.entities, (ae, be) => ae.entity === be.name && ae.startPos === be.startToken && ae.endPos === be.endToken).length;
let examplesToBeDeleted = _.differenceWith(oldExamples, modelExamples, (a, b) => a.text === b.text);
let examplesToBeCreated = _.differenceWith(modelExamples, oldExamples, (a, b) => {
let eq = a.text === b.text &&
a.intent === b.intentLabel &&
a.entities.length === b.entityLabels.length &&
a.entities.length === _.intersectionWith(a.entities, b.entityLabels, (ae, be) => ae.entity === be.entityName && ae.startPos === be.startCharIndex && ae.endPos === be.endCharIndex).length;
return eq;
})
.map((example) => {
let entityLabels = example.entities.map(entity => {
let foundEntity = LuisTrainer.findEntity(example.text, entity.startPos, entity.endPos);
return {
entityName: entity.entity,
startCharIndex: foundEntity.startChar,
endCharIndex: foundEntity.endChar
};
});
entityLabels = (entityLabels.length === 0) ? null : entityLabels;
return {
exampleText: example.text,
selectedIntentName: example.intent,
entityLabels: example.entities.map(entity => {
let foundEntity = LuisTrainer.findEntity(example.text, entity.startPos, entity.endPos);
return {
entityType: entity.entity,
startToken: foundEntity.startChar,
endToken: foundEntity.endChar
};
})
text: example.text,
intentName: example.intent,
entityLabels: entityLabels
};

@@ -302,3 +322,3 @@ });

create: examplesToBeCreated.length,
delete: exampleIdsToBeDeleted.length
delete: examplesToBeDeleted.length
};

@@ -308,4 +328,4 @@ this.emit('startUpdateExamples', stats);

this.luisApiClient.on('createExampleBunch', (bunchLength) => this.emit('createExampleBunch', bunchLength));
return this.luisApiClient.deleteExamples(exampleIdsToBeDeleted)
.then(() => this.luisApiClient.createExamples(examplesToBeCreated))
return this.luisApiClient.deleteExamples(appVersion, examplesToBeDeleted)
.then(() => this.luisApiClient.createExamples(appVersion, examplesToBeCreated))
.then(() => {

@@ -317,3 +337,3 @@ this.emit('endUpdateExamples', stats);

}
train() {
train(appVersion) {
const delay = (t) => {

@@ -326,7 +346,7 @@ return new Promise(resolve => {

return delay(TRAINING_STATUS_POLLING_INTERVAL)
.then(() => this.luisApiClient.getTrainingStatus())
.then(() => this.luisApiClient.getTrainingStatus(appVersion))
.then((trainingStatus) => {
let finishedModels = trainingStatus.filter(modelStatus => modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.Success ||
modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.UpToDate ||
modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.Failed);
modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.Fail);
this.emit('trainingProgress', finishedModels.length, trainingStatus.length);

@@ -337,3 +357,3 @@ if (finishedModels.length < trainingStatus.length) {

let failedModels = trainingStatus
.filter(modelStatus => modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.Failed);
.filter(modelStatus => modelStatus.status === luis_api_client_1.LuisApi.TrainingStatuses.Fail);
if (failedModels.length) {

@@ -352,3 +372,3 @@ let err = new Error(`${failedModels.length} model(s) have failed with the following reasons:\n` +

this.emit('startTraining');
return this.luisApiClient.startTraining()
return this.luisApiClient.startTraining(appVersion)
.then((trainingStatus) => {

@@ -359,5 +379,5 @@ return waitForTraining();

}
publish() {
publish(appVersion) {
this.emit('startPublish');
return this.luisApiClient.publish()
return this.luisApiClient.publish(appVersion)
.then(publishResult => {

@@ -364,0 +384,0 @@ this.emit('endPublish');

{
"name": "@telefonica/luis-cli",
"version": "4.0.0",
"version": "5.0.0",
"description": "CLI for Microsoft LUIS API",
"license": "Apache-2.0",
"repository": "https://github.com/Telefonica/yot-bot",
"repository": "https://github.com/Telefonica/luis-cli",
"author": "TDAF <tdaf@tid.es>",

@@ -21,28 +21,29 @@ "bin": {

"lint": "tslint './src/**/*.ts'",
"prepublish": "npm run build",
"security": "nsp check",
"test": "npm run build && mocha -R spec 'lib/**/*.spec.js'"
"test": "npm run build && mocha -R spec lib/**/*.spec.js"
},
"dependencies": {
"colors": "^1.1.2",
"commander": "^2.9.0",
"commander": "^2.11.0",
"lodash": "^4.17.4",
"promise-retry": "^1.1.1",
"promise-throttle": "^0.3.1",
"request": "^2.79.0",
"request-promise-native": "^1.0.3",
"sprintf-js": "^1.0.3"
"request": "^2.83.0",
"request-promise-native": "^1.0.5",
"sprintf-js": "^1.1.1"
},
"devDependencies": {
"@telefonica/language-model-converter": "^2.5.0",
"@types/colors": "^1.1.1",
"@types/commander": "^2.3.31",
"@types/lodash": "^4.14.52",
"@types/node": "^6.0.40",
"@types/request-promise-native": "^1.0.2",
"@types/sprintf-js": "0.0.27",
"mocha": "^3.2.0",
"nsp": "^2.6.2",
"@telefonica/language-model-converter": "^2.7.0",
"@types/colors": "^1.1.3",
"@types/commander": "^2.11.0",
"@types/lodash": "^4.14.78",
"@types/node": "^6",
"@types/request-promise-native": "^1.0.8",
"@types/sprintf-js": "1.1.0",
"mocha": "^4.0.1",
"nsp": "^2.8.1",
"shx": "^0.2.2",
"tslint": "^4.4.2",
"typescript": "^2.1.5"
"tslint": "^5.8.0",
"typescript": "^2.5.3"
},

@@ -49,0 +50,0 @@ "engines": {

@@ -22,6 +22,11 @@ # Luis Command-line Interface

Type `luis-cli -h` to get more info about the available commands and general options.
To know more about one command, type `luis-cli <command> -h`.
Type `./bin/luis-cli -h` to get more info about the available commands and general options.
To know more about one command, type `./bin/luis-cli <command> -h`.
For example: to update, to train and to publish a concrete LUIS application version based on a new LUIS model:
`./bin/luis-cli update -s aac4012305e94ffebe2fa8408954aab3 -a b073cd3d-3b86-4367-813a-638840c83e3c -m ./training_model.json -v 0.3`
For additional information about LUIS training models, please visit: https://github.com/Telefonica/language-model-converter
## LICENSE

@@ -28,0 +33,0 @@

@@ -24,24 +24,6 @@ /**

import { RequestResponse } from 'request';
// Fix the `RequestResponse` interface exported by the `request` module which is missing the `body` property
declare module 'request' {
interface RequestResponse {
body?: any;
}
}
export namespace LuisApi {
export interface RecognizedEntity {
entity: string;
type: string;
startIndex: number;
endIndex: number;
}
export interface RecognitionResult {
sentence: string;
intent: string;
entities: RecognizedEntity[];
}
export interface AppInfo {
/* See: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c37 */
export interface AppInfoGET {
id: string;

@@ -51,63 +33,131 @@ name: string;

culture: string;
active: boolean;
numberOfIntents: number;
numberOfEntities: number;
isTrained: boolean;
}
export interface IntentClassifier {
/*
See:
- GET: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0d
- POST: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0c
- DELETE: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c1c
*/
export interface IntentGET {
id: string;
name: string;
typeId: number;
readableType: string;
}
export interface IntentPOST {
name: string;
}
export interface IntentDELETE {
id: string;
}
export interface EntityExtractor {
/*
See:
- GET: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0f
- POST: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0e
- DELETE: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c1f
*/
export interface EntityGET {
id: string;
name: string;
typeId: number;
readableType: string;
}
export interface EntityPOST {
name: string;
}
export interface EntityDELETE {
id: string;
}
export enum PhraseListModes { Exchangeable, NonExchangeable }
export interface PhraseList {
id?: string;
/*
See:
- GET: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c00
- POST: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9bff
- DELETE: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c07
*/
export interface PhraseListGET {
id: number;
name: string;
mode: PhraseListModes;
isActive?: boolean;
editable?: boolean;
isActive: boolean;
isExchangeable: boolean;
phrases: string;
}
export interface LabeledEntity {
export interface PhraseListPOST {
name: string;
startToken: number;
endToken: number;
word: string;
isBuiltInExtractor: boolean;
phrases: string;
isExchangeable: boolean;
}
export interface LabeledUtterance {
export interface PhraseListDELETE {
id: string;
utteranceText: string;
tokenizedText: string[];
intent: string;
predictedIntents: [{
name: string;
score: number;
}];
entities: LabeledEntity[];
predictedEntities: LabeledEntity[];
}
export interface Entity {
entityType: string;
startToken: number;
endToken: number;
/*
See:
- GET: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0a
- POST: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c08
- DELETE: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c0b
*/
export interface EntityLabelExampleGET {
entityName: string;
startTokenIndex: number;
endTokenIndex: number;
}
export interface EntityLabelExamplePOST {
entityName: string;
startCharIndex: number;
endCharIndex: number;
}
export interface IntentPrediction {
name: string;
score: number;
}
export interface EntityPrediction {
entityName: string;
startIndex: number;
endIndex: number;
phrase: string;
}
export interface ExampleGET {
id: number;
text: string;
tokenizedText: string[];
intentLabel: string;
entityLabels: EntityLabelExampleGET[];
intentPredictions: IntentPrediction[];
entityPredictions: EntityPrediction[];
}
export interface ExamplePOST {
text: string;
intentName: string;
entityLabels: EntityLabelExamplePOST[];
}
export interface ExampleDELETE {
id: number;
}
export interface Example {
exampleText: string;
selectedIntentName: string;
entityLabels?: Entity[];
/*
See:
- GET: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78
*/
export interface RecognizedIntent {
intent: string;
score: number;
}
export interface RecognizedEntity {
entity: string;
type: string;
startIndex: number;
endIndex: number;
score: number;
}
export interface RecognitionResult {
query: string;
topScoringIntent: RecognizedIntent;
intents: RecognizedIntent[];
entities: RecognizedEntity[];
}
export enum TrainingStatuses { Success = 0, Failed = 1, UpToDate = 2, InProgress = 3 }
export enum TrainingStatuses { Success = 0, Fail = 1, UpToDate = 2, InProgress = 3 }

@@ -124,5 +174,6 @@ export interface ModelTrainingStatus {

export interface PublishResult {
url: string;
endpointUrl: string;
subscriptionKey: string;
publishDate: Date;
endpointRegion: string;
isStaging: boolean;
}

@@ -132,4 +183,2 @@ }

const LUIS_API_BASE_URL = 'https://westus.api.cognitive.microsoft.com';
// Alternative endpoint only for the service API in order to use the private endpoint deployed in EU
const LUIS_SERVICE_API_BASE_URL = 'http://luis-europe-west-endpoint.cloudapp.net';
const RETRY_OPTS = {

@@ -168,8 +217,5 @@ retries: 10,

this.serviceReq = request.defaults({
// XXX: the public service API use a slightly different path, so this should be changed in the future
baseUrl: `${LUIS_SERVICE_API_BASE_URL}/api/v2.0/apps/${this.applicationId}`,
qs: {
'subscription-key': config.subscriptionKey,
verbose: false,
spellCheck: false
baseUrl: `${baseUrl}/luis/v2.0/apps/`,
headers: {
'Ocp-Apim-Subscription-Key': config.subscriptionKey
},

@@ -181,3 +227,3 @@ json: true,

this.provisionReq = request.defaults({
baseUrl: `${baseUrl}/luis/v1.0/prog/apps`,
baseUrl: `${baseUrl}/luis/api/v2.0/apps/`,
headers: {

@@ -219,4 +265,4 @@ 'Ocp-Apim-Subscription-Key': config.subscriptionKey

*/
private throttler(fn: Function, items: any[]): Promise<any> {
let promises = items.map(item => this.promiseThrottle.add(fn.bind(this, item)));
private throttler(fn: Function, appVersion: string, items: any[]): Promise<any> {
let promises = items.map(item => this.promiseThrottle.add(fn.bind(this, appVersion, item)));
return Promise.all(promises);

@@ -228,3 +274,3 @@ }

method: 'GET',
uri: '',
uri: `/${this.applicationId}`,
qs: { q: sentence }

@@ -241,4 +287,5 @@ };

return {
sentence: res.body.query,
intent: res.body.topScoringIntent.intent,
query: res.body.query,
topScoringIntent: res.body.topScoringIntent,
intents: res.body.intents,
entities: res.body.entities.map((entity: any) => {

@@ -249,3 +296,4 @@ return {

startIndex: entity.startIndex,
endIndex: entity.endIndex
endIndex: entity.endIndex,
score: entity.score
} as LuisApi.RecognizedEntity;

@@ -257,3 +305,3 @@ })

recognizeSentences(sentences: string[]): Promise<LuisApi.RecognitionResult[]> {
recognizeSentences(queries: string[]): Promise<LuisApi.RecognitionResult[]> {
let promiseThrottle = new PromiseThrottle({

@@ -263,10 +311,10 @@ requestsPerSecond: SERVICE_API_REQUESTS_PER_SECOND,

});
let promises = sentences.map(sentence => promiseThrottle.add(this.recognizeSentence.bind(this, sentence)));
let promises = queries.map(query => promiseThrottle.add(this.recognizeSentence.bind(this, query)));
return Promise.all(promises) as Promise<LuisApi.RecognitionResult[]>;
}
getApp(): Promise<LuisApi.AppInfo> {
getApp(): Promise<LuisApi.AppInfoGET> {
let opts: request.Options = {
method: 'GET',
uri: this.applicationId
uri: `/${this.applicationId}`
};

@@ -277,63 +325,52 @@ return this.retryRequest(opts, 200)

return {
id: body.ID,
name: body.Name,
description: body.Description,
culture: body.Culture,
active: body.Active,
numberOfIntents: body.NumberOfIntents,
numberOfEntities: body.NumberOfEntities,
isTrained: body.IsTrained
} as LuisApi.AppInfo;
id: body.id,
name: body.name,
description: body.description,
culture: body.culture
} as LuisApi.AppInfoGET;
});
}
getIntents(): Promise<LuisApi.IntentClassifier[]> {
getIntents(appVersion: string): Promise<LuisApi.IntentGET[]> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/intents`
uri: `/${this.applicationId}/versions/${appVersion}/intents`
};
return this.retryRequest(opts, 200)
.then((res: RequestResponse) => res.body.Result)
.then((intents) => intents.map((intent: any) => {
return {
id: intent.id,
name: intent.name
} as LuisApi.IntentClassifier;
}));
.then((res: RequestResponse) => res.body as LuisApi.IntentGET[]);
}
createIntent(intentName: string): Promise<string> {
createIntent(appVersion: string, intent: LuisApi.IntentPOST): Promise<string> {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/intents`,
body: { name: intentName }
uri: `/${this.applicationId}/versions/${appVersion}/intents`,
body: intent
};
return this.retryRequest(opts, 201)
.then((res: RequestResponse) => res.body)
.then(intentId => {
.then((res: RequestResponse) => {
this.emit('createIntent', {
id: intentId,
name: intentName
id: res.body,
name: intent.name
});
return intentId;
return res.body;
});
}
createIntents(intentNames: string[]): Promise<string[]> {
return this.throttler(this.createIntent, intentNames) as Promise<string[]>;
createIntents(appVersion: string, intents: LuisApi.IntentPOST[]): Promise<string[]> {
return this.throttler(this.createIntent, appVersion, intents) as Promise<string[]>;
}
deleteIntent(intentId: string): Promise<void> {
deleteIntent(appVersion: string, intent: LuisApi.IntentDELETE): Promise<void> {
let opts: request.Options = {
method: 'DELETE',
uri: `${this.applicationId}/intents/${intentId}`
uri: `/${this.applicationId}/versions/${appVersion}/intents/${intent.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteIntent', intentId);
this.emit('deleteIntent', intent.id);
});
}
deleteIntents(intentIds: string[]): Promise<void> {
return this.throttler(this.deleteIntent, intentIds)
deleteIntents(appVersion: string, intents: LuisApi.IntentDELETE[]): Promise<void> {
return this.throttler(this.deleteIntent, appVersion, intents)
.then(() => Promise.resolve());

@@ -343,134 +380,97 @@

getEntities(): Promise<LuisApi.EntityExtractor[]> {
getEntities(appVersion: string): Promise<LuisApi.EntityGET[]> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/entities`
uri: `/${this.applicationId}/versions/${appVersion}/entities`
};
return this.retryRequest(opts, 200)
.then((res: RequestResponse) => res.body.Result)
.then((entities) => entities.map((entity: any) => {
return {
id: entity.id,
name: entity.name
} as LuisApi.EntityExtractor;
}));
.then((res: RequestResponse) => res.body as LuisApi.EntityGET[]);
}
createEntity(entityName: string): Promise<string> {
createEntity(appVersion: string, entity: LuisApi.EntityPOST): Promise<string> {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/entities`,
body: { name: entityName }
uri: `/${this.applicationId}/versions/${appVersion}/entities`,
body: entity
};
return this.retryRequest(opts, 201)
.then((res: RequestResponse) => res.body)
.then(entityId => {
.then((res: RequestResponse) => {
this.emit('createEntity', {
id: entityId,
name: entityName
id: res.body,
name: entity.name
});
return entityId;
});
return res.body;
})
.catch(err => console.log(JSON.stringify(err)));
}
createEntities(entityNames: string[]): Promise<string[]> {
return this.throttler(this.createEntity, entityNames) as Promise<string[]>;
createEntities(appVersion: string, entities: LuisApi.EntityPOST[]): Promise<string[]> {
return this.throttler(this.createEntity, appVersion, entities) as Promise<string[]>;
}
deleteEntity(entityId: string): Promise<void> {
deleteEntity(appVersion: string, entity: LuisApi.EntityDELETE): Promise<void> {
let opts: request.Options = {
method: 'DELETE',
uri: `${this.applicationId}/entities/${entityId}`
uri: `/${this.applicationId}/versions/${appVersion}/entities/${entity.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteEntity', entityId);
this.emit('deleteEntity', entity.id);
});
}
deleteEntities(entityIds: string[]): Promise<void> {
return this.throttler(this.deleteEntity, entityIds)
deleteEntities(appVersion: string, entities: LuisApi.EntityDELETE[]): Promise<void> {
return this.throttler(this.deleteEntity, appVersion, entities)
.then(() => Promise.resolve());
}
getPhraseLists(): Promise<LuisApi.PhraseList[]> {
getPhraseLists(appVersion: string): Promise<LuisApi.PhraseListGET[]> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/phraselists`
uri: `/${this.applicationId}/versions/${appVersion}/phraselists`
};
return this.retryRequest(opts, 200)
.then((res: RequestResponse) => res.body)
.then((phraseLists) => phraseLists.map((phraseList: any) => {
return {
id: phraseList.Id.toString(),
name: phraseList.Name,
mode: phraseList.Mode.toLowerCase() === 'non-exchangeable' ?
LuisApi.PhraseListModes.NonExchangeable : LuisApi.PhraseListModes.Exchangeable,
isActive: phraseList.IsActive,
editable: phraseList.Editable,
phrases: phraseList.Phrases
} as LuisApi.PhraseList;
}));
.then((res: RequestResponse) => res.body as LuisApi.PhraseListGET[]);
}
createPhraseList(phraseList: LuisApi.PhraseList): Promise<string> {
createPhraseList(appVersion: string, phraseList: LuisApi.PhraseListPOST): Promise<string> {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/phraselists`,
body: {
Name: phraseList.name,
Mode: phraseList.mode === LuisApi.PhraseListModes.NonExchangeable ?
'Non-exchangeable' : 'Exchangeable',
IsActive: phraseList.isActive !== undefined ? phraseList.isActive : true,
Editable: phraseList.editable !== undefined ? phraseList.editable : true,
Phrases: phraseList.phrases
}
uri: `/${this.applicationId}/versions/${appVersion}/phraselists`,
body: phraseList
};
return this.retryRequest(opts, 201)
.then((res: RequestResponse) => res.body)
.then(phraseListId => {
.then((res: RequestResponse) => {
this.emit('createPhraseList', {
id: phraseListId,
id: res.body,
name: phraseList.name
});
return phraseListId;
return res.body;
});
}
createPhraseLists(phraseLists: LuisApi.PhraseList[]): Promise<string[]> {
return this.throttler(this.createPhraseList, phraseLists) as Promise<string[]>;
createPhraseLists(appVersion: string, phraseLists: LuisApi.PhraseListPOST[]): Promise<string[]> {
return this.throttler(this.createPhraseList, appVersion, phraseLists) as Promise<string[]>;
}
deletePhraseList(phraseListId: string): Promise<void> {
deletePhraseList(appVersion: string, phraseList: LuisApi.PhraseListDELETE): Promise<void> {
let opts: request.Options = {
method: 'DELETE',
uri: `${this.applicationId}/phraselists/${phraseListId}`
uri: `/${this.applicationId}/versions/${appVersion}/phraselists/${phraseList.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deletePhraseList', phraseListId);
this.emit('deletePhraseList', phraseList.id);
});
}
deletePhraseLists(phraseListIds: string[]): Promise<void> {
return this.throttler(this.deletePhraseList, phraseListIds)
deletePhraseLists(appVersion: string, phraseLists: LuisApi.PhraseListDELETE[]): Promise<void> {
return this.throttler(this.deletePhraseList, appVersion, phraseLists)
.then(() => Promise.resolve());
}
getExamples(skip: number, count: number): Promise<LuisApi.LabeledUtterance[]> {
function mapEntities(entities: any[]): LuisApi.LabeledEntity[] {
return entities.map((entity: any) => {
return {
name: entity.name,
startToken: entity.indeces.startToken,
endToken: entity.indeces.endToken,
word: entity.word,
isBuiltInExtractor: entity.isBuiltInExtractor
} as LuisApi.LabeledEntity;
});
}
getExamples(appVersion: string, skip: number, count: number): Promise<LuisApi.ExampleGET[]> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/examples`,
uri: `/${this.applicationId}/versions/${appVersion}/examples`,
qs: { skip, count }

@@ -480,25 +480,8 @@ };

.then((res: RequestResponse) => {
let examples = res.body;
if (examples.length) {
if (res.body.length) {
// Don't emit events once the last example has been reached
this.emit('getExamples', skip, skip + examples.length - 1);
this.emit('getExamples', skip, skip + res.body.length - 1);
}
return examples;
})
.then(examples => examples.map((example: any) => {
return {
id: example.exampleId,
utteranceText: example.utteranceText,
tokenizedText: example.tokenizedText,
intent: example.IntentsResults.Name,
predictedIntents: example.PredictedIntentResults.map((intent: any) => {
return {
name: intent.Name,
score: intent.score
};
}),
entities: mapEntities(example.EntitiesResults),
predictedEntities: mapEntities(example.PredictedEntitiesResults)
} as LuisApi.LabeledUtterance;
}));
return res.body as LuisApi.ExampleGET[];
});
}

@@ -509,4 +492,4 @@

*/
getAllExamples(): Promise<LuisApi.LabeledUtterance[]> {
let examplesBunches: LuisApi.LabeledUtterance[][] = [];
getAllExamples(appVersion: string): Promise<LuisApi.ExampleGET[]> {
let examplesBunches: LuisApi.ExampleGET[][] = [];

@@ -518,5 +501,6 @@ // Recursively get examples in bunches of MAX_PARALLEL_EXAMPLES_REQUESTS parallel requests

.map((e, i) => skip + i * MAX_EXAMPLES_COUNT);
let promises = skipList.map(skip => this.promiseThrottle.add(this.getExamples.bind(this, skip, MAX_EXAMPLES_COUNT)));
let promises = skipList.map(skip => this.promiseThrottle.add(
this.getExamples.bind(this, appVersion, skip, MAX_EXAMPLES_COUNT)));
return Promise.all(promises)
.then((examplesBunch: LuisApi.LabeledUtterance[][]) => {
.then((examplesBunch: LuisApi.ExampleGET[][]) => {
let lastBunchFound = examplesBunch.some(bunch => {

@@ -538,14 +522,13 @@ if (bunch.length) {

createExamples(examples: LuisApi.Example[]): Promise<string[]> {
createExamples(appVersion: string, examples: LuisApi.ExamplePOST[]): Promise<string[]> {
// Create examples up to MAX_EXAMPLES_UPLOAD
let createLimitedExamples = (examples: LuisApi.Example[]) => {
let createLimitedExamples = (appVersion: string, examples: LuisApi.ExamplePOST[]) => {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/examples`,
uri: `/${this.applicationId}/versions/${appVersion}/examples`,
body: examples
};
return this.retryRequest(opts, 201)
.then((res: RequestResponse) => res.body)
.then((body: any[]) => {
let errors = body.filter(r => r.hasError);
.then((res: RequestResponse) => {
let errors = res.body.filter((r: any) => r.hasError);
if (errors.length) {

@@ -556,3 +539,3 @@ return Promise.reject(new Error('LuisApiClient: The following examples have errors:\n'

this.emit('createExampleBunch', examples.length);
return body.map(r => r.value.ExampleId.toString());
return res.body.map((r: any) => r.value.ExampleId.toString());
});

@@ -563,19 +546,19 @@ };

let examplesBunches = _.chunk(examples, MAX_EXAMPLES_UPLOAD);
return this.throttler(createLimitedExamples, examplesBunches)
return this.throttler(createLimitedExamples, appVersion, examplesBunches)
.then(_.flatten);
}
deleteExample(exampleId: string): Promise<void> {
deleteExample(appVersion: string, example: LuisApi.ExampleDELETE): Promise<void> {
let opts: request.Options = {
method: 'DELETE',
uri: `${this.applicationId}/examples/${exampleId}`
uri: `/${this.applicationId}/versions/${appVersion}/examples/${example.id}`
};
return this.retryRequest(opts, 200)
.then(() => {
this.emit('deleteExample', exampleId);
this.emit('deleteExample', example.id);
});
}
deleteExamples(exampleIds: string[]): Promise<void> {
return this.throttler(this.deleteExample, exampleIds)
deleteExamples(appVersion: string, examples: LuisApi.ExampleDELETE[]): Promise<void> {
return this.throttler(this.deleteExample, appVersion, examples)
.then(() => Promise.resolve());

@@ -587,8 +570,8 @@ }

let modelTrainingStatus: LuisApi.ModelTrainingStatus = {
modelId: modelStatus.ModelId,
status: modelStatus.Details.StatusId as LuisApi.TrainingStatuses,
exampleCount: modelStatus.Details.ExampleCount
modelId: modelStatus.modelId,
status: modelStatus.details.statusId as LuisApi.TrainingStatuses,
exampleCount: modelStatus.details.exampleCount
};
if (modelTrainingStatus.status === LuisApi.TrainingStatuses.Failed) {
modelTrainingStatus.failureReason = modelStatus.Details.FailureReason;
if (modelTrainingStatus.status === LuisApi.TrainingStatuses.Fail) {
modelTrainingStatus.failureReason = modelStatus.details.failureReason;
}

@@ -599,16 +582,15 @@ return modelTrainingStatus;

startTraining(): Promise<LuisApi.TrainingStatus> {
startTraining(appVersion: string): Promise<LuisApi.TrainingStatus> {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/train`
uri: `/${this.applicationId}/versions/${appVersion}/train`
};
return this.retryRequest(opts, 202)
.then((res: RequestResponse) => res.body)
.then(this.convertTrainingStatus);
.then((res: RequestResponse) => res.body);
}
getTrainingStatus(): Promise<LuisApi.TrainingStatus> {
getTrainingStatus(appVersion: string): Promise<LuisApi.TrainingStatus> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/train`
uri: `/${this.applicationId}/versions/${appVersion}/train`
};

@@ -620,12 +602,11 @@ return this.retryRequest(opts, 200)

publish(): Promise<LuisApi.PublishResult> {
publish(appVersion: string): Promise<LuisApi.PublishResult> {
let opts: request.Options = {
method: 'POST',
uri: `${this.applicationId}/publish`,
uri: `/${this.applicationId}/publish`,
// We don't really know what this body is for but it must be included for the API to work
body: {
BotFramework: { Enabled: false, AppId: '', SubscriptionKey: '', Endpoint: '' },
Slack: { Enabled: false, ClientId: '', ClientSecret: '', RedirectUri: '' },
PrivacyStatement: '',
TermsOfUse: ''
versionId: appVersion,
isStaging: false,
region: 'westus'
}

@@ -637,5 +618,6 @@ };

return {
url: body.URL,
subscriptionKey: body.SubscriptionKey,
publishDate: new Date(body.PublishDate)
endpointUrl: body.endpointUrl,
subscriptionKey: body['subscription-key'],
endpointRegion: body.endpointRegion,
isStaging: body.isStaging
} as LuisApi.PublishResult;

@@ -645,6 +627,6 @@ });

export(): Promise<any> {
export(appVersion: string): Promise<any> {
let opts: request.Options = {
method: 'GET',
uri: `${this.applicationId}/export`
uri: `/${this.applicationId}/versions/${appVersion}/export`
};

@@ -651,0 +633,0 @@ return this.retryRequest(opts, 200)

@@ -28,20 +28,10 @@ /**

enum Commands { Update, Export, CheckPredictions, TestExamples };
let command: Commands;
enum Commands { Update, Export, CheckPredictions, TestExamples }
let runner: Promise<number> = null;
let runner: Promise<number | void> = null;
interface ProgramOptions extends commander.ICommand {
parent?: ProgramOptions; // When using commands, `parent` holds options from the main program
endpoint?: string;
applicationId?: string;
model?: string;
errors?: string;
subscriptionKey?: string;
}
const program: ProgramOptions = commander
const program = commander
.usage('command [options]')
.option('-e, --endpoint <endpoint>', `LUIS endpoint (also got from the LUIS_ENDPOINT env var) [${DEFAULT_LUIS_ENDPOINT}]`,
DEFAULT_LUIS_ENDPOINT)
DEFAULT_LUIS_ENDPOINT)
.option('-s, --subscription-key <subscription-key>', 'LUIS subscription key (also got from the LUIS_SUBSCRIPTION_KEY env var)');

@@ -54,4 +44,5 @@

.option('-m, --model <filename>', 'JSON file containing the model to upload to the LUIS application')
.option('-v, --app-version <app-version>', 'The version of the application to update')
.action(options => selectRunner(Commands.Update, options))
.on('--help', function() {
.on('--help', function () {
console.log(' Example:');

@@ -69,3 +60,3 @@ console.log();

.action(options => selectRunner(Commands.Export, options))
.on('--help', function() {
.on('--help', function () {
console.log(' Example:');

@@ -83,3 +74,3 @@ console.log();

.action(options => selectRunner(Commands.CheckPredictions, options))
.on('--help', function() {
.on('--help', function () {
console.log(' Example:');

@@ -98,6 +89,7 @@ console.log();

.action(options => selectRunner(Commands.TestExamples, options))
.on('--help', function() {
.on('--help', function () {
console.log(' Example:');
console.log();
console.log(` Check whether a set of examples are correctly recognized by an application in terms of intent and entities and save differences to 'errors.json':`);
console.log(' Check whether a set of examples are correctly recognized by an application in terms of intent and entities ' +
'and save differences to \'errors.json\':');
console.log(' $ luis-cli check -m model.json -a XXX -f errors.json -s YYY');

@@ -118,3 +110,3 @@ });

function selectRunner(command: Commands, options: ProgramOptions) {
function selectRunner(command: Commands, options: any) {
// Get values from env vars if not provided via options

@@ -131,3 +123,3 @@ let endpoint = options.parent.endpoint || process.env.LUIS_ENDPOINT;

if ((command === Commands.Update || command === Commands.Export || command === Commands.CheckPredictions ||
command === Commands.TestExamples) && !applicationId) {
command === Commands.TestExamples) && !applicationId) {
printError('unknown LUIS application id. Provide one through the `-a, --application-id` option ' +

@@ -148,20 +140,29 @@ 'or the `LUIS_APPLICATION_ID` env var.');

case Commands.Update:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.model) {
printError('missing JSON file from which the model will be read. Provide one through the `-m, --model` option.');
}
runner = updateApp(luisTrainer, applicationId, options.model);
runner = updateApp(luisTrainer, applicationId, options.appVersion, options.model);
break;
case Commands.Export:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.model) {
printError('missing JSON file to which the application will be exported. Provide one through the `-m, --model` option.');
}
runner = exportApp(luisTrainer, applicationId, options.model);
runner = exportApp(luisTrainer, applicationId, options.appVersion, options.model);
break;
case Commands.CheckPredictions:
if (!options.appVersion) {
printError('missing version of the model to update. Provide one through the `-v, --version` option.');
}
if (!options.errors) {
printError('missing JSON file to which the differences will be saved. Provide one through the `-r, --errors` option.');
}
runner = checkPrediction(luisTrainer, applicationId, options.errors);
runner = checkPrediction(luisTrainer, applicationId, options.appVersion, options.errors);
break;

@@ -181,3 +182,3 @@

function updateApp(luisTrainer: LuisTrainer, applicationId: string, modelFilename: string): Promise<number> {
function updateApp(luisTrainer: LuisTrainer, applicationId: string, appVersion: string, modelFilename: string): Promise<number | void> {
let model: LuisModel.Model;

@@ -194,3 +195,3 @@ try {

console.log(`Updating the application ${applicationId} with the model from "${modelFilename}"...`);
console.log(`Updating the version ${appVersion} of the application ${applicationId} with the model from "${modelFilename}"...`);
console.log();

@@ -274,3 +275,3 @@ luisTrainer.on('startUpdateIntents', (stats: UpdateEvent) => {

return luisTrainer.update(model)
return luisTrainer.update(appVersion, model)
.then(() => {

@@ -283,5 +284,5 @@ console.log('The application has been successfully updated');

function exportApp(luisTrainer: LuisTrainer, applicationId: string, modelFilename: string): Promise<number> {
function exportApp(luisTrainer: LuisTrainer, applicationId: string, appVersion: string, modelFilename: string): Promise<number | void> {
console.log(`Exporting the application ${applicationId} to "${modelFilename}"...`);
return luisTrainer.export()
return luisTrainer.export(appVersion)
.then(model => {

@@ -295,3 +296,4 @@ fs.writeFileSync(modelFilename, JSON.stringify(model, null, 2));

function checkPrediction(luisTrainer: LuisTrainer, applicationId: string, errorsFilename: string): Promise<number> {
function checkPrediction(luisTrainer: LuisTrainer, applicationId: string, appVersion: string,
errorsFilename: string): Promise<number | void> {
console.log(`Checking predictions for the application ${applicationId}...`);

@@ -307,3 +309,3 @@

return luisTrainer.checkPredictions()
return luisTrainer.checkPredictions(appVersion)
.then(predictionResult => {

@@ -321,3 +323,4 @@ if (predictionResult.errors.length) {

function testExamples(luisTrainer: LuisTrainer, applicationId: string, modelFilename: string, errorsFilename: string): Promise<number> {
function testExamples(luisTrainer: LuisTrainer, applicationId: string, modelFilename: string,
errorsFilename: string): Promise<number | void> {
let model: LuisModel.Model;

@@ -362,5 +365,5 @@ try {

let intentErrors = predictionResult.errors.filter(error =>
error.predictedIntents && !error.ambiguousPredictedIntent).length;
error.intentPredictions && !error.ambiguousPredictedIntent).length;
let ambiguousIntentErrors = predictionResult.errors.filter(error =>
error.predictedIntents && error.ambiguousPredictedIntent).length;
error.intentPredictions && error.ambiguousPredictedIntent).length;
let entityErrors = predictionResult.errors.filter(error => error.predictedEntities).length;

@@ -391,3 +394,3 @@ let tokenizationErrors = predictionResult.errors.filter(error => error.tokenizedText).length;

const HEADERS = {COL1: 'INTENTS', COL2: 'ERRORS', COL3: 'AMBIGUITIES'};
const HEADERS = { COL1: 'INTENTS', COL2: 'ERRORS', COL3: 'AMBIGUITIES' };
let longestIntentLen = 0;

@@ -398,3 +401,3 @@ let total = 0;

let strStats: {intent: string, errors: string, ambiguities: string, color: Function}[] = [];
let strStats: { intent: string, errors: string, ambiguities: string, color: Function }[] = [];
let sortedStats = new Map([...predictionResult.stats.entries()].sort());

@@ -401,0 +404,0 @@ sortedStats.forEach((stats, intent) => {

@@ -36,6 +36,6 @@ /**

intent: string;
predictedIntents?: string[];
intentPredictions?: LuisApi.IntentPrediction[];
ambiguousPredictedIntent?: boolean;
entities?: LuisApi.LabeledEntity[];
predictedEntities?: LuisApi.LabeledEntity[];
entities?: (LuisApi.EntityLabelExampleGET | LuisApi.EntityLabelExamplePOST)[];
predictedEntities?: LuisApi.EntityLabelExamplePOST[];
}

@@ -72,4 +72,4 @@

export(): Promise<LuisModel.Model> {
return this.luisApiClient.export()
export(appVersion: string): Promise<LuisModel.Model> {
return this.luisApiClient.export(appVersion)
.catch((reason: Error) => {

@@ -82,10 +82,21 @@ let err = new Error('Error trying to export the app') as any;

update(model: LuisModel.Model): Promise<void> {
return this.checkCulture(model.culture)
.then(() => this.updateIntents(model.intents.map(intent => intent.name)))
.then(() => this.updateEntities(model.entities.map(entity => entity.name)))
.then(() => this.updatePhraseLists(model.model_features))
.then(() => this.updateExamples(model.utterances))
.then(() => this.train())
.then(() => this.publish())
update(appVersion: string, model: LuisModel.Model): Promise<void> {
return this.checkCulture(appVersion, model.culture)
.then(() => this.updateIntents(appVersion, model.intents.map(
intent => {
return {
name: intent.name
};
})))
.then(() => this.updateEntities(appVersion, model.entities.map(
entity => {
return {
name: entity.name
};
}
)))
.then(() => this.updatePhraseLists(appVersion, model.model_features))
.then(() => this.updateExamples(appVersion, model.utterances))
.then(() => this.train(appVersion))
.then(() => this.publish(appVersion))
.then(() => Promise.resolve());

@@ -103,20 +114,25 @@ }

*/
checkPredictions(): Promise<PredictionResult> {
checkPredictions(appVersion: string): Promise<PredictionResult> {
// Return the top scoring predicted intents. In case of tie, all the top ones will be returned.
function getTopPredictedIntents(example: LuisApi.LabeledUtterance): string[] {
return example.predictedIntents
function getTopPredictedIntents(example: LuisApi.ExampleGET): LuisApi.IntentPrediction[] {
return example.intentPredictions
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
.filter((predictedIntent, i, sortedPredictedIntents) =>
predictedIntent.score === sortedPredictedIntents[0].score)
.map(predictedIntent => predictedIntent.name);
.map(predictedIntent => {
return {
name: predictedIntent.name,
score: predictedIntent.score
};
});
}
function matchPredictedEntities(example: LuisApi.LabeledUtterance): boolean {
function matchPredictedEntities(example: LuisApi.ExampleGET): boolean {
// Predicted entities must have at least the labeled entities.
// If there are some extra predicted entities, it is not an issue.
return _.differenceWith(example.entities, example.predictedEntities, _.isEqual).length === 0;
return _.differenceWith(example.entityLabels, example.entityPredictions, _.isEqual).length === 0;
}
function matchTokenizedText(example: LuisApi.LabeledUtterance): boolean {
return _.isEqual(example.tokenizedText, LuisTrainer.tokenizeSentence(example.utteranceText));
function matchTokenizedText(example: LuisApi.ExampleGET): boolean {
return _.isEqual(example.tokenizedText, LuisTrainer.tokenizeSentence(example.text));
}

@@ -127,3 +143,3 @@

this.emit('getExamples', first, last));
return this.luisApiClient.getAllExamples()
return this.luisApiClient.getAllExamples(appVersion)
.then(examples => {

@@ -134,3 +150,3 @@ this.emit('endGetAllExamples', examples.length);

// sortedExamples = sortedExamples.map(example => {
// example.predictedIntents = example.predictedIntents.sort((a, b) => a.name.localeCompare(b.name));
// example.intentPredictions = example.intentPredictions.sort((a, b) => a.name.localeCompare(b.name));
// return example;

@@ -146,11 +162,11 @@ // });

let error: PredictionError = {
text: example.utteranceText,
intent: example.intent
text: example.text,
intent: example.intentLabel
};
let topPredictedIntents = getTopPredictedIntents(example);
if (topPredictedIntents.indexOf(example.intent) === -1) {
if (topPredictedIntents.findIndex(element => element.name === example.intentLabel) === -1) {
// None of the top scoring predicted intents matches the labeled one
error.ambiguousPredictedIntent = false;
error.predictedIntents = topPredictedIntents;
error.intentPredictions = topPredictedIntents;
} else if (topPredictedIntents.length > 1) {

@@ -160,3 +176,3 @@ // The labeled intent is one of the top scoring predicted ones

error.ambiguousPredictedIntent = true;
error.predictedIntents = topPredictedIntents;
error.intentPredictions = topPredictedIntents;
} else {

@@ -167,4 +183,12 @@ // The labeled intent matches the only one top scoring predicted intent

if (!matchPredictedEntities(example)) {
error.entities = example.entities;
error.predictedEntities = example.predictedEntities;
error.entities = example.entityLabels;
error.predictedEntities = example.entityPredictions.map(
entityPrediction => {
return {
entityName: entityPrediction.entityName,
startCharIndex: entityPrediction.startIndex,
endCharIndex: entityPrediction.endIndex
};
}
);
}

@@ -176,3 +200,3 @@

if (error.predictedIntents || error.predictedEntities || error.tokenizedText) {
if (error.intentPredictions || error.predictedEntities || error.tokenizedText) {
return error;

@@ -186,8 +210,8 @@ } else {

// Calculate stats
let exampleCounter = _.countBy(examples, example => example.intent);
let exampleCounter = _.countBy(examples, example => example.intentLabel);
let intentErrorCounter = _.countBy(
errors.filter(error => error.predictedIntents && !error.ambiguousPredictedIntent),
errors.filter(error => error.intentPredictions && !error.ambiguousPredictedIntent),
error => error.intent);
let ambiguousIntentCounter = _.countBy(
errors.filter(error => error.predictedIntents && error.ambiguousPredictedIntent),
errors.filter(error => error.intentPredictions && error.ambiguousPredictedIntent),
error => error.intent);

@@ -217,12 +241,11 @@ let stats: PredictionStats = new Map();

function matchRecognizedEntities(
labeledEntities: LuisApi.LabeledEntity[],
labeledEntities: LuisApi.EntityLabelExamplePOST[],
recognizedEntities: LuisApi.RecognizedEntity[]): boolean {
// Recognized entities must have at least the labeled entities.
// If there are some extra recognized entities, it is not an issue.
return _.differenceWith(labeledEntities, recognizedEntities,
(labeled: LuisApi.LabeledEntity, recognized: LuisApi.RecognizedEntity) =>
labeled.name === recognized.type &&
labeled.word === recognized.entity &&
labeled.startToken === recognized.startIndex &&
labeled.endToken === recognized.endIndex
return _.differenceWith<any>(labeledEntities, recognizedEntities,
(labeled: LuisApi.EntityLabelExamplePOST, recognized: LuisApi.RecognizedEntity) =>
labeled.entityName === recognized.entity &&
labeled.startCharIndex === recognized.startIndex &&
labeled.endCharIndex === recognized.endIndex
).length === 0;

@@ -246,5 +269,10 @@ }

// Compare intents
if (example.intent !== recognitionResult.intent) {
if (example.intent !== recognitionResult.topScoringIntent.intent) {
error.ambiguousPredictedIntent = false;
error.predictedIntents = [recognitionResult.intent];
error.intentPredictions = [
{
name: recognitionResult.topScoringIntent.intent,
score: recognitionResult.topScoringIntent.score
}
];
}

@@ -256,8 +284,6 @@

return {
name: entity.entity,
startToken: foundEntity.startChar,
endToken: foundEntity.endChar,
word: foundEntity.word,
isBuiltInExtractor: false
} as LuisApi.LabeledEntity;
entityName: foundEntity.word,
startCharIndex: foundEntity.startChar,
endCharIndex: foundEntity.endChar
};
});

@@ -268,12 +294,10 @@ if (!matchRecognizedEntities(labeledEntities, recognitionResult.entities)) {

return {
name: entity.type,
startToken: entity.startIndex,
endToken: entity.endIndex,
word: entity.entity,
isBuiltInExtractor: false
} as LuisApi.LabeledEntity;
entityName: entity.entity,
startCharIndex: entity.startIndex,
endCharIndex: entity.endIndex
};
});
}
if (error.predictedIntents || error.predictedEntities) {
if (error.intentPredictions || error.predictedEntities) {
return error;

@@ -288,3 +312,3 @@ } else {

let exampleCounter = _.countBy(examples, example => example.intent);
let intentErrorCounter = _.countBy(errors.filter(error => error.predictedIntents), error => error.intent);
let intentErrorCounter = _.countBy(errors.filter(error => error.intentPredictions), error => error.intent);
let stats: PredictionStats = new Map();

@@ -315,3 +339,3 @@ _.forEach(exampleCounter, (counter, intent) => {

private checkCulture(culture: string): Promise<void> {
private checkCulture(appVersion: string, culture: string): Promise<void> {
return this.luisApiClient.getApp()

@@ -331,18 +355,17 @@ .then(appInfo => {

private updateIntents(newIntents: string[]): Promise<void> {
return this.luisApiClient.getIntents()
private updateIntents(appVersion: string, intents: LuisApi.IntentPOST[]): Promise<void> {
return this.luisApiClient.getIntents(appVersion)
.then(oldIntents => {
let intentIdsToBeDeleted = _.differenceWith(oldIntents, newIntents,
(a: LuisApi.IntentClassifier, b: string) => a.name === b
).map(intent => intent.id);
let intentsToBeCreated = _.differenceWith(newIntents, oldIntents,
(a: string, b: LuisApi.IntentClassifier) => a === b.name
let intentsToBeDeleted = _.differenceWith<any>(oldIntents, intents,
(a: LuisApi.IntentGET, b: LuisApi.IntentPOST) => a.name === b.name);
let intentsToBeCreated = _.differenceWith<LuisApi.IntentPOST>(intents, oldIntents,
(a: LuisApi.IntentPOST, b: LuisApi.IntentGET) => a.name === b.name
);
let stats: UpdateEvent = {
create: intentsToBeCreated.length,
delete: intentIdsToBeDeleted.length
delete: intentsToBeDeleted.length
};
this.emit('startUpdateIntents', stats);
return this.luisApiClient.deleteIntents(intentIdsToBeDeleted)
.then(() => this.luisApiClient.createIntents(intentsToBeCreated))
return this.luisApiClient.deleteIntents(appVersion, intentsToBeDeleted)
.then(() => this.luisApiClient.createIntents(appVersion, intentsToBeCreated))
.then(() => {

@@ -355,18 +378,18 @@ this.emit('endUpdateIntents', stats);

private updateEntities(newEntities: string[]): Promise<void> {
return this.luisApiClient.getEntities()
private updateEntities(appVersion: string, entities: LuisApi.EntityPOST[]): Promise<void> {
return this.luisApiClient.getEntities(appVersion)
.then(oldEntities => {
let entityIdsToBeDeleted = _.differenceWith(oldEntities, newEntities,
(a: LuisApi.EntityExtractor, b: string) => a.name === b
).map(entity => entity.id);
let entitiesToBeCreated = _.differenceWith(newEntities, oldEntities,
(a: string, b: LuisApi.EntityExtractor) => a === b.name
let entitiesToBeDeleted = _.differenceWith<any>(
oldEntities, entities,
(a: LuisApi.EntityGET, b: LuisApi.EntityPOST) => a.name === b.name);
let entitiesToBeCreated = _.differenceWith<LuisApi.EntityPOST>(entities, oldEntities,
(a: LuisApi.EntityPOST, b: LuisApi.EntityGET) => a.name === b.name
);
let stats: UpdateEvent = {
create: entitiesToBeCreated.length,
delete: entityIdsToBeDeleted.length
delete: entitiesToBeDeleted.length
};
this.emit('startUpdateEntities', stats);
return this.luisApiClient.deleteEntities(entityIdsToBeDeleted)
.then(() => this.luisApiClient.createEntities(entitiesToBeCreated))
return this.luisApiClient.deleteEntities(appVersion, entitiesToBeDeleted)
.then(() => this.luisApiClient.createEntities(appVersion, entitiesToBeCreated))
.then(() => {

@@ -379,35 +402,33 @@ this.emit('endUpdateEntities', stats);

private updatePhraseLists(newModelPhraseLists: LuisModel.ModelFeature[]): Promise<void> {
private updatePhraseLists(appVersion: string, modelPhraseLists: LuisModel.ModelFeature[]): Promise<void> {
/**
* Compare phraseLists ignoring non-meaningful properties
*/
const comparePhraseLists = (a: LuisApi.PhraseList, b: LuisApi.PhraseList) => {
const comparePhraseLists = (a: any, b: any) => {
return a.name === b.name &&
a.mode === b.mode &&
a.isActive === b.isActive &&
a.isExchangeable === b.isExchangeable &&
a.phrases === b.phrases;
};
return this.luisApiClient.getPhraseLists()
return this.luisApiClient.getPhraseLists(appVersion)
.then(oldPhraseLists => {
// Convert data from the model to the format used by the API
let newPhraseLists = newModelPhraseLists.map((phraseList: LuisModel.ModelFeature) => {
let phraseLists = modelPhraseLists.map((phraseList: LuisModel.ModelFeature) => {
return {
name: phraseList.name,
mode: phraseList.mode === false ?
LuisApi.PhraseListModes.NonExchangeable : LuisApi.PhraseListModes.Exchangeable,
isActive: phraseList.activated,
isExchangeable: true,
phrases: phraseList.words
} as LuisApi.PhraseList;
} as LuisApi.PhraseListPOST;
});
let phraseListIdsToBeDeleted = _.differenceWith(oldPhraseLists, newPhraseLists, comparePhraseLists)
.map(phraseList => phraseList.id);
let phraseListToBeCreated = _.differenceWith(newPhraseLists, oldPhraseLists, comparePhraseLists);
let phraseListsToBeDeleted = _.differenceWith<any>(oldPhraseLists, phraseLists, comparePhraseLists);
let phraseListToBeCreated = _.differenceWith<LuisApi.PhraseListPOST>(phraseLists, oldPhraseLists, comparePhraseLists);
let stats: UpdateEvent = {
create: phraseListToBeCreated.length,
delete: phraseListIdsToBeDeleted.length
delete: phraseListsToBeDeleted.length
};
this.emit('startUpdatePhraseLists', stats);
return this.luisApiClient.deletePhraseLists(phraseListIdsToBeDeleted)
.then(() => this.luisApiClient.createPhraseLists(phraseListToBeCreated))
return this.luisApiClient.deletePhraseLists(appVersion, phraseListsToBeDeleted)
.then(() => this.luisApiClient.createPhraseLists(appVersion, phraseListToBeCreated))
.then(() => {

@@ -420,7 +441,7 @@ this.emit('endUpdatePhraseLists', stats);

private updateExamples(newExamples: LuisModel.Utterance[]): Promise<void> {
private updateExamples(appVersion: string, modelExamples: LuisModel.Utterance[]): Promise<void> {
this.emit('startGetAllExamples');
this.luisApiClient.on('getExamples', (first: number, last: number) =>
this.emit('getExamples', first, last));
return this.luisApiClient.getAllExamples()
return this.luisApiClient.getAllExamples(appVersion)
.then(oldExamples => {

@@ -431,5 +452,4 @@ this.emit('endGetAllExamples', oldExamples.length);

// but those will be overwritten when uploading the so called `examplesToBeCreated`.
let exampleIdsToBeDeleted = _.differenceWith(oldExamples, newExamples,
(a: LuisApi.LabeledUtterance, b: LuisModel.Utterance) => a.utteranceText === b.text
).map((example: LuisApi.LabeledUtterance) => example.id);
let examplesToBeDeleted = _.differenceWith<any>(oldExamples, modelExamples,
(a: LuisApi.ExampleGET, b: LuisModel.Utterance) => a.text === b.text);

@@ -439,11 +459,11 @@ // Examples to be uploaded (overwriting those that already exist with the same text although

// differs from existing one by the intent or entities.
let examplesToBeCreated = _.differenceWith(newExamples, oldExamples,
(a: LuisModel.Utterance, b: LuisApi.LabeledUtterance) => {
let eq = a.text === b.utteranceText &&
a.intent === b.intent &&
a.entities.length === b.entities.length &&
let examplesToBeCreated = _.differenceWith<any>(modelExamples, oldExamples,
(a: LuisModel.Utterance, b: LuisApi.ExampleGET) => {
let eq = a.text === b.text &&
a.intent === b.intentLabel &&
a.entities.length === b.entityLabels.length &&
// Compare array of entities w/o assuming the same order
a.entities.length === _.intersectionWith(a.entities, b.entities,
(ae: LuisModel.EntityPosition, be: LuisApi.LabeledEntity) =>
ae.entity === be.name && ae.startPos === be.startToken && ae.endPos === be.endToken
a.entities.length === _.intersectionWith<any>(a.entities, b.entityLabels,
(ae: LuisModel.EntityPosition, be: LuisApi.EntityLabelExamplePOST) =>
ae.entity === be.entityName && ae.startPos === be.startCharIndex && ae.endPos === be.endCharIndex
).length;

@@ -455,14 +475,16 @@ return eq;

.map((example: LuisModel.Utterance) => {
let entityLabels = example.entities.map(entity => {
let foundEntity = LuisTrainer.findEntity(example.text, entity.startPos, entity.endPos);
return {
entityName: entity.entity,
startCharIndex: foundEntity.startChar,
endCharIndex: foundEntity.endChar
};
});
entityLabels = (entityLabels.length === 0) ? null : entityLabels;
return {
exampleText: example.text,
selectedIntentName: example.intent,
entityLabels: example.entities.map(entity => {
let foundEntity = LuisTrainer.findEntity(example.text, entity.startPos, entity.endPos);
return {
entityType: entity.entity,
startToken: foundEntity.startChar,
endToken: foundEntity.endChar
} as LuisApi.Entity;
})
} as LuisApi.Example;
text: example.text,
intentName: example.intent,
entityLabels: entityLabels
} as LuisApi.ExamplePOST;
});

@@ -473,3 +495,4 @@

// require('fs').writeFileSync('old.txt', oldExamples.map(example => example.utteranceText).sort().join('\n'), 'utf-8');
// require('fs').writeFileSync('create.txt', examplesToBeCreated.map(example => example.exampleText).sort().join('\n'), 'utf-8');
// require('fs').writeFileSync('create.txt', examplesToBeCreated.map(example =>
// example.exampleText).sort().join('\n'), 'utf-8');
// let SENTENCE = '';

@@ -486,3 +509,3 @@ // console.log('='.repeat(50));

create: examplesToBeCreated.length,
delete: exampleIdsToBeDeleted.length
delete: examplesToBeDeleted.length
};

@@ -494,4 +517,4 @@ this.emit('startUpdateExamples', stats);

this.emit('createExampleBunch', bunchLength));
return this.luisApiClient.deleteExamples(exampleIdsToBeDeleted)
.then(() => this.luisApiClient.createExamples(examplesToBeCreated))
return this.luisApiClient.deleteExamples(appVersion, examplesToBeDeleted)
.then(() => this.luisApiClient.createExamples(appVersion, examplesToBeCreated))
.then(() => {

@@ -504,3 +527,3 @@ this.emit('endUpdateExamples', stats);

private train(): Promise<void> {
private train(appVersion: string): Promise<void> {
const delay = (t: number): Promise<void> => {

@@ -514,3 +537,3 @@ return new Promise<void>(resolve => {

return delay(TRAINING_STATUS_POLLING_INTERVAL)
.then(() => this.luisApiClient.getTrainingStatus())
.then(() => this.luisApiClient.getTrainingStatus(appVersion))
.then((trainingStatus: LuisApi.TrainingStatus) => {

@@ -523,3 +546,3 @@ // Debug stuff

modelStatus.status === LuisApi.TrainingStatuses.UpToDate ||
modelStatus.status === LuisApi.TrainingStatuses.Failed);
modelStatus.status === LuisApi.TrainingStatuses.Fail);
this.emit('trainingProgress', finishedModels.length, trainingStatus.length);

@@ -531,3 +554,3 @@ if (finishedModels.length < trainingStatus.length) {

let failedModels = trainingStatus
.filter(modelStatus => modelStatus.status === LuisApi.TrainingStatuses.Failed);
.filter(modelStatus => modelStatus.status === LuisApi.TrainingStatuses.Fail);
if (failedModels.length) {

@@ -548,3 +571,3 @@ let err = new Error(

this.emit('startTraining');
return this.luisApiClient.startTraining()
return this.luisApiClient.startTraining(appVersion)
.then((trainingStatus) => {

@@ -559,5 +582,5 @@ // Debug stuff

private publish(): Promise<void> {
private publish(appVersion: string): Promise<void> {
this.emit('startPublish');
return this.luisApiClient.publish()
return this.luisApiClient.publish(appVersion)
.then(publishResult => {

@@ -564,0 +587,0 @@ this.emit('endPublish');

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc