wingbot 3.71.5 → 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 +184 -3
  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 +142 -11
  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 +54 -4
  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
@@ -20,10 +20,14 @@ const {
20
20
  FEATURE_SSML, FEATURE_TEXT, FEATURE_VOICE
21
21
  } = require('../features');
22
22
  const { vars, VAR_TYPES } = require('../utils/stateVariables');
23
+ const LLM = require('../LLM');
23
24
 
24
25
  /** @typedef {import('../Responder').VoiceControl} VoiceControl */
26
+ /** @typedef {import('../BuildRouter').LinksMap} LinksMap */
25
27
  /** @typedef {import('./utils').Translations} Translations */
26
28
  /** @typedef {import('./utils').TextObject} TextObject */
29
+ /** @typedef {import('../utils/getCondition').ConditionDefinition} ConditionDefinition */
30
+ /** @typedef {import('../utils/getCondition').ConditionContext} ConditionContext */
27
31
  /**
28
32
  * Returns voice control props from params
29
33
  *
@@ -67,6 +71,30 @@ function getVoiceControlFromParams (params, lang = null) {
67
71
  return Object.keys(voiceControl).length > 0 ? voiceControl : null;
68
72
  }
69
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
+ */
70
98
  function parseReplies (replies, linksMap, context) {
71
99
  return replies.map((reply) => {
72
100
 
@@ -313,8 +341,9 @@ function message (params, context = {}) {
313
341
  /**
314
342
  * @param {Request} req
315
343
  * @param {Responder} res
344
+ * @param {Function } postBack
316
345
  */
317
- return async (req, res) => {
346
+ return async (req, res, postBack) => {
318
347
  if (condition === undefined) {
319
348
  condition = getCondition(params, context, 'Message condition');
320
349
  }
@@ -417,29 +446,50 @@ function message (params, context = {}) {
417
446
  }
418
447
 
419
448
  if (params.type === 'prompt') {
449
+ res.typingOn()
450
+ .wait(1000);
420
451
  const session = await res.llmSessionWithHistory(params.llmContextType);
421
452
 
422
453
  await session.systemPrompt(text)
423
- .debug()
424
454
  .generate();
425
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
+
426
470
  // if (!response.content) {
427
471
  // // no response?
428
472
  // }
429
473
 
430
474
  const messages = session.messagesToSend();
431
475
 
476
+ res.setFlag(LLM.GPT_FLAG);
477
+
432
478
  const lastMessageI = messages.length - 1;
433
479
  messages.forEach((m, i) => {
434
480
  if (lastMessageI === i) {
435
481
  text = m.content;
436
482
  } else {
437
- res.text(m.content);
483
+ res.text(m.content, null, null, {
484
+ disableAutoTyping: true
485
+ });
438
486
  }
439
487
  });
440
488
  }
441
489
 
442
- res.text(text, sendReplies, voiceControl);
490
+ res.text(text, sendReplies, voiceControl, {
491
+ disableAutoTyping: params.type === 'prompt'
492
+ });
443
493
 
444
494
  if (isLastMessage && !req.actionData()._resolverTag) {
445
495
  res.finalMessageSent = true;
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