wingbot 3.69.7 → 3.69.8
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/jsconfig.json +2 -2
- package/package.json +2 -2
- package/src/BuildRouter.js +68 -75
- package/src/ChatGpt.js +84 -34
- package/src/LLM.js +160 -0
- package/src/LLMMockProvider.js +60 -0
- package/src/LLMSession.js +227 -0
- package/src/Processor.js +28 -7
- package/src/Responder.js +123 -12
- package/src/ReturnSender.js +1 -0
- package/src/Tester.js +5 -0
- package/src/resolvers/button.js +1 -1
- package/src/resolvers/include.js +3 -2
- package/src/resolvers/message.js +13 -4
- package/src/resolvers/postback.js +5 -1
- package/src/utils/stateData.js +20 -10
package/jsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"module": "commonjs",
|
|
4
|
-
"lib": ["
|
|
5
|
-
"target": "
|
|
4
|
+
"lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"],
|
|
5
|
+
"target": "ESNext",
|
|
6
6
|
"allowSyntheticDefaultImports": true,
|
|
7
7
|
"checkJs": true,
|
|
8
8
|
"resolveJsonModule": true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.69.
|
|
3
|
+
"version": "3.69.8",
|
|
4
4
|
"description": "Enterprise Messaging Bot Conversation Engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"graphql": "^16.8.1",
|
|
62
62
|
"jsonwebtoken": "^9.0.2",
|
|
63
63
|
"node-fetch": "^2.6.7",
|
|
64
|
-
"path-to-regexp": "^6.
|
|
64
|
+
"path-to-regexp": "^6.3.0",
|
|
65
65
|
"uuid": "^9.0.1",
|
|
66
66
|
"webalize": "^0.1.0"
|
|
67
67
|
},
|
package/src/BuildRouter.js
CHANGED
|
@@ -77,6 +77,10 @@ const PLUGIN_RESOLVER_NAME = 'botbuild.customCode';
|
|
|
77
77
|
* @typedef {Map<string|number,string>} LinksMap
|
|
78
78
|
*/
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {Map<string|number, Block>} BlockMap
|
|
82
|
+
*/
|
|
83
|
+
|
|
80
84
|
/** @type {TransformedRoute} */
|
|
81
85
|
const DUMMY_ROUTE = { id: 0, path: null, resolvers: [] };
|
|
82
86
|
|
|
@@ -136,6 +140,7 @@ const DUMMY_ROUTE = { id: 0, path: null, resolvers: [] };
|
|
|
136
140
|
|
|
137
141
|
/**
|
|
138
142
|
* @typedef {object} BotContextExtention
|
|
143
|
+
* @prop {BlockMap} [nestedBlocksByStaticId]
|
|
139
144
|
* @prop {LinksMap} [linksMap]
|
|
140
145
|
* @prop {boolean} [isLastIndex]
|
|
141
146
|
* @prop {boolean} [isLastMessage]
|
|
@@ -565,11 +570,10 @@ class BuildRouter extends Router {
|
|
|
565
570
|
...this._resolvedContext, blockName, blockType, isRoot, staticBlockId, BuildRouter
|
|
566
571
|
};
|
|
567
572
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
this._setExpectedFromResponderRoutes(block.routes);
|
|
573
|
+
const [linksMap, nestedBlocksByStaticId] = this._createLinksMap(block);
|
|
574
|
+
this._linksMap = linksMap;
|
|
571
575
|
|
|
572
|
-
this._buildRoutes(block.routes);
|
|
576
|
+
this._buildRoutes(block.routes, nestedBlocksByStaticId);
|
|
573
577
|
|
|
574
578
|
this._configTs = setConfigTimestamp;
|
|
575
579
|
|
|
@@ -577,74 +581,78 @@ class BuildRouter extends Router {
|
|
|
577
581
|
this.emit('rebuild');
|
|
578
582
|
}
|
|
579
583
|
|
|
580
|
-
_setExpectedFromResponderRoutes (routes) {
|
|
581
|
-
const set = new Set();
|
|
582
|
-
|
|
583
|
-
routes.forEach((route) => {
|
|
584
|
-
if (!route.isResponder) {
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// create the pseudopath ant set to set to corresponding route
|
|
589
|
-
const referredRoutePath = this._linksMap.get(route.respondsToRouteId);
|
|
590
|
-
|
|
591
|
-
if (!referredRoutePath) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const expectedPath = `${referredRoutePath}_responder`
|
|
596
|
-
.replace(/^\//, '');
|
|
597
|
-
|
|
598
|
-
Object.assign(route, { path: expectedPath });
|
|
599
|
-
|
|
600
|
-
// set expectedPath to referredRoute
|
|
601
|
-
|
|
602
|
-
if (set.has(route.respondsToRouteId)) {
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
set.add(route.respondsToRouteId);
|
|
606
|
-
|
|
607
|
-
const referredRoute = routes.find((r) => r.id === route.respondsToRouteId);
|
|
608
|
-
|
|
609
|
-
Object.assign(referredRoute, { expectedPath });
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
584
|
/**
|
|
614
585
|
*
|
|
615
586
|
* @param {Block} block
|
|
616
|
-
* @returns {LinksMap}
|
|
587
|
+
* @returns {[LinksMap, BlockMap]}
|
|
617
588
|
*/
|
|
618
589
|
_createLinksMap (block) {
|
|
590
|
+
const { linksMap: prevLinksMap, blocks = [] } = this._resolvedContext;
|
|
591
|
+
|
|
619
592
|
/** @type {LinksMap} */
|
|
620
593
|
const linksMap = new Map();
|
|
621
594
|
|
|
595
|
+
if (prevLinksMap) {
|
|
596
|
+
for (const [from, to] of prevLinksMap.entries()) {
|
|
597
|
+
linksMap.set(from, `../${to}`); // this._joinPaths('..', to)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const expectedFromResponders = new Set();
|
|
602
|
+
|
|
603
|
+
const blocksById = new Map();
|
|
604
|
+
|
|
622
605
|
block.routes
|
|
623
|
-
.
|
|
624
|
-
|
|
606
|
+
.forEach((route) => {
|
|
607
|
+
if (!route.isResponder) {
|
|
608
|
+
linksMap.set(route.id, route.path);
|
|
609
|
+
}
|
|
610
|
+
blocksById.set(route.id, route);
|
|
611
|
+
});
|
|
625
612
|
|
|
626
|
-
|
|
613
|
+
let { nestedBlocksByStaticId } = this._resolvedContext;
|
|
614
|
+
if (!nestedBlocksByStaticId) {
|
|
615
|
+
nestedBlocksByStaticId = new Map();
|
|
627
616
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
linksMap.set(from, this._joinPaths('..', to));
|
|
617
|
+
blocks.forEach((b) => {
|
|
618
|
+
if (b.staticBlockId && !b.disabled) {
|
|
619
|
+
nestedBlocksByStaticId.set(b.staticBlockId, b);
|
|
632
620
|
}
|
|
633
|
-
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
Object.assign(this._resolvedContext, { nestedBlocksByStaticId });
|
|
634
624
|
}
|
|
635
625
|
|
|
636
626
|
block.routes.forEach((route) => {
|
|
637
|
-
const enabledNestedBlock =
|
|
638
|
-
if (
|
|
639
|
-
|
|
627
|
+
const enabledNestedBlock = nestedBlocksByStaticId.get(this._getIncludedBlockId(route));
|
|
628
|
+
if (enabledNestedBlock) {
|
|
629
|
+
const routeConfig = this._getRouteConfig(route);
|
|
630
|
+
if (this._enabledByRouteConfig(routeConfig)) {
|
|
631
|
+
this._findEntryPointsInResolver(linksMap, enabledNestedBlock, route);
|
|
632
|
+
}
|
|
640
633
|
}
|
|
641
|
-
|
|
642
|
-
if (
|
|
643
|
-
|
|
634
|
+
|
|
635
|
+
if (route.isResponder) {
|
|
636
|
+
// create the pseudopath ant set to set to corresponding route
|
|
637
|
+
const referredRoutePath = linksMap.get(route.respondsToRouteId);
|
|
638
|
+
|
|
639
|
+
if (referredRoutePath) {
|
|
640
|
+
const expectedPath = `${referredRoutePath}_responder`
|
|
641
|
+
.replace(/^\//, '');
|
|
642
|
+
|
|
643
|
+
Object.assign(route, { path: expectedPath });
|
|
644
|
+
|
|
645
|
+
if (!expectedFromResponders.has(route.respondsToRouteId)) {
|
|
646
|
+
expectedFromResponders.add(route.respondsToRouteId);
|
|
647
|
+
|
|
648
|
+
const referredRoute = blocksById.get(route.respondsToRouteId);
|
|
649
|
+
Object.assign(referredRoute, { expectedPath });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
644
652
|
}
|
|
645
653
|
});
|
|
646
654
|
|
|
647
|
-
return linksMap;
|
|
655
|
+
return [linksMap, nestedBlocksByStaticId];
|
|
648
656
|
}
|
|
649
657
|
|
|
650
658
|
/**
|
|
@@ -682,24 +690,6 @@ class BuildRouter extends Router {
|
|
|
682
690
|
: null;
|
|
683
691
|
}
|
|
684
692
|
|
|
685
|
-
/**
|
|
686
|
-
*
|
|
687
|
-
* @param {string} staticBlockId
|
|
688
|
-
* @returns {Block|null}
|
|
689
|
-
*/
|
|
690
|
-
_getBlockById (staticBlockId) {
|
|
691
|
-
if (!staticBlockId) {
|
|
692
|
-
return null;
|
|
693
|
-
}
|
|
694
|
-
const nestedBlock = (this._resolvedContext.blocks || [])
|
|
695
|
-
.find((b) => b.staticBlockId === staticBlockId);
|
|
696
|
-
|
|
697
|
-
if (!nestedBlock || nestedBlock.disabled) {
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
return nestedBlock;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
693
|
/**
|
|
704
694
|
*
|
|
705
695
|
* @param {TransformedRoute} route
|
|
@@ -811,8 +801,9 @@ class BuildRouter extends Router {
|
|
|
811
801
|
/**
|
|
812
802
|
*
|
|
813
803
|
* @param {TransformedRoute[]} routes
|
|
804
|
+
* @param {BlockMap} nestedBlocksByStaticId
|
|
814
805
|
*/
|
|
815
|
-
_buildRoutes (routes) {
|
|
806
|
+
_buildRoutes (routes, nestedBlocksByStaticId) {
|
|
816
807
|
routes.forEach((route, i) => {
|
|
817
808
|
const routeConfig = this._getRouteConfig(route);
|
|
818
809
|
|
|
@@ -821,7 +812,7 @@ class BuildRouter extends Router {
|
|
|
821
812
|
}
|
|
822
813
|
|
|
823
814
|
const includedBlockId = this._getIncludedBlockId(route);
|
|
824
|
-
const nestedBlock =
|
|
815
|
+
const nestedBlock = nestedBlocksByStaticId.get(includedBlockId);
|
|
825
816
|
|
|
826
817
|
if (includedBlockId && (!nestedBlock || !this._enabledByRouteConfig(routeConfig))) {
|
|
827
818
|
return;
|
|
@@ -882,9 +873,10 @@ class BuildRouter extends Router {
|
|
|
882
873
|
* @param {Resolver[]} resolvers
|
|
883
874
|
* @param {TransformedRoute} [route]
|
|
884
875
|
* @param {BuildInfo} [buildInfo]
|
|
876
|
+
* @param {BlockMap} [nestedBlocksByStaticId=null]
|
|
885
877
|
* @returns {Middleware<S,C>[]}
|
|
886
878
|
*/
|
|
887
|
-
buildResolvers (resolvers, route = DUMMY_ROUTE, buildInfo = {}) {
|
|
879
|
+
buildResolvers (resolvers, route = DUMMY_ROUTE, buildInfo = {}, nestedBlocksByStaticId = null) {
|
|
888
880
|
const {
|
|
889
881
|
path: ctxPath, isFallback, isResponder, expectedPath, id
|
|
890
882
|
} = route;
|
|
@@ -911,7 +903,8 @@ class BuildRouter extends Router {
|
|
|
911
903
|
expectedPath,
|
|
912
904
|
routeId: id,
|
|
913
905
|
configuration,
|
|
914
|
-
resolverId: resolver.id
|
|
906
|
+
resolverId: resolver.id,
|
|
907
|
+
nestedBlocksByStaticId
|
|
915
908
|
};
|
|
916
909
|
|
|
917
910
|
const resFn = this._resolverFactory(resolver, context, buildInfo);
|
package/src/ChatGpt.js
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
const nodeFetch = require('node-fetch').default;
|
|
7
7
|
const util = require('util');
|
|
8
8
|
const { PHONE_REGEX, EMAIL_REGEX } = require('./systemEntities/regexps');
|
|
9
|
+
const LLM = require('./LLM');
|
|
9
10
|
|
|
10
11
|
/** @typedef {import('node-fetch').default} Fetch */
|
|
11
12
|
/** @typedef {import('./Request')} Request */
|
|
12
13
|
/** @typedef {import('./Responder')} Responder */
|
|
13
14
|
/** @typedef {import('./Responder').QuickReply} QuickReply */
|
|
15
|
+
/** @typedef {import('./LLM').LLMMessage} LLMMessage */
|
|
16
|
+
/** @typedef {import('./LLM').LLMProviderOptions} LLMProviderOptions */
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* @typedef {object} Transcript
|
|
@@ -59,7 +62,7 @@ const { PHONE_REGEX, EMAIL_REGEX } = require('./systemEntities/regexps');
|
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
64
|
* @typedef {object} ChatGPTChoice
|
|
62
|
-
* @prop {'stop'|'length'|'
|
|
65
|
+
* @prop {'stop'|'length'|'tool_calls'|'content_filter'|null} finish_reason
|
|
63
66
|
* @prop {number} index
|
|
64
67
|
* @prop {Message} message
|
|
65
68
|
*/
|
|
@@ -237,16 +240,31 @@ class ChatGpt {
|
|
|
237
240
|
}));
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @param {LLMMessage[]} prompt
|
|
245
|
+
* @param {LLMProviderOptions} [options]
|
|
246
|
+
* @returns {Promise<LLMMessage>}
|
|
247
|
+
*/
|
|
248
|
+
async requestChat (prompt, options) {
|
|
249
|
+
|
|
250
|
+
const choice = await this._request(prompt, options);
|
|
251
|
+
|
|
252
|
+
const { finish_reason: finishReason, message } = choice;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
finishReason,
|
|
256
|
+
...message
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
240
260
|
/**
|
|
241
261
|
*
|
|
242
|
-
* @param {
|
|
243
|
-
* @param {
|
|
244
|
-
* @param {
|
|
245
|
-
* @param {RequestOptions} [requestOptions]
|
|
246
|
-
* @param {string|Request} [user]
|
|
262
|
+
* @param {LLMMessage[]} chat
|
|
263
|
+
* @param {RequestOptions} requestOptions
|
|
264
|
+
* @param {string} user
|
|
247
265
|
* @returns {Promise<ChatGPTChoice>}
|
|
248
266
|
*/
|
|
249
|
-
async
|
|
267
|
+
async _request (chat, requestOptions, user = null) {
|
|
250
268
|
const {
|
|
251
269
|
requestTokens,
|
|
252
270
|
tokensLimit,
|
|
@@ -259,50 +277,51 @@ class ChatGpt {
|
|
|
259
277
|
...requestOptions
|
|
260
278
|
};
|
|
261
279
|
|
|
280
|
+
let messages = chat;
|
|
262
281
|
const maxTokens = Math.min(requestTokens, tokensLimit);
|
|
263
282
|
|
|
264
283
|
let body;
|
|
265
|
-
|
|
266
284
|
try {
|
|
285
|
+
let lastUserIndex = 0;
|
|
286
|
+
let totalTokens = messages
|
|
287
|
+
.reduce((total, m, i) => {
|
|
288
|
+
if (m.role === LLM.ROLE_USER) {
|
|
289
|
+
lastUserIndex = i;
|
|
290
|
+
}
|
|
291
|
+
return (m.content ? 0 : m.content.length) + total;
|
|
292
|
+
}, 0);
|
|
293
|
+
|
|
294
|
+
if (totalTokens > tokensLimit) {
|
|
295
|
+
messages = messages.filter((m, i) => {
|
|
296
|
+
if (m.role === LLM.ROLE_SYSTEM
|
|
297
|
+
|| i >= lastUserIndex
|
|
298
|
+
|| totalTokens <= tokensLimit
|
|
299
|
+
|| !m.content) {
|
|
300
|
+
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
totalTokens -= m.content.length;
|
|
304
|
+
return false;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
267
308
|
body = {
|
|
268
309
|
model,
|
|
269
310
|
frequency_penalty: 0,
|
|
270
311
|
presence_penalty: presencePenalty,
|
|
271
312
|
max_tokens: maxTokens,
|
|
272
313
|
temperature,
|
|
273
|
-
|
|
314
|
+
messages
|
|
274
315
|
};
|
|
275
316
|
|
|
276
|
-
if (
|
|
317
|
+
if (user) {
|
|
277
318
|
Object.assign(body, { user });
|
|
278
|
-
} else if (user) {
|
|
279
|
-
Object.assign(body, { user: `${user.pageId}|${user.senderId}` });
|
|
280
|
-
} else if (this._defaultUser) {
|
|
281
|
-
Object.assign(body, { user: this._defaultUser });
|
|
282
319
|
}
|
|
283
320
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
+ content.length;
|
|
287
|
-
|
|
288
|
-
const ts = transcript.slice();
|
|
289
|
-
|
|
290
|
-
for (let i = ts.length - 1; i >= 0; i--) {
|
|
291
|
-
total += ts[i].text.length;
|
|
292
|
-
if (total > tokensLimit) {
|
|
293
|
-
ts.splice(i, 1);
|
|
294
|
-
}
|
|
321
|
+
if (functions.length) {
|
|
322
|
+
Object.assign(body, { functions });
|
|
295
323
|
}
|
|
296
324
|
|
|
297
|
-
/** @type {Message[]} */
|
|
298
|
-
const messages = [
|
|
299
|
-
...(system ? [{ role: 'system', content: system }] : []),
|
|
300
|
-
...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
|
|
301
|
-
{ role: 'user', content }
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
Object.assign(body, { messages });
|
|
305
|
-
|
|
306
325
|
const apiUrl = `${this._openAiEndpoint}/chat/completions${this._apiKey ? '?api-version=2023-03-15-preview' : ''}`;
|
|
307
326
|
|
|
308
327
|
this._log('#GPT request', body);
|
|
@@ -342,6 +361,37 @@ class ChatGpt {
|
|
|
342
361
|
}
|
|
343
362
|
}
|
|
344
363
|
|
|
364
|
+
/**
|
|
365
|
+
*
|
|
366
|
+
* @deprecated
|
|
367
|
+
* @param {string} content
|
|
368
|
+
* @param {string} [system]
|
|
369
|
+
* @param {Transcript[]} [transcript]
|
|
370
|
+
* @param {RequestOptions} [requestOptions]
|
|
371
|
+
* @param {string|Request} [user]
|
|
372
|
+
* @returns {Promise<ChatGPTChoice>}
|
|
373
|
+
*/
|
|
374
|
+
async request (content, system = null, transcript = [], requestOptions = {}, user = null) {
|
|
375
|
+
|
|
376
|
+
/** @type {Message[]} */
|
|
377
|
+
const messages = [
|
|
378
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
379
|
+
...transcript.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
|
|
380
|
+
{ role: 'user', content }
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
let useUser;
|
|
384
|
+
if (typeof user === 'string') {
|
|
385
|
+
useUser = user;
|
|
386
|
+
} else if (user) {
|
|
387
|
+
useUser = `${user.pageId}|${user.senderId}`;
|
|
388
|
+
} else if (this._defaultUser) {
|
|
389
|
+
useUser = this._defaultUser;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return this._request(messages, requestOptions, useUser);
|
|
393
|
+
}
|
|
394
|
+
|
|
345
395
|
/**
|
|
346
396
|
*
|
|
347
397
|
* @param {ChatGPTChoice} choice
|
package/src/LLM.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author David Menger
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { PHONE_REGEX, EMAIL_REGEX } = require('./systemEntities/regexps');
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('./Responder')} Responder */
|
|
9
|
+
/** @typedef {import('./Responder').Persona} Persona */
|
|
10
|
+
/** @typedef {import('./Router').BaseConfiguration} BaseConfiguration */
|
|
11
|
+
/** @typedef {import('./LLMSession').LLMMessage} LLMMessage */
|
|
12
|
+
/** @typedef {import('./LLMSession').LLMRole} LLMRole */
|
|
13
|
+
/** @typedef {import('./LLMSession')} LLMSession */
|
|
14
|
+
/** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @callback LLMChatProviderPrompt
|
|
18
|
+
* @param {LLMMessage[]} prompt
|
|
19
|
+
* @param {LLMProviderOptions} [options]
|
|
20
|
+
* @returns {Promise<LLMMessage>}
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} LLMProviderOptions
|
|
25
|
+
* @prop {string} [model]
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} LLMChatProvider
|
|
30
|
+
* @prop {LLMChatProviderPrompt} requestChat
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** @typedef {import('node-fetch').default} Fetch */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} LLMConfiguration
|
|
37
|
+
* @prop {LLMChatProvider} provider
|
|
38
|
+
* @prop {string} [model]
|
|
39
|
+
* @prop {number} [transcriptLength=-5]
|
|
40
|
+
* @prop {'gpt'|string} [transcriptFlag]
|
|
41
|
+
* @prop {boolean} [transcriptAnonymize]
|
|
42
|
+
* @prop {Persona|string|null} [persona]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} AnonymizeRegexp
|
|
47
|
+
* @prop {string} [replacement]
|
|
48
|
+
* @prop {RegExp} regex
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @class LLM
|
|
53
|
+
*/
|
|
54
|
+
class LLM {
|
|
55
|
+
|
|
56
|
+
/** @type {LLMRole} */
|
|
57
|
+
static ROLE_USER = 'user';
|
|
58
|
+
|
|
59
|
+
/** @type {LLMRole} */
|
|
60
|
+
static ROLE_ASSISTANT = 'assistant';
|
|
61
|
+
|
|
62
|
+
/** @type {LLMRole} */
|
|
63
|
+
static ROLE_SYSTEM = 'system';
|
|
64
|
+
|
|
65
|
+
static GPT_FLAG = 'gpt';
|
|
66
|
+
|
|
67
|
+
/** @type {AnonymizeRegexp[]} */
|
|
68
|
+
static anonymizeRegexps = [
|
|
69
|
+
{ replacement: '@PHONE', regex: new RegExp(PHONE_REGEX.source, 'g') },
|
|
70
|
+
{ replacement: '@EMAIL', regex: new RegExp(EMAIL_REGEX.source, 'g') }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @param {LLMConfiguration} configuration
|
|
76
|
+
*/
|
|
77
|
+
constructor (configuration) {
|
|
78
|
+
const { provider, ...rest } = configuration;
|
|
79
|
+
|
|
80
|
+
this._configuration = {
|
|
81
|
+
transcriptFlag: 'gpt',
|
|
82
|
+
transcriptLength: 5,
|
|
83
|
+
provider: null,
|
|
84
|
+
...rest
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** @type {LLMChatProvider} */
|
|
88
|
+
this._provider = provider;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @returns {Omit<LLMConfiguration, 'provider'>}
|
|
93
|
+
*/
|
|
94
|
+
get configuration () {
|
|
95
|
+
return this._configuration;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
*
|
|
100
|
+
* @param {Transcript[]} chat
|
|
101
|
+
* @param {boolean} [transcriptAnonymize]
|
|
102
|
+
* @returns {LLMMessage[]}
|
|
103
|
+
*/
|
|
104
|
+
static anonymizeTranscript (chat, transcriptAnonymize) {
|
|
105
|
+
return chat.map((c) => ({
|
|
106
|
+
role: c.fromBot ? LLM.ROLE_ASSISTANT : LLM.ROLE_USER,
|
|
107
|
+
content: transcriptAnonymize
|
|
108
|
+
? LLM.anonymizeRegexps
|
|
109
|
+
.reduce((text, { replacement, regex }) => {
|
|
110
|
+
const replaced = text.replace(regex, replacement);
|
|
111
|
+
return replaced;
|
|
112
|
+
}, c.text)
|
|
113
|
+
: c.text
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
*
|
|
119
|
+
* @param {LLMSession} session
|
|
120
|
+
* @param {LLMProviderOptions} [options={}]
|
|
121
|
+
* @returns {Promise<LLMMessage>}
|
|
122
|
+
*/
|
|
123
|
+
async generate (session, options = {}) {
|
|
124
|
+
/** @type {LLMProviderOptions} */
|
|
125
|
+
const opts = {
|
|
126
|
+
model: this._configuration.model,
|
|
127
|
+
...options
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const prompt = session.toArray();
|
|
131
|
+
const result = await this._provider.requestChat(prompt, opts);
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* @param {LLMMessage} result
|
|
139
|
+
* @returns {LLMMessage[]}
|
|
140
|
+
*/
|
|
141
|
+
static toMessages (result) {
|
|
142
|
+
let filtered = result.content
|
|
143
|
+
.replace(/\n\n+/g, '\n')
|
|
144
|
+
.split(/\n+(?!-)/g)
|
|
145
|
+
.map((t) => t.trim())
|
|
146
|
+
.filter((t) => !!t);
|
|
147
|
+
|
|
148
|
+
if (result.finishReason === 'length' && filtered.length <= 0) {
|
|
149
|
+
filtered = filtered.slice(0, filtered.length - 1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return filtered.map((content) => ({
|
|
153
|
+
content,
|
|
154
|
+
role: result.role
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = LLM;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author David Menger
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const LLM = require('./LLM');
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('./LLM').LLMChatProvider} LLMChatProvider */
|
|
9
|
+
/** @typedef {import('./LLM').LLMMessage} LLMMessage */
|
|
10
|
+
/** @typedef {import('./LLM').LLMProviderOptions} LLMProviderOptions */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class LLMMockProvider
|
|
14
|
+
* @implements {LLMChatProvider}
|
|
15
|
+
*/
|
|
16
|
+
class LLMMockProvider {
|
|
17
|
+
|
|
18
|
+
static DEFAULT_MODEL = 'mockmodel';
|
|
19
|
+
|
|
20
|
+
constructor () {
|
|
21
|
+
this._index = 0;
|
|
22
|
+
this._sequence = [
|
|
23
|
+
'lorem',
|
|
24
|
+
'ipsum',
|
|
25
|
+
'dolor',
|
|
26
|
+
'sit',
|
|
27
|
+
'amet'
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {LLMMessage[]} prompt
|
|
33
|
+
* @param {LLMProviderOptions} [options]
|
|
34
|
+
* @returns {Promise<LLMMessage>}
|
|
35
|
+
*/
|
|
36
|
+
// eslint-disable-next-line no-unused-vars
|
|
37
|
+
async requestChat (prompt, options) {
|
|
38
|
+
if (prompt.length === 0) {
|
|
39
|
+
throw new Error('Empty prompt');
|
|
40
|
+
}
|
|
41
|
+
const stats = prompt.reduce((o, m) => Object.assign(o, {
|
|
42
|
+
[m.role]: (o[m.role] || 0) + 1
|
|
43
|
+
}), { system: 0, assistant: 0, user: 0 });
|
|
44
|
+
|
|
45
|
+
const statsText = JSON.stringify(stats)
|
|
46
|
+
.replace(/"/g, '');
|
|
47
|
+
|
|
48
|
+
const message = this._sequence[this._index];
|
|
49
|
+
this._index = (this._index + 1) % this._sequence.length;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
role: LLM.ROLE_ASSISTANT,
|
|
53
|
+
finishReason: 'length',
|
|
54
|
+
content: `${statsText} > ${LLMMockProvider.DEFAULT_MODEL}: ${message}`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = LLMMockProvider;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author David Menger
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const LLM = require('./LLM');
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('./Responder').QuickReply} QuickReply */
|
|
9
|
+
/** @typedef {'user'|'assistant'} LLMChatRole */
|
|
10
|
+
/** @typedef {'system'} LLMSystemRole */
|
|
11
|
+
/** @typedef {LLMChatRole|LLMSystemRole|string} LLMRole */
|
|
12
|
+
/** @typedef {import('./LLM').LLMProviderOptions} LLMProviderOptions */
|
|
13
|
+
|
|
14
|
+
/** @typedef {'stop'|'length'|'tool_calls'|'content_filter'} LLMFinishReason */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @template {LLMRole} [R=LLMRole]
|
|
18
|
+
* @typedef {object} LLMMessage
|
|
19
|
+
* @prop {R} role
|
|
20
|
+
* @prop {string} content
|
|
21
|
+
* @prop {LLMFinishReason} [finishReason]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @callback SendCallback
|
|
26
|
+
* @param {LLMMessage[]} messages
|
|
27
|
+
* @param {QuickReply[]} quickReplies
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @class LLMSession
|
|
32
|
+
*/
|
|
33
|
+
class LLMSession {
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {LLM} llm
|
|
38
|
+
* @param {LLMMessage[]} [chat]
|
|
39
|
+
* @param {SendCallback} [onSend]
|
|
40
|
+
*/
|
|
41
|
+
constructor (llm, chat = [], onSend = () => {}) {
|
|
42
|
+
this._llm = llm;
|
|
43
|
+
|
|
44
|
+
this._onSend = onSend;
|
|
45
|
+
|
|
46
|
+
/** @type {LLMMessage[]} */
|
|
47
|
+
this._chat = chat;
|
|
48
|
+
|
|
49
|
+
this._generatedIndex = null;
|
|
50
|
+
|
|
51
|
+
this._sort();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_sort (what = this._chat) {
|
|
55
|
+
what.sort((a, z) => {
|
|
56
|
+
if (a.role === z.role
|
|
57
|
+
|| (a.role !== LLM.ROLE_SYSTEM && z.role !== LLM.ROLE_SYSTEM)) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
return a.role === LLM.ROLE_SYSTEM ? -1 : 1;
|
|
61
|
+
});
|
|
62
|
+
return what;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_mergeSystem () {
|
|
66
|
+
const sysMessages = [];
|
|
67
|
+
|
|
68
|
+
const otherMessages = this._chat.filter((message) => {
|
|
69
|
+
if (message.role !== LLM.ROLE_SYSTEM) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
sysMessages.push(message);
|
|
73
|
+
return false;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (sysMessages.length === 0) {
|
|
77
|
+
return otherMessages;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const promptRegex = /\$\{prompt\(\)\}/g;
|
|
81
|
+
|
|
82
|
+
const content = sysMessages.reduce((reduced, current, i) => {
|
|
83
|
+
if (i === 0) {
|
|
84
|
+
return current.content;
|
|
85
|
+
}
|
|
86
|
+
if (!reduced.match(promptRegex)) {
|
|
87
|
+
return `${reduced}\n\n${current.content}`;
|
|
88
|
+
}
|
|
89
|
+
return reduced.replace(promptRegex, current.content).trim();
|
|
90
|
+
}, '')
|
|
91
|
+
.replace(promptRegex, '')
|
|
92
|
+
.trim();
|
|
93
|
+
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
role: LLM.ROLE_SYSTEM, content
|
|
97
|
+
},
|
|
98
|
+
...otherMessages
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @returns {LLMMessage[]}
|
|
104
|
+
*/
|
|
105
|
+
toArray () {
|
|
106
|
+
return this._mergeSystem();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
*
|
|
111
|
+
* @param {LLMMessage} msg
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
_msgPrefix (msg) {
|
|
115
|
+
switch (msg.role) {
|
|
116
|
+
case LLM.ROLE_SYSTEM:
|
|
117
|
+
return '-';
|
|
118
|
+
case LLM.ROLE_ASSISTANT:
|
|
119
|
+
return msg.content ? '<' : '#';
|
|
120
|
+
case LLM.ROLE_USER:
|
|
121
|
+
return '>';
|
|
122
|
+
default:
|
|
123
|
+
return '*';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
* @param {LLMMessage[]} [messages]
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
toString (messages = this._chat) {
|
|
133
|
+
if (messages.length === 0) {
|
|
134
|
+
return '[<empty>]';
|
|
135
|
+
}
|
|
136
|
+
return messages.map((m) => {
|
|
137
|
+
switch (m.role) {
|
|
138
|
+
case LLM.ROLE_SYSTEM:
|
|
139
|
+
return `--- system ---\n${m.content}\n--------------`;
|
|
140
|
+
default:
|
|
141
|
+
return `${this._msgPrefix(m)} ${m.content}`;
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
.join('\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
toJSON () {
|
|
148
|
+
return this.toArray();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param {boolean} [needRaw=false]
|
|
154
|
+
* @returns {this}
|
|
155
|
+
*/
|
|
156
|
+
debug (needRaw = false) {
|
|
157
|
+
// eslint-disable-next-line no-console
|
|
158
|
+
console.log('LLMSession#debug\n', this.toString(
|
|
159
|
+
needRaw ? this._chat : this.toArray()
|
|
160
|
+
));
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
*
|
|
166
|
+
* @param {LLMMessage} message
|
|
167
|
+
* @returns {this}
|
|
168
|
+
*/
|
|
169
|
+
push (message) {
|
|
170
|
+
// if its system, append it on top
|
|
171
|
+
this._chat.push(message);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param {string} content
|
|
178
|
+
* @returns {this}
|
|
179
|
+
*/
|
|
180
|
+
systemPrompt (content) {
|
|
181
|
+
this.push({
|
|
182
|
+
role: LLM.ROLE_SYSTEM,
|
|
183
|
+
content
|
|
184
|
+
});
|
|
185
|
+
this._sort();
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param {LLMProviderOptions} [options={}]
|
|
192
|
+
* @returns {Promise<LLMMessage>}
|
|
193
|
+
*/
|
|
194
|
+
async generate (options = {}) {
|
|
195
|
+
const result = await this._llm.generate(this, options);
|
|
196
|
+
|
|
197
|
+
this._generatedIndex = this._chat.length;
|
|
198
|
+
this._chat.push(result);
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @param {QuickReply[]} quickReplies
|
|
206
|
+
* @returns {this}
|
|
207
|
+
*/
|
|
208
|
+
send (quickReplies = undefined) {
|
|
209
|
+
if (!this._generatedIndex) {
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.log('LLMSession', this.toString());
|
|
212
|
+
throw new Error('LLMSession: no message to send');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let messages = this._chat.splice(this._generatedIndex);
|
|
216
|
+
messages = messages.flatMap((msg) => LLM.toMessages(msg));
|
|
217
|
+
this._generatedIndex = null;
|
|
218
|
+
this._chat.push(...messages);
|
|
219
|
+
|
|
220
|
+
this._onSend(messages, quickReplies);
|
|
221
|
+
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = LLMSession;
|
package/src/Processor.js
CHANGED
|
@@ -11,6 +11,8 @@ const Request = require('./Request');
|
|
|
11
11
|
const Ai = require('./Ai');
|
|
12
12
|
const ReturnSender = require('./ReturnSender');
|
|
13
13
|
const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVariables');
|
|
14
|
+
const LLM = require('./LLM');
|
|
15
|
+
const LLMMockProvider = require('./LLMMockProvider');
|
|
14
16
|
|
|
15
17
|
/** @typedef {import('./wingbot/CustomEntityDetectionModel').Intent} Intent */
|
|
16
18
|
/** @typedef {import('./ReducerWrapper')} ReducerWrapper */
|
|
@@ -19,6 +21,7 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
|
|
|
19
21
|
/** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
|
|
20
22
|
/** @typedef {import('./analytics/consts').TrackingType} TrackingType */
|
|
21
23
|
/** @typedef {import('./analytics/consts').ResponseFlag} ResponseFlag */
|
|
24
|
+
/** @typedef {import('./LLM').LLMConfiguration} LLMConfiguration */
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
27
|
* @typedef {object} AutoTypingConfig
|
|
@@ -110,6 +113,7 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
|
|
|
110
113
|
* @prop {string} [apiUrl] - Url for calling orchestrator API
|
|
111
114
|
* @prop {Function} [fetch] - Fetch function for calling orchestrator API
|
|
112
115
|
* @prop {number} [sessionDuration] - Session duration for analytic purposes
|
|
116
|
+
* @prop {LLMConfiguration} [llm] - LLM model configuration
|
|
113
117
|
* @prop {Preloader<R>} [preloader]
|
|
114
118
|
*/
|
|
115
119
|
|
|
@@ -401,14 +405,19 @@ class Processor extends EventEmitter {
|
|
|
401
405
|
return { status: 304 };
|
|
402
406
|
}
|
|
403
407
|
|
|
408
|
+
const llm = new LLM({
|
|
409
|
+
provider: new LLMMockProvider(),
|
|
410
|
+
...this.options.llm
|
|
411
|
+
});
|
|
412
|
+
|
|
404
413
|
const result = await this
|
|
405
|
-
._dispatch(message, pageId, messageSender, responderData, preloadPromise);
|
|
414
|
+
._dispatch(message, pageId, messageSender, responderData, preloadPromise, llm);
|
|
406
415
|
|
|
407
416
|
messageSender.defer(preloadPromise, this.options.log);
|
|
408
417
|
return result;
|
|
409
418
|
}
|
|
410
419
|
|
|
411
|
-
async _dispatch (message, pageId, messageSender, responderData, preloadPromise) {
|
|
420
|
+
async _dispatch (message, pageId, messageSender, responderData, preloadPromise, llm) {
|
|
412
421
|
let req;
|
|
413
422
|
let res;
|
|
414
423
|
let state;
|
|
@@ -418,7 +427,14 @@ class Processor extends EventEmitter {
|
|
|
418
427
|
({
|
|
419
428
|
req, res, data, state
|
|
420
429
|
} = await this
|
|
421
|
-
._processMessage(
|
|
430
|
+
._processMessage(
|
|
431
|
+
message,
|
|
432
|
+
pageId,
|
|
433
|
+
messageSender,
|
|
434
|
+
responderData,
|
|
435
|
+
llm,
|
|
436
|
+
preloadPromise
|
|
437
|
+
));
|
|
422
438
|
|
|
423
439
|
messageSender.defer(this._emitInteractionEvent(req, res, messageSender, state, data));
|
|
424
440
|
|
|
@@ -548,6 +564,7 @@ class Processor extends EventEmitter {
|
|
|
548
564
|
pageId,
|
|
549
565
|
messageSender,
|
|
550
566
|
responderData,
|
|
567
|
+
llm,
|
|
551
568
|
preloadPromise = null,
|
|
552
569
|
senderMeta = null
|
|
553
570
|
) {
|
|
@@ -664,13 +681,13 @@ class Processor extends EventEmitter {
|
|
|
664
681
|
_sts: sessionTs,
|
|
665
682
|
_snew: sessionCreated
|
|
666
683
|
});
|
|
667
|
-
} else {
|
|
684
|
+
} /* else {
|
|
668
685
|
Object.assign(state, {
|
|
669
686
|
_snew: false
|
|
670
687
|
});
|
|
671
|
-
}
|
|
688
|
+
} */
|
|
672
689
|
|
|
673
|
-
prepareState(state, fromEvent, state._snew);
|
|
690
|
+
prepareState(state, fromEvent, state._snew && fromEvent);
|
|
674
691
|
|
|
675
692
|
const features = [
|
|
676
693
|
...(this.options.features || []),
|
|
@@ -690,7 +707,8 @@ class Processor extends EventEmitter {
|
|
|
690
707
|
options,
|
|
691
708
|
responderData,
|
|
692
709
|
configuration,
|
|
693
|
-
senderMeta
|
|
710
|
+
senderMeta,
|
|
711
|
+
llm
|
|
694
712
|
);
|
|
695
713
|
const postBack = this._createPostBack(postbackAcumulator, req, res, features);
|
|
696
714
|
|
|
@@ -852,6 +870,7 @@ class Processor extends EventEmitter {
|
|
|
852
870
|
pageId,
|
|
853
871
|
messageSender,
|
|
854
872
|
responderData,
|
|
873
|
+
llm,
|
|
855
874
|
res.senderMeta
|
|
856
875
|
);
|
|
857
876
|
|
|
@@ -949,6 +968,7 @@ class Processor extends EventEmitter {
|
|
|
949
968
|
pageId,
|
|
950
969
|
messageSender,
|
|
951
970
|
responderData,
|
|
971
|
+
llm,
|
|
952
972
|
senderMeta
|
|
953
973
|
) {
|
|
954
974
|
return postbackAcumulator.reduce((promise, postback) => promise
|
|
@@ -966,6 +986,7 @@ class Processor extends EventEmitter {
|
|
|
966
986
|
pageId,
|
|
967
987
|
messageSender,
|
|
968
988
|
responderData,
|
|
989
|
+
llm,
|
|
969
990
|
null,
|
|
970
991
|
senderMeta
|
|
971
992
|
);
|
package/src/Responder.js
CHANGED
|
@@ -17,6 +17,8 @@ const {
|
|
|
17
17
|
FEATURE_PHRASES
|
|
18
18
|
} = require('./features');
|
|
19
19
|
const transcriptFromHistory = require('./transcript/transcriptFromHistory');
|
|
20
|
+
const LLM = require('./LLM');
|
|
21
|
+
const LLMSession = require('./LLMSession');
|
|
20
22
|
|
|
21
23
|
const TYPE_RESPONSE = 'RESPONSE';
|
|
22
24
|
const TYPE_UPDATE = 'UPDATE';
|
|
@@ -28,8 +30,11 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
|
|
|
28
30
|
/** @typedef {import('./ReturnSender').SendOptions} SendOptions */
|
|
29
31
|
/** @typedef {import('./ReturnSender').TextFilter} TextFilter */
|
|
30
32
|
/** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
|
|
31
|
-
/** @typedef {import('./analytics/consts').TrackingType} TrackingType */
|
|
32
33
|
/** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
|
|
34
|
+
/** @typedef {import('./analytics/consts').TrackingType} TrackingType */
|
|
35
|
+
|
|
36
|
+
/** @typedef {import('./LLM').LLMConfiguration} LLMConfiguration */
|
|
37
|
+
/** @typedef {import('./LLMSession').LLMMessage} LLMMessage */
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
40
|
* @enum {string} ExpectedInput
|
|
@@ -114,12 +119,14 @@ class Responder {
|
|
|
114
119
|
options = {},
|
|
115
120
|
data = {},
|
|
116
121
|
configuration = {},
|
|
117
|
-
senderMeta = null
|
|
122
|
+
senderMeta = null,
|
|
123
|
+
llm = null
|
|
118
124
|
) {
|
|
119
125
|
this._messageSender = messageSender;
|
|
120
126
|
this._senderId = senderId;
|
|
121
127
|
this._pageId = options.pageId;
|
|
122
128
|
this.token = token;
|
|
129
|
+
|
|
123
130
|
this._configuration = configuration;
|
|
124
131
|
|
|
125
132
|
/**
|
|
@@ -214,20 +221,124 @@ class Responder {
|
|
|
214
221
|
|
|
215
222
|
/** @type {SendOptions} */
|
|
216
223
|
this._nextMessageSendOptions = null;
|
|
224
|
+
|
|
225
|
+
/** @type {LLM} */
|
|
226
|
+
this.llm = llm;
|
|
227
|
+
|
|
228
|
+
this.LLM_CTX_DEFAULT = 'default';
|
|
229
|
+
|
|
230
|
+
/** @type {Map<string,string[]>} */
|
|
231
|
+
this._llmContext = new Map([
|
|
232
|
+
[this.LLM_CTX_DEFAULT, []]
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
* @param {string} systemPrompt
|
|
239
|
+
* @param {string} contextType
|
|
240
|
+
* @returns {this}
|
|
241
|
+
*/
|
|
242
|
+
llmAddSystemPrompt (systemPrompt, contextType = this.LLM_CTX_DEFAULT) {
|
|
243
|
+
if (!systemPrompt || !systemPrompt.trim()) {
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
if (!this._llmContext.has(contextType)) {
|
|
247
|
+
// @todo make it array of messages / maybe keep it in a single array
|
|
248
|
+
this._llmContext.set(contextType, []);
|
|
249
|
+
}
|
|
250
|
+
this._llmContext.get(contextType).push(systemPrompt.trim());
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
llmSession (contextType = this.LLM_CTX_DEFAULT) {
|
|
255
|
+
const chat = this._getSystemContentForType(contextType)
|
|
256
|
+
.map((content) => ({ role: LLM.ROLE_SYSTEM, content }));
|
|
257
|
+
|
|
258
|
+
return new LLMSession(this.llm, chat, this._llmSend.bind(this));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
*
|
|
263
|
+
* @param {string} contextType
|
|
264
|
+
* @param {string[]} [callStack]
|
|
265
|
+
* @returns {string[]}
|
|
266
|
+
*/
|
|
267
|
+
_getSystemContentForType (contextType, callStack = []) {
|
|
268
|
+
if (new Set(callStack).size < callStack.length) {
|
|
269
|
+
throw new Error(`Circular reference detected: contextType -> ${callStack}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!this._llmContext.has(contextType)) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return this._llmContext.get(contextType)
|
|
277
|
+
.map((c) => c.replace(/\$\{([a-zA-Z0-9\s]+)\}/g, (str, requestType) => this
|
|
278
|
+
._getSystemContentForType(requestType, [...callStack, contextType])
|
|
279
|
+
.join('\n\n')).trim());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async llmSessionWithHistory (contextType = this.LLM_CTX_DEFAULT) {
|
|
283
|
+
const {
|
|
284
|
+
transcriptAnonymize,
|
|
285
|
+
transcriptFlag,
|
|
286
|
+
transcriptLength
|
|
287
|
+
} = this.llm.configuration;
|
|
288
|
+
|
|
289
|
+
const systems = this._getSystemContentForType(contextType);
|
|
290
|
+
const transcript = await this.getTranscript(transcriptLength, transcriptFlag);
|
|
291
|
+
|
|
292
|
+
const chat = [
|
|
293
|
+
...systems.map((content) => ({ role: LLM.ROLE_SYSTEM, content })),
|
|
294
|
+
...LLM.anonymizeTranscript(transcript, transcriptAnonymize)
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
return new LLMSession(this.llm, chat, this._llmSend.bind(this));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
*
|
|
302
|
+
* @param {LLMMessage[]} messages
|
|
303
|
+
* @param {QuickReply[]} quickReplies
|
|
304
|
+
*/
|
|
305
|
+
_llmSend (messages, quickReplies) {
|
|
306
|
+
this.setFlag(LLM.GPT_FLAG);
|
|
307
|
+
|
|
308
|
+
const { persona } = this.llm.configuration;
|
|
309
|
+
|
|
310
|
+
if (typeof persona === 'string') {
|
|
311
|
+
this.setPersona({ name: persona });
|
|
312
|
+
} else if (persona) {
|
|
313
|
+
this.setPersona(persona);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
messages.forEach((m, i) => {
|
|
317
|
+
const addQuickReply = i === (messages.length - 1);
|
|
318
|
+
this.text(m.content, addQuickReply ? quickReplies : null);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (persona) {
|
|
322
|
+
this.setPersona({ name: null });
|
|
323
|
+
}
|
|
217
324
|
}
|
|
218
325
|
|
|
219
326
|
_findPersonaConfiguration (name) {
|
|
327
|
+
// @ts-ignore
|
|
220
328
|
if (!name || !this._configuration.persona) {
|
|
221
329
|
return null;
|
|
222
330
|
}
|
|
331
|
+
// @ts-ignore
|
|
223
332
|
if (!this._configuration._cachedPersonas) {
|
|
224
|
-
//
|
|
333
|
+
// @ts-ignore
|
|
225
334
|
this._configuration._cachedPersonas = new Map(
|
|
335
|
+
// @ts-ignore
|
|
226
336
|
Object.entries(this._configuration.persona)
|
|
227
337
|
.map(([k, v]) => [k === PERSONA_DEFAULT ? k : tokenize(k), v])
|
|
228
338
|
);
|
|
229
339
|
}
|
|
230
340
|
const nameKey = name === PERSONA_DEFAULT ? PERSONA_DEFAULT : tokenize(name);
|
|
341
|
+
// @ts-ignore
|
|
231
342
|
return this._configuration._cachedPersonas.get(nameKey);
|
|
232
343
|
}
|
|
233
344
|
|
|
@@ -252,16 +363,16 @@ class Responder {
|
|
|
252
363
|
*/
|
|
253
364
|
async getTranscript (limit = 10, onlyFlag = null, skipThisTurnaround = false) {
|
|
254
365
|
const { chatLogStorage, timestamp } = this._messageSender;
|
|
255
|
-
|
|
256
|
-
|
|
366
|
+
let transcript = [];
|
|
367
|
+
if (chatLogStorage) {
|
|
368
|
+
transcript = await transcriptFromHistory(
|
|
369
|
+
chatLogStorage,
|
|
370
|
+
this._senderId,
|
|
371
|
+
this._pageId,
|
|
372
|
+
limit,
|
|
373
|
+
onlyFlag
|
|
374
|
+
);
|
|
257
375
|
}
|
|
258
|
-
const transcript = await transcriptFromHistory(
|
|
259
|
-
chatLogStorage,
|
|
260
|
-
this._senderId,
|
|
261
|
-
this._pageId,
|
|
262
|
-
limit,
|
|
263
|
-
onlyFlag
|
|
264
|
-
);
|
|
265
376
|
if (!skipThisTurnaround) {
|
|
266
377
|
const { responseTexts = [], requestTexts = [] } = this._messageSender;
|
|
267
378
|
transcript.push(...requestTexts.map((text) => ({
|
package/src/ReturnSender.js
CHANGED
|
@@ -11,6 +11,7 @@ const extractText = require('./transcript/extractText');
|
|
|
11
11
|
/** @typedef {import('./Request')} Request */
|
|
12
12
|
/** @typedef {import('./Responder')} Responder */
|
|
13
13
|
/** @typedef {import('./Processor').TrackingObject} TrackingObject */
|
|
14
|
+
/** @typedef {import('./LLMContext').LLMMessage} LLMMessage */
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @callback GetInteractions
|
package/src/Tester.js
CHANGED
|
@@ -18,6 +18,7 @@ const ResponseAssert = require('./testTools/ResponseAssert');
|
|
|
18
18
|
const Router = require('./Router'); // eslint-disable-line no-unused-vars
|
|
19
19
|
const ReducerWrapper = require('./ReducerWrapper'); // eslint-disable-line no-unused-vars
|
|
20
20
|
const { FEATURE_TEXT } = require('./features');
|
|
21
|
+
const LLMMockProvider = require('./LLMMockProvider');
|
|
21
22
|
|
|
22
23
|
/** @typedef {import('./Processor').ProcessorOptions<Router>} ProcessorOptions */
|
|
23
24
|
|
|
@@ -100,6 +101,10 @@ class Tester {
|
|
|
100
101
|
log,
|
|
101
102
|
// @ts-ignore
|
|
102
103
|
loadUsers: false,
|
|
104
|
+
llm: {
|
|
105
|
+
provider: new LLMMockProvider(),
|
|
106
|
+
...processorOptions.llm
|
|
107
|
+
},
|
|
103
108
|
...processorOptions
|
|
104
109
|
}));
|
|
105
110
|
|
package/src/resolvers/button.js
CHANGED
|
@@ -28,8 +28,8 @@ function button (params, context) {
|
|
|
28
28
|
buttons = [],
|
|
29
29
|
text = null
|
|
30
30
|
} = params;
|
|
31
|
-
const compiledText = cachedTranslatedCompilator(text);
|
|
32
31
|
|
|
32
|
+
const compiledText = cachedTranslatedCompilator(text);
|
|
33
33
|
const condition = getCondition(params, context, 'button');
|
|
34
34
|
|
|
35
35
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
package/src/resolvers/include.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
function include (params, context, plugins) {
|
|
7
|
-
const includedRouter = context.
|
|
8
|
-
.
|
|
7
|
+
const includedRouter = context.nestedBlocksByStaticId
|
|
8
|
+
? context.nestedBlocksByStaticId.get(params.staticBlockId)
|
|
9
|
+
: context.blocks.find((block) => block.staticBlockId === params.staticBlockId);
|
|
9
10
|
|
|
10
11
|
if (!includedRouter) {
|
|
11
12
|
throw new Error(`Block ${params.staticBlockId} not found!`);
|
package/src/resolvers/message.js
CHANGED
|
@@ -289,15 +289,13 @@ function message (params, context = {}) {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// parse quick replies
|
|
292
|
-
let quickReplies
|
|
292
|
+
let quickReplies;
|
|
293
293
|
if (params.replies && !Array.isArray(params.replies)) {
|
|
294
294
|
throw new Error('Replies should be an array');
|
|
295
|
-
} else if (params.replies && params.replies.length > 0) {
|
|
296
|
-
quickReplies = parseReplies(params.replies, linksMap, context);
|
|
297
295
|
}
|
|
298
296
|
|
|
299
297
|
// compile condition
|
|
300
|
-
|
|
298
|
+
let condition;
|
|
301
299
|
|
|
302
300
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
303
301
|
|
|
@@ -306,6 +304,17 @@ function message (params, context = {}) {
|
|
|
306
304
|
* @param {Responder} res
|
|
307
305
|
*/
|
|
308
306
|
return (req, res) => {
|
|
307
|
+
if (condition === undefined) {
|
|
308
|
+
condition = getCondition(params, context, 'Message condition');
|
|
309
|
+
}
|
|
310
|
+
if (quickReplies === undefined) {
|
|
311
|
+
if (params.replies && params.replies.length > 0) {
|
|
312
|
+
quickReplies = parseReplies(params.replies, linksMap, context);
|
|
313
|
+
} else {
|
|
314
|
+
quickReplies = null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
309
318
|
const data = stateData(req, res, configuration);
|
|
310
319
|
|
|
311
320
|
// filter supported messages
|
|
@@ -32,11 +32,15 @@ function postback (params, context) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
let condition;
|
|
36
36
|
|
|
37
37
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
38
38
|
|
|
39
39
|
return (req, res, postBack) => {
|
|
40
|
+
if (condition === undefined) {
|
|
41
|
+
condition = getCondition(params, context, 'postback');
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
if (!action || !shouldExecuteResolver(req, params)) {
|
|
41
45
|
return ret;
|
|
42
46
|
}
|
package/src/utils/stateData.js
CHANGED
|
@@ -20,16 +20,21 @@ const { dateToISO8601String, zeroHourDate } = require('./datetime');
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
*
|
|
23
|
-
* @param {IStateRequest} req
|
|
24
|
-
* @param {Responder} res
|
|
25
|
-
* @param {object} configuration
|
|
23
|
+
* @param {IStateRequest} [req]
|
|
24
|
+
* @param {Responder} [res]
|
|
25
|
+
* @param {object} [configuration]
|
|
26
26
|
* @param {object} [stateOverride]
|
|
27
27
|
* @returns {object}
|
|
28
28
|
*/
|
|
29
|
-
module.exports = function stateData (
|
|
30
|
-
|
|
29
|
+
module.exports = function stateData (
|
|
30
|
+
req = null,
|
|
31
|
+
res = null,
|
|
32
|
+
configuration = null,
|
|
33
|
+
stateOverride = {}
|
|
34
|
+
) {
|
|
35
|
+
const c = configuration || (req && req.configuration) || (res && res._configuration);
|
|
31
36
|
|
|
32
|
-
const $this = req.text();
|
|
37
|
+
const $this = req ? req.text() : '';
|
|
33
38
|
|
|
34
39
|
const now = new Date();
|
|
35
40
|
|
|
@@ -42,15 +47,20 @@ module.exports = function stateData (req, res = null, configuration = null, stat
|
|
|
42
47
|
|
|
43
48
|
const {
|
|
44
49
|
senderId,
|
|
45
|
-
pageId
|
|
46
|
-
|
|
50
|
+
pageId,
|
|
51
|
+
state
|
|
52
|
+
} = req || {
|
|
53
|
+
senderId: res._senderId,
|
|
54
|
+
pageId: res._pageId,
|
|
55
|
+
state: res.options.state
|
|
56
|
+
};
|
|
47
57
|
|
|
48
58
|
return {
|
|
49
59
|
senderId,
|
|
50
60
|
pageId,
|
|
51
61
|
c,
|
|
52
62
|
configuration: c,
|
|
53
|
-
...
|
|
63
|
+
...state,
|
|
54
64
|
...(res ? res.newState : {}),
|
|
55
65
|
...stateOverride,
|
|
56
66
|
$this,
|
|
@@ -59,7 +69,7 @@ module.exports = function stateData (req, res = null, configuration = null, stat
|
|
|
59
69
|
$tomorrow,
|
|
60
70
|
$yesterday,
|
|
61
71
|
// yes - res because of circular dependency
|
|
62
|
-
...(res && req.actionData()),
|
|
72
|
+
...(res && req && req.actionData()),
|
|
63
73
|
...(res ? res.data : {}),
|
|
64
74
|
$input: $this
|
|
65
75
|
};
|