@memberjunction/ai-prompts 3.4.0 → 4.0.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.
@@ -1,33 +1,10 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.ParallelExecutionCoordinator = void 0;
27
- const core_1 = require("@memberjunction/core");
28
- const global_1 = require("@memberjunction/global");
29
- const ai_1 = require("@memberjunction/ai");
30
- const aiengine_1 = require("@memberjunction/aiengine");
1
+ import { LogError, LogStatus, Metadata } from '@memberjunction/core';
2
+ import { MJGlobal } from '@memberjunction/global';
3
+ import { BaseLLM, ChatParams, ChatMessageRole, GetAIAPIKey } from '@memberjunction/ai';
4
+ import { AIEngine } from '@memberjunction/aiengine';
5
+ /**
6
+ * Helper class for tracking parallel execution progress
7
+ */
31
8
  class ParallelProgressTracker {
32
9
  constructor(totalTasks, totalGroups, progressCallbacks) {
33
10
  this.completedTasks = 0;
@@ -72,6 +49,7 @@ class ParallelProgressTracker {
72
49
  else {
73
50
  this.failedTasks++;
74
51
  }
52
+ // Send task completion update if enabled
75
53
  if (this.progressCallbacks?.getStreamingConfig()?.enabled) {
76
54
  const config = this.progressCallbacks.getStreamingConfig();
77
55
  if (config.callbacks?.OnTaskComplete) {
@@ -103,23 +81,53 @@ class ParallelProgressTracker {
103
81
  }
104
82
  }
105
83
  }
106
- class ParallelExecutionCoordinator {
84
+ /**
85
+ * Coordinates parallel execution of AI prompt tasks across multiple models and execution groups.
86
+ *
87
+ * This class manages the complex orchestration of:
88
+ * - Grouping tasks by execution group
89
+ * - Sequential execution of groups with parallel execution within groups
90
+ * - Result aggregation and selection
91
+ * - Error handling and partial result collection
92
+ * - Performance monitoring and metrics collection
93
+ * - Real-time progress tracking and streaming updates
94
+ */
95
+ export class ParallelExecutionCoordinator {
96
+ /**
97
+ * Creates a new parallel execution coordinator with default configuration.
98
+ */
107
99
  constructor() {
108
- this._metadata = new core_1.Metadata();
100
+ this._metadata = new Metadata();
109
101
  this._defaultConfig = {
110
102
  maxConcurrentExecutions: 5,
111
- taskTimeoutMS: 30000,
103
+ taskTimeoutMS: 30000, // 30 seconds
112
104
  failFast: false,
113
105
  collectPartialResults: true,
114
106
  maxRetries: 3,
115
107
  retryDelayMS: 1000,
116
108
  };
117
109
  }
118
- async executeTasksInParallel(params, tasks, config, parentPromptRunId, cancellationToken, progressCallbacks, agentRunId) {
110
+ /**
111
+ * Executes multiple tasks in parallel according to their execution groups.
112
+ *
113
+ * Tasks are grouped by their executionGroup property and executed sequentially by group.
114
+ * Within each group, tasks are executed in parallel up to the concurrency limit.
115
+ *
116
+ * @param tasks - Array of execution tasks to process
117
+ * @param config - Optional configuration to override defaults
118
+ * @param parentPromptRunId - Optional parent prompt run ID for hierarchical logging
119
+ * @param cancellationToken - Optional cancellation token to abort execution
120
+ * @param progressCallbacks - Optional callbacks for progress tracking
121
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
122
+ * @returns Promise<ParallelExecutionResult> - Aggregated results from all executions
123
+ */
124
+ async executeTasksInParallel(params, tasks, config, parentPromptRunId, cancellationToken, progressCallbacks, // Using interface to avoid circular dependency
125
+ agentRunId) {
119
126
  const startTime = new Date();
120
127
  const executionConfig = { ...this._defaultConfig, ...config };
128
+ // Check for cancellation at the start
121
129
  if (cancellationToken?.aborted) {
122
- (0, core_1.LogStatus)('Parallel execution cancelled before starting');
130
+ LogStatus('Parallel execution cancelled before starting');
123
131
  return {
124
132
  success: false,
125
133
  taskResults: [],
@@ -134,22 +142,27 @@ class ParallelExecutionCoordinator {
134
142
  endTime: new Date(),
135
143
  };
136
144
  }
137
- (0, core_1.LogStatus)(`Starting parallel execution of ${tasks.length} tasks with config: ${JSON.stringify(executionConfig)}`);
145
+ LogStatus(`Starting parallel execution of ${tasks.length} tasks with config: ${JSON.stringify(executionConfig)}`);
138
146
  try {
147
+ // Propagate cancellation token to all tasks
139
148
  if (cancellationToken) {
140
149
  tasks.forEach((task) => {
141
150
  task.cancellationToken = cancellationToken;
142
151
  });
143
152
  }
153
+ // Group tasks by execution group
144
154
  const executionGroups = this.groupTasksByExecutionGroup(tasks);
155
+ // Initialize progress tracker
145
156
  const progressTracker = new ParallelProgressTracker(tasks.length, executionGroups.length, progressCallbacks);
157
+ // Execute groups sequentially, tasks within groups in parallel
146
158
  const allResults = await this.executeGroupsSequentially(params, executionGroups, executionConfig, parentPromptRunId, cancellationToken, progressTracker, agentRunId);
159
+ // Aggregate results and calculate metrics
147
160
  const result = this.aggregateResults(allResults, startTime, new Date());
148
- (0, core_1.LogStatus)(`Parallel execution completed: ${result.successCount}/${tasks.length} tasks successful, ${result.failureCount} failed, ${result.cancelledCount} cancelled`);
161
+ LogStatus(`Parallel execution completed: ${result.successCount}/${tasks.length} tasks successful, ${result.failureCount} failed, ${result.cancelledCount} cancelled`);
149
162
  return result;
150
163
  }
151
164
  catch (error) {
152
- (0, core_1.LogError)(`Parallel execution failed: ${error.message}`);
165
+ LogError(`Parallel execution failed: ${error.message}`);
153
166
  return {
154
167
  success: false,
155
168
  taskResults: [],
@@ -165,6 +178,17 @@ class ParallelExecutionCoordinator {
165
178
  };
166
179
  }
167
180
  }
181
+ /**
182
+ * Selects the best result from multiple parallel execution results.
183
+ *
184
+ * Uses various strategies to determine which result should be considered
185
+ * the "best" output from the parallel executions.
186
+ *
187
+ * @param results - Array of successful execution results to choose from
188
+ * @param config - Configuration for result selection method
189
+ * @param parentPromptRunId - Optional parent prompt run ID for hierarchical logging
190
+ * @returns Promise<ExecutionTaskResult | null> - The selected best result, or null if none suitable
191
+ */
168
192
  async selectBestResult(results, config, parentPromptRunId, cancellationToken) {
169
193
  if (results.length === 0) {
170
194
  return null;
@@ -172,7 +196,7 @@ class ParallelExecutionCoordinator {
172
196
  if (results.length === 1) {
173
197
  return results[0];
174
198
  }
175
- (0, core_1.LogStatus)(`Selecting best result from ${results.length} candidates using method: ${config.method}`);
199
+ LogStatus(`Selecting best result from ${results.length} candidates using method: ${config.method}`);
176
200
  switch (config.method) {
177
201
  case 'First':
178
202
  return this.selectFirstResult(results);
@@ -183,12 +207,20 @@ class ParallelExecutionCoordinator {
183
207
  case 'Consensus':
184
208
  return this.selectConsensusResult(results);
185
209
  default:
186
- (0, core_1.LogError)(`Unknown result selection method: ${config.method}`);
210
+ LogError(`Unknown result selection method: ${config.method}`);
187
211
  return results[0];
188
212
  }
189
213
  }
214
+ /**
215
+ * Groups execution tasks by their execution group number.
216
+ * Tasks with the same group number will be executed in parallel.
217
+ *
218
+ * @param tasks - Array of tasks to group
219
+ * @returns ExecutionGroup[] - Sorted array of execution groups
220
+ */
190
221
  groupTasksByExecutionGroup(tasks) {
191
222
  const groupMap = new Map();
223
+ // Group tasks by execution group number
192
224
  for (const task of tasks) {
193
225
  const groupNumber = task.executionGroup;
194
226
  if (!groupMap.has(groupNumber)) {
@@ -196,23 +228,39 @@ class ParallelExecutionCoordinator {
196
228
  }
197
229
  groupMap.get(groupNumber).push(task);
198
230
  }
231
+ // Convert to ExecutionGroup array and sort by group number
199
232
  const groups = [];
200
233
  for (const [groupNumber, groupTasks] of groupMap) {
234
+ // Sort tasks within group by priority (higher priority first)
201
235
  groupTasks.sort((a, b) => b.priority - a.priority);
202
236
  groups.push({
203
237
  groupNumber,
204
238
  tasks: groupTasks,
205
239
  });
206
240
  }
241
+ // Sort groups by group number (execute in ascending order)
207
242
  groups.sort((a, b) => a.groupNumber - b.groupNumber);
208
- (0, core_1.LogStatus)(`Grouped ${tasks.length} tasks into ${groups.length} execution groups`);
243
+ LogStatus(`Grouped ${tasks.length} tasks into ${groups.length} execution groups`);
209
244
  return groups;
210
245
  }
246
+ /**
247
+ * Executes execution groups sequentially, with parallel execution within each group.
248
+ *
249
+ * @param groups - Array of execution groups to process
250
+ * @param config - Execution configuration
251
+ * @param parentPromptRunId - Optional parent prompt run ID for tracking
252
+ * @param cancellationToken - Optional cancellation token to abort execution
253
+ * @param progressTracker - Progress tracker for monitoring execution
254
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
255
+ * @returns Promise<ExecutionTaskResult[]> - All task results from all groups
256
+ */
211
257
  async executeGroupsSequentially(params, groups, config, parentPromptRunId, cancellationToken, progressTracker, agentRunId) {
212
258
  const allResults = [];
213
259
  for (const group of groups) {
260
+ // Check for cancellation before each group
214
261
  if (cancellationToken?.aborted) {
215
- (0, core_1.LogStatus)(`Group execution cancelled at group ${group.groupNumber}`);
262
+ LogStatus(`Group execution cancelled at group ${group.groupNumber}`);
263
+ // Create cancelled results for remaining tasks
216
264
  const cancelledResults = group.tasks.map((task) => ({
217
265
  task,
218
266
  success: false,
@@ -225,27 +273,43 @@ class ParallelExecutionCoordinator {
225
273
  allResults.push(...cancelledResults);
226
274
  break;
227
275
  }
228
- (0, core_1.LogStatus)(`Executing group ${group.groupNumber} with ${group.tasks.length} tasks`);
276
+ LogStatus(`Executing group ${group.groupNumber} with ${group.tasks.length} tasks`);
277
+ // Update progress tracker for current group
229
278
  progressTracker?.updateProgress(group.groupNumber);
230
279
  const groupResults = await this.executeGroupInParallel(params, group, config, parentPromptRunId, cancellationToken, progressTracker, agentRunId);
231
280
  allResults.push(...groupResults);
281
+ // Check if we should fail fast
232
282
  if (config.failFast && groupResults.some((r) => !r.success)) {
233
283
  const failedTasks = groupResults.filter((r) => !r.success);
234
- (0, core_1.LogError)(`Failing fast due to ${failedTasks.length} failed tasks in group ${group.groupNumber}`);
284
+ LogError(`Failing fast due to ${failedTasks.length} failed tasks in group ${group.groupNumber}`);
235
285
  break;
236
286
  }
237
287
  }
238
288
  return allResults;
239
289
  }
290
+ /**
291
+ * Executes all tasks within a single execution group in parallel.
292
+ *
293
+ * @param group - The execution group to process
294
+ * @param config - Execution configuration
295
+ * @param parentPromptRunId - Optional parent prompt run ID for tracking
296
+ * @param cancellationToken - Optional cancellation token to abort execution
297
+ * @param progressTracker - Progress tracker for monitoring execution
298
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
299
+ * @returns Promise<ExecutionTaskResult[]> - Results from all tasks in the group
300
+ */
240
301
  async executeGroupInParallel(params, group, config, parentPromptRunId, cancellationToken, progressTracker, agentRunId) {
241
302
  const maxConcurrent = Math.min(config.maxConcurrentExecutions, group.tasks.length);
242
303
  const results = [];
243
304
  const executing = [];
244
305
  let taskIndex = 0;
245
306
  let executionOrder = 0;
307
+ // Process tasks with concurrency limit
246
308
  while (taskIndex < group.tasks.length || executing.length > 0) {
309
+ // Check for cancellation
247
310
  if (cancellationToken?.aborted) {
248
- (0, core_1.LogStatus)(`Task execution cancelled in group ${group.groupNumber}`);
311
+ LogStatus(`Task execution cancelled in group ${group.groupNumber}`);
312
+ // Cancel remaining tasks
249
313
  while (taskIndex < group.tasks.length) {
250
314
  const task = group.tasks[taskIndex++];
251
315
  results.push({
@@ -260,16 +324,20 @@ class ParallelExecutionCoordinator {
260
324
  }
261
325
  break;
262
326
  }
327
+ // Start new tasks up to concurrency limit
263
328
  while (executing.length < maxConcurrent && taskIndex < group.tasks.length) {
264
329
  const task = group.tasks[taskIndex++];
265
330
  progressTracker?.addActiveTask(task.taskId);
266
331
  const execution = this.executeTask(params, task, config, parentPromptRunId, executionOrder++, agentRunId);
267
332
  executing.push(execution);
268
333
  }
334
+ // Wait for at least one task to complete
269
335
  if (executing.length > 0) {
270
336
  const result = await Promise.race(executing);
271
337
  results.push(result);
338
+ // Notify progress tracker of task completion
272
339
  progressTracker?.taskCompleted(result);
340
+ // Remove completed task from executing array
273
341
  const completedIndex = executing.findIndex((p) => p === Promise.resolve(result));
274
342
  if (completedIndex !== -1) {
275
343
  executing.splice(completedIndex, 1);
@@ -278,17 +346,29 @@ class ParallelExecutionCoordinator {
278
346
  }
279
347
  const successfulResults = results.filter((r) => r.success);
280
348
  const cancelledResults = results.filter((r) => r.cancelled);
281
- (0, core_1.LogStatus)(`Group ${group.groupNumber} completed: ${successfulResults.length}/${results.length} successful, ${cancelledResults.length} cancelled`);
349
+ LogStatus(`Group ${group.groupNumber} completed: ${successfulResults.length}/${results.length} successful, ${cancelledResults.length} cancelled`);
282
350
  return results;
283
351
  }
352
+ /**
353
+ * Executes a single task with retry logic and timeout handling.
354
+ *
355
+ * @param task - The execution task to process
356
+ * @param config - Execution configuration
357
+ * @param parentPromptRunId - Optional parent prompt run ID for hierarchical logging
358
+ * @param executionOrder - Execution order for this task
359
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
360
+ * @returns Promise<ExecutionTaskResult> - Result of the task execution
361
+ */
284
362
  async executeTask(params, task, config, parentPromptRunId, executionOrder, agentRunId) {
285
363
  const startTime = new Date();
286
364
  let lastError = null;
365
+ // Attempt execution with retries
287
366
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
288
367
  try {
368
+ // Check for cancellation before each attempt
289
369
  if (task.cancellationToken?.aborted) {
290
370
  const endTime = new Date();
291
- (0, core_1.LogStatus)(`Task ${task.taskId} cancelled before attempt ${attempt + 1}`);
371
+ LogStatus(`Task ${task.taskId} cancelled before attempt ${attempt + 1}`);
292
372
  return {
293
373
  task,
294
374
  success: false,
@@ -300,8 +380,9 @@ class ParallelExecutionCoordinator {
300
380
  };
301
381
  }
302
382
  if (attempt > 0) {
383
+ // Apply retry delay
303
384
  await this.delay(config.retryDelayMS * attempt);
304
- (0, core_1.LogStatus)(`Retrying task ${task.taskId}, attempt ${attempt + 1}/${config.maxRetries + 1}`);
385
+ LogStatus(`Retrying task ${task.taskId}, attempt ${attempt + 1}/${config.maxRetries + 1}`);
305
386
  }
306
387
  const result = await this.executeSingleTask(params, task, config.taskTimeoutMS, parentPromptRunId, executionOrder, agentRunId);
307
388
  result.startTime = startTime;
@@ -310,12 +391,14 @@ class ParallelExecutionCoordinator {
310
391
  }
311
392
  catch (error) {
312
393
  lastError = error;
313
- (0, core_1.LogError)(`Task ${task.taskId} attempt ${attempt + 1} failed: ${error.message}`);
394
+ LogError(`Task ${task.taskId} attempt ${attempt + 1} failed: ${error.message}`);
395
+ // Don't retry if this was the last attempt
314
396
  if (attempt === config.maxRetries) {
315
397
  break;
316
398
  }
317
399
  }
318
400
  }
401
+ // All attempts failed
319
402
  const endTime = new Date();
320
403
  return {
321
404
  task,
@@ -326,23 +409,39 @@ class ParallelExecutionCoordinator {
326
409
  endTime,
327
410
  };
328
411
  }
412
+ /**
413
+ * Executes a single task without retry logic.
414
+ *
415
+ * @param task - The execution task to process
416
+ * @param timeoutMS - Timeout for the execution in milliseconds
417
+ * @param parentPromptRunId - Optional parent prompt run ID for hierarchical logging
418
+ * @param executionOrder - Execution order for this task
419
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
420
+ * @returns Promise<ExecutionTaskResult> - Result of the task execution
421
+ */
329
422
  async executeSingleTask(params, task, timeoutMS, parentPromptRunId, executionOrder, agentRunId) {
423
+ // TODO: This will need to integrate with AIPromptRunner's execution logic
424
+ // For now, implementing a simplified version
330
425
  const startTime = new Date();
331
426
  let childPromptRun = null;
332
427
  try {
428
+ // Create child prompt run log if parent ID is provided
333
429
  if (parentPromptRunId) {
334
430
  childPromptRun = await this.createChildPromptRun(task, startTime, parentPromptRunId, executionOrder, agentRunId);
335
431
  }
432
+ // Create LLM instance using vendor-specific driver class if available
336
433
  const driverClass = task.vendorDriverClass || task.model.DriverClass;
337
434
  const apiName = task.vendorApiName || task.model.APIName;
338
435
  if (!driverClass) {
339
436
  throw new Error(`No driver class available for model ${task.model.Name}. Vendor selection may have failed.`);
340
437
  }
341
- const apiKey = (0, ai_1.GetAIAPIKey)(driverClass, params.apiKeys, params.verbose);
342
- const llm = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(ai_1.BaseLLM, driverClass, apiKey);
343
- const innerParams = new ai_1.ChatParams();
438
+ const apiKey = GetAIAPIKey(driverClass, params.apiKeys, params.verbose);
439
+ const llm = MJGlobal.Instance.ClassFactory.CreateInstance(BaseLLM, driverClass, apiKey);
440
+ // Prepare chat parameters
441
+ const innerParams = new ChatParams();
344
442
  innerParams.model = apiName;
345
443
  innerParams.cancellationToken = task.cancellationToken;
444
+ // Configure streaming if enabled in task
346
445
  if (task.streamingConfig?.enabled && task.streamingConfig.callbacks?.OnContent) {
347
446
  innerParams.streaming = true;
348
447
  innerParams.streamingCallbacks = {
@@ -351,12 +450,17 @@ class ParallelExecutionCoordinator {
351
450
  OnError: task.streamingConfig.callbacks.OnError,
352
451
  };
353
452
  }
453
+ // Build message array with rendered prompt and conversation messages
354
454
  innerParams.messages = this.buildMessageArray(task.renderedPrompt, task.conversationMessages, task.templateMessageRole || 'system');
455
+ // Apply model-specific parameters if available
355
456
  if (task.modelParameters) {
356
457
  Object.assign(params, task.modelParameters);
357
458
  }
459
+ // Execute with timeout and cancellation support
358
460
  const racePromises = [llm.ChatCompletion(innerParams)];
461
+ // Add timeout promise
359
462
  racePromises.push(new Promise((_, reject) => setTimeout(() => reject(new Error('Task execution timeout')), timeoutMS)));
463
+ // Add cancellation promise if cancellation token is available
360
464
  if (task.cancellationToken) {
361
465
  racePromises.push(new Promise((_, reject) => {
362
466
  if (task.cancellationToken.aborted) {
@@ -372,6 +476,7 @@ class ParallelExecutionCoordinator {
372
476
  const modelResult = (await Promise.race(racePromises));
373
477
  const endTime = new Date();
374
478
  const executionTimeMS = endTime.getTime() - startTime.getTime();
479
+ // Update child prompt run with results
375
480
  if (childPromptRun) {
376
481
  await this.updateChildPromptRun(childPromptRun, modelResult, endTime, executionTimeMS);
377
482
  }
@@ -384,13 +489,15 @@ class ParallelExecutionCoordinator {
384
489
  modelResult,
385
490
  startTime,
386
491
  endTime,
387
- promptRun: childPromptRun,
492
+ promptRun: childPromptRun, // Include the child prompt run in the result
388
493
  };
389
494
  }
390
495
  catch (error) {
391
496
  const endTime = new Date();
392
497
  const executionTimeMS = endTime.getTime() - startTime.getTime();
498
+ // Check if this was a cancellation error
393
499
  const isCancelled = error.message.includes('cancelled');
500
+ // Update child prompt run with error if it exists
394
501
  if (childPromptRun) {
395
502
  childPromptRun.CompletedAt = endTime;
396
503
  childPromptRun.ExecutionTimeMS = executionTimeMS;
@@ -399,6 +506,7 @@ class ParallelExecutionCoordinator {
399
506
  childPromptRun.Result = `ERROR: ${error.message}`;
400
507
  await childPromptRun.Save();
401
508
  }
509
+ // Return cancelled result instead of throwing if this was a cancellation
402
510
  if (isCancelled) {
403
511
  return {
404
512
  task,
@@ -414,12 +522,21 @@ class ParallelExecutionCoordinator {
414
522
  throw new Error(`Task execution failed: ${error.message}`);
415
523
  }
416
524
  }
525
+ /**
526
+ * Aggregates individual task results into a comprehensive parallel execution result.
527
+ *
528
+ * @param taskResults - Array of all task execution results
529
+ * @param startTime - Start time of the parallel execution
530
+ * @param endTime - End time of the parallel execution
531
+ * @returns ParallelExecutionResult - Aggregated results with metrics
532
+ */
417
533
  aggregateResults(taskResults, startTime, endTime) {
418
534
  const groupResults = new Map();
419
535
  const errors = [];
420
536
  let totalTokensUsed = 0;
421
537
  let successCount = 0;
422
538
  let cancelledCount = 0;
539
+ // Group results and calculate metrics
423
540
  for (const result of taskResults) {
424
541
  const groupNumber = result.task.executionGroup;
425
542
  if (!groupResults.has(groupNumber)) {
@@ -453,37 +570,63 @@ class ParallelExecutionCoordinator {
453
570
  endTime,
454
571
  };
455
572
  }
573
+ /**
574
+ * Selects the first successful result from the array.
575
+ *
576
+ * @param results - Array of successful results to choose from
577
+ * @returns ExecutionTaskResult - The first result in the array
578
+ */
456
579
  selectFirstResult(results) {
457
580
  return results[0];
458
581
  }
582
+ /**
583
+ * Selects a random result from the array.
584
+ *
585
+ * @param results - Array of successful results to choose from
586
+ * @returns ExecutionTaskResult - A randomly selected result
587
+ */
459
588
  selectRandomResult(results) {
460
589
  const randomIndex = Math.floor(Math.random() * results.length);
461
590
  return results[randomIndex];
462
591
  }
592
+ /**
593
+ * Uses an AI prompt to select the best result from multiple options.
594
+ *
595
+ * @param results - Array of successful results to choose from
596
+ * @param selectorPromptId - ID of the prompt to use for selection
597
+ * @param parentPromptRunId - Optional parent prompt run ID for hierarchical logging
598
+ * @returns Promise<ExecutionTaskResult> - The AI-selected best result with all results ranked
599
+ */
463
600
  async selectResultWithPrompt(results, selectorPromptId, parentPromptRunId, _cancellationToken) {
464
601
  try {
465
- const { AIPromptRunner } = await Promise.resolve().then(() => __importStar(require('./AIPromptRunner')));
466
- await aiengine_1.AIEngine.Instance.Config(false);
467
- const judgePrompt = aiengine_1.AIEngine.Instance.Prompts.find((p) => p.ID === selectorPromptId);
602
+ // Import AIPromptRunner here to avoid circular dependency
603
+ const { AIPromptRunner } = await import('./AIPromptRunner.js');
604
+ // Load the judge prompt from AIEngine
605
+ await AIEngine.Instance.Config(false);
606
+ const judgePrompt = AIEngine.Instance.Prompts.find((p) => p.ID === selectorPromptId);
468
607
  if (!judgePrompt) {
469
- (0, core_1.LogError)(`Judge prompt with ID ${selectorPromptId} not found`);
470
- return results[0];
608
+ LogError(`Judge prompt with ID ${selectorPromptId} not found`);
609
+ return results[0]; // Fallback to first result
471
610
  }
611
+ // Prepare the data for the judge prompt
472
612
  const judgeData = this.formatResultsForJudge(results);
613
+ // Create conversation messages for the judge
473
614
  const conversationMessages = [
474
615
  {
475
- role: ai_1.ChatMessageRole.system,
616
+ role: ChatMessageRole.system,
476
617
  content: 'You are an expert AI judge tasked with ranking multiple AI responses to determine the best one. Analyze each response for quality, accuracy, completeness, and relevance to the original prompt.',
477
618
  },
478
619
  {
479
- role: ai_1.ChatMessageRole.user,
620
+ role: ChatMessageRole.user,
480
621
  content: JSON.stringify(judgeData, null, 2),
481
622
  },
482
623
  ];
624
+ // Create a ResultSelector prompt run log entry if parent ID is provided
483
625
  let resultSelectorPromptRun = null;
484
626
  if (parentPromptRunId) {
485
627
  resultSelectorPromptRun = await this.createResultSelectorPromptRun(judgePrompt, judgeData, parentPromptRunId, results.length);
486
628
  }
629
+ // Execute the judge prompt
487
630
  const judgeRunner = new AIPromptRunner();
488
631
  const judgeStartTime = Date.now();
489
632
  const judgeResult = await judgeRunner.ExecutePrompt({
@@ -493,6 +636,7 @@ class ParallelExecutionCoordinator {
493
636
  });
494
637
  const judgeEndTime = Date.now();
495
638
  const judgeExecutionTimeMS = judgeEndTime - judgeStartTime;
639
+ // Update the result selector prompt run with the result
496
640
  if (resultSelectorPromptRun && judgeResult.promptRun) {
497
641
  resultSelectorPromptRun.CompletedAt = new Date(judgeEndTime);
498
642
  resultSelectorPromptRun.ExecutionTimeMS = judgeExecutionTimeMS;
@@ -504,31 +648,41 @@ class ParallelExecutionCoordinator {
504
648
  await resultSelectorPromptRun.Save();
505
649
  }
506
650
  if (!judgeResult.success || !judgeResult.rawResult) {
507
- (0, core_1.LogError)(`Judge prompt execution failed: ${judgeResult.errorMessage}`);
508
- return results[0];
651
+ LogError(`Judge prompt execution failed: ${judgeResult.errorMessage}`);
652
+ return results[0]; // Fallback to first result
509
653
  }
654
+ // Parse the judge's decision
510
655
  const rankings = this.parseJudgeResult(judgeResult.rawResult);
511
656
  if (!rankings || rankings.length === 0) {
512
- (0, core_1.LogError)('Failed to parse judge rankings');
513
- return results[0];
657
+ LogError('Failed to parse judge rankings');
658
+ return results[0]; // Fallback to first result
514
659
  }
660
+ // Apply rankings to results
515
661
  this.applyRankingsToResults(results, rankings);
662
+ // Store judge metadata for later use
516
663
  const bestCandidateId = rankings.find((r) => r.rank === 1)?.candidateId;
517
664
  const bestResultIndex = results.findIndex((r) => r.task.taskId === bestCandidateId);
518
665
  const bestResult = bestResultIndex >= 0 ? results[bestResultIndex] : results[0];
666
+ // Add judge metadata to the best result
519
667
  bestResult.judgeMetadata = {
520
668
  judgePromptId: selectorPromptId,
521
669
  judgeExecutionTimeMS,
522
670
  judgeTokensUsed: judgeResult.tokensUsed,
523
671
  };
524
- (0, core_1.LogStatus)(`Judge selected candidate ${bestCandidateId} as the best result`);
672
+ LogStatus(`Judge selected candidate ${bestCandidateId} as the best result`);
525
673
  return bestResult;
526
674
  }
527
675
  catch (error) {
528
- (0, core_1.LogError)(`Error in AI judge selection: ${error.message}`);
529
- return results[0];
676
+ LogError(`Error in AI judge selection: ${error.message}`);
677
+ return results[0]; // Fallback to first result
530
678
  }
531
679
  }
680
+ /**
681
+ * Formats execution results for the judge prompt.
682
+ *
683
+ * @param results - Array of execution results to format
684
+ * @returns Structured data for judge evaluation
685
+ */
532
686
  formatResultsForJudge(results) {
533
687
  return {
534
688
  originalPrompt: results[0]?.task.renderedPrompt || 'Original prompt not available',
@@ -547,8 +701,15 @@ class ParallelExecutionCoordinator {
547
701
  },
548
702
  };
549
703
  }
704
+ /**
705
+ * Parses the judge's result to extract rankings.
706
+ *
707
+ * @param judgeResult - Raw result from the judge prompt
708
+ * @returns Array of ranking objects or null if parsing fails
709
+ */
550
710
  parseJudgeResult(judgeResult) {
551
711
  try {
712
+ // Try to extract JSON from the result (in case there's extra text)
552
713
  const jsonMatch = judgeResult.match(/\{[\s\S]*\}/);
553
714
  const jsonString = jsonMatch ? jsonMatch[0] : judgeResult;
554
715
  const parsed = JSON.parse(jsonString);
@@ -559,14 +720,20 @@ class ParallelExecutionCoordinator {
559
720
  rationale: ranking.rationale || 'No rationale provided',
560
721
  }));
561
722
  }
562
- (0, core_1.LogError)('Judge result does not contain valid rankings array');
723
+ LogError('Judge result does not contain valid rankings array');
563
724
  return null;
564
725
  }
565
726
  catch (error) {
566
- (0, core_1.LogError)(`Failed to parse judge result: ${error.message}`);
727
+ LogError(`Failed to parse judge result: ${error.message}`);
567
728
  return null;
568
729
  }
569
730
  }
731
+ /**
732
+ * Applies rankings to execution task results.
733
+ *
734
+ * @param results - Array of execution results to rank
735
+ * @param rankings - Rankings from the judge
736
+ */
570
737
  applyRankingsToResults(results, rankings) {
571
738
  for (const result of results) {
572
739
  const ranking = rankings.find((r) => r.candidateId === result.task.taskId);
@@ -576,7 +743,14 @@ class ParallelExecutionCoordinator {
576
743
  }
577
744
  }
578
745
  }
746
+ /**
747
+ * Selects the result that appears most frequently (consensus).
748
+ *
749
+ * @param results - Array of successful results to choose from
750
+ * @returns ExecutionTaskResult - The most common result
751
+ */
579
752
  selectConsensusResult(results) {
753
+ // Group results by their content
580
754
  const resultGroups = new Map();
581
755
  for (const result of results) {
582
756
  const key = result.rawResult || '';
@@ -585,41 +759,62 @@ class ParallelExecutionCoordinator {
585
759
  }
586
760
  resultGroups.get(key).push(result);
587
761
  }
762
+ // Find the group with the most results
588
763
  let largestGroup = [];
589
764
  for (const group of resultGroups.values()) {
590
765
  if (group.length > largestGroup.length) {
591
766
  largestGroup = group;
592
767
  }
593
768
  }
769
+ // Return the first result from the largest group
594
770
  return largestGroup[0];
595
771
  }
772
+ /**
773
+ * Builds the message array combining rendered prompt with conversation messages
774
+ */
596
775
  buildMessageArray(renderedPrompt, conversationMessages, templateMessageRole = 'system') {
597
776
  const messages = [];
777
+ // Add rendered template as system or user message if not 'none'
598
778
  if (renderedPrompt && templateMessageRole !== 'none') {
599
779
  messages.push({
600
- role: templateMessageRole === 'system' ? ai_1.ChatMessageRole.system : ai_1.ChatMessageRole.user,
780
+ role: templateMessageRole === 'system' ? ChatMessageRole.system : ChatMessageRole.user,
601
781
  content: renderedPrompt,
602
782
  });
603
783
  }
784
+ // Add conversation messages if provided
604
785
  if (conversationMessages && conversationMessages.length > 0) {
605
786
  messages.push(...conversationMessages);
606
787
  }
788
+ // If no conversation messages and no rendered prompt as user message,
789
+ // add a default user message to ensure we have at least one user message
607
790
  if ((!conversationMessages || conversationMessages.length === 0) && templateMessageRole !== 'user' && renderedPrompt) {
791
+ // If we only have a system message, we need a user message too
608
792
  if (templateMessageRole === 'system') {
609
793
  messages.push({
610
- role: ai_1.ChatMessageRole.user,
794
+ role: ChatMessageRole.user,
611
795
  content: 'Please proceed with the above instructions.',
612
796
  });
613
797
  }
614
798
  }
615
799
  else if ((!conversationMessages || conversationMessages.length === 0) && !renderedPrompt) {
800
+ // Fallback: if no conversation and no rendered prompt, add a basic user message
616
801
  messages.push({
617
- role: ai_1.ChatMessageRole.user,
802
+ role: ChatMessageRole.user,
618
803
  content: 'Hello',
619
804
  });
620
805
  }
621
806
  return messages;
622
807
  }
808
+ /**
809
+ * Creates a child AIPromptRun entity for individual parallel execution tracking.
810
+ *
811
+ * @param task - The execution task
812
+ * @param startTime - When the execution started
813
+ * @param parentPromptRunId - ID of the parent prompt run
814
+ * @param executionOrder - Execution order within the parallel group
815
+ * @param agentRunId - Optional agent run ID to link prompt executions to parent agent run
816
+ * @returns Promise<AIPromptRunEntityExtended> - The created child prompt run
817
+ */
623
818
  async createChildPromptRun(task, startTime, parentPromptRunId, executionOrder, agentRunId) {
624
819
  try {
625
820
  const promptRun = await this._metadata.GetEntityObject('MJ: AI Prompt Runs', task.contextUser);
@@ -632,18 +827,23 @@ class ParallelExecutionCoordinator {
632
827
  if (executionOrder !== undefined) {
633
828
  promptRun.ExecutionOrder = executionOrder;
634
829
  }
830
+ // Set AgentRunID if provided for agent-prompt execution tracking
635
831
  if (agentRunId) {
636
832
  promptRun.AgentRunID = agentRunId;
637
833
  }
834
+ // Set vendor ID from task if available (use task vendor ID which comes from vendor selection)
638
835
  if (task.vendorId) {
639
836
  promptRun.VendorID = task.vendorId;
640
837
  }
641
838
  else if (task.promptModel?.VendorID) {
839
+ // Fallback to prompt model vendor if task vendor not set
642
840
  promptRun.VendorID = task.promptModel.VendorID;
643
841
  }
842
+ // Set configuration ID from task if available
644
843
  if (task.configurationId) {
645
844
  promptRun.ConfigurationID = task.configurationId;
646
845
  }
846
+ // Store the input data/context as JSON in Messages field
647
847
  const messagesData = {
648
848
  renderedPrompt: task.renderedPrompt,
649
849
  conversationMessages: task.conversationMessages,
@@ -654,16 +854,24 @@ class ParallelExecutionCoordinator {
654
854
  const saveResult = await promptRun.Save();
655
855
  if (!saveResult) {
656
856
  const error = `Failed to save child AIPromptRun: ${promptRun.LatestResult?.Message || 'Unknown error'}`;
657
- (0, core_1.LogError)(error);
857
+ LogError(error);
658
858
  throw new Error(error);
659
859
  }
660
860
  return promptRun;
661
861
  }
662
862
  catch (error) {
663
- (0, core_1.LogError)(`Error creating child prompt run record: ${error.message}`);
863
+ LogError(`Error creating child prompt run record: ${error.message}`);
664
864
  throw error;
665
865
  }
666
866
  }
867
+ /**
868
+ * Updates a child AIPromptRun entity with execution results.
869
+ *
870
+ * @param promptRun - The child prompt run entity to update
871
+ * @param modelResult - The result from the AI model
872
+ * @param endTime - When the execution completed
873
+ * @param executionTimeMS - Total execution time in milliseconds
874
+ */
667
875
  async updateChildPromptRun(promptRun, modelResult, endTime, executionTimeMS) {
668
876
  try {
669
877
  promptRun.CompletedAt = endTime;
@@ -671,6 +879,7 @@ class ParallelExecutionCoordinator {
671
879
  promptRun.Success = modelResult.success;
672
880
  if (modelResult.success) {
673
881
  promptRun.Result = modelResult.data?.choices?.[0]?.message?.content || '';
882
+ // Extract token usage if available
674
883
  if (modelResult.data?.usage) {
675
884
  promptRun.TokensUsed = modelResult.data.usage.totalTokens;
676
885
  promptRun.TokensPrompt = modelResult.data.usage.promptTokens;
@@ -683,22 +892,33 @@ class ParallelExecutionCoordinator {
683
892
  }
684
893
  const saveResult = await promptRun.Save();
685
894
  if (!saveResult) {
686
- (0, core_1.LogError)(`Failed to update child AIPromptRun with results: ${promptRun.LatestResult?.Message || 'Unknown error'}`);
895
+ LogError(`Failed to update child AIPromptRun with results: ${promptRun.LatestResult?.Message || 'Unknown error'}`);
687
896
  }
688
897
  }
689
898
  catch (error) {
690
- (0, core_1.LogError)(`Error updating child prompt run: ${error.message}`);
899
+ LogError(`Error updating child prompt run: ${error.message}`);
691
900
  }
692
901
  }
902
+ /**
903
+ * Creates a ResultSelector AIPromptRun entity for judge execution tracking.
904
+ *
905
+ * @param judgePrompt - The judge prompt being executed
906
+ * @param judgeData - The data being sent to the judge
907
+ * @param parentPromptRunId - ID of the parent prompt run
908
+ * @param executionOrder - Execution order within the parallel group
909
+ * @returns Promise<AIPromptRunEntityExtended> - The created result selector prompt run
910
+ */
693
911
  async createResultSelectorPromptRun(judgePrompt, judgeData, parentPromptRunId, executionOrder) {
694
912
  try {
695
913
  const promptRun = await this._metadata.GetEntityObject('MJ: AI Prompt Runs');
696
914
  promptRun.NewRecord();
697
915
  promptRun.PromptID = judgePrompt.ID;
916
+ // We don't have a specific model ID for the judge yet, it will be set by AIPromptRunner
698
917
  promptRun.RunAt = new Date();
699
918
  promptRun.RunType = 'ResultSelector';
700
919
  promptRun.ParentID = parentPromptRunId;
701
920
  promptRun.ExecutionOrder = executionOrder;
921
+ // Store the judge data as JSON in Messages field
702
922
  promptRun.Messages = JSON.stringify({
703
923
  judgeData,
704
924
  candidateCount: Array.isArray(judgeData.candidates)
@@ -708,19 +928,24 @@ class ParallelExecutionCoordinator {
708
928
  const saveResult = await promptRun.Save();
709
929
  if (!saveResult) {
710
930
  const error = `Failed to save ResultSelector AIPromptRun: ${promptRun.LatestResult?.Message || 'Unknown error'}`;
711
- (0, core_1.LogError)(error);
931
+ LogError(error);
712
932
  throw new Error(error);
713
933
  }
714
934
  return promptRun;
715
935
  }
716
936
  catch (error) {
717
- (0, core_1.LogError)(`Error creating result selector prompt run record: ${error.message}`);
937
+ LogError(`Error creating result selector prompt run record: ${error.message}`);
718
938
  throw error;
719
939
  }
720
940
  }
941
+ /**
942
+ * Creates a delay for the specified number of milliseconds.
943
+ *
944
+ * @param ms - Number of milliseconds to delay
945
+ * @returns Promise<void> - Promise that resolves after the delay
946
+ */
721
947
  delay(ms) {
722
948
  return new Promise((resolve) => setTimeout(resolve, ms));
723
949
  }
724
950
  }
725
- exports.ParallelExecutionCoordinator = ParallelExecutionCoordinator;
726
951
  //# sourceMappingURL=ParallelExecutionCoordinator.js.map