node-nlp
Version:
Library for NLU (Natural Language Understanding) done in Node.js
484 lines (483 loc) • 19.3 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var WaterfallDialog_1 = require("../dialogs/WaterfallDialog");
var ActionSet_1 = require("../dialogs/ActionSet");
var IntentRecognizerSet_1 = require("../dialogs/IntentRecognizerSet");
var Session_1 = require("../Session");
var consts = require("../consts");
var utils = require("../utils");
var events_1 = require("events");
var path = require("path");
var async = require("async");
var Library = (function (_super) {
__extends(Library, _super);
function Library(name) {
var _this = _super.call(this) || this;
_this.name = name;
_this.dialogs = {};
_this.libraries = {};
_this.actions = new ActionSet_1.ActionSet();
_this.recognizers = new IntentRecognizerSet_1.IntentRecognizerSet();
_this.triggersAdded = false;
return _this;
}
Library.prototype.clone = function (copyTo, newName) {
var obj = copyTo || new Library(newName || this.name);
for (var id in this.dialogs) {
obj.dialogs[id] = this.dialogs[id];
}
for (var name in this.libraries) {
obj.libraries[name] = this.libraries[name];
}
this.actions.clone(obj.actions);
this.recognizers.clone(obj.recognizers);
obj._localePath = this._localePath;
obj._onFindRoutes = this._onFindRoutes;
obj._onSelectRoute = this._onSelectRoute;
obj.triggersAdded = this.triggersAdded;
return obj;
};
Library.prototype.localePath = function (path) {
if (path) {
this._localePath = path;
}
return this._localePath;
};
Library.prototype.recognize = function (context, callback) {
var _this = this;
if (this.recognizers.length > 0 && context.libraryName !== this.name) {
this.recognizers.recognize(context, function (err, result) {
if (result && result.score > 0) {
context.logger.log(null, _this.logPrefix() + 'recognize() recognized: ' +
result.intent + '(' + result.score + ')');
}
callback(err, result);
});
}
else {
callback(null, context.intent || { intent: 'None', score: 0.0 });
}
};
Library.prototype.recognizer = function (plugin) {
this.recognizers.recognizer(plugin);
return this;
};
Library.prototype.findRoutes = function (context, callback) {
var _this = this;
if (!this.triggersAdded) {
this.forEachDialog(function (dialog, id) { return dialog.addDialogTrigger(_this.actions, _this.name + ':' + id); });
this.triggersAdded = true;
}
if (this._onFindRoutes) {
this._onFindRoutes(context, callback);
}
else {
this.defaultFindRoutes(context, callback);
}
};
Library.prototype.onFindRoutes = function (handler) {
this._onFindRoutes = handler;
};
Library.prototype.selectRoute = function (session, route) {
if (this._onSelectRoute) {
this._onSelectRoute(session, route);
}
else {
this.defaultSelectRoute(session, route);
}
};
Library.prototype.onSelectRoute = function (handler) {
this._onSelectRoute = handler;
};
Library.prototype.findActiveDialogRoutes = function (context, callback, dialogStack) {
var _this = this;
if (!dialogStack) {
dialogStack = context.dialogStack();
}
var results = Library.addRouteResult({ score: 0.0, libraryName: this.name });
var entry = Session_1.Session.activeDialogStackEntry(dialogStack);
var parts = entry ? entry.id.split(':') : null;
if (parts && parts[0] == this.name) {
var dialog = this.dialog(parts[1]);
if (dialog) {
var ctx = utils.clone(context);
ctx.libraryName = this.name;
ctx.dialogData = entry.state;
ctx.activeDialog = true;
dialog.recognize(ctx, function (err, result) {
if (!err) {
if (result.score < 0.1) {
result.score = 0.1;
}
callback(null, Library.addRouteResult({
score: result.score,
libraryName: _this.name,
routeType: Library.RouteTypes.ActiveDialog,
routeData: result
}, results));
}
else {
callback(err, null);
}
});
}
else {
context.logger.warn(context.dialogStack(), "Active dialog '" + entry.id + "' not found in library.");
callback(null, results);
}
}
else {
callback(null, results);
}
};
Library.prototype.selectActiveDialogRoute = function (session, route, newStack) {
if (!route || route.libraryName !== this.name || route.routeType !== Library.RouteTypes.ActiveDialog) {
throw new Error('Invalid route type passed to Library.selectActiveDialogRoute().');
}
if (newStack) {
session.dialogStack(newStack);
}
session.routeToActiveDialog(route.routeData);
};
Library.prototype.findStackActionRoutes = function (context, callback, dialogStack) {
var _this = this;
if (!dialogStack) {
dialogStack = context.dialogStack();
}
var results = Library.addRouteResult({ score: 0.0, libraryName: this.name });
var ctx = utils.clone(context);
ctx.libraryName = this.name;
ctx.routeType = Library.RouteTypes.StackAction;
async.forEachOf(dialogStack || [], function (entry, index, next) {
var parts = entry.id.split(':');
if (parts[0] == _this.name) {
var dialog = _this.dialog(parts[1]);
if (dialog) {
dialog.findActionRoutes(ctx, function (err, ra) {
if (!err) {
for (var i = 0; i < ra.length; i++) {
var r = ra[i];
if (r.routeData) {
r.routeData.dialogId = entry.id;
r.routeData.dialogIndex = index;
}
results = Library.addRouteResult(r, results);
}
}
next(err);
});
}
else {
ctx.logger.warn(ctx.dialogStack(), "Dialog '" + entry.id + "' not found in library.");
next(null);
}
}
else {
next(null);
}
}, function (err) {
if (!err) {
callback(null, results);
}
else {
callback(err, null);
}
});
};
Library.prototype.selectStackActionRoute = function (session, route, newStack) {
if (!route || route.libraryName !== this.name || route.routeType !== Library.RouteTypes.StackAction) {
throw new Error('Invalid route type passed to Library.selectStackActionRoute().');
}
if (newStack) {
session.dialogStack(newStack);
}
var routeData = route.routeData;
var parts = routeData.dialogId.split(':');
this.dialog(parts[1]).selectActionRoute(session, route);
};
Library.prototype.findGlobalActionRoutes = function (context, callback) {
var results = Library.addRouteResult({ score: 0.0, libraryName: this.name });
var ctx = utils.clone(context);
ctx.libraryName = this.name;
ctx.routeType = Library.RouteTypes.GlobalAction;
this.actions.findActionRoutes(ctx, function (err, ra) {
if (!err) {
for (var i = 0; i < ra.length; i++) {
var r = ra[i];
results = Library.addRouteResult(r, results);
}
callback(null, results);
}
else {
callback(err, null);
}
});
};
Library.prototype.selectGlobalActionRoute = function (session, route) {
if (!route || route.libraryName !== this.name || route.routeType !== Library.RouteTypes.GlobalAction) {
throw new Error('Invalid route type passed to Library.selectGlobalActionRoute().');
}
this.actions.selectActionRoute(session, route);
};
Library.prototype.defaultFindRoutes = function (context, callback) {
var _this = this;
var explain = '';
var results = Library.addRouteResult({ score: 0.0, libraryName: this.name });
this.recognize(context, function (err, topIntent) {
if (!err) {
var ctx = utils.clone(context);
ctx.intent = topIntent && topIntent.score > 0 ? topIntent : null;
ctx.libraryName = _this.name;
async.parallel([
function (cb) {
_this.findActiveDialogRoutes(ctx, function (err, routes) {
if (!err && routes) {
routes.forEach(function (r) {
results = Library.addRouteResult(r, results);
if (r.score > 0) {
explain += '\n\tActiveDialog(' + r.score + ')';
}
});
}
cb(err);
});
},
function (cb) {
_this.findStackActionRoutes(ctx, function (err, routes) {
if (!err && routes) {
routes.forEach(function (r) {
results = Library.addRouteResult(r, results);
if (r.score > 0) {
explain += '\n\tStackAction(' + r.score + ')';
}
});
}
cb(err);
});
},
function (cb) {
_this.findGlobalActionRoutes(ctx, function (err, routes) {
if (!err && routes) {
routes.forEach(function (r) {
results = Library.addRouteResult(r, results);
if (r.score > 0) {
explain += '\n\tGlobalAction(' + r.score + ')';
}
});
}
cb(err);
});
}
], function (err) {
if (!err) {
if (explain.length > 0) {
context.logger.log(null, _this.logPrefix() + '.findRoutes() explanation:' + explain);
}
callback(null, results);
}
else {
callback(err, null);
}
});
}
else {
callback(err, null);
}
});
};
Library.prototype.defaultSelectRoute = function (session, route) {
switch (route.routeType || '') {
case Library.RouteTypes.ActiveDialog:
this.selectActiveDialogRoute(session, route);
break;
case Library.RouteTypes.StackAction:
this.selectStackActionRoute(session, route);
break;
case Library.RouteTypes.GlobalAction:
this.selectGlobalActionRoute(session, route);
break;
default:
throw new Error('Invalid route type passed to Library.selectRoute().');
}
};
Library.addRouteResult = function (route, current) {
if (!current || current.length < 1 || route.score > current[0].score) {
current = [route];
}
else if (route.score == current[0].score) {
current.push(route);
}
return current;
};
Library.bestRouteResult = function (routes, dialogStack, rootLibraryName) {
var bestLibrary = rootLibraryName;
if (dialogStack) {
dialogStack.forEach(function (entry) {
var parts = entry.id.split(':');
for (var i = 0; i < routes.length; i++) {
if (routes[i].libraryName == parts[0]) {
bestLibrary = parts[0];
break;
}
}
});
}
var best;
var bestPriority = 5;
for (var i = 0; i < routes.length; i++) {
var r = routes[i];
if (r.score > 0.0) {
var priority;
switch (r.routeType) {
default:
priority = 1;
break;
case Library.RouteTypes.ActiveDialog:
priority = 2;
break;
case Library.RouteTypes.StackAction:
priority = 3;
break;
case Library.RouteTypes.GlobalAction:
priority = 4;
break;
}
if (priority < bestPriority) {
best = r;
bestPriority = priority;
}
else if (priority == bestPriority) {
switch (priority) {
case 3:
if (r.routeData.dialogIndex > best.routeData.dialogIndex) {
best = r;
}
break;
case 4:
if (bestLibrary && best.libraryName !== bestLibrary && r.libraryName == bestLibrary) {
best = r;
}
break;
}
}
}
}
return best;
};
Library.prototype.dialog = function (id, dialog, replace) {
var d;
if (dialog) {
if (id.indexOf(':') >= 0) {
id = id.split(':')[1];
}
if (this.dialogs.hasOwnProperty(id) && !replace) {
throw new Error("Dialog[" + id + "] already exists in library[" + this.name + "].");
}
if (Array.isArray(dialog) || typeof dialog === 'function') {
d = new WaterfallDialog_1.WaterfallDialog(dialog);
}
else {
d = dialog;
}
this.dialogs[id] = d;
}
else if (this.dialogs.hasOwnProperty(id)) {
d = this.dialogs[id];
}
return d;
};
Library.prototype.findDialog = function (libName, dialogId) {
var d;
var lib = this.library(libName);
if (lib) {
d = lib.dialog(dialogId);
}
return d;
};
Library.prototype.forEachDialog = function (callback) {
for (var id in this.dialogs) {
callback(this.dialog(id), id);
}
};
Library.prototype.library = function (lib) {
var l;
if (typeof lib === 'string') {
if (lib == this.name) {
l = this;
}
else if (this.libraries.hasOwnProperty(lib)) {
l = this.libraries[lib];
}
else {
for (var name in this.libraries) {
l = this.libraries[name].library(lib);
if (l) {
break;
}
}
}
}
else {
l = this.libraries[lib.name] = lib;
}
return l;
};
Library.prototype.forEachLibrary = function (callback) {
for (var lib in this.libraries) {
callback(this.libraries[lib]);
}
};
Library.prototype.libraryList = function (reverse) {
if (reverse === void 0) { reverse = false; }
var list = [];
var added = {};
function addChildren(lib) {
if (!added.hasOwnProperty(lib.name)) {
added[lib.name] = true;
if (!reverse) {
list.push(lib);
}
lib.forEachLibrary(function (child) { return addChildren(child); });
if (reverse) {
list.push(lib);
}
}
}
addChildren(this);
return list;
};
Library.prototype.beginDialogAction = function (name, id, options) {
this.actions.beginDialogAction(name, id, options);
return this;
};
Library.prototype.endConversationAction = function (name, msg, options) {
this.actions.endConversationAction(name, msg, options);
return this;
};
Library.prototype.customAction = function (options) {
this.actions.customAction(options);
return this;
};
Library.prototype.logPrefix = function () {
return 'Library("' + this.name + '")';
};
Library.RouteTypes = {
GlobalAction: 'GlobalAction',
StackAction: 'StackAction',
ActiveDialog: 'ActiveDialog'
};
return Library;
}(events_1.EventEmitter));
exports.Library = Library;
exports.systemLib = new Library(consts.Library.system);
exports.systemLib.localePath(path.join(__dirname, '../locale/'));