wingbot 3.77.0-alpha.2 → 3.77.0-alpha.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.77.0-alpha.2",
3
+ "version": "3.77.0-alpha.3",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/src/LLMSession.js CHANGED
@@ -170,6 +170,14 @@ const stateData = require('./utils/stateData');
170
170
  * @prop {Responder} [res]
171
171
  * @prop {object} [data]
172
172
  * @prop {LLMCallPreset} [preset]
173
+ * @prop {SystemSource} [system] - the system prompt of the session - compiled again,
174
+ * when recompileSystemPrompt() is called (e.g. a tool changed the state)
175
+ */
176
+
177
+ /**
178
+ * @callback SystemSource
179
+ * @param {boolean} recompile - false at the start, true to compile the instructions again
180
+ * @returns {Promise<LLMMessage[]>}
173
181
  */
174
182
 
175
183
  // max number of consecutive tool-call rounds resolved within a single generate()
@@ -219,11 +227,20 @@ class LLMSession {
219
227
  data = {},
220
228
  req = null,
221
229
  res = null,
222
- preset = undefined
230
+ preset = undefined,
231
+ system = null
223
232
  } = options;
224
233
 
225
234
  this._llm = llm;
226
235
 
236
+ /** @type {SystemSource|null} */
237
+ this._systemSource = system;
238
+
239
+ /** @type {Set<object>} - the system messages compiled from the system source */
240
+ this._compiledSystem = new Set();
241
+
242
+ this._systemRecompileRequested = false;
243
+
227
244
  this._onSend = onSend;
228
245
 
229
246
  this._inExecution = false;
@@ -234,6 +251,9 @@ class LLMSession {
234
251
 
235
252
  /** @type {LLMMessageSrc[]} */
236
253
  this._chat = [];
254
+ if (system) {
255
+ this.push(this._compileSystem(false));
256
+ }
237
257
  this.push(...chat);
238
258
 
239
259
  this._generatedIndex = null;
@@ -822,6 +842,55 @@ class LLMSession {
822
842
  return this;
823
843
  }
824
844
 
845
+ /**
846
+ * @param {boolean} recompile
847
+ * @returns {Promise<LLMMessage[]>}
848
+ */
849
+ async _compileSystem (recompile) {
850
+ const messages = await this._systemSource(recompile);
851
+ messages.forEach((m) => this._compiledSystem.add(m));
852
+ return messages;
853
+ }
854
+
855
+ /**
856
+ * The system prompt (from the system source of the session) is compiled again
857
+ * before the next request to the LLM - e.g. a tool changed the language or the buttons
858
+ * under the answer. The other system messages stay.
859
+ *
860
+ * @returns {this}
861
+ */
862
+ recompileSystemPrompt () {
863
+ this._systemRecompileRequested = !!this._systemSource;
864
+ return this;
865
+ }
866
+
867
+ async _recompileSystemIfRequested () {
868
+ if (!this._systemRecompileRequested) {
869
+ return;
870
+ }
871
+ this._systemRecompileRequested = false;
872
+
873
+ const previous = this._compiledSystem;
874
+ this._compiledSystem = new Set();
875
+ const messages = await this._compileSystem(true);
876
+
877
+ // the new messages at the place of the previous ones
878
+ const at = this._chat.findIndex((m) => previous.has(m));
879
+ const chat = this._chat.filter((m) => !previous.has(m));
880
+ chat.splice(at === -1 ? 0 : at, 0, ...messages);
881
+ this._chat = chat;
882
+ }
883
+
884
+ /**
885
+ * @param {LLMCallPreset} providerOptions
886
+ * @param {LLMLogOptions} logOptions
887
+ * @returns {Promise<LLMMessage<any>>}
888
+ */
889
+ async _requestLlm (providerOptions, logOptions) {
890
+ await this._recompileSystemIfRequested();
891
+ return this._llm.generate(this, providerOptions, logOptions);
892
+ }
893
+
825
894
  /**
826
895
  *
827
896
  * @param {string|Promise<string>|Renderer} content
@@ -1032,7 +1101,7 @@ class LLMSession {
1032
1101
  */
1033
1102
  async _generate (providerOptions = this._preset, logOptions = {}) {
1034
1103
  const { maxToolCallRounds = MAX_TOOL_CALL_ROUNDS } = this._llm.llmOptions(providerOptions);
1035
- let result = await this._llm.generate(this, providerOptions, logOptions);
1104
+ let result = await this._requestLlm(providerOptions, logOptions);
1036
1105
 
1037
1106
  // the model may chain several rounds of tool calls before it produces
1038
1107
  // a final text answer - keep resolving them until it stops (bounded)
@@ -1068,7 +1137,7 @@ class LLMSession {
1068
1137
  }
1069
1138
 
1070
1139
  if (toolCalls.length) {
1071
- result = await this._llm.generate(this, providerOptions, logOptions);
1140
+ result = await this._requestLlm(providerOptions, logOptions);
1072
1141
  } else {
1073
1142
  // everything failed - force a final text answer without tools
1074
1143
  this._llm.log.error(
@@ -1115,7 +1184,7 @@ class LLMSession {
1115
1184
  ...providerOptions,
1116
1185
  toolChoice: 'none'
1117
1186
  };
1118
- return this._llm.generate(this, overrideChoice, logOptions);
1187
+ return this._requestLlm(overrideChoice, logOptions);
1119
1188
  }
1120
1189
 
1121
1190
  /**
@@ -463,6 +463,14 @@ class LLMToolRunner {
463
463
  // only the buttons of the last situation are offered (the earlier ones are outdated)
464
464
  this.quickReplies = [];
465
465
  this._addButtons(sitDef, situation, values);
466
+
467
+ // the answer of the LLM follows - its system prompt compiled again: the tool could change
468
+ // the state (e.g. the language) and these buttons go under the answer ({{$qrTexts}})
469
+ res.setData({ _qRepliesToSend: [...this.quickReplies] });
470
+ if (session && typeof session.recompileSystemPrompt === 'function') {
471
+ session.recompileSystemPrompt();
472
+ }
473
+
466
474
  return this._instructionText(sitDef, situation, values);
467
475
  }
468
476
 
package/src/Responder.js CHANGED
@@ -262,6 +262,17 @@ class Responder {
262
262
  [this.LLM_ERROR, []]
263
263
  ]);
264
264
 
265
+ /**
266
+ * the instructions as added - _llmContext keeps them compiled (once in the request),
267
+ * these compile them again (e.g. after a tool changed the state)
268
+ *
269
+ * @type {Map<string,PromptContextItem[]>}
270
+ */
271
+ this._llmContextSource = new Map([
272
+ [this.LLM_CTX_DEFAULT, []],
273
+ [this.LLM_ERROR, []]
274
+ ]);
275
+
265
276
  /** @type {Map<string,PreprocessedRule[]>} */
266
277
  this._llmResultRules = new Map([
267
278
  [this.LLM_CTX_DEFAULT, []]
@@ -298,8 +309,10 @@ class Responder {
298
309
  if (!this._llmContext.has(contextType)) {
299
310
  // @todo make it array of messages / maybe keep it in a single array
300
311
  this._llmContext.set(contextType, []);
312
+ this._llmContextSource.set(contextType, []);
301
313
  }
302
314
  this._llmContext.get(contextType).push(systemPrompt);
315
+ this._llmContextSource.get(contextType).push(systemPrompt);
303
316
 
304
317
  return this;
305
318
  }
@@ -390,10 +403,11 @@ class Responder {
390
403
  /**
391
404
  *
392
405
  * @param {string} contextType
406
+ * @param {boolean} [recompile] - compile the instructions again (the state could change)
393
407
  * @returns {AsyncLLMMessage}
394
408
  */
395
- async _getSystemMessagesForType (contextType) {
396
- const system = await this._getSystemContentForType(contextType);
409
+ async _getSystemMessagesForType (contextType, recompile = false) {
410
+ const system = await this._getSystemContentForType(contextType, [], recompile);
397
411
  return system.map((content) => ({ role: LLM.ROLE_SYSTEM, content }));
398
412
  }
399
413
 
@@ -405,14 +419,14 @@ class Responder {
405
419
  * @returns {LLMSession}
406
420
  */
407
421
  llmSession (contextType = this.LLM_CTX_DEFAULT, callPreset = undefined, errorPrompt = undefined) { // eslint-disable-line max-len
408
- const system = this._getSystemMessagesForType(contextType);
409
-
410
422
  const filters = this._filtersForContext(contextType);
411
- const session = new LLMSession(this.llm, [system], {
423
+ const session = new LLMSession(this.llm, [], {
412
424
  onSend: this._llmSend.bind(this),
413
425
  filters,
414
426
  preset: callPreset,
415
- res: this
427
+ res: this,
428
+ // compiled again on demand (session.recompileSystemPrompt())
429
+ system: (recompile) => this._getSystemMessagesForType(contextType, recompile)
416
430
  });
417
431
  session.onDelegatedError(
418
432
  errorPrompt ?? (() => this._getSystemContentForType(this.LLM_ERROR)
@@ -483,9 +497,10 @@ class Responder {
483
497
  *
484
498
  * @param {string} contextType
485
499
  * @param {string[]} [callStack]
500
+ * @param {boolean} [recompile] - compile the instructions again (the state could change)
486
501
  * @returns {Promise<string[]>}
487
502
  */
488
- async _getSystemContentForType (contextType, callStack = []) {
503
+ async _getSystemContentForType (contextType, callStack = [], recompile = false) {
489
504
  if (new Set(callStack).size < callStack.length) {
490
505
  throw new Error(`Circular reference detected: contextType -> ${callStack}`);
491
506
  }
@@ -494,9 +509,14 @@ class Responder {
494
509
  return [];
495
510
  }
496
511
 
512
+ // compiled once in the request - again only on demand (from the instructions as added)
513
+ const parts = recompile && this._llmContextSource.has(contextType)
514
+ ? this._llmContextSource.get(contextType)
515
+ : this._llmContext.get(contextType);
516
+
497
517
  /** @typedef {Promise<string>} PromisedString */
498
518
  /** @type {PromisedString[]} */
499
- const promiseStrings = this._llmContext.get(contextType)
519
+ const promiseStrings = parts
500
520
  .map(async (p) => (typeof p === 'function' ? p(this) : p));
501
521
  this._llmContext.set(contextType, promiseStrings);
502
522
 
@@ -506,8 +526,11 @@ class Responder {
506
526
  const s = await promiseString;
507
527
 
508
528
  const replaced = await this._replaceAsync(s.trim(), /\$\{([a-zA-Z0-9\s]+)\}/g, async (str, reqType) => {
509
- const nested = await this
510
- ._getSystemContentForType(reqType, [...callStack, contextType]);
529
+ const nested = await this._getSystemContentForType(
530
+ reqType,
531
+ [...callStack, contextType],
532
+ recompile
533
+ );
511
534
 
512
535
  return nested.join('\n\n');
513
536
  });
@@ -539,13 +562,14 @@ class Responder {
539
562
 
540
563
  const filters = this._filtersForContext(contextType);
541
564
  const session = new LLMSession(this.llm, [
542
- this._getSystemMessagesForType(contextType),
543
565
  this._getTranscriptMessages(transcriptLength, transcriptFlag, transcriptAnonymize)
544
566
  ], {
545
567
  onSend: this._llmSend.bind(this),
546
568
  filters,
547
569
  preset: callPreset,
548
- res: this
570
+ res: this,
571
+ // compiled again on demand (session.recompileSystemPrompt())
572
+ system: (recompile) => this._getSystemMessagesForType(contextType, recompile)
549
573
  });
550
574
  session.onDelegatedError(
551
575
  errorPrompt ?? (() => this._getSystemContentForType(this.LLM_ERROR)