@memberjunction/ai-prompts 2.103.0 → 2.105.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.
@@ -25,13 +25,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.LoadAIPromptRunner = exports.AIPromptRunner = void 0;
27
27
  const ai_1 = require("@memberjunction/ai");
28
+ const ai_core_plus_1 = require("@memberjunction/ai-core-plus");
28
29
  const core_1 = require("@memberjunction/core");
29
30
  const global_1 = require("@memberjunction/global");
30
31
  const templates_1 = require("@memberjunction/templates");
31
32
  const ExecutionPlanner_1 = require("./ExecutionPlanner");
32
33
  const ParallelExecutionCoordinator_1 = require("./ParallelExecutionCoordinator");
33
34
  const aiengine_1 = require("@memberjunction/aiengine");
34
- const ai_core_plus_1 = require("@memberjunction/ai-core-plus");
35
+ const ai_core_plus_2 = require("@memberjunction/ai-core-plus");
35
36
  const JSON5 = __importStar(require("json5"));
36
37
  class AIPromptRunner {
37
38
  constructor() {
@@ -61,8 +62,11 @@ class AIPromptRunner {
61
62
  }
62
63
  }
63
64
  logError(error, options) {
64
- const errorMessage = error instanceof Error ? error.message : error;
65
+ let errorMessage = error instanceof Error ? error.message : error;
65
66
  const errorObj = error instanceof Error ? error : undefined;
67
+ if (options?.maxErrorLength !== undefined && errorMessage.length > options.maxErrorLength) {
68
+ errorMessage = errorMessage.substring(0, options.maxErrorLength) + '... [truncated]';
69
+ }
66
70
  const metadata = {
67
71
  ...options?.metadata
68
72
  };
@@ -191,7 +195,8 @@ class AIPromptRunner {
191
195
  metadata: {
192
196
  executionPhase: 'main-execution',
193
197
  hasChildPrompts: !!params.childPrompts?.length
194
- }
198
+ },
199
+ maxErrorLength: params.maxErrorLength
195
200
  });
196
201
  const endTime = new Date();
197
202
  const executionTimeMS = endTime.getTime() - startTime.getTime();
@@ -215,7 +220,8 @@ class AIPromptRunner {
215
220
  metadata: {
216
221
  promptRunId: promptRun.ID,
217
222
  errorMessage: promptRun.LatestResult?.Message
218
- }
223
+ },
224
+ maxErrorLength: params.maxErrorLength
219
225
  });
220
226
  }
221
227
  }
@@ -239,15 +245,19 @@ class AIPromptRunner {
239
245
  let modelSelectionInfo = existingModelSelectionInfo;
240
246
  let vendorDriverClass;
241
247
  let vendorApiName;
248
+ let vendorSupportsEffortLevel;
249
+ let allCandidates = [];
242
250
  if (modelSelectionInfo) {
243
- const vendorID = modelSelectionInfo.vendorSelected.ID;
251
+ const vendorID = modelSelectionInfo.vendorSelected?.ID;
244
252
  const modelID = modelSelectionInfo.modelSelected.ID;
245
253
  const modelVendor = aiengine_1.AIEngine.Instance.ModelVendors.find(mv => mv.VendorID === vendorID &&
246
254
  mv.ModelID === modelID);
247
255
  if (modelVendor) {
248
256
  vendorDriverClass = modelVendor.DriverClass;
249
257
  vendorApiName = modelVendor.APIName;
258
+ vendorSupportsEffortLevel = modelVendor.SupportsEffortLevel;
250
259
  }
260
+ allCandidates = this.buildCandidatesFromSelectionInfo(modelSelectionInfo);
251
261
  }
252
262
  if (!selectedModel) {
253
263
  let modelSelectionPrompt = prompt;
@@ -259,7 +269,9 @@ class AIPromptRunner {
259
269
  selectedModel = modelResult.model;
260
270
  vendorDriverClass = modelResult.vendorDriverClass;
261
271
  vendorApiName = modelResult.vendorApiName;
272
+ vendorSupportsEffortLevel = modelResult.vendorSupportsEffortLevel;
262
273
  modelSelectionInfo = modelResult.selectionInfo;
274
+ allCandidates = modelResult.allCandidates || [];
263
275
  if (!selectedModel) {
264
276
  throw new Error(`No suitable model found for prompt ${modelSelectionPrompt.Name}`);
265
277
  }
@@ -271,7 +283,7 @@ class AIPromptRunner {
271
283
  if (params.cancellationToken?.aborted) {
272
284
  throw new Error('Prompt execution was cancelled before model execution');
273
285
  }
274
- const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, vendorDriverClass, vendorApiName);
286
+ const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
275
287
  const endTime = new Date();
276
288
  const executionTimeMS = endTime.getTime() - startTime.getTime();
277
289
  await this.updatePromptRun(promptRun, prompt, modelResult, parsedResult, endTime, executionTimeMS, validationAttempts, cumulativeTokens);
@@ -414,7 +426,8 @@ class AIPromptRunner {
414
426
  promptRunId: consolidatedPromptRun.ID,
415
427
  executionTasks: executionTasks.length,
416
428
  successfulResults: successfulResults.length
417
- }
429
+ },
430
+ maxErrorLength: params.maxErrorLength
418
431
  });
419
432
  }
420
433
  const additionalResults = [];
@@ -559,7 +572,8 @@ class AIPromptRunner {
559
572
  category: 'ChildTemplateRendering',
560
573
  metadata: {
561
574
  placeholder: childParam.parentPlaceholder
562
- }
575
+ },
576
+ maxErrorLength: params.maxErrorLength
563
577
  });
564
578
  return {
565
579
  placeholder: childParam.parentPlaceholder,
@@ -578,7 +592,8 @@ class AIPromptRunner {
578
592
  failedCount: failedChildren.length,
579
593
  totalCount: childResults.length,
580
594
  failedPlaceholders: failedChildren.map(fc => fc.placeholder)
581
- }
595
+ },
596
+ maxErrorLength: params.maxErrorLength
582
597
  });
583
598
  throw new Error(`Failed to render ${failedChildren.length} child prompt templates: ${failedChildren.map(fc => fc.placeholder).join(', ')}`);
584
599
  }
@@ -600,7 +615,7 @@ class AIPromptRunner {
600
615
  if (!template) {
601
616
  throw new Error(`Template with ID ${prompt.TemplateID} not found for prompt ${prompt.Name}`);
602
617
  }
603
- const systemPlaceholders = await ai_core_plus_1.SystemPlaceholderManager.resolveAllPlaceholders(params);
618
+ const systemPlaceholders = await ai_core_plus_2.SystemPlaceholderManager.resolveAllPlaceholders(params);
604
619
  const mergedData = {
605
620
  ...systemPlaceholders,
606
621
  ...params.data,
@@ -624,7 +639,8 @@ class AIPromptRunner {
624
639
  metadata: {
625
640
  childPromptCount: params.childPrompts?.length || 0,
626
641
  templateId: prompt.TemplateID
627
- }
642
+ },
643
+ maxErrorLength: params.maxErrorLength
628
644
  });
629
645
  throw error;
630
646
  }
@@ -654,18 +670,23 @@ class AIPromptRunner {
654
670
  this.logError(`No suitable model candidates found for prompt ${prompt.Name}`, {
655
671
  category: 'ModelSelection',
656
672
  prompt: prompt,
657
- severity: 'critical'
673
+ severity: 'critical',
674
+ maxErrorLength: params?.maxErrorLength
658
675
  });
659
676
  return {
660
677
  model: null,
661
- selectionInfo: {
678
+ vendorDriverClass: undefined,
679
+ vendorApiName: undefined,
680
+ vendorSupportsEffortLevel: undefined,
681
+ allCandidates: [],
682
+ selectionInfo: this.createSelectionInfo({
662
683
  aiConfiguration: configuration,
663
684
  modelsConsidered: [],
664
685
  modelSelected: undefined,
665
686
  selectionReason: 'No suitable model candidates found',
666
687
  fallbackUsed: false,
667
688
  selectionStrategy
668
- }
689
+ })
669
690
  };
670
691
  }
671
692
  const { selected, consideredModels } = await this.selectModelWithAPIKeyTracked(candidates, params);
@@ -675,14 +696,16 @@ class AIPromptRunner {
675
696
  model: null,
676
697
  vendorDriverClass: undefined,
677
698
  vendorApiName: undefined,
678
- selectionInfo: {
699
+ vendorSupportsEffortLevel: undefined,
700
+ allCandidates: candidates,
701
+ selectionInfo: this.createSelectionInfo({
679
702
  aiConfiguration: configuration,
680
703
  modelsConsidered,
681
704
  modelSelected: undefined,
682
705
  selectionReason: 'No API keys found for any model-vendor combination',
683
706
  fallbackUsed: false,
684
707
  selectionStrategy
685
- }
708
+ })
686
709
  };
687
710
  }
688
711
  let selectionReason = `Selected ${selected.model.Name} via ${selected.vendorName || 'default vendor'}`;
@@ -710,7 +733,9 @@ class AIPromptRunner {
710
733
  model: selected.model,
711
734
  vendorDriverClass: selected.driverClass,
712
735
  vendorApiName: selected.apiName,
713
- selectionInfo: {
736
+ vendorSupportsEffortLevel: selected.supportsEffortLevel,
737
+ allCandidates: candidates,
738
+ selectionInfo: this.createSelectionInfo({
714
739
  aiConfiguration: configuration,
715
740
  modelsConsidered,
716
741
  modelSelected: selected.model,
@@ -718,26 +743,29 @@ class AIPromptRunner {
718
743
  selectionReason,
719
744
  fallbackUsed,
720
745
  selectionStrategy
721
- }
746
+ })
722
747
  };
723
748
  }
724
749
  catch (error) {
725
750
  this.logError(error, {
726
751
  category: 'ModelSelection',
727
- prompt: prompt
752
+ prompt: prompt,
753
+ maxErrorLength: params?.maxErrorLength
728
754
  });
729
755
  return {
730
756
  model: null,
731
757
  vendorDriverClass: undefined,
732
758
  vendorApiName: undefined,
733
- selectionInfo: {
759
+ vendorSupportsEffortLevel: undefined,
760
+ allCandidates: [],
761
+ selectionInfo: this.createSelectionInfo({
734
762
  aiConfiguration: configuration,
735
763
  modelsConsidered: [],
736
764
  modelSelected: undefined,
737
765
  selectionReason: `Error during model selection: ${error.message}`,
738
766
  fallbackUsed: false,
739
767
  selectionStrategy: 'Default'
740
- }
768
+ })
741
769
  };
742
770
  }
743
771
  }
@@ -758,6 +786,7 @@ class AIPromptRunner {
758
786
  vendorName: preferredVendor.Vendor,
759
787
  driverClass: preferredVendor.DriverClass || model.DriverClass,
760
788
  apiName: preferredVendor.APIName || model.APIName,
789
+ supportsEffortLevel: preferredVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
761
790
  isPreferredVendor: true,
762
791
  priority: basePriority + 1000,
763
792
  source
@@ -772,6 +801,7 @@ class AIPromptRunner {
772
801
  vendorName: vendor.Vendor,
773
802
  driverClass: vendor.DriverClass || model.DriverClass,
774
803
  apiName: vendor.APIName || model.APIName,
804
+ supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
775
805
  isPreferredVendor: false,
776
806
  priority: basePriority + (vendor.Priority || 0),
777
807
  source
@@ -783,6 +813,7 @@ class AIPromptRunner {
783
813
  model,
784
814
  driverClass: model.DriverClass,
785
815
  apiName: model.APIName,
816
+ supportsEffortLevel: model.SupportsEffortLevel ?? false,
786
817
  isPreferredVendor: false,
787
818
  priority: basePriority,
788
819
  source
@@ -808,6 +839,7 @@ class AIPromptRunner {
808
839
  vendorName: preferredVendor.Vendor,
809
840
  driverClass: preferredVendor.DriverClass || model.DriverClass,
810
841
  apiName: preferredVendor.APIName || model.APIName,
842
+ supportsEffortLevel: preferredVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
811
843
  isPreferredVendor: true,
812
844
  priority: basePriority + 1000,
813
845
  source: 'prompt-model'
@@ -822,6 +854,7 @@ class AIPromptRunner {
822
854
  vendorName: vendor.Vendor,
823
855
  driverClass: vendor.DriverClass || model.DriverClass,
824
856
  apiName: vendor.APIName || model.APIName,
857
+ supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
825
858
  isPreferredVendor: false,
826
859
  priority: basePriority + (vendor.Priority || 0) * 10,
827
860
  source: 'prompt-model'
@@ -833,6 +866,7 @@ class AIPromptRunner {
833
866
  model,
834
867
  driverClass: model.DriverClass,
835
868
  apiName: model.APIName,
869
+ supportsEffortLevel: model.SupportsEffortLevel ?? false,
836
870
  isPreferredVendor: false,
837
871
  priority: basePriority,
838
872
  source: 'prompt-model'
@@ -898,6 +932,21 @@ class AIPromptRunner {
898
932
  candidates.push(...modelCandidates);
899
933
  }
900
934
  }
935
+ if (configurationId) {
936
+ const nullConfigModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
937
+ (pm.Status === 'Active' || pm.Status === 'Preview') &&
938
+ !pm.ConfigurationID);
939
+ if (nullConfigModels.length > 0 && verbose) {
940
+ (0, core_1.LogStatus)(`Adding ${nullConfigModels.length} NULL configuration models as fallback candidates`);
941
+ }
942
+ for (const pm of nullConfigModels) {
943
+ const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
944
+ if (model && model.IsActive) {
945
+ const modelCandidates = createCandidatesForModel(model, 2000, 'prompt-model', pm.Priority);
946
+ candidates.push(...modelCandidates);
947
+ }
948
+ }
949
+ }
901
950
  }
902
951
  else {
903
952
  let modelPool = aiengine_1.AIEngine.Instance.Models.filter(m => m.IsActive &&
@@ -938,12 +987,35 @@ class AIPromptRunner {
938
987
  candidates.sort((a, b) => b.priority - a.priority);
939
988
  return candidates;
940
989
  }
990
+ createSelectionInfo(data) {
991
+ const info = new ai_core_plus_1.AIModelSelectionInfo();
992
+ Object.assign(info, data);
993
+ return info;
994
+ }
995
+ buildCandidatesFromSelectionInfo(selectionInfo) {
996
+ const validModels = selectionInfo.extractValidCandidates();
997
+ return validModels.map(considered => {
998
+ const modelVendor = considered.vendor
999
+ ? aiengine_1.AIEngine.Instance.ModelVendors.find(mv => mv.ModelID === considered.model.ID &&
1000
+ mv.VendorID === considered.vendor.ID)
1001
+ : undefined;
1002
+ return {
1003
+ model: considered.model,
1004
+ vendorId: considered.vendor?.ID,
1005
+ vendorName: considered.vendor?.Name,
1006
+ driverClass: modelVendor?.DriverClass || considered.model.DriverClass,
1007
+ apiName: modelVendor?.APIName || considered.model.APIName,
1008
+ supportsEffortLevel: modelVendor?.SupportsEffortLevel ?? considered.model.SupportsEffortLevel ?? false,
1009
+ isPreferredVendor: false,
1010
+ priority: considered.priority,
1011
+ source: (selectionInfo.selectionStrategy === 'ByPower' ? 'power-rank' : 'model-type')
1012
+ };
1013
+ }).sort((a, b) => b.priority - a.priority);
1014
+ }
941
1015
  async selectModelWithAPIKeyTracked(candidates, params) {
942
1016
  const checkedDrivers = new Map();
943
1017
  const consideredModels = [];
944
- let attemptCount = 0;
945
1018
  for (const candidate of candidates) {
946
- attemptCount++;
947
1019
  let hasKey;
948
1020
  if (checkedDrivers.has(candidate.driverClass)) {
949
1021
  hasKey = checkedDrivers.get(candidate.driverClass);
@@ -964,25 +1036,31 @@ class AIPromptRunner {
964
1036
  available: hasKey,
965
1037
  unavailableReason: hasKey ? undefined : `No API key for driver ${candidate.driverClass}`
966
1038
  });
967
- if (hasKey) {
968
- this.logStatus(` Selected model ${candidate.model.Name} with ${candidate.vendorName || 'default'} vendor (driver: ${candidate.driverClass})`, true);
969
- if (candidate.isPreferredVendor) {
970
- this.logStatus(` Using preferred vendor${candidate.vendorId ? ` (${candidate.vendorName})` : ''}`, true, params);
971
- }
972
- this.logStatus(` Checked ${attemptCount} candidate(s) before finding valid API key`, true, params);
973
- return { selected: candidate, consideredModels };
974
- }
975
1039
  }
976
- const triedSummary = candidates.slice(0, 5).map(c => `${c.model.Name}/${c.vendorName || 'default'}(${c.driverClass})`).join(', ');
977
- this.logError(`No API keys found for any model-vendor combination. Tried: ${triedSummary}${candidates.length > 5 ? `... (${candidates.length} total)` : ''}`, {
978
- category: 'APIKeyValidation',
979
- severity: 'critical',
980
- metadata: {
981
- candidatesChecked: candidates.length,
982
- modelsChecked: consideredModels.length
1040
+ const selected = consideredModels.find(m => m.available);
1041
+ const selectedCandidate = selected ? candidates.find(c => c.model.ID === selected.model.ID &&
1042
+ c.vendorId === selected.vendor?.ID) : null;
1043
+ if (selectedCandidate) {
1044
+ const validCount = consideredModels.filter(m => m.available).length;
1045
+ this.logStatus(` Selected model ${selectedCandidate.model.Name} with ${selectedCandidate.vendorName || 'default'} vendor (driver: ${selectedCandidate.driverClass})`, true);
1046
+ if (selectedCandidate.isPreferredVendor) {
1047
+ this.logStatus(` Using preferred vendor${selectedCandidate.vendorId ? ` (${selectedCandidate.vendorName})` : ''}`, true, params);
983
1048
  }
984
- });
985
- return { selected: null, consideredModels };
1049
+ this.logStatus(` Found ${validCount} valid candidate(s) out of ${candidates.length} total`, true, params);
1050
+ }
1051
+ else {
1052
+ const triedSummary = candidates.slice(0, 5).map(c => `${c.model.Name}/${c.vendorName || 'default'}(${c.driverClass})`).join(', ');
1053
+ this.logError(`No API keys found for any model-vendor combination. Tried: ${triedSummary}${candidates.length > 5 ? `... (${candidates.length} total)` : ''}`, {
1054
+ category: 'APIKeyValidation',
1055
+ severity: 'critical',
1056
+ metadata: {
1057
+ candidatesChecked: candidates.length,
1058
+ modelsChecked: consideredModels.length
1059
+ },
1060
+ maxErrorLength: params?.maxErrorLength
1061
+ });
1062
+ }
1063
+ return { selected: selectedCandidate, consideredModels };
986
1064
  }
987
1065
  async createPromptRun(prompt, model, params, systemPromptText, startTime, vendorId, modelSelectionInfo) {
988
1066
  const promptRun = await this._metadata.GetEntityObject('MJ: AI Prompt Runs', params.contextUser);
@@ -1020,13 +1098,12 @@ class AIPromptRunner {
1020
1098
  promptRun.ModelPowerRank = model.PowerRank;
1021
1099
  }
1022
1100
  }
1023
- const promptRunWithFailover = promptRun;
1024
- promptRunWithFailover.OriginalModelID = model.ID;
1025
- promptRunWithFailover.OriginalRequestStartTime = startTime;
1026
- promptRunWithFailover.FailoverAttempts = 0;
1027
- promptRunWithFailover.FailoverErrors = null;
1028
- promptRunWithFailover.FailoverDurations = null;
1029
- promptRunWithFailover.TotalFailoverDuration = 0;
1101
+ promptRun.OriginalModelID = model.ID;
1102
+ promptRun.OriginalRequestStartTime = startTime;
1103
+ promptRun.FailoverAttempts = 0;
1104
+ promptRun.FailoverErrors = null;
1105
+ promptRun.FailoverDurations = null;
1106
+ promptRun.TotalFailoverDuration = 0;
1030
1107
  const modelWithVendor = model;
1031
1108
  if (modelSelectionInfo) {
1032
1109
  promptRun.VendorID = modelSelectionInfo.vendorSelected?.ID || vendorId || modelWithVendor._selectedVendorId;
@@ -1148,7 +1225,8 @@ class AIPromptRunner {
1148
1225
  promptId: prompt.ID,
1149
1226
  modelId: model.ID,
1150
1227
  vendorId
1151
- }
1228
+ },
1229
+ maxErrorLength: params.maxErrorLength
1152
1230
  });
1153
1231
  throw new Error(error);
1154
1232
  }
@@ -1169,7 +1247,8 @@ class AIPromptRunner {
1169
1247
  metadata: {
1170
1248
  promptRunId: promptRun.ID,
1171
1249
  saveError: promptRun.LatestResult?.Message
1172
- }
1250
+ },
1251
+ maxErrorLength: params.maxErrorLength
1173
1252
  });
1174
1253
  throw new Error(msg);
1175
1254
  }
@@ -1180,7 +1259,7 @@ class AIPromptRunner {
1180
1259
  if (!templateContent) {
1181
1260
  throw new Error(`No content found for template ${template.Name}`);
1182
1261
  }
1183
- const systemPlaceholders = await ai_core_plus_1.SystemPlaceholderManager.resolveAllPlaceholders(params);
1262
+ const systemPlaceholders = await ai_core_plus_2.SystemPlaceholderManager.resolveAllPlaceholders(params);
1184
1263
  const mergedData = {
1185
1264
  ...systemPlaceholders,
1186
1265
  ...params.data,
@@ -1195,22 +1274,23 @@ class AIPromptRunner {
1195
1274
  templateId: template.ID,
1196
1275
  templateName: template.Name,
1197
1276
  hasChildPrompts: !!params.childPrompts?.length
1198
- }
1277
+ },
1278
+ maxErrorLength: params.maxErrorLength
1199
1279
  });
1200
1280
  throw error;
1201
1281
  }
1202
1282
  }
1203
- async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName) {
1283
+ async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel) {
1204
1284
  const failoverConfig = this.getFailoverConfiguration(prompt);
1205
1285
  if (failoverConfig.strategy === 'None') {
1206
- return this.executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName);
1286
+ return this.executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
1207
1287
  }
1208
1288
  const failoverAttempts = [];
1209
1289
  let lastError = null;
1210
1290
  let currentModel = model;
1211
1291
  let currentVendorId = vendorId;
1212
1292
  let attemptNumber = 0;
1213
- if (!allCandidates) {
1293
+ if (!allCandidates || allCandidates.length === 0) {
1214
1294
  allCandidates = await this.buildFailoverCandidates(prompt);
1215
1295
  }
1216
1296
  while (attemptNumber <= failoverConfig.maxAttempts) {
@@ -1218,6 +1298,9 @@ class AIPromptRunner {
1218
1298
  const attemptStartTime = Date.now();
1219
1299
  try {
1220
1300
  if (attemptNumber > 1) {
1301
+ const vendorName = currentVendorId
1302
+ ? aiengine_1.AIEngine.Instance.Vendors.find(v => v.ID === currentVendorId)?.Name || 'Unknown'
1303
+ : 'default';
1221
1304
  (0, core_1.LogStatusEx)({
1222
1305
  message: `🔄 Failover attempt ${attemptNumber} with model ${currentModel.Name} (vendor: ${currentVendorId || 'default'})`,
1223
1306
  category: 'AI',
@@ -1229,7 +1312,7 @@ class AIPromptRunner {
1229
1312
  }]
1230
1313
  });
1231
1314
  }
1232
- const result = await this.executeModel(currentModel, renderedPrompt, prompt, params, currentVendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName);
1315
+ const result = await this.executeModel(currentModel, renderedPrompt, prompt, params, currentVendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
1233
1316
  if (failoverAttempts.length > 0 && promptRun) {
1234
1317
  this.updatePromptRunWithFailoverSuccess(promptRun, failoverAttempts, currentModel, currentVendorId);
1235
1318
  }
@@ -1249,26 +1332,25 @@ class AIPromptRunner {
1249
1332
  timestamp: new Date()
1250
1333
  };
1251
1334
  failoverAttempts.push(failoverAttempt);
1335
+ allCandidates = this.filterAuthenticationFailedVendor(errorAnalysis.errorType, currentVendorId, allCandidates);
1336
+ const shouldContinueRateLimit = await this.handleRateLimitRetry(errorAnalysis, currentModel, currentVendorId, failoverAttempts, prompt, attemptNumber, failoverConfig.maxAttempts, failoverAttempt);
1337
+ if (shouldContinueRateLimit) {
1338
+ continue;
1339
+ }
1252
1340
  const shouldFailover = this.shouldAttemptFailover(lastError, failoverConfig, attemptNumber);
1253
1341
  if (!shouldFailover) {
1254
1342
  this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1255
1343
  break;
1256
1344
  }
1257
- const nextCandidates = this.selectFailoverCandidates(currentModel, currentVendorId, failoverConfig.strategy, failoverConfig.modelStrategy, allCandidates, failoverAttempts);
1258
- if (nextCandidates.length === 0) {
1259
- this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1345
+ const transitionResult = await this.transitionToNextCandidate(currentModel, currentVendorId, failoverConfig, allCandidates, failoverAttempts, prompt.ID, failoverAttempt, attemptNumber);
1346
+ if (!transitionResult) {
1260
1347
  break;
1261
1348
  }
1262
- const nextCandidate = nextCandidates[0];
1263
- currentModel = nextCandidate.model;
1264
- currentVendorId = nextCandidate.vendorId;
1265
- vendorDriverClass = nextCandidate.driverClass;
1266
- vendorApiName = nextCandidate.apiName;
1267
- this.logFailoverAttempt(prompt.ID, failoverAttempt, true);
1268
- if (attemptNumber < failoverConfig.maxAttempts) {
1269
- const delay = this.calculateFailoverDelay(attemptNumber, failoverConfig.delaySeconds);
1270
- await new Promise(resolve => setTimeout(resolve, delay));
1271
- }
1349
+ currentModel = transitionResult.model;
1350
+ currentVendorId = transitionResult.vendorId;
1351
+ vendorDriverClass = transitionResult.driverClass;
1352
+ vendorApiName = transitionResult.apiName;
1353
+ vendorSupportsEffortLevel = transitionResult.supportsEffortLevel;
1272
1354
  }
1273
1355
  }
1274
1356
  if (promptRun && failoverAttempts.length > 0) {
@@ -1302,6 +1384,7 @@ class AIPromptRunner {
1302
1384
  vendorName: undefined,
1303
1385
  driverClass: model.DriverClass,
1304
1386
  apiName: model.APIName,
1387
+ supportsEffortLevel: model.SupportsEffortLevel ?? false,
1305
1388
  isPreferredVendor: false,
1306
1389
  priority: model.PowerRank || 0,
1307
1390
  source: 'power-rank'
@@ -1315,6 +1398,7 @@ class AIPromptRunner {
1315
1398
  vendorName: vendor.Vendor,
1316
1399
  driverClass: vendor.DriverClass || model.DriverClass,
1317
1400
  apiName: vendor.APIName || model.APIName,
1401
+ supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
1318
1402
  isPreferredVendor: vendor.Priority > 0,
1319
1403
  priority: (model.PowerRank || 0) + (vendor.Priority || 0),
1320
1404
  source: 'power-rank'
@@ -1325,17 +1409,16 @@ class AIPromptRunner {
1325
1409
  return candidates;
1326
1410
  }
1327
1411
  updatePromptRunWithFailoverSuccess(promptRun, failoverAttempts, currentModel, currentVendorId) {
1328
- const promptRunWithFailover = promptRun;
1329
- promptRunWithFailover.FailoverAttempts = failoverAttempts.length;
1330
- promptRunWithFailover.FailoverErrors = JSON.stringify(failoverAttempts.map(a => ({
1412
+ promptRun.FailoverAttempts = failoverAttempts.length;
1413
+ promptRun.FailoverErrors = JSON.stringify(failoverAttempts.map(a => ({
1331
1414
  model: a.modelId,
1332
1415
  vendor: a.vendorId,
1333
1416
  error: a.error.message,
1334
1417
  errorType: a.errorType
1335
1418
  })));
1336
- promptRunWithFailover.FailoverDurations = JSON.stringify(failoverAttempts.map(a => a.duration));
1337
- promptRunWithFailover.TotalFailoverDuration = failoverAttempts.reduce((sum, a) => sum + a.duration, 0);
1338
- if (currentModel.ID !== promptRunWithFailover.OriginalModelID) {
1419
+ promptRun.FailoverDurations = JSON.stringify(failoverAttempts.map(a => a.duration));
1420
+ promptRun.TotalFailoverDuration = failoverAttempts.reduce((sum, a) => sum + a.duration, 0);
1421
+ if (currentModel.ID !== promptRun.OriginalModelID) {
1339
1422
  promptRun.ModelID = currentModel.ID;
1340
1423
  }
1341
1424
  if (currentVendorId && currentVendorId !== promptRun.VendorID) {
@@ -1343,16 +1426,15 @@ class AIPromptRunner {
1343
1426
  }
1344
1427
  }
1345
1428
  updatePromptRunWithFailoverFailure(promptRun, failoverAttempts) {
1346
- const promptRunWithFailover = promptRun;
1347
- promptRunWithFailover.FailoverAttempts = failoverAttempts.length;
1348
- promptRunWithFailover.FailoverErrors = JSON.stringify(failoverAttempts.map(a => ({
1429
+ promptRun.FailoverAttempts = failoverAttempts.length;
1430
+ promptRun.FailoverErrors = JSON.stringify(failoverAttempts.map(a => ({
1349
1431
  model: a.modelId,
1350
1432
  vendor: a.vendorId,
1351
1433
  error: a.error.message,
1352
1434
  errorType: a.errorType
1353
1435
  })));
1354
- promptRunWithFailover.FailoverDurations = JSON.stringify(failoverAttempts.map(a => a.duration));
1355
- promptRunWithFailover.TotalFailoverDuration = failoverAttempts.reduce((sum, a) => sum + a.duration, 0);
1436
+ promptRun.FailoverDurations = JSON.stringify(failoverAttempts.map(a => a.duration));
1437
+ promptRun.TotalFailoverDuration = failoverAttempts.reduce((sum, a) => sum + a.duration, 0);
1356
1438
  }
1357
1439
  createFailoverErrorResult(lastError, failoverAttempts) {
1358
1440
  const startTime = new Date();
@@ -1368,25 +1450,29 @@ class AIPromptRunner {
1368
1450
  data: null
1369
1451
  };
1370
1452
  }
1371
- async executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, vendorDriverClass, vendorApiName) {
1453
+ async executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel) {
1372
1454
  let driverClass;
1373
1455
  let apiName;
1374
1456
  let llm;
1375
1457
  let chatParams;
1376
1458
  try {
1377
1459
  const verbose = params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)();
1460
+ let supportsEffortLevel = false;
1378
1461
  if (vendorDriverClass && vendorApiName) {
1379
1462
  driverClass = vendorDriverClass;
1380
1463
  apiName = vendorApiName;
1464
+ supportsEffortLevel = vendorSupportsEffortLevel ?? false;
1381
1465
  }
1382
1466
  else {
1383
1467
  driverClass = model.DriverClass;
1384
1468
  apiName = model.APIName;
1469
+ supportsEffortLevel = model.SupportsEffortLevel ?? false;
1385
1470
  if (vendorId) {
1386
1471
  const modelVendor = aiengine_1.AIEngine.Instance.ModelVendors.find((mv) => mv.ModelID === model.ID && mv.VendorID === vendorId && mv.Status === 'Active' && this.isInferenceProvider(mv));
1387
1472
  if (modelVendor) {
1388
1473
  driverClass = modelVendor.DriverClass || driverClass;
1389
1474
  apiName = modelVendor.APIName || apiName;
1475
+ supportsEffortLevel = modelVendor.SupportsEffortLevel ?? supportsEffortLevel;
1390
1476
  }
1391
1477
  else {
1392
1478
  this.logStatus(`⚠️ Vendor ${vendorId} not found or is not an inference provider for model ${model.Name}, using model defaults`, true, params);
@@ -1472,11 +1558,21 @@ class AIPromptRunner {
1472
1558
  chatParams.topLogProbs = params.additionalParameters.topLogProbs;
1473
1559
  }
1474
1560
  }
1475
- if (params.effortLevel !== undefined && params.effortLevel !== null) {
1476
- chatParams.effortLevel = params.effortLevel.toString();
1477
- }
1478
- else if (prompt.EffortLevel !== undefined && prompt.EffortLevel !== null) {
1479
- chatParams.effortLevel = prompt.EffortLevel.toString();
1561
+ const hasEffortLevel = (params.effortLevel !== undefined && params.effortLevel !== null) ||
1562
+ (prompt.EffortLevel !== undefined && prompt.EffortLevel !== null);
1563
+ if (hasEffortLevel) {
1564
+ if (supportsEffortLevel) {
1565
+ if (params.effortLevel !== undefined && params.effortLevel !== null) {
1566
+ chatParams.effortLevel = params.effortLevel.toString();
1567
+ }
1568
+ else if (prompt.EffortLevel !== undefined && prompt.EffortLevel !== null) {
1569
+ chatParams.effortLevel = prompt.EffortLevel.toString();
1570
+ }
1571
+ }
1572
+ else {
1573
+ const effortValue = params.effortLevel ?? prompt.EffortLevel;
1574
+ console.log(`⚠️ Effort Level ${effortValue} specified but will be ignored - model ${model.Name} does not support effort levels`);
1575
+ }
1480
1576
  }
1481
1577
  if (prompt.ResponseFormat && prompt.ResponseFormat !== 'Any') {
1482
1578
  chatParams.responseFormat = prompt.ResponseFormat;
@@ -1510,7 +1606,8 @@ class AIPromptRunner {
1510
1606
  model: model,
1511
1607
  metadata: {
1512
1608
  vendorId
1513
- }
1609
+ },
1610
+ maxErrorLength: params.maxErrorLength
1514
1611
  });
1515
1612
  throw error;
1516
1613
  }
@@ -1542,7 +1639,7 @@ class AIPromptRunner {
1542
1639
  }
1543
1640
  return messages;
1544
1641
  }
1545
- async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, vendorDriverClass, vendorApiName) {
1642
+ async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel) {
1546
1643
  const validationAttempts = [];
1547
1644
  const maxRetries = Math.max(0, prompt.MaxRetries || 0);
1548
1645
  let lastError = null;
@@ -1558,7 +1655,7 @@ class AIPromptRunner {
1558
1655
  (0, core_1.LogStatus)(` 🔄 Retrying execution due to validation failure, attempt ${attempt + 1}/${maxRetries + 1}`);
1559
1656
  await this.applyRetryDelay(prompt, attempt);
1560
1657
  }
1561
- const modelResult = await this.executeModelWithFailover(selectedModel, renderedPromptText, prompt, params, promptRun.VendorID, params.conversationMessages, params.templateMessageRole || 'system', params.cancellationToken, undefined, promptRun, vendorDriverClass, vendorApiName);
1658
+ const modelResult = await this.executeModelWithFailover(selectedModel, renderedPromptText, prompt, params, promptRun.VendorID, params.conversationMessages, params.templateMessageRole || 'system', params.cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
1562
1659
  if (modelResult.data?.usage) {
1563
1660
  cumulativePromptTokens += modelResult.data.usage.promptTokens || 0;
1564
1661
  cumulativeCompletionTokens += modelResult.data.usage.completionTokens || 0;
@@ -1618,7 +1715,8 @@ class AIPromptRunner {
1618
1715
  attempt: attempt + 1,
1619
1716
  maxRetries: maxRetries + 1,
1620
1717
  modelName: selectedModel.Name
1621
- }
1718
+ },
1719
+ maxErrorLength: params.maxErrorLength
1622
1720
  });
1623
1721
  const validationAttempt = {
1624
1722
  attemptNumber: attempt + 1,
@@ -1635,7 +1733,10 @@ class AIPromptRunner {
1635
1733
  }
1636
1734
  throw lastError || new Error('Execution failed after all retry attempts');
1637
1735
  }
1638
- async applyRetryDelay(prompt, attemptNumber) {
1736
+ calculateRetryDelay(prompt, attemptNumber, suggestedDelaySeconds) {
1737
+ if (suggestedDelaySeconds && suggestedDelaySeconds > 0) {
1738
+ return suggestedDelaySeconds * 1000;
1739
+ }
1639
1740
  const baseDelay = prompt.RetryDelayMS || 1000;
1640
1741
  let delay = baseDelay;
1641
1742
  switch (prompt.RetryStrategy) {
@@ -1651,9 +1752,73 @@ class AIPromptRunner {
1651
1752
  default:
1652
1753
  delay = baseDelay;
1653
1754
  }
1654
- (0, core_1.LogStatus)(` Applying retry delay: ${delay}ms (strategy: ${prompt.RetryStrategy})`);
1755
+ return delay;
1756
+ }
1757
+ async applyRetryDelay(prompt, attemptNumber, suggestedDelaySeconds) {
1758
+ const delay = this.calculateRetryDelay(prompt, attemptNumber, suggestedDelaySeconds);
1759
+ const delaySeconds = (delay / 1000).toFixed(1);
1760
+ (0, core_1.LogStatus)(` Waiting ${delaySeconds}s before retry (strategy: ${prompt.RetryStrategy || 'Fixed'})...`);
1655
1761
  await new Promise(resolve => setTimeout(resolve, delay));
1656
1762
  }
1763
+ filterAuthenticationFailedVendor(errorType, currentVendorId, allCandidates) {
1764
+ if (errorType !== 'Authentication') {
1765
+ return allCandidates;
1766
+ }
1767
+ const failedVendorId = currentVendorId || 'default';
1768
+ const beforeCount = allCandidates.length;
1769
+ const filteredCandidates = allCandidates.filter(c => (c.vendorId || 'default') !== failedVendorId);
1770
+ const removedCount = beforeCount - filteredCandidates.length;
1771
+ if (removedCount > 0) {
1772
+ const vendorName = aiengine_1.AIEngine.Instance.Vendors.find(v => v.ID === failedVendorId)?.Name || failedVendorId;
1773
+ const remainingCount = filteredCandidates.length;
1774
+ this.logStatus(` 🔒 Invalid API key for ${vendorName} - excluding ${removedCount} model${removedCount === 1 ? '' : 's'} from this vendor (${remainingCount} remaining)`, true);
1775
+ }
1776
+ return filteredCandidates;
1777
+ }
1778
+ async handleRateLimitRetry(errorAnalysis, currentModel, currentVendorId, failoverAttempts, prompt, attemptNumber, maxAttempts, failoverAttempt) {
1779
+ const isRateLimit = errorAnalysis.errorType === 'RateLimit';
1780
+ if (!isRateLimit) {
1781
+ return false;
1782
+ }
1783
+ const rateLimitRetryCount = failoverAttempts.filter(a => a.modelId === currentModel.ID &&
1784
+ a.vendorId === currentVendorId &&
1785
+ a.errorType === 'RateLimit').length;
1786
+ const maxRetries = prompt.MaxRetries ?? 3;
1787
+ const shouldRetry = rateLimitRetryCount <= maxRetries;
1788
+ if (shouldRetry) {
1789
+ const modelName = currentModel.Name;
1790
+ const vendorName = currentVendorId
1791
+ ? aiengine_1.AIEngine.Instance.Vendors.find(v => v.ID === currentVendorId)?.Name || 'default'
1792
+ : 'default';
1793
+ this.logStatus(` ⏳ Rate limit hit - retrying ${modelName} (${vendorName}) with backoff (attempt ${rateLimitRetryCount}/${maxRetries})`, true);
1794
+ this.logFailoverAttempt(prompt.ID, failoverAttempt, true);
1795
+ if (attemptNumber < maxAttempts) {
1796
+ await this.applyRetryDelay(prompt, rateLimitRetryCount, errorAnalysis.suggestedRetryDelaySeconds);
1797
+ }
1798
+ return true;
1799
+ }
1800
+ return false;
1801
+ }
1802
+ async transitionToNextCandidate(currentModel, currentVendorId, failoverConfig, allCandidates, failoverAttempts, promptId, failoverAttempt, attemptNumber) {
1803
+ const nextCandidates = this.selectFailoverCandidates(currentModel, currentVendorId, failoverConfig.strategy, failoverConfig.modelStrategy, allCandidates, failoverAttempts);
1804
+ if (nextCandidates.length === 0) {
1805
+ this.logFailoverAttempt(promptId, failoverAttempt, false);
1806
+ return null;
1807
+ }
1808
+ const nextCandidate = nextCandidates[0];
1809
+ this.logFailoverAttempt(promptId, failoverAttempt, true);
1810
+ if (attemptNumber < failoverConfig.maxAttempts) {
1811
+ const delay = this.calculateFailoverDelay(attemptNumber, failoverConfig.delaySeconds);
1812
+ await new Promise(resolve => setTimeout(resolve, delay));
1813
+ }
1814
+ return {
1815
+ model: nextCandidate.model,
1816
+ vendorId: nextCandidate.vendorId,
1817
+ driverClass: nextCandidate.driverClass,
1818
+ apiName: nextCandidate.apiName,
1819
+ supportsEffortLevel: nextCandidate.supportsEffortLevel || false
1820
+ };
1821
+ }
1657
1822
  getValidationDecisionDescription(finalSuccess, totalAttempts, validationBehavior) {
1658
1823
  if (finalSuccess) {
1659
1824
  return totalAttempts === 1
@@ -1816,7 +1981,8 @@ class AIPromptRunner {
1816
1981
  rawOutput: rawOutput?.substring(0, 200),
1817
1982
  outputType: prompt.OutputType,
1818
1983
  parseAttempt: true
1819
- }
1984
+ },
1985
+ maxErrorLength: params?.maxErrorLength
1820
1986
  });
1821
1987
  const validationResult = new global_1.ValidationResult();
1822
1988
  validationResult.Success = false;
@@ -1834,7 +2000,8 @@ class AIPromptRunner {
1834
2000
  metadata: {
1835
2001
  validationPath: error.dataPath,
1836
2002
  validationMessage: error.message
1837
- }
2003
+ },
2004
+ maxErrorLength: params?.maxErrorLength
1838
2005
  });
1839
2006
  return { result: modelResult.data?.choices?.[0]?.message?.content, validationResult, validationErrors: validationResult.Errors };
1840
2007
  case 'None':
@@ -1914,7 +2081,8 @@ class AIPromptRunner {
1914
2081
  metadata: {
1915
2082
  originalError: originalError.message,
1916
2083
  rawOutput: rawOutput.substring(0, 500)
1917
- }
2084
+ },
2085
+ maxErrorLength: params.maxErrorLength
1918
2086
  });
1919
2087
  throw new Error(`JSON repair skipped: raw output does not contain JSON-like characters. Original error: ${originalError.message}`);
1920
2088
  }
@@ -1930,7 +2098,8 @@ class AIPromptRunner {
1930
2098
  metadata: {
1931
2099
  originalError: originalError.message,
1932
2100
  rawOutput: rawOutput.substring(0, 500)
1933
- }
2101
+ },
2102
+ maxErrorLength: params.maxErrorLength
1934
2103
  });
1935
2104
  }
1936
2105
  const json5Result = JSON5.parse(jsonToParse);
@@ -1973,7 +2142,8 @@ class AIPromptRunner {
1973
2142
  json5Error: json5Error.message,
1974
2143
  aiError: aiRepairError.message,
1975
2144
  rawOutput: rawOutput.substring(0, 500)
1976
- }
2145
+ },
2146
+ maxErrorLength: params.maxErrorLength
1977
2147
  });
1978
2148
  throw new Error(`JSON repair failed after both JSON5 and AI attempts: ${originalError.message}`);
1979
2149
  }
@@ -2156,11 +2326,22 @@ class AIPromptRunner {
2156
2326
  }
2157
2327
  const saveResult = await promptRun.Save();
2158
2328
  if (!saveResult) {
2159
- this.logError(`Failed to update AIPromptRun with results: ${promptRun.LatestResult?.Message || 'Unknown error'}`, {
2329
+ let errorMsg = 'Unknown error';
2330
+ try {
2331
+ if (promptRun.LatestResult?.Message) {
2332
+ errorMsg = typeof promptRun.LatestResult.Message === 'string'
2333
+ ? promptRun.LatestResult.Message
2334
+ : String(promptRun.LatestResult.Message);
2335
+ }
2336
+ }
2337
+ catch (msgError) {
2338
+ errorMsg = 'Error accessing error message';
2339
+ }
2340
+ this.logError(`Failed to update AIPromptRun with results: ${errorMsg}`, {
2160
2341
  category: 'PromptRunUpdate',
2161
2342
  metadata: {
2162
2343
  promptRunId: promptRun.ID,
2163
- updateError: promptRun.LatestResult?.Message
2344
+ updateError: errorMsg
2164
2345
  }
2165
2346
  });
2166
2347
  }
@@ -2175,13 +2356,12 @@ class AIPromptRunner {
2175
2356
  }
2176
2357
  }
2177
2358
  getFailoverConfiguration(prompt) {
2178
- const promptWithFailover = prompt;
2179
2359
  return {
2180
- strategy: promptWithFailover.FailoverStrategy || 'None',
2181
- maxAttempts: promptWithFailover.FailoverMaxAttempts || 3,
2182
- delaySeconds: promptWithFailover.FailoverDelaySeconds || 1,
2183
- modelStrategy: promptWithFailover.FailoverModelStrategy || 'PreferSameModel',
2184
- errorScope: promptWithFailover.FailoverErrorScope || 'All'
2360
+ strategy: prompt.FailoverStrategy || 'None',
2361
+ maxAttempts: prompt.FailoverMaxAttempts || 3,
2362
+ delaySeconds: prompt.FailoverDelaySeconds || 1,
2363
+ modelStrategy: prompt.FailoverModelStrategy || 'PreferSameModel',
2364
+ errorScope: prompt.FailoverErrorScope || 'All'
2185
2365
  };
2186
2366
  }
2187
2367
  shouldAttemptFailover(error, config, attemptNumber) {