wingbot 3.75.9-alpha.4 → 3.75.9-alpha.6

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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npx mocha:*)",
5
+ "Bash(npx tsc *)",
6
+ "Bash(npm test *)",
7
+ "Bash(node -e \"const p = require\\('./bot/plugins/BTMLLM'\\); console.log\\('factory type:', typeof p\\); console.log\\('factory name:', p.name\\);\")",
8
+ "Bash(grep -n \"prompt\\\\`\\\\|tagged\\\\|render\\\\|compile\\\\|hbs\" /Users/ondrejveres/Wingbot/wingbot-llm/src/prompt.js)"
9
+ ]
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.75.9-alpha.4",
3
+ "version": "3.75.9-alpha.6",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/src/ChatGpt.js CHANGED
@@ -341,6 +341,10 @@ class ChatGpt {
341
341
  });
342
342
 
343
343
  let body;
344
+ let responseData;
345
+ let responseText;
346
+ let status;
347
+ let statusText;
344
348
  try {
345
349
  let lastUserIndex = 0;
346
350
  let totalTokens = messages
@@ -445,26 +449,26 @@ class ChatGpt {
445
449
  body: JSON.stringify(body)
446
450
  });
447
451
 
448
- /** @type {ChatGPTResponse} */
449
- const data = await response.json();
452
+ ({ status, statusText } = response);
450
453
 
451
- if (response.status !== 200
452
- || !Array.isArray(data.choices)) {
453
- const { status, statusText } = response;
454
+ responseText = await response.text();
455
+ /** @type {ChatGPTResponse} */
456
+ responseData = JSON.parse(responseText);
454
457
 
455
- this._logger.error('#GPT failed', {
456
- status, statusText, data, body
457
- });
458
+ if (status !== 200
459
+ || !Array.isArray(responseData.choices)) {
458
460
  throw new Error(`Chat GPT ${status}`);
459
461
  }
460
462
 
461
- const [choice] = data.choices;
463
+ const [choice] = responseData.choices;
462
464
 
463
- this._log('#GPT response', { choice, data });
465
+ this._log('#GPT response', { choice, responseData });
464
466
 
465
467
  return choice;
466
468
  } catch (e) {
467
- this._logger.error('#GPT failed', e, body);
469
+ this._logger.log('#GPT failed', {
470
+ message: e.message, body, responseText, responseData, status, statusText
471
+ });
468
472
  throw e;
469
473
  }
470
474
  }
package/src/LLM.js CHANGED
@@ -106,6 +106,7 @@ const LLMSession = require('./LLMSession');
106
106
  * @prop {'none'|'low'|'medium'|'high'|string} [reasoningEffort]
107
107
  * @prop {'low'|'medium'|'high'|string} [verbosity]
108
108
  * @prop {'text'|SimpleJsonSchema} [responseFormat]
109
+ * @prop {number} [temperature]
109
110
  */
110
111
 
111
112
  /**
@@ -294,12 +295,34 @@ class LLM {
294
295
  /** @type {LLMMessage} */
295
296
  this._lastResult = null;
296
297
 
298
+ /** @type {Error|null} */
299
+ this._delegatedError = null;
300
+
297
301
  this.log = log;
298
302
 
299
303
  this.req = null;
300
304
  this.res = null;
301
305
  }
302
306
 
307
+ /**
308
+ * @param {Error|null} [error]
309
+ */
310
+ setDelegatedError (error = null) {
311
+ this._delegatedError = error;
312
+ }
313
+
314
+ /**
315
+ * @param {boolean} [reset=false]
316
+ * @returns {boolean}
317
+ */
318
+ hasDelegatedError (reset = false) {
319
+ const has = this._delegatedError !== null;
320
+ if (reset && has) {
321
+ this._delegatedError = null;
322
+ }
323
+ return has;
324
+ }
325
+
303
326
  setReqRes (req, res) {
304
327
  this.req = req;
305
328
  this.res = res;
package/src/LLMSession.js CHANGED
@@ -791,6 +791,49 @@ class LLMSession {
791
791
  return this;
792
792
  }
793
793
 
794
+ /**
795
+ * If there is a delegated error on the LLM instance, appends (or replaces)
796
+ * a system message with `prompt` and resets the delegated error.
797
+ * If `prompt` resolves to an empty string, nothing happens.
798
+ *
799
+ * @param {PossiblyAsyncContent} prompt
800
+ * @param {boolean} [replaceCurrent=false]
801
+ * @returns {this}
802
+ */
803
+ onDelegatedError (prompt, replaceCurrent = false) {
804
+ this._job(async () => {
805
+ if (!this._llm.hasDelegatedError()) {
806
+ return;
807
+ }
808
+
809
+ const content = typeof prompt === 'function'
810
+ ? await Promise.resolve(prompt())
811
+ : await Promise.resolve(prompt);
812
+
813
+ if (!content) {
814
+ return;
815
+ }
816
+
817
+ if (replaceCurrent) {
818
+ const lastSystemIdx = this._chat.reduceRight((found, msg, i) => {
819
+ if (found !== -1) return found;
820
+ if ('role' in msg && msg.role === ROLE_SYSTEM) return i;
821
+ return found;
822
+ }, -1);
823
+ if (lastSystemIdx !== -1) {
824
+ this._chat[lastSystemIdx] = { role: ROLE_SYSTEM, content };
825
+ } else {
826
+ this._chat.push({ role: ROLE_SYSTEM, content });
827
+ }
828
+ } else {
829
+ this._chat.push({ role: ROLE_SYSTEM, content });
830
+ }
831
+
832
+ this._llm.setDelegatedError(null);
833
+ });
834
+ return this;
835
+ }
836
+
794
837
  /**
795
838
  *
796
839
  * @param {LLMFilter|LLMFilter[]} filter
@@ -836,19 +879,27 @@ class LLMSession {
836
879
  }
837
880
 
838
881
  this._job(async () => {
839
- const result = await this._generate({
840
- ...(typeof providerOptions === 'object' ? providerOptions : {}),
841
- ...(typeof providerOptions === 'string' ? { preset: providerOptions } : {}),
842
- responseFormat
843
- }, logOptions);
844
-
845
882
  try {
846
- return JSON.parse(result.content);
883
+ const result = await this._generate({
884
+ ...(typeof providerOptions === 'object' ? providerOptions : {}),
885
+ ...(typeof providerOptions === 'string' ? { preset: providerOptions } : {}),
886
+ responseFormat
887
+ }, logOptions);
888
+
889
+ try {
890
+ return JSON.parse(result.content);
891
+ } catch (e) {
892
+ this._llm.log.error(`LLM structured output parse error (${responseFormat.name}): ${e.message}`, e, {
893
+ rawContent: result.content
894
+ });
895
+ throw new Error(
896
+ `LLM structured output is not valid JSON: ${e.message}. Raw content: ${JSON.stringify(result.content)}`,
897
+ { cause: e }
898
+ );
899
+ }
847
900
  } catch (e) {
848
- throw new Error(
849
- `LLM structured output is not valid JSON: ${e.message}. Raw content: ${JSON.stringify(result.content)}`,
850
- { cause: e }
851
- );
901
+ this._llm.setDelegatedError(e);
902
+ throw e;
852
903
  }
853
904
  });
854
905
  return this;
package/src/Responder.js CHANGED
@@ -248,12 +248,14 @@ class Responder {
248
248
  this.llm = llm;
249
249
 
250
250
  this.LLM_CTX_DEFAULT = 'default';
251
+ this.LLM_ERROR = 'error';
251
252
 
252
253
  /** @typedef {PromptSource|Promise<string>} PromptContextItem */
253
254
 
254
255
  /** @type {Map<string,PromptContextItem[]>} */
255
256
  this._llmContext = new Map([
256
- [this.LLM_CTX_DEFAULT, []]
257
+ [this.LLM_CTX_DEFAULT, []],
258
+ [this.LLM_ERROR, []]
257
259
  ]);
258
260
 
259
261
  /** @type {Map<string,PreprocessedRule[]>} */
@@ -373,18 +375,24 @@ class Responder {
373
375
  *
374
376
  * @param {string} contextType
375
377
  * @param {LLMCallPreset} [callPreset]
378
+ * @param {PossiblyAsyncContent} [errorPrompt]
376
379
  * @returns {LLMSession}
377
380
  */
378
- llmSession (contextType = this.LLM_CTX_DEFAULT, callPreset = undefined) {
381
+ llmSession (contextType = this.LLM_CTX_DEFAULT, callPreset = undefined, errorPrompt = undefined) { // eslint-disable-line max-len
379
382
  const system = this._getSystemMessagesForType(contextType);
380
383
 
381
384
  const filters = this._filtersForContext(contextType);
382
- return new LLMSession(this.llm, [system], {
385
+ const session = new LLMSession(this.llm, [system], {
383
386
  onSend: this._llmSend.bind(this),
384
387
  filters,
385
388
  preset: callPreset,
386
389
  res: this
387
390
  });
391
+ session.onDelegatedError(
392
+ errorPrompt ?? (() => this._getSystemContentForType(this.LLM_ERROR)
393
+ .then((parts) => parts.join('\n\n')))
394
+ );
395
+ return session;
388
396
  }
389
397
 
390
398
  /**
@@ -488,12 +496,14 @@ class Responder {
488
496
  /**
489
497
  *
490
498
  * @param {'default'|string} contextType
491
- * @param {LLMCallPreset} callPreset
499
+ * @param {LLMCallPreset} [callPreset]
500
+ * @param {import('./LLMSession').PossiblyAsyncContent} [errorPrompt]
492
501
  * @returns {LLMSession}
493
502
  */
494
503
  llmSessionWithHistory (
495
504
  contextType = this.LLM_CTX_DEFAULT,
496
- callPreset = undefined
505
+ callPreset = undefined,
506
+ errorPrompt = undefined
497
507
  ) {
498
508
  const {
499
509
  transcriptAnonymize,
@@ -502,7 +512,7 @@ class Responder {
502
512
  } = this.llm.llmOptions(callPreset);
503
513
 
504
514
  const filters = this._filtersForContext(contextType);
505
- return new LLMSession(this.llm, [
515
+ const session = new LLMSession(this.llm, [
506
516
  this._getSystemMessagesForType(contextType),
507
517
  this._getTranscriptMessages(transcriptLength, transcriptFlag, transcriptAnonymize)
508
518
  ], {
@@ -511,6 +521,11 @@ class Responder {
511
521
  preset: callPreset,
512
522
  res: this
513
523
  });
524
+ session.onDelegatedError(
525
+ errorPrompt ?? (() => this._getSystemContentForType(this.LLM_ERROR)
526
+ .then((parts) => parts.join('\n\n')))
527
+ );
528
+ return session;
514
529
  }
515
530
 
516
531
  async _getTranscriptMessages (transcriptLength, transcriptFlag, transcriptAnonymize) {