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.
Files changed (152) hide show
  1. package/.babelrc +0 -0
  2. package/.github/workflows/deploy.yml +2 -2
  3. package/.github/workflows/pullRequest.yml +2 -2
  4. package/LICENSE +0 -0
  5. package/README.md +0 -0
  6. package/index.js +0 -0
  7. package/jsconfig.json +0 -0
  8. package/package.json +1 -1
  9. package/plugins/ai.wingbot.clearMessageSequences/plugin.js +0 -0
  10. package/plugins/ai.wingbot.conditionIfGoBackPossible/plugin.js +0 -0
  11. package/plugins/ai.wingbot.disambiguation/plugin.js +0 -0
  12. package/plugins/ai.wingbot.goBack/plugin.js +0 -0
  13. package/plugins/ai.wingbot.goToLastBreadcrumb/plugin.js +0 -0
  14. package/plugins/ai.wingbot.ifImageDetected/plugin.js +0 -0
  15. package/plugins/ai.wingbot.ifStickerDetected/plugin.js +0 -0
  16. package/plugins/ai.wingbot.jumpBack/plugin.js +0 -0
  17. package/plugins/ai.wingbot.jumpTo/plugin.js +0 -0
  18. package/plugins/ai.wingbot.keepInInteraction/plugin.js +0 -0
  19. package/plugins/ai.wingbot.keepInInteractionJustOnce/plugin.js +0 -0
  20. package/plugins/ai.wingbot.keepPreviousHandlers/plugin.js +0 -0
  21. package/plugins/ai.wingbot.keepPreviousHandlersJustOnce/plugin.js +0 -0
  22. package/plugins/ai.wingbot.oneTimeNotificationRequest/plugin.js +0 -0
  23. package/plugins/ai.wingbot.openai/plugin.js +0 -0
  24. package/plugins/ai.wingbot.passThreadToBot/plugin.js +0 -0
  25. package/plugins/ai.wingbot.persona/plugin.js +0 -0
  26. package/plugins/ai.wingbot.putABreadcrumb/plugin.js +0 -0
  27. package/plugins/ai.wingbot.regexp/plugin.js +0 -0
  28. package/plugins/ai.wingbot.setState/plugin.js +0 -0
  29. package/plugins/ai.wingbot.setStateFromInput/plugin.js +0 -0
  30. package/plugins/ai.wingbot.slotsContinue/plugin.js +0 -0
  31. package/plugins/ai.wingbot.slotsRegister/plugin.js +0 -0
  32. package/plugins/ai.wingbot.stopResponding/plugin.js +0 -0
  33. package/plugins/ai.wingbot.trackingEvent/plugin.js +0 -0
  34. package/plugins/ai.wingbot.upload/plugin.js +0 -0
  35. package/plugins/ai.wingbot.waitASecond/plugin.js +0 -0
  36. package/plugins/index.js +0 -0
  37. package/plugins/plugins.json +0 -0
  38. package/src/Ai.js +44 -3
  39. package/src/AiMatching.js +66 -40
  40. package/src/BatchConversationTester.js +0 -0
  41. package/src/BotApp.js +0 -0
  42. package/src/BotAppSender.js +0 -0
  43. package/src/BuildRouter.js +2 -0
  44. package/src/CallbackAuditLog.js +0 -0
  45. package/src/ChatGpt.js +0 -0
  46. package/src/ConversationTester.js +0 -0
  47. package/src/LLM.js +183 -2
  48. package/src/LLMMockProvider.js +0 -0
  49. package/src/LLMSession.js +90 -3
  50. package/src/MockAiModel.js +0 -0
  51. package/src/OrchestratorClient.js +0 -0
  52. package/src/Plugins.js +0 -0
  53. package/src/Processor.js +1 -1
  54. package/src/ReducerWrapper.js +0 -0
  55. package/src/Request.js +0 -0
  56. package/src/Responder.js +143 -3
  57. package/src/ReturnSender.js +4 -4
  58. package/src/Router.js +0 -0
  59. package/src/RouterWrap.js +0 -0
  60. package/src/SecurityMiddleware.js +0 -0
  61. package/src/Tester.js +44 -2
  62. package/src/analytics/GA4.js +0 -0
  63. package/src/analytics/consts.js +0 -0
  64. package/src/analytics/onInteractionHandler.js +0 -0
  65. package/src/defaultResourceMap.js +0 -0
  66. package/src/features.js +0 -0
  67. package/src/flags.js +0 -0
  68. package/src/fuzzy/factoryFuzzySearch.js +0 -0
  69. package/src/fuzzy/fuzzyUtils.js +0 -0
  70. package/src/fuzzy/index.js +0 -0
  71. package/src/fuzzy/levenshtein.js +0 -0
  72. package/src/fuzzy/normalize.js +0 -0
  73. package/src/fuzzy/prepareFuzzyIndex.js +0 -0
  74. package/src/graphApi/GraphApi.js +0 -0
  75. package/src/graphApi/WingbotApiConnector.js +0 -0
  76. package/src/graphApi/apiAuthorizer.js +0 -0
  77. package/src/graphApi/conversationTestApi.js +0 -0
  78. package/src/graphApi/conversationsApi.js +0 -0
  79. package/src/graphApi/index.js +0 -0
  80. package/src/graphApi/postBackApi.js +0 -0
  81. package/src/graphApi/schema.gql +0 -0
  82. package/src/graphApi/validateBotApi.js +0 -0
  83. package/src/notifications/Notifications.js +0 -0
  84. package/src/notifications/NotificationsStorage.js +0 -0
  85. package/src/notifications/api/index.js +0 -0
  86. package/src/notifications/api/notificationsApiFactory.js +0 -0
  87. package/src/notifications/index.js +0 -0
  88. package/src/resolvers/bounce.js +0 -0
  89. package/src/resolvers/button.js +0 -0
  90. package/src/resolvers/carousel.js +0 -0
  91. package/src/resolvers/contextMessage.js +21 -4
  92. package/src/resolvers/expected.js +0 -0
  93. package/src/resolvers/expectedInput.js +0 -0
  94. package/src/resolvers/hbs.js +0 -0
  95. package/src/resolvers/include.js +0 -0
  96. package/src/resolvers/index.js +0 -0
  97. package/src/resolvers/media.js +0 -0
  98. package/src/resolvers/message.js +43 -1
  99. package/src/resolvers/passThread.js +0 -0
  100. package/src/resolvers/path.js +0 -0
  101. package/src/resolvers/plugin.js +0 -0
  102. package/src/resolvers/postback.js +0 -0
  103. package/src/resolvers/resolverTags.js +0 -0
  104. package/src/resolvers/setState.js +0 -0
  105. package/src/resolvers/skip.js +0 -0
  106. package/src/resolvers/subscribtions.js +0 -0
  107. package/src/resolvers/utils.js +14 -5
  108. package/src/systemEntities/email.js +0 -0
  109. package/src/systemEntities/index.js +0 -0
  110. package/src/systemEntities/phone.js +0 -0
  111. package/src/systemEntities/regexps.js +0 -0
  112. package/src/templates/BaseTemplate.js +0 -0
  113. package/src/templates/ButtonTemplate.js +0 -0
  114. package/src/templates/GenericTemplate.js +0 -0
  115. package/src/templates/ListTemplate.js +0 -0
  116. package/src/templates/ReceiptTemplate.js +0 -0
  117. package/src/testTools/AnyResponseAssert.js +0 -0
  118. package/src/testTools/PromptAssert.js +184 -0
  119. package/src/testTools/ResponseAssert.js +0 -0
  120. package/src/testTools/asserts.js +42 -4
  121. package/src/testTools/index.js +0 -0
  122. package/src/tools/MemoryBotConfigStorage.js +0 -0
  123. package/src/tools/MemoryChatLogStorage.js +0 -0
  124. package/src/tools/MemoryStateStorage.js +0 -0
  125. package/src/tools/bufferloader.js +0 -0
  126. package/src/tools/index.js +0 -0
  127. package/src/tools/routeToEvents.js +0 -0
  128. package/src/transcript/extractText.js +0 -0
  129. package/src/transcript/textBodyFromTranscript.js +0 -0
  130. package/src/transcript/transcriptFromHistory.js +0 -0
  131. package/src/utils/ai.js +0 -0
  132. package/src/utils/compileWithState.js +0 -0
  133. package/src/utils/customCondition.js +14 -1
  134. package/src/utils/customFn.js +0 -0
  135. package/src/utils/datetime.js +0 -0
  136. package/src/utils/deepMapTools.js +0 -0
  137. package/src/utils/generateToken.js +0 -0
  138. package/src/utils/getCondition.js +16 -4
  139. package/src/utils/getUpdate.js +4 -4
  140. package/src/utils/headersToAuditMeta.js +0 -0
  141. package/src/utils/index.js +0 -0
  142. package/src/utils/pathUtils.js +0 -0
  143. package/src/utils/quickReplies.js +0 -0
  144. package/src/utils/slots.js +0 -0
  145. package/src/utils/stateData.js +2 -0
  146. package/src/utils/stateVariables.js +0 -0
  147. package/src/utils/tokenizer.js +0 -0
  148. package/src/utils/wrapPluginFunction.js +0 -0
  149. package/src/wingbot/CachedModel.js +0 -0
  150. package/src/wingbot/CustomEntityDetectionModel.js +0 -0
  151. package/src/wingbot/WingbotModel.js +0 -0
  152. 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} [showPrivateKeys]
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
  }
File without changes
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
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
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
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 {object} params
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.llmAddSystemPrompt((r) => {
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
File without changes
File without changes
File without changes
File without changes
@@ -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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -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 {string|string[]} [p] - purposes
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
@@ -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
- const actuals = Array.isArray(actual) ? actual : [actual];
34
- return `${message}\n + expected: "${expected}"\n - actual: ${actuals
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
  };
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