wingbot 3.69.7 → 3.70.0-alpha.4
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/index.js +0 -2
- package/jsconfig.json +3 -3
- package/package.json +4 -3
- package/src/BuildRouter.js +71 -75
- package/src/ChatGpt.js +84 -34
- package/src/LLM.js +160 -0
- package/src/LLMMockProvider.js +67 -0
- package/src/LLMSession.js +228 -0
- package/src/Processor.js +28 -7
- package/src/Responder.js +175 -14
- package/src/ReturnSender.js +1 -0
- package/src/Tester.js +5 -0
- package/src/resolvers/button.js +1 -1
- package/src/resolvers/contextMessage.js +39 -0
- package/src/resolvers/include.js +3 -2
- package/src/resolvers/index.js +2 -0
- package/src/resolvers/message.js +45 -9
- package/src/resolvers/postback.js +5 -1
- package/src/resolvers/utils.js +1 -1
- package/src/utils/stateData.js +20 -10
package/index.js
CHANGED
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", "dom"],
|
|
5
|
+
"target": "ESNext",
|
|
6
6
|
"allowSyntheticDefaultImports": true,
|
|
7
7
|
"checkJs": true,
|
|
8
8
|
"resolveJsonModule": true
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
"doc",
|
|
15
15
|
"docs"
|
|
16
16
|
]
|
|
17
|
-
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.70.0-alpha.4",
|
|
4
4
|
"description": "Enterprise Messaging Bot Conversation Engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/wingbot.ai/wingbot#readme",
|
|
39
39
|
"devDependencies": {
|
|
40
|
+
"@types/mocha": "^10.0.10",
|
|
40
41
|
"cpy-cli": "^5.0.0",
|
|
41
42
|
"eslint": "^8.56.0",
|
|
42
43
|
"eslint-config-airbnb": "^19.0.4",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"graphql": "^16.8.1",
|
|
62
63
|
"jsonwebtoken": "^9.0.2",
|
|
63
64
|
"node-fetch": "^2.6.7",
|
|
64
|
-
"path-to-regexp": "^6.
|
|
65
|
+
"path-to-regexp": "^6.3.0",
|
|
65
66
|
"uuid": "^9.0.1",
|
|
66
67
|
"webalize": "^0.1.0"
|
|
67
68
|
},
|
|
@@ -69,4 +70,4 @@
|
|
|
69
70
|
"axios": "^1.6.4",
|
|
70
71
|
"handlebars": "^4.0.0"
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
}
|
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,12 @@ class BuildRouter extends Router {
|
|
|
565
570
|
...this._resolvedContext, blockName, blockType, isRoot, staticBlockId, BuildRouter
|
|
566
571
|
};
|
|
567
572
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
this.
|
|
573
|
+
const [linksMap, nestedBlocksByStaticId] = this._createLinksMap(block);
|
|
574
|
+
// @ts-ignore
|
|
575
|
+
this._linksMap = linksMap;
|
|
571
576
|
|
|
572
|
-
|
|
577
|
+
// @ts-ignore
|
|
578
|
+
this._buildRoutes(block.routes, nestedBlocksByStaticId);
|
|
573
579
|
|
|
574
580
|
this._configTs = setConfigTimestamp;
|
|
575
581
|
|
|
@@ -577,74 +583,79 @@ class BuildRouter extends Router {
|
|
|
577
583
|
this.emit('rebuild');
|
|
578
584
|
}
|
|
579
585
|
|
|
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
586
|
/**
|
|
587
|
+
*
|
|
588
|
+
* returns {[LinksMap, BlockMap]}
|
|
614
589
|
*
|
|
615
590
|
* @param {Block} block
|
|
616
|
-
* @returns {LinksMap}
|
|
617
591
|
*/
|
|
618
592
|
_createLinksMap (block) {
|
|
593
|
+
const { linksMap: prevLinksMap, blocks = [] } = this._resolvedContext;
|
|
594
|
+
|
|
619
595
|
/** @type {LinksMap} */
|
|
620
596
|
const linksMap = new Map();
|
|
621
597
|
|
|
598
|
+
if (prevLinksMap) {
|
|
599
|
+
for (const [from, to] of prevLinksMap.entries()) {
|
|
600
|
+
linksMap.set(from, `../${to}`); // this._joinPaths('..', to)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const expectedFromResponders = new Set();
|
|
605
|
+
|
|
606
|
+
const blocksById = new Map();
|
|
607
|
+
|
|
622
608
|
block.routes
|
|
623
|
-
.
|
|
624
|
-
|
|
609
|
+
.forEach((route) => {
|
|
610
|
+
if (!route.isResponder) {
|
|
611
|
+
linksMap.set(route.id, route.path);
|
|
612
|
+
}
|
|
613
|
+
blocksById.set(route.id, route);
|
|
614
|
+
});
|
|
625
615
|
|
|
626
|
-
|
|
616
|
+
let { nestedBlocksByStaticId } = this._resolvedContext;
|
|
617
|
+
if (!nestedBlocksByStaticId) {
|
|
618
|
+
nestedBlocksByStaticId = new Map();
|
|
627
619
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
linksMap.set(from, this._joinPaths('..', to));
|
|
620
|
+
blocks.forEach((b) => {
|
|
621
|
+
if (b.staticBlockId && !b.disabled) {
|
|
622
|
+
nestedBlocksByStaticId.set(b.staticBlockId, b);
|
|
632
623
|
}
|
|
633
|
-
}
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
Object.assign(this._resolvedContext, { nestedBlocksByStaticId });
|
|
634
627
|
}
|
|
635
628
|
|
|
636
629
|
block.routes.forEach((route) => {
|
|
637
|
-
const enabledNestedBlock =
|
|
638
|
-
if (
|
|
639
|
-
|
|
630
|
+
const enabledNestedBlock = nestedBlocksByStaticId.get(this._getIncludedBlockId(route));
|
|
631
|
+
if (enabledNestedBlock) {
|
|
632
|
+
const routeConfig = this._getRouteConfig(route);
|
|
633
|
+
if (this._enabledByRouteConfig(routeConfig)) {
|
|
634
|
+
this._findEntryPointsInResolver(linksMap, enabledNestedBlock, route);
|
|
635
|
+
}
|
|
640
636
|
}
|
|
641
|
-
|
|
642
|
-
if (
|
|
643
|
-
|
|
637
|
+
|
|
638
|
+
if (route.isResponder) {
|
|
639
|
+
// create the pseudopath ant set to set to corresponding route
|
|
640
|
+
const referredRoutePath = linksMap.get(route.respondsToRouteId);
|
|
641
|
+
|
|
642
|
+
if (referredRoutePath) {
|
|
643
|
+
const expectedPath = `${referredRoutePath}_responder`
|
|
644
|
+
.replace(/^\//, '');
|
|
645
|
+
|
|
646
|
+
Object.assign(route, { path: expectedPath });
|
|
647
|
+
|
|
648
|
+
if (!expectedFromResponders.has(route.respondsToRouteId)) {
|
|
649
|
+
expectedFromResponders.add(route.respondsToRouteId);
|
|
650
|
+
|
|
651
|
+
const referredRoute = blocksById.get(route.respondsToRouteId);
|
|
652
|
+
Object.assign(referredRoute, { expectedPath });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
644
655
|
}
|
|
645
656
|
});
|
|
646
657
|
|
|
647
|
-
return linksMap;
|
|
658
|
+
return [linksMap, nestedBlocksByStaticId];
|
|
648
659
|
}
|
|
649
660
|
|
|
650
661
|
/**
|
|
@@ -682,24 +693,6 @@ class BuildRouter extends Router {
|
|
|
682
693
|
: null;
|
|
683
694
|
}
|
|
684
695
|
|
|
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
696
|
/**
|
|
704
697
|
*
|
|
705
698
|
* @param {TransformedRoute} route
|
|
@@ -811,8 +804,9 @@ class BuildRouter extends Router {
|
|
|
811
804
|
/**
|
|
812
805
|
*
|
|
813
806
|
* @param {TransformedRoute[]} routes
|
|
807
|
+
* @param {BlockMap} nestedBlocksByStaticId
|
|
814
808
|
*/
|
|
815
|
-
_buildRoutes (routes) {
|
|
809
|
+
_buildRoutes (routes, nestedBlocksByStaticId) {
|
|
816
810
|
routes.forEach((route, i) => {
|
|
817
811
|
const routeConfig = this._getRouteConfig(route);
|
|
818
812
|
|
|
@@ -821,7 +815,7 @@ class BuildRouter extends Router {
|
|
|
821
815
|
}
|
|
822
816
|
|
|
823
817
|
const includedBlockId = this._getIncludedBlockId(route);
|
|
824
|
-
const nestedBlock =
|
|
818
|
+
const nestedBlock = nestedBlocksByStaticId.get(includedBlockId);
|
|
825
819
|
|
|
826
820
|
if (includedBlockId && (!nestedBlock || !this._enabledByRouteConfig(routeConfig))) {
|
|
827
821
|
return;
|
|
@@ -882,9 +876,10 @@ class BuildRouter extends Router {
|
|
|
882
876
|
* @param {Resolver[]} resolvers
|
|
883
877
|
* @param {TransformedRoute} [route]
|
|
884
878
|
* @param {BuildInfo} [buildInfo]
|
|
879
|
+
* @param {BlockMap} [nestedBlocksByStaticId=null]
|
|
885
880
|
* @returns {Middleware<S,C>[]}
|
|
886
881
|
*/
|
|
887
|
-
buildResolvers (resolvers, route = DUMMY_ROUTE, buildInfo = {}) {
|
|
882
|
+
buildResolvers (resolvers, route = DUMMY_ROUTE, buildInfo = {}, nestedBlocksByStaticId = null) {
|
|
888
883
|
const {
|
|
889
884
|
path: ctxPath, isFallback, isResponder, expectedPath, id
|
|
890
885
|
} = route;
|
|
@@ -911,7 +906,8 @@ class BuildRouter extends Router {
|
|
|
911
906
|
expectedPath,
|
|
912
907
|
routeId: id,
|
|
913
908
|
configuration,
|
|
914
|
-
resolverId: resolver.id
|
|
909
|
+
resolverId: resolver.id,
|
|
910
|
+
nestedBlocksByStaticId
|
|
915
911
|
};
|
|
916
912
|
|
|
917
913
|
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<any>} 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,67 @@
|
|
|
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
|
+
// eslint-disable-next-line max-len
|
|
55
|
+
// content: `${statsText} > ${LLMMockProvider.DEFAULT_MODEL}: ${prompt.map((m) => m.content).join(' ')}`
|
|
56
|
+
// };
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
role: LLM.ROLE_ASSISTANT,
|
|
60
|
+
finishReason: 'length',
|
|
61
|
+
content: `${options.model || LLMMockProvider.DEFAULT_MODEL}:${prompt.map((m) => m.content).join(' ')}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = LLMMockProvider;
|