wingbot 3.42.1 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.42.1",
3
+ "version": "3.43.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,10 +11,12 @@ const { vars } = require('../../src/utils/stateVariables');
11
11
  /**
12
12
  * @param {object} params
13
13
  * @param {string} [params.skip]
14
+ * @param {string} [params.fill]
14
15
  * @returns {SlotsResolver}
15
16
  */
16
17
  function slotsContinue ({
17
- skip
18
+ skip,
19
+ fill
18
20
  }) {
19
21
 
20
22
  /**
@@ -39,6 +41,10 @@ function slotsContinue ({
39
41
  .split(',')
40
42
  .map((e) => e.trim());
41
43
 
44
+ const fillEntities = compileWithState(req, res, fill)
45
+ .split(',')
46
+ .map((e) => e.trim());
47
+
42
48
  const clear = {};
43
49
 
44
50
  slotState = slotState.map((s) => {
@@ -52,7 +58,7 @@ function slotsContinue ({
52
58
  return { ...s, s: StepState.INITIALIZED };
53
59
  }
54
60
 
55
- return s.e === step.entity
61
+ return s.e === step.entity || fillEntities.includes(s.e)
56
62
  ? { ...s, s: StepState.FILLED }
57
63
  : s;
58
64
  });
@@ -415,7 +415,7 @@
415
415
  {
416
416
  "type": "select",
417
417
  "name": "type",
418
- "label": "Question",
418
+ "label": "Type",
419
419
  "options": [
420
420
  { "label": "Required", "value": "req" },
421
421
  { "label": "Multi value", "value": "mul" },
@@ -454,6 +454,18 @@
454
454
  "validations": [
455
455
  { "type": "regexp", "value": "^\\s*@[a-zA-Z0-9-]+\\s*(,\\s*@[a-zA-Z0-9-]+\\s*)*$", "message": "the entity for the slot filling should be valid" }
456
456
  ]
457
+ },
458
+ {
459
+ "name": "fill",
460
+ "label": "Skip entity (mark as filled)",
461
+ "type": "text",
462
+ "validations": [
463
+ {
464
+ "type": "regexp",
465
+ "value": "^(\\s*@[a-zA-Z0-9-]+\\s*,?)*$",
466
+ "message": "the entity to skip should be valid"
467
+ }
468
+ ]
457
469
  }
458
470
  ],
459
471
  "items": [
package/src/Ai.js CHANGED
@@ -7,12 +7,15 @@ const { WingbotModel } = require('./wingbot');
7
7
  const AiMatching = require('./AiMatching');
8
8
  const { vars } = require('./utils/stateVariables');
9
9
  const { deepEqual } = require('./utils/deepMapTools');
10
+ const systemEntities = require('./systemEntities');
10
11
  const CustomEntityDetectionModel = require('./wingbot/CustomEntityDetectionModel');
11
12
 
12
13
  const DEFAULT_PREFIX = 'default';
13
14
 
14
15
  let uq = 1;
15
16
 
17
+ /** @typedef {import('./AiMatching').Compare} Compare */
18
+
16
19
  /**
17
20
  * @typedef {object} EntityExpression
18
21
  * @prop {string} entity - the requested entity
@@ -43,6 +46,8 @@ let uq = 1;
43
46
  /** @typedef {import('./Responder')} Responder */
44
47
  /** @typedef {import('./wingbot/CachedModel').Result} Result */
45
48
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').Phrases} Phrases */
49
+ /** @typedef {import('./wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
50
+ /** @typedef {import('./wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
46
51
 
47
52
  /**
48
53
  * @class Ai
@@ -50,8 +55,20 @@ let uq = 1;
50
55
  class Ai {
51
56
 
52
57
  constructor () {
58
+ /**
59
+ * @private
60
+ * @type {Map<string,CustomEntityDetectionModel>}
61
+ */
53
62
  this._keyworders = new Map();
54
63
 
64
+ /**
65
+ * @private
66
+ * @type {Map<string,[string,EntityDetector|RegExp,DetectorOptions]>}
67
+ */
68
+ this._detectors = new Map(
69
+ systemEntities.map((a) => [a[0], a])
70
+ );
71
+
55
72
  /**
56
73
  * Upper threshold - for match method and for navigate method
57
74
  *
@@ -157,29 +174,86 @@ class Ai {
157
174
  /**
158
175
  * Registers Wingbot AI model
159
176
  *
160
- * @template T
177
+ * @template {CustomEntityDetectionModel} T
161
178
  * @param {string|WingbotModel|T} model - wingbot model name or AI plugin
162
179
  * @param {string} prefix - model prefix
163
180
  *
164
- * @returns {WingbotModel|T}
181
+ * @returns {T}
165
182
  * @memberOf Ai
166
183
  */
167
184
  register (model, prefix = 'default') {
185
+ /** @type {T} */
168
186
  let modelObj;
169
187
 
170
188
  if (typeof model === 'string') {
189
+ // @ts-ignore
171
190
  modelObj = new WingbotModel({
172
191
  model
173
192
  }, this.logger);
174
193
  } else {
194
+ // @ts-ignore
175
195
  modelObj = model;
176
196
  }
177
197
 
178
198
  this._keyworders.set(prefix, modelObj);
179
199
 
200
+ for (const entityArgs of this._detectors.values()) {
201
+ modelObj.setEntityDetector(...entityArgs);
202
+ }
203
+
180
204
  return modelObj;
181
205
  }
182
206
 
207
+ /**
208
+ *
209
+ * @param {string} name
210
+ * @param {EntityDetector|RegExp} detector
211
+ * @param {object} [options]
212
+ * @param {boolean} [options.anonymize] - if true, value will not be sent to NLP
213
+ * @param {Function|string} [options.extractValue] - entity extractor
214
+ * @param {boolean} [options.matchWholeWords] - match whole words at regular expression
215
+ * @param {boolean} [options.replaceDiacritics] - keep diacritics when matching regexp
216
+ * @param {string[]} [options.dependencies] - array of dependent entities
217
+ * @param {boolean} [options.clearOverlaps] - let longer entities from NLP to replace entity
218
+ * @returns {this}
219
+ */
220
+ registerEntityDetector (name, detector, options = {}) {
221
+ const useOptions = { clearOverlaps: true, ...options };
222
+
223
+ this._detectors.set(name, [name, detector, useOptions]);
224
+
225
+ for (const model of this._keyworders.values()) {
226
+ model.setEntityDetector(name, detector, useOptions);
227
+ }
228
+
229
+ return this;
230
+ }
231
+
232
+ /**
233
+ * Sets options to entity detector.
234
+ * Useful for disabling anonymization of local system entities.
235
+ *
236
+ * @param {string} name
237
+ * @param {object} options
238
+ * @param {boolean} [options.anonymize]
239
+ * @returns {this}
240
+ * @example
241
+ *
242
+ * ai.register('wingbot-model-name')
243
+ * .setDetectorOptions('phone', { anonymize: false })
244
+ * .setDetectorOptions('email', { anonymize: false })
245
+ */
246
+ configureEntityDetector (name, options) {
247
+ if (!this._detectors.has(name)) {
248
+ throw new Error(`Can't set entity detector options. Entity "${name}" does not exist.`);
249
+ }
250
+ Object.assign(this._detectors.get(name)[2], options);
251
+ for (const model of this._keyworders.values()) {
252
+ model.setDetectorOptions(name, options);
253
+ }
254
+ return this;
255
+ }
256
+
183
257
  /**
184
258
  * Remove registered model
185
259
  *
@@ -194,7 +268,7 @@ class Ai {
194
268
  *
195
269
  * @param {string} prefix - model prefix
196
270
  *
197
- * @returns {WingbotModel}
271
+ * @returns {CustomEntityDetectionModel}
198
272
  * @memberOf Ai
199
273
  */
200
274
  getModel (prefix = 'default') {
@@ -3,6 +3,11 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
+
9
+ /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
6
10
  module.exports = ['email', /(?<=(\s|^|:))[a-zA-Z0-9!#$%&'*+\-=?^_`{|}~"][^@:\s]*@[^.@\s]+\.[^@\s,]+/, {
7
- anonymize: true
11
+ anonymize: true,
12
+ clearOverlaps: true
8
13
  }];
@@ -3,11 +3,16 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
+
9
+ /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
6
10
  module.exports = [
7
11
  'phone',
8
12
  /((00|\+)[\s-]?[0-9]{1,4}[\s-]?)?([0-9]{3,4}[\s-]?([0-9]{2,3}[\s-]?[0-9]{2}[\s-]?[0-9]{2,3}|[0-9]{3,4}[\s-]?[0-9]{3,4}))(?=(\s|$|[,!.?\-:]))/,
9
13
  {
10
14
  anonymize: true,
15
+ clearOverlaps: true,
11
16
  extractValue: (match) => {
12
17
  let [, internat,, number] = match;
13
18
 
@@ -35,7 +35,7 @@ class CachedModel extends CustomEntityDetectionModel {
35
35
  * @param {object} options
36
36
  * @param {number} [options.cacheSize]
37
37
  * @param {number} [options.cachePhrasesTime]
38
- * @param {{ warn: Function, error: Function }} [log]
38
+ * @param {{ warn: Function, error: Function, log: Function }} [log]
39
39
  */
40
40
  constructor (options, log = console) {
41
41
  super(options, log);
@@ -88,11 +88,19 @@ class CachedModel extends CustomEntityDetectionModel {
88
88
  const res = await promise;
89
89
  let { intents = [], entities = [] } = Array.isArray(res) ? { intents: res } : res;
90
90
 
91
- entities = [...entities, ...local.entities];
92
- intents = intents.map((i) => ({
93
- ...i,
94
- entities: [...(i.entities || []), ...local.entities]
95
- }));
91
+ const expectedEntities = req ? req.expectedEntities() : [];
92
+
93
+ const before = local.entities
94
+ .filter((e) => this._entityDetectors.has(e.entity)
95
+ && this._entityDetectors.get(e.entity).clearOverlaps);
96
+
97
+ [intents, entities] = this._attachEntities(intents, entities, before, expectedEntities);
98
+
99
+ const after = local.entities
100
+ .filter((e) => !this._entityDetectors.has(e.entity)
101
+ || !this._entityDetectors.get(e.entity).clearOverlaps);
102
+
103
+ [intents, entities] = this._attachEntities(intents, entities, after);
96
104
 
97
105
  return {
98
106
  text: local.text,
@@ -101,6 +109,26 @@ class CachedModel extends CustomEntityDetectionModel {
101
109
  };
102
110
  }
103
111
 
112
+ _attachEntities (intents, entities, attachEntities, expectedEntities = null) {
113
+ const retEntities = [...entities, ...attachEntities];
114
+ const retIntents = intents
115
+ .map((i) => {
116
+ const ents = [...(i.entities || []), ...attachEntities];
117
+
118
+ return {
119
+ ...i,
120
+ entities: expectedEntities
121
+ ? this.nonOverlapping(ents, expectedEntities)
122
+ : ents
123
+ };
124
+ });
125
+
126
+ return [
127
+ retIntents,
128
+ expectedEntities ? this.nonOverlapping(retEntities, expectedEntities) : retEntities
129
+ ];
130
+ }
131
+
104
132
  async getPhrases () {
105
133
  if (this._phrasesCachedAt > (Date.now() - this.phrasesCacheTime)) {
106
134
  return this._phrasesCache;
@@ -51,6 +51,16 @@ const { replaceDiacritics } = require('../utils');
51
51
  * @prop {Intent[]} intents
52
52
  */
53
53
 
54
+ /**
55
+ * @typedef {object} DetectorOptions
56
+ * @prop {boolean} [anonymize] - if true, value will not be sent to NLP
57
+ * @prop {Function|string} [extractValue] - entity extractor
58
+ * @prop {boolean} [matchWholeWords] - match whole words at regular expression
59
+ * @prop {boolean} [replaceDiacritics] - keep diacritics when matching regexp
60
+ * @prop {string[]} [dependencies] - array of dependent entities
61
+ * @prop {boolean} [clearOverlaps] - let longer entities from NLP to replace entity
62
+ */
63
+
54
64
  /**
55
65
  * @typedef {object} Phrases
56
66
  * @prop {Map<string,string[]>} phrases
@@ -70,7 +80,7 @@ class CustomEntityDetectionModel {
70
80
 
71
81
  /**
72
82
  * @param {object} options
73
- * @param {{ warn: Function, error: Function }} [log]
83
+ * @param {{ warn: Function, error: Function, log: Function }} [log]
74
84
  */
75
85
  constructor (options, log = console) {
76
86
  this._options = options;
@@ -582,12 +592,7 @@ class CustomEntityDetectionModel {
582
592
  *
583
593
  * @param {string} name
584
594
  * @param {EntityDetector|RegExp} detector
585
- * @param {object} [options]
586
- * @param {boolean} [options.anonymize] - if true, value will not be sent to NLP
587
- * @param {Function|string} [options.extractValue] - entity extractor
588
- * @param {boolean} [options.matchWholeWords] - match whole words at regular expression
589
- * @param {boolean} [options.replaceDiacritics] - keep diacritics when matching regexp
590
- * @param {string[]} [options.dependencies] - array of dependent entities
595
+ * @param {DetectorOptions} [options]
591
596
  * @returns {this}
592
597
  */
593
598
  setEntityDetector (name, detector, options = {}) {
@@ -610,7 +615,8 @@ class CustomEntityDetectionModel {
610
615
  entityDetector,
611
616
  detector,
612
617
  dependencies,
613
- anonymize: !!options.anonymize
618
+ anonymize: !!options.anonymize,
619
+ clearOverlaps: !!options.clearOverlaps
614
620
  });
615
621
 
616
622
  return this;
@@ -623,6 +629,7 @@ class CustomEntityDetectionModel {
623
629
  * @param {string} name
624
630
  * @param {object} options
625
631
  * @param {boolean} [options.anonymize]
632
+ * @param {boolean} [options.clearOverlaps]
626
633
  * @returns {this}
627
634
  * @example
628
635
  *
@@ -632,7 +639,7 @@ class CustomEntityDetectionModel {
632
639
  */
633
640
  setDetectorOptions (name, options) {
634
641
  if (!this._entityDetectors.has(name)) {
635
- throw new Error('Can\'t set entity detector options. Entity "name" does not exist.');
642
+ throw new Error(`Can't set entity detector options. Entity "${name}" does not exist.`);
636
643
  }
637
644
  Object.assign(this._entityDetectors.get(name), options);
638
645
  return this;
@@ -3,7 +3,6 @@
3
3
  const { default: fetch } = require('node-fetch');
4
4
  const assert = require('assert');
5
5
  const CachedModel = require('./CachedModel');
6
- const systemEntities = require('../systemEntities');
7
6
 
8
7
  const DEFAULT_MATCHES = 3;
9
8
  const SERVICE_URL = 'https://model.wingbot.ai';
@@ -42,7 +41,7 @@ class WingbotModel extends CachedModel {
42
41
  * @param {number} [options.cacheSize]
43
42
  * @param {number} [options.matches]
44
43
  * @param {Function} [options.fetch]
45
- * @param {{ warn: Function, log: Function }} [log]
44
+ * @param {{ warn: Function, log: Function, error: Function }} [log]
46
45
  */
47
46
  constructor (options, log = console) {
48
47
  super(options, log);
@@ -60,11 +59,6 @@ class WingbotModel extends CachedModel {
60
59
  this._serviceUrl = options.serviceUrl || SERVICE_URL;
61
60
  this._trainingUrl = options.trainingUrl || TRAINING_URL;
62
61
  this._model = options.model;
63
-
64
- // apply default entities
65
- systemEntities
66
- // @ts-ignore
67
- .forEach(([name, d, opts = {}]) => this.setEntityDetector(name, d, opts));
68
62
  }
69
63
 
70
64
  async _getPhrases () {