wingbot 3.71.6 → 3.72.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/.babelrc +0 -0
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/pullRequest.yml +2 -2
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/index.js +0 -0
- package/jsconfig.json +0 -0
- package/package.json +1 -1
- package/plugins/ai.wingbot.clearMessageSequences/plugin.js +0 -0
- package/plugins/ai.wingbot.conditionIfGoBackPossible/plugin.js +0 -0
- package/plugins/ai.wingbot.disambiguation/plugin.js +0 -0
- package/plugins/ai.wingbot.goBack/plugin.js +0 -0
- package/plugins/ai.wingbot.goToLastBreadcrumb/plugin.js +0 -0
- package/plugins/ai.wingbot.ifImageDetected/plugin.js +0 -0
- package/plugins/ai.wingbot.ifStickerDetected/plugin.js +0 -0
- package/plugins/ai.wingbot.jumpBack/plugin.js +0 -0
- package/plugins/ai.wingbot.jumpTo/plugin.js +0 -0
- package/plugins/ai.wingbot.keepInInteraction/plugin.js +0 -0
- package/plugins/ai.wingbot.keepInInteractionJustOnce/plugin.js +0 -0
- package/plugins/ai.wingbot.keepPreviousHandlers/plugin.js +0 -0
- package/plugins/ai.wingbot.keepPreviousHandlersJustOnce/plugin.js +0 -0
- package/plugins/ai.wingbot.oneTimeNotificationRequest/plugin.js +0 -0
- package/plugins/ai.wingbot.openai/plugin.js +0 -0
- package/plugins/ai.wingbot.passThreadToBot/plugin.js +0 -0
- package/plugins/ai.wingbot.persona/plugin.js +0 -0
- package/plugins/ai.wingbot.putABreadcrumb/plugin.js +0 -0
- package/plugins/ai.wingbot.regexp/plugin.js +0 -0
- package/plugins/ai.wingbot.setState/plugin.js +0 -0
- package/plugins/ai.wingbot.setStateFromInput/plugin.js +0 -0
- package/plugins/ai.wingbot.slotsContinue/plugin.js +0 -0
- package/plugins/ai.wingbot.slotsRegister/plugin.js +0 -0
- package/plugins/ai.wingbot.stopResponding/plugin.js +0 -0
- package/plugins/ai.wingbot.trackingEvent/plugin.js +0 -0
- package/plugins/ai.wingbot.upload/plugin.js +0 -0
- package/plugins/ai.wingbot.waitASecond/plugin.js +0 -0
- package/plugins/index.js +0 -0
- package/plugins/plugins.json +0 -0
- package/src/Ai.js +44 -3
- package/src/AiMatching.js +66 -40
- package/src/BatchConversationTester.js +0 -0
- package/src/BotApp.js +0 -0
- package/src/BotAppSender.js +0 -0
- package/src/BuildRouter.js +2 -0
- package/src/CallbackAuditLog.js +0 -0
- package/src/ChatGpt.js +0 -0
- package/src/ConversationTester.js +0 -0
- package/src/LLM.js +183 -2
- package/src/LLMMockProvider.js +0 -0
- package/src/LLMSession.js +90 -3
- package/src/MockAiModel.js +0 -0
- package/src/OrchestratorClient.js +0 -0
- package/src/Plugins.js +0 -0
- package/src/Processor.js +1 -1
- package/src/ReducerWrapper.js +0 -0
- package/src/Request.js +0 -0
- package/src/Responder.js +143 -3
- package/src/ReturnSender.js +4 -4
- package/src/Router.js +0 -0
- package/src/RouterWrap.js +0 -0
- package/src/SecurityMiddleware.js +0 -0
- package/src/Tester.js +44 -2
- package/src/analytics/GA4.js +0 -0
- package/src/analytics/consts.js +0 -0
- package/src/analytics/onInteractionHandler.js +0 -0
- package/src/defaultResourceMap.js +0 -0
- package/src/features.js +0 -0
- package/src/flags.js +0 -0
- package/src/fuzzy/factoryFuzzySearch.js +0 -0
- package/src/fuzzy/fuzzyUtils.js +0 -0
- package/src/fuzzy/index.js +0 -0
- package/src/fuzzy/levenshtein.js +0 -0
- package/src/fuzzy/normalize.js +0 -0
- package/src/fuzzy/prepareFuzzyIndex.js +0 -0
- package/src/graphApi/GraphApi.js +0 -0
- package/src/graphApi/WingbotApiConnector.js +0 -0
- package/src/graphApi/apiAuthorizer.js +0 -0
- package/src/graphApi/conversationTestApi.js +0 -0
- package/src/graphApi/conversationsApi.js +0 -0
- package/src/graphApi/index.js +0 -0
- package/src/graphApi/postBackApi.js +0 -0
- package/src/graphApi/schema.gql +0 -0
- package/src/graphApi/validateBotApi.js +0 -0
- package/src/notifications/Notifications.js +0 -0
- package/src/notifications/NotificationsStorage.js +0 -0
- package/src/notifications/api/index.js +0 -0
- package/src/notifications/api/notificationsApiFactory.js +0 -0
- package/src/notifications/index.js +0 -0
- package/src/resolvers/bounce.js +0 -0
- package/src/resolvers/button.js +0 -0
- package/src/resolvers/carousel.js +0 -0
- package/src/resolvers/contextMessage.js +21 -4
- package/src/resolvers/expected.js +0 -0
- package/src/resolvers/expectedInput.js +0 -0
- package/src/resolvers/hbs.js +0 -0
- package/src/resolvers/include.js +0 -0
- package/src/resolvers/index.js +0 -0
- package/src/resolvers/media.js +0 -0
- package/src/resolvers/message.js +43 -1
- package/src/resolvers/passThread.js +0 -0
- package/src/resolvers/path.js +0 -0
- package/src/resolvers/plugin.js +0 -0
- package/src/resolvers/postback.js +0 -0
- package/src/resolvers/resolverTags.js +0 -0
- package/src/resolvers/setState.js +0 -0
- package/src/resolvers/skip.js +0 -0
- package/src/resolvers/subscribtions.js +0 -0
- package/src/resolvers/utils.js +14 -5
- package/src/systemEntities/email.js +0 -0
- package/src/systemEntities/index.js +0 -0
- package/src/systemEntities/phone.js +0 -0
- package/src/systemEntities/regexps.js +0 -0
- package/src/templates/BaseTemplate.js +0 -0
- package/src/templates/ButtonTemplate.js +0 -0
- package/src/templates/GenericTemplate.js +0 -0
- package/src/templates/ListTemplate.js +0 -0
- package/src/templates/ReceiptTemplate.js +0 -0
- package/src/testTools/AnyResponseAssert.js +0 -0
- package/src/testTools/PromptAssert.js +184 -0
- package/src/testTools/ResponseAssert.js +0 -0
- package/src/testTools/asserts.js +42 -4
- package/src/testTools/index.js +0 -0
- package/src/tools/MemoryBotConfigStorage.js +0 -0
- package/src/tools/MemoryChatLogStorage.js +0 -0
- package/src/tools/MemoryStateStorage.js +0 -0
- package/src/tools/bufferloader.js +0 -0
- package/src/tools/index.js +0 -0
- package/src/tools/routeToEvents.js +0 -0
- package/src/transcript/extractText.js +0 -0
- package/src/transcript/textBodyFromTranscript.js +0 -0
- package/src/transcript/transcriptFromHistory.js +0 -0
- package/src/utils/ai.js +0 -0
- package/src/utils/compileWithState.js +0 -0
- package/src/utils/customCondition.js +14 -1
- package/src/utils/customFn.js +0 -0
- package/src/utils/datetime.js +0 -0
- package/src/utils/deepMapTools.js +0 -0
- package/src/utils/generateToken.js +0 -0
- package/src/utils/getCondition.js +16 -4
- package/src/utils/getUpdate.js +4 -4
- package/src/utils/headersToAuditMeta.js +0 -0
- package/src/utils/index.js +0 -0
- package/src/utils/pathUtils.js +0 -0
- package/src/utils/quickReplies.js +0 -0
- package/src/utils/slots.js +0 -0
- package/src/utils/stateData.js +2 -0
- package/src/utils/stateVariables.js +0 -0
- package/src/utils/tokenizer.js +0 -0
- package/src/utils/wrapPluginFunction.js +0 -0
- package/src/wingbot/CachedModel.js +0 -0
- package/src/wingbot/CustomEntityDetectionModel.js +0 -0
- package/src/wingbot/WingbotModel.js +0 -0
- package/src/wingbot/index.js +0 -0
package/src/Tester.js
CHANGED
|
@@ -19,8 +19,12 @@ 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
21
|
const LLMMockProvider = require('./LLMMockProvider');
|
|
22
|
+
const PromptAssert = require('./testTools/PromptAssert');
|
|
22
23
|
|
|
23
24
|
/** @typedef {import('./Processor').ProcessorOptions<Router>} ProcessorOptions */
|
|
25
|
+
/** @typedef {import('./LLM').PromptInfo} PromptInfo */
|
|
26
|
+
/** @typedef {import('./LLM').LLMRole} LLMRole */
|
|
27
|
+
/** @typedef {import('./LLM').LLMMessage} LLMMessage */
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Utility for testing requests
|
|
@@ -128,6 +132,9 @@ class Tester {
|
|
|
128
132
|
this.responses = [];
|
|
129
133
|
this.actions = [];
|
|
130
134
|
|
|
135
|
+
/** @type {PromptInfo[]} */
|
|
136
|
+
this.prompts = [];
|
|
137
|
+
|
|
131
138
|
/**
|
|
132
139
|
* @prop {object} predefined test data to use
|
|
133
140
|
*/
|
|
@@ -201,6 +208,7 @@ class Tester {
|
|
|
201
208
|
this._actionsCollector = [];
|
|
202
209
|
this._pluginBlocksCollector = [];
|
|
203
210
|
this._responsesMock = [];
|
|
211
|
+
this.prompts = [];
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
/**
|
|
@@ -259,6 +267,7 @@ class Tester {
|
|
|
259
267
|
throw Object.assign(new Error(`Processor failed with status ${res.status}`), { code: res.status });
|
|
260
268
|
}
|
|
261
269
|
this.responses = messageSender.responses;
|
|
270
|
+
this.prompts = messageSender.prompts;
|
|
262
271
|
this.pluginBlocks = this._pluginBlocksCollector;
|
|
263
272
|
this.actions = this._actionsCollector;
|
|
264
273
|
this._actionsCollector = [];
|
|
@@ -388,6 +397,36 @@ class Tester {
|
|
|
388
397
|
this.storage.saveState(stateObj);
|
|
389
398
|
}
|
|
390
399
|
|
|
400
|
+
/**
|
|
401
|
+
*
|
|
402
|
+
* @returns {PromptAssert}
|
|
403
|
+
*/
|
|
404
|
+
anyPrompt () {
|
|
405
|
+
return new PromptAssert(this.prompts);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
*
|
|
410
|
+
* @returns {PromptAssert}
|
|
411
|
+
*/
|
|
412
|
+
lastPrompt () {
|
|
413
|
+
return new PromptAssert(
|
|
414
|
+
this.prompts.length
|
|
415
|
+
? [this.prompts[this.prompts.length - 1]]
|
|
416
|
+
: []
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
*
|
|
422
|
+
* @returns {LLMMessage}
|
|
423
|
+
*/
|
|
424
|
+
getLastPromptResult () {
|
|
425
|
+
return this.prompts.length
|
|
426
|
+
? this.prompts[this.prompts.length - 1].result
|
|
427
|
+
: null;
|
|
428
|
+
}
|
|
429
|
+
|
|
391
430
|
/**
|
|
392
431
|
* Assert, that state contains a subset of provided value
|
|
393
432
|
*
|
|
@@ -606,9 +645,10 @@ class Tester {
|
|
|
606
645
|
/**
|
|
607
646
|
* Prints last conversation turnaround
|
|
608
647
|
*
|
|
609
|
-
* @param {boolean} [
|
|
648
|
+
* @param {boolean} [full=false]
|
|
649
|
+
* @param {boolean} [showPrivateKeys=false]
|
|
610
650
|
*/
|
|
611
|
-
debug (showPrivateKeys = false) {
|
|
651
|
+
debug (full = false, showPrivateKeys = false) {
|
|
612
652
|
// eslint-disable-next-line no-console
|
|
613
653
|
console.log(
|
|
614
654
|
'\n===== actions =====\n',
|
|
@@ -625,6 +665,8 @@ class Tester {
|
|
|
625
665
|
Object.entries(this.getState().state)
|
|
626
666
|
.filter((e) => showPrivateKeys || !e[0].startsWith('_'))
|
|
627
667
|
),
|
|
668
|
+
'\n------- LLM -------\n',
|
|
669
|
+
...PromptAssert.debug(this.prompts, full, true),
|
|
628
670
|
'\n===================\n'
|
|
629
671
|
);
|
|
630
672
|
}
|
package/src/analytics/GA4.js
CHANGED
|
File without changes
|
package/src/analytics/consts.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/features.js
CHANGED
|
File without changes
|
package/src/flags.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/fuzzy/fuzzyUtils.js
CHANGED
|
File without changes
|
package/src/fuzzy/index.js
CHANGED
|
File without changes
|
package/src/fuzzy/levenshtein.js
CHANGED
|
File without changes
|
package/src/fuzzy/normalize.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/graphApi/GraphApi.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/graphApi/index.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/graphApi/schema.gql
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/resolvers/bounce.js
CHANGED
|
File without changes
|
package/src/resolvers/button.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -8,30 +8,47 @@ const Router = require('../Router');
|
|
|
8
8
|
const Responder = require('../Responder');
|
|
9
9
|
const { getLanguageText } = require('./utils');
|
|
10
10
|
const compileWithState = require('../utils/compileWithState');
|
|
11
|
+
const LLM = require('../LLM');
|
|
11
12
|
|
|
12
13
|
/** @typedef {import('../BuildRouter').BotContext<any>} BotContext */
|
|
13
14
|
/** @typedef {import('../Router').Resolver<any>} Resolver */
|
|
14
15
|
/** @typedef {import('./utils').Translations} Translations */
|
|
16
|
+
/** @typedef {import('../LLM').EvaluationRule} EvaluationRule */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} ContextMessageParams
|
|
20
|
+
* @property {Translations} context
|
|
21
|
+
* @property {string} [type]
|
|
22
|
+
* @property {EvaluationRule[]} [rules]
|
|
23
|
+
*/
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
26
|
*
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {Translations} params.context
|
|
20
|
-
* @param {string} [params.type]
|
|
27
|
+
* @param {ContextMessageParams} params
|
|
21
28
|
* @param {BotContext} context
|
|
22
29
|
* @returns {Resolver}
|
|
23
30
|
*/
|
|
24
31
|
// eslint-disable-next-line no-unused-vars
|
|
25
32
|
function contextMessage (params, context) {
|
|
26
33
|
|
|
34
|
+
const { rules = [] } = params;
|
|
35
|
+
const preprocessedRules = LLM.preprocessEvaluationRules(rules, context);
|
|
36
|
+
|
|
27
37
|
return async (req, res) => {
|
|
28
|
-
res.
|
|
38
|
+
res.llmAddInstructions((r) => {
|
|
29
39
|
const translated = getLanguageText(params.context, req.state.lang);
|
|
30
40
|
const translatedText = Array.isArray(translated) ? translated[0] : translated;
|
|
31
41
|
const statefulPrompt = compileWithState(req, r, translatedText);
|
|
32
42
|
return statefulPrompt;
|
|
33
43
|
}, params.type);
|
|
34
44
|
|
|
45
|
+
preprocessedRules.forEach((rule) => res.llmAddResultRule(
|
|
46
|
+
rule,
|
|
47
|
+
undefined,
|
|
48
|
+
undefined,
|
|
49
|
+
params.type
|
|
50
|
+
));
|
|
51
|
+
|
|
35
52
|
return Router.CONTINUE;
|
|
36
53
|
};
|
|
37
54
|
}
|
|
File without changes
|
|
File without changes
|
package/src/resolvers/hbs.js
CHANGED
|
File without changes
|
package/src/resolvers/include.js
CHANGED
|
File without changes
|
package/src/resolvers/index.js
CHANGED
|
File without changes
|
package/src/resolvers/media.js
CHANGED
|
File without changes
|
package/src/resolvers/message.js
CHANGED
|
@@ -23,8 +23,11 @@ const { vars, VAR_TYPES } = require('../utils/stateVariables');
|
|
|
23
23
|
const LLM = require('../LLM');
|
|
24
24
|
|
|
25
25
|
/** @typedef {import('../Responder').VoiceControl} VoiceControl */
|
|
26
|
+
/** @typedef {import('../BuildRouter').LinksMap} LinksMap */
|
|
26
27
|
/** @typedef {import('./utils').Translations} Translations */
|
|
27
28
|
/** @typedef {import('./utils').TextObject} TextObject */
|
|
29
|
+
/** @typedef {import('../utils/getCondition').ConditionDefinition} ConditionDefinition */
|
|
30
|
+
/** @typedef {import('../utils/getCondition').ConditionContext} ConditionContext */
|
|
28
31
|
/**
|
|
29
32
|
* Returns voice control props from params
|
|
30
33
|
*
|
|
@@ -68,6 +71,30 @@ function getVoiceControlFromParams (params, lang = null) {
|
|
|
68
71
|
return Object.keys(voiceControl).length > 0 ? voiceControl : null;
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {object} QuickReplyData
|
|
76
|
+
* @prop {boolean} [isLocation]
|
|
77
|
+
* @prop {boolean} [isEmail]
|
|
78
|
+
* @prop {boolean} [isPhone]
|
|
79
|
+
* @prop {boolean} [trackAsNegative]
|
|
80
|
+
* @prop {string} [action]
|
|
81
|
+
* @prop {string} [targetRouteId]
|
|
82
|
+
* @prop {{l:string,t:string[]}[] | string} [title]
|
|
83
|
+
* @prop {object} [setState]
|
|
84
|
+
* @prop {string[]} [aiTags]
|
|
85
|
+
* @prop {{l:string,t:string[]}[] | string} [aiTitle]
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @typedef {ConditionDefinition & QuickReplyData} QuickReply
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @param {QuickReply[]} replies
|
|
95
|
+
* @param {LinksMap} linksMap
|
|
96
|
+
* @param {ConditionContext} context
|
|
97
|
+
*/
|
|
71
98
|
function parseReplies (replies, linksMap, context) {
|
|
72
99
|
return replies.map((reply) => {
|
|
73
100
|
|
|
@@ -314,8 +341,9 @@ function message (params, context = {}) {
|
|
|
314
341
|
/**
|
|
315
342
|
* @param {Request} req
|
|
316
343
|
* @param {Responder} res
|
|
344
|
+
* @param {Function } postBack
|
|
317
345
|
*/
|
|
318
|
-
return async (req, res) => {
|
|
346
|
+
return async (req, res, postBack) => {
|
|
319
347
|
if (condition === undefined) {
|
|
320
348
|
condition = getCondition(params, context, 'Message condition');
|
|
321
349
|
}
|
|
@@ -425,6 +453,20 @@ function message (params, context = {}) {
|
|
|
425
453
|
await session.systemPrompt(text)
|
|
426
454
|
.generate();
|
|
427
455
|
|
|
456
|
+
const evaluation = await res.llmEvaluate(session, params.llmContextType);
|
|
457
|
+
|
|
458
|
+
if (evaluation.discard) {
|
|
459
|
+
if (isLastMessage && !req.actionData()._resolverTag) {
|
|
460
|
+
res.finalMessageSent = true;
|
|
461
|
+
}
|
|
462
|
+
return ret;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (evaluation.action) {
|
|
466
|
+
postBack(evaluation.action);
|
|
467
|
+
return Router.END;
|
|
468
|
+
}
|
|
469
|
+
|
|
428
470
|
// if (!response.content) {
|
|
429
471
|
// // no response?
|
|
430
472
|
// }
|
|
File without changes
|
package/src/resolvers/path.js
CHANGED
|
File without changes
|
package/src/resolvers/plugin.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/resolvers/skip.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/resolvers/utils.js
CHANGED
|
@@ -31,17 +31,26 @@ const WEBVIEW_COMPACT = 'compact';
|
|
|
31
31
|
* @prop {string|null} p - purpose
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* null = text+voice
|
|
36
|
+
* t = text only
|
|
37
|
+
* v = voice only
|
|
38
|
+
* s = ssml
|
|
39
|
+
*
|
|
40
|
+
* @typedef {"t"|"v"|"s"|null} Purpose
|
|
41
|
+
*/
|
|
42
|
+
|
|
34
43
|
/**
|
|
35
44
|
* @typedef Translation
|
|
36
45
|
* @property {string|string[]} t - text alternatives
|
|
37
46
|
* @property {string | null} l - language
|
|
38
|
-
* @property {
|
|
39
|
-
* null = default + voice
|
|
40
|
-
* t = text
|
|
41
|
-
* v = voice
|
|
42
|
-
* s = ssml
|
|
47
|
+
* @property {Purpose | Purpose[]} [p] - purposes
|
|
43
48
|
*/
|
|
44
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @param {string | string[] | Translation | Translation[]} translations
|
|
52
|
+
* @returns {translations is Translation[]}
|
|
53
|
+
*/
|
|
45
54
|
function isArrayOfObjects (translations) {
|
|
46
55
|
return Array.isArray(translations)
|
|
47
56
|
&& typeof translations[0] === 'object'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author David Menger
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const asserts = require('./asserts');
|
|
8
|
+
|
|
9
|
+
const { ex } = asserts;
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../LLM').PromptInfo} PromptInfo */
|
|
12
|
+
/** @typedef {import('../LLM').LLMMessage} LLMMessage */
|
|
13
|
+
/** @typedef {import('../LLM').LLMRole} LLMRole */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @class PromptAssert
|
|
17
|
+
*/
|
|
18
|
+
class PromptAssert {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {PromptInfo[]} prompts
|
|
23
|
+
*/
|
|
24
|
+
constructor (prompts) {
|
|
25
|
+
this._prompts = prompts;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if recent instruction contains selected string
|
|
30
|
+
*
|
|
31
|
+
* @param {string} search
|
|
32
|
+
* @returns {this}
|
|
33
|
+
*/
|
|
34
|
+
instructionContains (search) {
|
|
35
|
+
const messages = this._flatPrompt((m) => m.role === 'system');
|
|
36
|
+
this._promptContains(search, messages, false, 'of role "system"');
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if recent prompt input contains selected string
|
|
42
|
+
*
|
|
43
|
+
* @param {string} search
|
|
44
|
+
* @returns {this}
|
|
45
|
+
*/
|
|
46
|
+
promptContains (search) {
|
|
47
|
+
this._promptContains(search, this._flatPrompt());
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if recent results contains selected string
|
|
53
|
+
*
|
|
54
|
+
* @param {string} search
|
|
55
|
+
* @returns {this}
|
|
56
|
+
*/
|
|
57
|
+
resultContains (search) {
|
|
58
|
+
this._promptContains(search, this._flatResults());
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {(value: LLMMessage, index: number, array: LLMMessage[]) => unknown} filter
|
|
65
|
+
* @returns {LLMMessage[]}
|
|
66
|
+
*/
|
|
67
|
+
_flatPrompt (filter = () => true) {
|
|
68
|
+
return this._prompts
|
|
69
|
+
.flatMap((prompt) => prompt.prompt
|
|
70
|
+
.filter(filter));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @returns {LLMMessage[]}
|
|
76
|
+
*/
|
|
77
|
+
_flatResults () {
|
|
78
|
+
return this._prompts
|
|
79
|
+
.map((prompt) => prompt.result);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_promptContains (search, messages, notContains = false, addMessage = 'No LLM message found') {
|
|
83
|
+
if (messages.length === 0) {
|
|
84
|
+
PromptAssert.debug(this._prompts, true);
|
|
85
|
+
assert.fail(ex(`No LLM message ${addMessage}`, search));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const found = messages.some((m) => asserts.llmContains(m, search, false));
|
|
89
|
+
// console.log({ found, messages, notContains });
|
|
90
|
+
if (notContains === found) {
|
|
91
|
+
PromptAssert.debug(this._prompts, true);
|
|
92
|
+
assert.fail(ex(
|
|
93
|
+
`Text${notContains ? '' : ' not'} found in LLM messages ${addMessage}`,
|
|
94
|
+
search
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
*
|
|
101
|
+
* @param {LLMMessage[]} prompt
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
static promptStats (prompt) {
|
|
105
|
+
const stats = prompt.reduce((o, m) => Object.assign(o, {
|
|
106
|
+
[m.role]: (o[m.role] || 0) + 1
|
|
107
|
+
}), {
|
|
108
|
+
system: 0, assistant: 0, user: 0, tool: 0
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return `[ ${Object.entries(stats).map(([k, v]) => `${k.toUpperCase()}: ${v}`).join(' ')} ]`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @param {LLMRole} role
|
|
117
|
+
* @return {string}
|
|
118
|
+
*/
|
|
119
|
+
static _marker (role) {
|
|
120
|
+
switch (role) {
|
|
121
|
+
case 'user':
|
|
122
|
+
return '>';
|
|
123
|
+
case 'system':
|
|
124
|
+
return '*';
|
|
125
|
+
case 'assistant':
|
|
126
|
+
return '<';
|
|
127
|
+
default:
|
|
128
|
+
return '-';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static _content (msg, full) {
|
|
133
|
+
if (typeof msg.content !== 'string') {
|
|
134
|
+
return msg.content === null ? '<null>' : typeof msg.content;
|
|
135
|
+
}
|
|
136
|
+
const txt = msg.content.replace(/\n+/g, ' ').replace(/\s\s+/g, ' ');
|
|
137
|
+
return `'${txt.length <= 100 || full ? txt : `${txt.substring(0, 100)}...`}'`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
* @param {PromptInfo[]} prompts
|
|
143
|
+
* @param {boolean} [full=false]
|
|
144
|
+
* @param {boolean} [silent=false]
|
|
145
|
+
* @returns {string[]}
|
|
146
|
+
*/
|
|
147
|
+
static debug (prompts, full = false, silent = false) {
|
|
148
|
+
let out;
|
|
149
|
+
if (prompts.length === 0) {
|
|
150
|
+
out = ['no LLM prompts occured'];
|
|
151
|
+
} else {
|
|
152
|
+
out = prompts.map((p, i) => {
|
|
153
|
+
const lastIndexOfUser = full
|
|
154
|
+
? 0
|
|
155
|
+
: p.prompt.reduce((li, m, c) => (m.role === 'user' ? c : li), 0);
|
|
156
|
+
|
|
157
|
+
const prompt = p.prompt.reduce((a, m, c) => {
|
|
158
|
+
if (['user', 'assistant'].includes(m.role) && c < lastIndexOfUser) {
|
|
159
|
+
const prev = a[a.length - 1];
|
|
160
|
+
if (prev === '...') {
|
|
161
|
+
return a;
|
|
162
|
+
}
|
|
163
|
+
return [...a, '...'];
|
|
164
|
+
}
|
|
165
|
+
return [...a, `${PromptAssert._marker(m.role)} ${PromptAssert._content(m, full)}`];
|
|
166
|
+
}, []);
|
|
167
|
+
|
|
168
|
+
return [
|
|
169
|
+
`${i + 1}) ${PromptAssert.promptStats(p.prompt)}`,
|
|
170
|
+
`${prompt.join('\n ')}`,
|
|
171
|
+
`# ${PromptAssert._content(p.result)}`
|
|
172
|
+
].join('\n ');
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (!silent) {
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log(...out);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = PromptAssert;
|
|
File without changes
|
package/src/testTools/asserts.js
CHANGED
|
@@ -7,6 +7,9 @@ const assert = require('assert');
|
|
|
7
7
|
const deepExtend = require('deep-extend');
|
|
8
8
|
const { actionMatches, parseActionPayload } = require('../utils');
|
|
9
9
|
|
|
10
|
+
/** @typedef {import('../LLM').PromptInfo} PromptInfo */
|
|
11
|
+
/** @typedef {import('../LLM').LLMMessage} LLMMessage */
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Format message
|
|
12
15
|
*
|
|
@@ -29,9 +32,21 @@ function m (text, actual = null, expected = null) {
|
|
|
29
32
|
return `${text}${result}`;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
function ex (message, expected, actual) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
function ex (message, expected, actual = null) {
|
|
36
|
+
let actuals;
|
|
37
|
+
if (Array.isArray(actual)) {
|
|
38
|
+
actuals = actual;
|
|
39
|
+
} else {
|
|
40
|
+
actuals = actual === null ? [] : [actual];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const msg = `${message}\n + expected: "${expected}"`;
|
|
44
|
+
|
|
45
|
+
if (actuals.length === 0) {
|
|
46
|
+
return msg;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return `${msg}\n - actual: ${actuals
|
|
35
50
|
.map((a) => `"${a}"`).join('\n ')}`;
|
|
36
51
|
}
|
|
37
52
|
|
|
@@ -144,6 +159,28 @@ function contains (response, search, message = 'Should contain a text') {
|
|
|
144
159
|
return true;
|
|
145
160
|
}
|
|
146
161
|
|
|
162
|
+
/**
|
|
163
|
+
*
|
|
164
|
+
* @param {LLMMessage} llmMsg
|
|
165
|
+
* @param {string} search
|
|
166
|
+
* @param {string|false} message
|
|
167
|
+
*/
|
|
168
|
+
function llmContains (llmMsg, search, message = 'Should contain a text') {
|
|
169
|
+
const text = llmMsg.content;
|
|
170
|
+
|
|
171
|
+
const typeIsText = typeof text === 'string';
|
|
172
|
+
if (message === false && !typeIsText) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
assert.ok(typeIsText, m(message, search, 'not a text message'));
|
|
176
|
+
const match = searchMatchesText(search, text);
|
|
177
|
+
if (message === false) {
|
|
178
|
+
return match;
|
|
179
|
+
}
|
|
180
|
+
assert.ok(match, m(message, text, search));
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
147
184
|
/**
|
|
148
185
|
* Checks quick response action
|
|
149
186
|
*
|
|
@@ -374,5 +411,6 @@ module.exports = {
|
|
|
374
411
|
quickReplyText,
|
|
375
412
|
getText,
|
|
376
413
|
parseActionPayload,
|
|
377
|
-
ex
|
|
414
|
+
ex,
|
|
415
|
+
llmContains
|
|
378
416
|
};
|
package/src/testTools/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tools/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|