wingbot 3.53.5 → 3.55.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.53.5",
3
+ "version": "3.55.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1 @@
1
+ > This plugin allows you to use chatGPT
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @author wingbot.ai
3
+ */
4
+ 'use strict';
5
+
6
+ const fetch = require('node-fetch').default;
7
+ const compileWithState = require('../../src/utils/compileWithState');
8
+
9
+ function chatgptPlugin (params) {
10
+
11
+ async function chatgpt (req, res) {
12
+ const content = req.text();
13
+
14
+ const token = compileWithState(req, res, params.token).trim();
15
+
16
+ // gpt-3.5-turbo-0301 gpt-3.5-turbo
17
+ const model = compileWithState(req, res, params.model) || 'gpt-3.5-turbo';
18
+
19
+ const system = compileWithState(req, res, params.system).trim();
20
+
21
+ // 0 - 2
22
+ const temperature = parseFloat(compileWithState(req, res, params.temperature).trim().replace(',', '.') || '1') || 1;
23
+ // presence_penalty between -2.0 and 2.0
24
+ const presence = parseFloat(compileWithState(req, res, params.presence).trim().replace(',', '.') || '0') || 0;
25
+
26
+ const maxTokens = parseFloat(compileWithState(req, res, params.maxTokens).trim() || '512') || 512;
27
+
28
+ const limit = parseInt(compileWithState(req, res, params.maxTokens).trim() || '10', 10) || 10;
29
+
30
+ const user = `${req.pageId}|${req.senderId}`;
31
+
32
+ const systemAfter = compileWithState(req, res, params.systemAfter).trim();
33
+
34
+ let body;
35
+
36
+ try {
37
+ res.typingOn();
38
+
39
+ body = {
40
+ model,
41
+ frequency_penalty: 0,
42
+ presence_penalty: presence,
43
+ max_tokens: maxTokens,
44
+ temperature,
45
+ user
46
+ };
47
+
48
+ const ts = await res.getTranscript(limit);
49
+
50
+ const messages = [
51
+ ...(system ? [{ role: 'system', content: system }] : []),
52
+ ...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
53
+ { role: 'user', content },
54
+ ...(systemAfter ? [{ role: 'system', content: systemAfter }] : [])
55
+ ];
56
+
57
+ Object.assign(body, { messages });
58
+
59
+ const useFetch = params.fetch || fetch;
60
+
61
+ const response = await useFetch('https://api.openai.com/v1/chat/completions', {
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ Authorization: `Bearer ${token}`
66
+ },
67
+ body: JSON.stringify(body)
68
+ });
69
+
70
+ const data = await response.json();
71
+
72
+ if (response.status !== 200
73
+ || !Array.isArray(data.choices)) {
74
+ const { status, statusText } = response;
75
+ // eslint-disable-next-line no-console
76
+ console.log('chat gpt error', { status, statusText, data });
77
+ throw new Error(`Chat GPT ${status}`);
78
+ }
79
+
80
+ // eslint-disable-next-line no-console
81
+ console.log('chat gpt', JSON.stringify(data));
82
+
83
+ const sent = data.choices
84
+ .filter((ch) => ch.message && ch.message.role === 'assistant' && ch.message.content)
85
+ .map((ch) => {
86
+ let filtered = ch.message.content
87
+ .replace(/\n\n/g, '\n')
88
+ .split(/\n+(?!-)/g)
89
+ .filter((t) => !!t.trim());
90
+
91
+ if (filtered.length > 2) {
92
+ filtered = filtered.slice(0, filtered.length - 1);
93
+ }
94
+
95
+ filtered
96
+ .forEach((t) => res.text(t.trim()));
97
+
98
+ return ch;
99
+ });
100
+
101
+ if (sent.length === 0) {
102
+ const { status, statusText } = response;
103
+ // eslint-disable-next-line no-console
104
+ console.log('chat gpt nothing to send', { status, statusText, data });
105
+ throw new Error('Chat GPT empty');
106
+ }
107
+
108
+ } catch (e) {
109
+ // eslint-disable-next-line no-console
110
+ console.error('chat gpt fail', e, body);
111
+ await res.run('fallback');
112
+ }
113
+
114
+ }
115
+
116
+ return chatgpt;
117
+ }
118
+
119
+ module.exports = chatgptPlugin;
@@ -484,6 +484,86 @@
484
484
  ],
485
485
  "items": [
486
486
  ]
487
+ },
488
+ {
489
+ "id": "ai.wingbot.openai",
490
+ "name": "OpenAI - Chat",
491
+ "description": "Use within fallback to attach ChatGPT to your chatbot",
492
+ "availableSince": 3.54,
493
+ "editable": false,
494
+ "isFactory": true,
495
+ "category": "conversation",
496
+ "inputs": [
497
+ {
498
+ "name": "token",
499
+ "label": "API Key",
500
+ "type": "text",
501
+ "validations": [
502
+ { "type": "regexp", "value": "^.+$", "message": "the token should be filled" }
503
+ ]
504
+ },
505
+ {
506
+ "type": "select",
507
+ "name": "model",
508
+ "label": "Chat GPT model",
509
+ "options": [
510
+ { "value": "gpt-3.5-turbo", "label": "gpt-3.5-turbo" },
511
+ { "value": "gpt-3.5-turbo-0301", "label": "gpt-3.5-turbo-0301" }
512
+ ]
513
+ },
514
+ {
515
+ "type": "textarea",
516
+ "name": "system",
517
+ "label": "Initial preset message (system)"
518
+ },
519
+ {
520
+ "type": "text",
521
+ "name": "temperature",
522
+ "label": "Temperature: 0.0 - 2.0 (1.0) - randomness of answers",
523
+ "validations": [
524
+ {
525
+ "type": "regexp",
526
+ "value": "^[0-9]*([.,][0-9]+)?$",
527
+ "message": "Temperature should be a valid float number"
528
+ }
529
+ ]
530
+ },
531
+ {
532
+ "type": "text",
533
+ "name": "presence",
534
+ "label": "Presence: -2.0 - 2.0 (0.0) - likelihood to talk about new topics",
535
+ "validations": [
536
+ {
537
+ "type": "regexp",
538
+ "value": "^-?[0-9]*([.,][0-9]+)?$",
539
+ "message": "Presence should be a valid float number"
540
+ }
541
+ ]
542
+ },
543
+ {
544
+ "type": "text",
545
+ "name": "maxTokens",
546
+ "label": "Max Tokens: 0 - 4096 (512) - max. number of returned tokens",
547
+ "validations": [
548
+ {
549
+ "type": "regexp",
550
+ "value": "^[0-9]*$",
551
+ "message": "Max tokens should be a valid number"
552
+ }
553
+ ]
554
+ },
555
+ {
556
+ "type": "textarea",
557
+ "name": "systemAfter",
558
+ "label": "Current message (system)"
559
+ }
560
+ ],
561
+ "items": [
562
+ {
563
+ "id": "fallback",
564
+ "description": "If API call fails"
565
+ }
566
+ ]
487
567
  }
488
568
  ],
489
569
  "categories": [
package/src/Processor.js CHANGED
@@ -58,6 +58,8 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
58
58
  * @prop {object} state
59
59
  * @prop {object} data
60
60
  * @prop {string|null} skill
61
+ * @prop {string|null} prevSkill
62
+ * @prop {string|null} pathname
61
63
  * @prop {TrackingObject} tracking - deprecated
62
64
  * @prop {TrackingEvent[]} events
63
65
  * @prop {ResponseFlag|null} flag
@@ -423,11 +425,14 @@ class Processor extends EventEmitter {
423
425
  */
424
426
  _emitInteractionEvent (req, res, messageSender, state, data) {
425
427
  const doNotTrack = data._initialEventShouldNotBeTracked === true;
426
- const { _lastAction: lastAction = null } = req.state;
428
+ const { _lastAction: lastAction = null, '§pathname': pathname = null } = req.state;
427
429
  const actions = messageSender.visitedInteractions;
428
430
  const skill = typeof res.newState._trackAsSkill === 'undefined'
429
431
  ? (req.state._trackAsSkill || null)
430
432
  : res.newState._trackAsSkill;
433
+ const prevSkill = typeof res.newState._trackPrevSkill === 'undefined'
434
+ ? (req.state._trackPrevSkill || null)
435
+ : res.newState._trackPrevSkill;
431
436
  const { events = [] } = messageSender.tracking;
432
437
 
433
438
  const event = {
@@ -439,6 +444,8 @@ class Processor extends EventEmitter {
439
444
  state,
440
445
  data,
441
446
  skill,
447
+ prevSkill,
448
+ pathname,
442
449
  tracking: messageSender.tracking,
443
450
  events,
444
451
  flag: res.senderMeta.flag,
@@ -646,7 +653,8 @@ class Processor extends EventEmitter {
646
653
  const options = {
647
654
  ...this.options,
648
655
  state: Object.freeze({ ...state }),
649
- features
656
+ features,
657
+ pageId
650
658
  };
651
659
 
652
660
  res = new Responder(senderId, messageSender, token, options, responderData);
package/src/Responder.js CHANGED
@@ -16,6 +16,7 @@ const {
16
16
  FEATURE_SSML,
17
17
  FEATURE_PHRASES
18
18
  } = require('./features');
19
+ const transcriptFromHistory = require('./transcript/transcriptFromHistory');
19
20
 
20
21
  const TYPE_RESPONSE = 'RESPONSE';
21
22
  const TYPE_UPDATE = 'UPDATE';
@@ -26,6 +27,7 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
26
27
  /** @typedef {import('./ReturnSender').UploadResult} UploadResult */
27
28
  /** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
28
29
  /** @typedef {import('./analytics/consts').TrackingType} TrackingType */
30
+ /** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
29
31
 
30
32
  /**
31
33
  * @enum {string} ExpectedInput
@@ -89,6 +91,7 @@ class Responder {
89
91
  constructor (senderId, messageSender, token = null, options = {}, data = {}) {
90
92
  this._messageSender = messageSender;
91
93
  this._senderId = senderId;
94
+ this._pageId = options.pageId;
92
95
  this.token = token;
93
96
 
94
97
  /**
@@ -178,6 +181,33 @@ class Responder {
178
181
  this._recipient = { id: senderId };
179
182
 
180
183
  this._textResponses = [];
184
+
185
+ this._typingSent = false;
186
+ }
187
+
188
+ /**
189
+ *
190
+ * Returns current conversation transcript
191
+ *
192
+ * @param {number} [limit]
193
+ * @returns {Promise<Transcript[]>}
194
+ */
195
+ async getTranscript (limit = 10) {
196
+ const { chatLogStorage } = this._messageSender;
197
+ if (!chatLogStorage) {
198
+ return [];
199
+ }
200
+ const transcript = await transcriptFromHistory(
201
+ chatLogStorage,
202
+ this._senderId,
203
+ this._pageId,
204
+ limit
205
+ );
206
+ const { responseTexts = [] } = chatLogStorage;
207
+ transcript.push(...responseTexts.map((text) => ({
208
+ fromBot: true, text
209
+ })));
210
+ return transcript;
181
211
  }
182
212
 
183
213
  /**
@@ -287,6 +317,7 @@ class Responder {
287
317
  });
288
318
  }
289
319
  this.startedOutput = true;
320
+ this._typingSent = data.sender_action === 'typing_on';
290
321
  this._messageSender.send(data);
291
322
  return this;
292
323
  }
@@ -979,10 +1010,11 @@ class Responder {
979
1010
  /**
980
1011
  * Sends "typing..." information
981
1012
  *
1013
+ * @param {boolean} [force] - send even if was recently sent
982
1014
  * @returns {this}
983
1015
  */
984
- typingOn () {
985
- return this._senderAction('typing_on');
1016
+ typingOn (force = false) {
1017
+ return this._senderAction('typing_on', force);
986
1018
  }
987
1019
 
988
1020
  /**
@@ -1230,7 +1262,15 @@ class Responder {
1230
1262
  * @returns {this}
1231
1263
  */
1232
1264
  trackAsSkill (skill) {
1233
- this.setState({ _trackAsSkill: skill });
1265
+ // @ts-ignore
1266
+ const { _trackAsSkill: currentSkill } = this.options.state;
1267
+ const setState = { _trackAsSkill: skill };
1268
+ if (currentSkill && currentSkill !== skill) {
1269
+ Object.assign(setState, {
1270
+ _trackPrevSkill: currentSkill
1271
+ });
1272
+ }
1273
+ this.setState(setState);
1234
1274
  return this;
1235
1275
  }
1236
1276
 
@@ -1243,7 +1283,10 @@ class Responder {
1243
1283
  return this._textResponses;
1244
1284
  }
1245
1285
 
1246
- _senderAction (action) {
1286
+ _senderAction (action, force = false) {
1287
+ if (action === 'typing_on' && this._typingSent && !force) {
1288
+ return this;
1289
+ }
1247
1290
  const messageData = {
1248
1291
  sender_action: action
1249
1292
  };
@@ -12,10 +12,21 @@ const extractText = require('./transcript/extractText');
12
12
  /** @typedef {import('./Responder')} Responder */
13
13
  /** @typedef {import('./Processor').TrackingObject} TrackingObject */
14
14
 
15
+ /**
16
+ * @callback GetInteractions
17
+ * @param {string} senderId
18
+ * @param {string} pageId
19
+ * @param {number} [limit]
20
+ * @param {number} [endAt] - iterate backwards to history
21
+ * @param {number} [startAt] - iterate forward to last interaction
22
+ * @returns {Promise<object[]>}
23
+ */
24
+
15
25
  /**
16
26
  * @typedef {object} ChatLogStorage
17
- * @property {Function} log
18
- * @property {Function} error
27
+ * @prop {Function} log
28
+ * @prop {Function} error
29
+ * @prop {GetInteractions} [getInteractions]
19
30
  */
20
31
 
21
32
  /**
@@ -165,6 +176,13 @@ class ReturnSender {
165
176
  .filter((t) => t && `${t}`.trim());
166
177
  }
167
178
 
179
+ /**
180
+ * @returns {ChatLogStorage}
181
+ */
182
+ get chatLogStorage () {
183
+ return this._logger;
184
+ }
185
+
168
186
  _gotAnotherEvent () {
169
187
  if (this._gotAnotherEventDefer) {
170
188
  this._gotAnotherEventDefer();
@@ -40,6 +40,8 @@ const {
40
40
  * @typedef {object} ConversationEventExtension
41
41
  * @prop {string} [lastAction]
42
42
  * @prop {string} [skill]
43
+ * @prop {string} [prevSkill]
44
+ * @prop {string} [pathname]
43
45
  * @prop {string} [text]
44
46
  * @prop {string} [expected]
45
47
  * @prop {boolean} expectedTaken
@@ -268,6 +270,8 @@ function onInteractionHandler (
268
270
  // state,
269
271
  // data,
270
272
  skill,
273
+ prevSkill,
274
+ pathname,
271
275
  events,
272
276
  flag,
273
277
  nonInteractive,
@@ -371,6 +375,7 @@ function onInteractionHandler (
371
375
  replaceDiacritics(req.text()).replace(/\s+/g, ' ').toLowerCase().trim()
372
376
  );
373
377
  const useSkill = (skill && webalize(skill)) || noneAction;
378
+ const usePrevSkill = (prevSkill && webalize(prevSkill)) || noneAction;
374
379
 
375
380
  let winnerAction = '';
376
381
  let winnerScore = 0;
@@ -425,6 +430,8 @@ function onInteractionHandler (
425
430
  withUser,
426
431
  feedback,
427
432
  skill: useSkill,
433
+ prevSkill: usePrevSkill,
434
+ pathname: pathname || noneAction,
428
435
  winnerAction,
429
436
  winnerIntent,
430
437
  winnerEntities: asArray(winnerEntities.map((e) => e.entity)),
@@ -33,6 +33,9 @@ const extractText = require('./extractText');
33
33
  * @returns {Promise<Transcript[]>}
34
34
  */
35
35
  async function transcriptFromHistory (chatLogStorage, senderId, pageId, limit = 20) {
36
+ if (typeof chatLogStorage.getInteractions !== 'function') {
37
+ return [];
38
+ }
36
39
  const data = await chatLogStorage.getInteractions(senderId, pageId, limit);
37
40
 
38
41
  return data