@memberjunction/ai-prompts 5.41.0 → 5.42.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.
- package/dist/AIPromptRunner.d.ts +24 -66
- package/dist/AIPromptRunner.d.ts.map +1 -1
- package/dist/AIPromptRunner.js +140 -94
- package/dist/AIPromptRunner.js.map +1 -1
- package/dist/ParallelExecution.d.ts +33 -2
- package/dist/ParallelExecution.d.ts.map +1 -1
- package/dist/ParallelExecutionCoordinator.d.ts +26 -27
- package/dist/ParallelExecutionCoordinator.d.ts.map +1 -1
- package/dist/ParallelExecutionCoordinator.js +72 -97
- package/dist/ParallelExecutionCoordinator.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/AIPromptRunner.d.ts
CHANGED
|
@@ -3,61 +3,8 @@ import { AIModelRunner } from './AIModelRunner.js';
|
|
|
3
3
|
import { AIPromptRunResult } from '@memberjunction/ai-core-plus';
|
|
4
4
|
import { IMetadataProvider } from '@memberjunction/core';
|
|
5
5
|
import { MJAIModelEntityExtended, MJAIPromptEntityExtended, MJAIPromptRunEntityExtended } from "@memberjunction/ai-core-plus";
|
|
6
|
+
import { type IParallelExecutionCoordinator } from './ParallelExecution.js';
|
|
6
7
|
import { TemplateMessageRole, AIPromptParams } from '@memberjunction/ai-core-plus';
|
|
7
|
-
/**
|
|
8
|
-
* Advanced AI Prompt execution engine with comprehensive template support, hierarchical template composition,
|
|
9
|
-
* sophisticated model selection, parallelization, output validation, and execution tracking.
|
|
10
|
-
*
|
|
11
|
-
* ## Core Features
|
|
12
|
-
* - **Template-based prompt generation** using MJ Templates system
|
|
13
|
-
* - **Hierarchical template composition** with depth-first rendering and parallel template processing
|
|
14
|
-
* - **Advanced model selection** strategies (Default, Specific, ByPower)
|
|
15
|
-
* - **Parallel execution** with multiple models and execution groups
|
|
16
|
-
* - **Structured output validation** and type conversion with retry logic
|
|
17
|
-
* - **Comprehensive execution tracking** with agent run linking
|
|
18
|
-
* - **Configuration-driven behavior** with caching and performance optimization
|
|
19
|
-
* - **Real-time progress updates** and streaming response support
|
|
20
|
-
*
|
|
21
|
-
* ## Hierarchical Template Composition
|
|
22
|
-
* When `childPrompts` array is provided in {@link AIPromptParams}:
|
|
23
|
-
* 1. Renders child prompt templates in depth-first manner (children before parents)
|
|
24
|
-
* 2. At each level, renders sibling templates in parallel for optimal performance
|
|
25
|
-
* 3. Recursively handles grandchild templates (unlimited nesting depth)
|
|
26
|
-
* 4. Substitutes rendered child templates into corresponding placeholders in parent template
|
|
27
|
-
* 5. Executes the final composed prompt as a single operation
|
|
28
|
-
*
|
|
29
|
-
* This enables complex prompt composition patterns where templates can be built from reusable
|
|
30
|
-
* sub-templates, creating sophisticated prompts through hierarchical template inheritance.
|
|
31
|
-
*
|
|
32
|
-
* ## Agent Integration
|
|
33
|
-
* - Links executions to agent runs via `agentRunId` parameter
|
|
34
|
-
* - Supports agent decision-making workflows with structured JSON responses
|
|
35
|
-
* - Enables hierarchical template patterns in AI agents
|
|
36
|
-
*
|
|
37
|
-
* @example Basic Usage
|
|
38
|
-
* ```typescript
|
|
39
|
-
* const runner = new AIPromptRunner();
|
|
40
|
-
* const params = new AIPromptParams();
|
|
41
|
-
* params.prompt = aiPrompt;
|
|
42
|
-
* params.data = { key: 'value' };
|
|
43
|
-
* const result = await runner.ExecutePrompt(params);
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @example Hierarchical Template Composition
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const params = new AIPromptParams();
|
|
49
|
-
* params.prompt = parentPrompt;
|
|
50
|
-
* params.childPrompts = [
|
|
51
|
-
* new ChildPromptParam(analysisPrompt, 'analysis'),
|
|
52
|
-
* new ChildPromptParam(summaryPrompt, 'summary'),
|
|
53
|
-
* new ChildPromptParam(complexChild, 'complex') // This can have its own child templates
|
|
54
|
-
* ];
|
|
55
|
-
* params.data = { userInput: 'complex data to process' };
|
|
56
|
-
* const result = await runner.ExecutePrompt(params);
|
|
57
|
-
* // Child templates render first, then parent template uses {{ analysis }}, {{ summary }}, {{ complex }}
|
|
58
|
-
* // Final composed prompt is executed once
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
8
|
/**
|
|
62
9
|
* Represents a model-vendor pair candidate for execution
|
|
63
10
|
*/
|
|
@@ -99,7 +46,7 @@ export declare class AIPromptRunner {
|
|
|
99
46
|
private _metadata;
|
|
100
47
|
private _templateEngine;
|
|
101
48
|
private _executionPlanner;
|
|
102
|
-
private _parallelCoordinator
|
|
49
|
+
private _parallelCoordinator?;
|
|
103
50
|
private _jsonValidator;
|
|
104
51
|
private _modelRunner;
|
|
105
52
|
private _provider;
|
|
@@ -136,6 +83,21 @@ export declare class AIPromptRunner {
|
|
|
136
83
|
get Provider(): IMetadataProvider;
|
|
137
84
|
set Provider(value: IMetadataProvider | null);
|
|
138
85
|
constructor();
|
|
86
|
+
/** ClassFactory key the parallel coordinator self-registers under (see {@link ParallelCoordinator}). */
|
|
87
|
+
private static readonly PARALLEL_COORDINATOR_KEY;
|
|
88
|
+
/**
|
|
89
|
+
* Lazily resolves the parallel execution coordinator.
|
|
90
|
+
*
|
|
91
|
+
* The coordinator is a SUBCLASS of AIPromptRunner — it inherits {@link executeModel} and the rest
|
|
92
|
+
* of the battle-tested execution path so there is a single source of truth for credential / driver
|
|
93
|
+
* / ChatParams / streaming resolution. That subclass relationship means the base cannot statically
|
|
94
|
+
* `new` it without a hard circular import, so we resolve it through the ClassFactory instead (the
|
|
95
|
+
* coordinator self-registers via `@RegisterClass(AIPromptRunner, PARALLEL_COORDINATOR_KEY)`).
|
|
96
|
+
* Created once per runner and reused; the runner's Provider override is propagated. Throws a clear
|
|
97
|
+
* error if the coordinator class was never loaded/registered — otherwise ClassFactory would silently
|
|
98
|
+
* fall back to a plain AIPromptRunner that lacks the parallel methods.
|
|
99
|
+
*/
|
|
100
|
+
protected get ParallelCoordinator(): IParallelExecutionCoordinator;
|
|
139
101
|
/**
|
|
140
102
|
* Access the underlying AIModelRunner for embedding and other non-LLM model calls.
|
|
141
103
|
* Use this when you need tracked embedding execution with AIPromptRun record creation.
|
|
@@ -438,14 +400,6 @@ export declare class AIPromptRunner {
|
|
|
438
400
|
* TypeScript requires instantiating the class to get the getValidCandidates() method.
|
|
439
401
|
*/
|
|
440
402
|
private createSelectionInfo;
|
|
441
|
-
/**
|
|
442
|
-
* Converts model selection info into ModelVendorCandidate array for retry logic.
|
|
443
|
-
* Extracts only the valid candidates (those with available API keys) from the selection info.
|
|
444
|
-
*
|
|
445
|
-
* @param selectionInfo - Model selection information containing considered models
|
|
446
|
-
* @returns Array of valid model-vendor candidates sorted by priority
|
|
447
|
-
*/
|
|
448
|
-
private buildCandidatesFromSelectionInfo;
|
|
449
403
|
/**
|
|
450
404
|
* Enhanced version of selectModelWithAPIKey that tracks all considered models
|
|
451
405
|
* for model selection reporting. Uses the hierarchical credential resolution
|
|
@@ -514,7 +468,7 @@ export declare class AIPromptRunner {
|
|
|
514
468
|
* - updatePromptRunWithFailoverFailure: Records failed failover metadata
|
|
515
469
|
* - createFailoverErrorResult: Creates standardized error response
|
|
516
470
|
*/
|
|
517
|
-
protected executeModelWithFailover(model: MJAIModelEntityExtended, renderedPrompt: string, prompt: MJAIPromptEntityExtended, params: AIPromptParams, vendorId: string | null, conversationMessages?: ChatMessage[], templateMessageRole?: TemplateMessageRole, cancellationToken?: AbortSignal, allCandidates?: ModelVendorCandidate[], promptRun?: MJAIPromptRunEntityExtended, vendorDriverClass?: string, vendorApiName?: string, vendorSupportsEffortLevel?: boolean, modelEffortLevel?: number): Promise<ChatResult>;
|
|
471
|
+
protected executeModelWithFailover(model: MJAIModelEntityExtended, renderedPrompt: string, prompt: MJAIPromptEntityExtended, params: AIPromptParams, vendorId: string | null, conversationMessages?: ChatMessage[], templateMessageRole?: TemplateMessageRole, cancellationToken?: AbortSignal, allCandidates?: ModelVendorCandidate[], promptRun?: MJAIPromptRunEntityExtended, vendorDriverClass?: string, vendorApiName?: string, vendorSupportsEffortLevel?: boolean, modelEffortLevel?: number, credentialAvailability?: Map<string, boolean>): Promise<ChatResult>;
|
|
518
472
|
/**
|
|
519
473
|
* Builds failover candidates for a prompt based on available models and type restrictions
|
|
520
474
|
*/
|
|
@@ -536,9 +490,13 @@ export declare class AIPromptRunner {
|
|
|
536
490
|
*/
|
|
537
491
|
private createFailoverErrorResult;
|
|
538
492
|
/**
|
|
539
|
-
* Executes the AI model with the rendered prompt
|
|
493
|
+
* Executes the AI model with the rendered prompt.
|
|
494
|
+
*
|
|
495
|
+
* `protected` so the parallel coordinator subclass reuses this exact code path — credential
|
|
496
|
+
* resolution, driver selection, ChatParams construction, prefill, media handling, and streaming
|
|
497
|
+
* all live here ONCE. Do not duplicate this logic elsewhere.
|
|
540
498
|
*/
|
|
541
|
-
|
|
499
|
+
protected executeModel(model: MJAIModelEntityExtended, renderedPrompt: string, prompt: MJAIPromptEntityExtended, params: AIPromptParams, vendorId: string | null, conversationMessages?: ChatMessage[], templateMessageRole?: TemplateMessageRole, cancellationToken?: AbortSignal, vendorDriverClass?: string, vendorApiName?: string, vendorSupportsEffortLevel?: boolean, modelEffortLevel?: number): Promise<ChatResult>;
|
|
542
500
|
/**
|
|
543
501
|
* Walks every message in chatParams and rewrites media content blocks
|
|
544
502
|
* (image_url / audio_url / video_url / file_url) into visible text markers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIPromptRunner.d.ts","sourceRoot":"","sources":["../src/AIPromptRunner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAmB,WAAW,EAAqE,MAAM,oBAAoB,CAAC;AACtK,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAqB,iBAAiB,EAAwB,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAmF,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG1I,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"AIPromptRunner.d.ts","sourceRoot":"","sources":["../src/AIPromptRunner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAmB,WAAW,EAAqE,MAAM,oBAAoB,CAAC;AACtK,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAqB,iBAAiB,EAAwB,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAmF,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG1I,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAK9H,OAAO,EAAyB,KAAK,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAIhG,OAAO,EACH,mBAAmB,EAEnB,cAAc,EACjB,MAAM,8BAA8B,CAAC;AAuGtC;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,uBAAuB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,GAAG,sBAAsB,CAAC;CAC5F;AAGD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,0BAA0B,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,iBAAiB,GAAG,sBAAsB,GAAG,kBAAkB,CAAC;IAChF,UAAU,CAAC,EAAE,KAAK,GAAG,aAAa,GAAG,eAAe,GAAG,kBAAkB,CAAC;CAC3E;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAqBD,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,iBAAiB,CAAmB;IAC5C,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,SAAS,CAAkC;IAEnD;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB,CAA4D;IACxF,2GAA2G;IAC3G,OAAO,CAAC,sBAAsB,CAA0B;IAExD;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAA2D;IAEtG;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAgI;IAE5K;;;;OAIG;IACH,IAAW,QAAQ,IAAI,iBAAiB,CAEvC;IACD,IAAW,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAElD;;IAUD,wGAAwG;IACxG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAkC;IAElF;;;;;;;;;;;OAWG;IACH,SAAS,KAAK,mBAAmB,IAAI,6BAA6B,CAiBjE;IAED;;;OAGG;IACH,IAAW,WAAW,IAAI,aAAa,CAEtC;IAED;;;OAGG;IACH,OAAO,CAAC,aAAa;IAUrB;;;;;OAKG;IACH,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,GAAE,OAAe,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI;IAYjG;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE;QAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC/B,MAAM,CAAC,EAAE,wBAAwB,CAAC;QAClC,KAAK,CAAC,EAAE,uBAAuB,CAAC;QAChC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;QAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,IAAI;IAmCR;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,6BAA6B;IAqE3C;;;OAGG;YACW,iCAAiC;IAiC/C;;OAEG;YACW,oBAAoB;IA4DlC;;;OAGG;YACW,qBAAqB;IA0BnC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAUnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,uBAAuB;IAuD/B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACU,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAqM9F;;;;;;;;OAQG;YACW,mBAAmB;IAgIjC;;;;;;;;OAQG;YACW,uBAAuB;IA6QrC;;OAEG;YACW,YAAY;IAkB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,4BAA4B;IAoBpC;;;;;;;OAOG;YACW,0BAA0B;IA8LxC;;;;;;;OAOG;YACW,8BAA8B;IAmE5C;;;;OAIG;YACW,WAAW;IAoLzB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,0BAA0B;IAsBlC;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IAoBvC;;;;;OAKG;IACH,OAAO,CAAC,kCAAkC;IAkD1C;;;;;;;;OAQG;IACH,OAAO,CAAC,oCAAoC;IA6C5C;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;OAGG;IACH,OAAO,CAAC,kCAAkC;IAiC1C;;;OAGG;IACH,OAAO,CAAC,iCAAiC;IAoBzC;;;;OAIG;IACH,OAAO,CAAC,mCAAmC;IA2B3C;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IA4BvC;;OAEG;IACH,OAAO,CAAC,gCAAgC;IA6BxC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IA2CrC;;;;OAIG;IACH,OAAO,CAAC,+BAA+B;IAiCvC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;;OAGG;IACH,OAAO,CAAC,kCAAkC;IAiE1C;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAe/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAwEhC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;;;;;;;;OASG;YACW,4BAA4B;IAiI1C;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAwBhC;;OAEG;IACH;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAoBpC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;OAIG;IACU,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC;YAI5C,eAAe;IAmN7B;;OAEG;YACW,oBAAoB;IAsDlC;;;;;;;;;;;;;;OAcG;cACa,wBAAwB,CACtC,KAAK,EAAE,uBAAuB,EAC9B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,wBAAwB,EAChC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,oBAAoB,CAAC,EAAE,WAAW,EAAE,EACpC,mBAAmB,GAAE,mBAA8B,EACnD,iBAAiB,CAAC,EAAE,WAAW,EAC/B,aAAa,CAAC,EAAE,oBAAoB,EAAE,EACtC,SAAS,CAAC,EAAE,2BAA2B,EACvC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,aAAa,CAAC,EAAE,MAAM,EACtB,yBAAyB,CAAC,EAAE,OAAO,EACnC,gBAAgB,CAAC,EAAE,MAAM,EACzB,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5C,OAAO,CAAC,UAAU,CAAC;IA4LtB;;OAEG;cACa,uBAAuB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IA2B1G;;OAEG;IACH,SAAS,CAAC,0BAA0B,CAAC,MAAM,EAAE,uBAAuB,EAAE,GAAG,oBAAoB,EAAE;IAuC/F;;OAEG;IACH,SAAS,CAAC,kCAAkC,CAC1C,SAAS,EAAE,2BAA2B,EACtC,gBAAgB,EAAE,eAAe,EAAE,EACnC,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,IAAI;IAoBP;;OAEG;IACH,OAAO,CAAC,kCAAkC;IAe1C;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAiCjC;;;;;;OAMG;cACa,YAAY,CAC1B,KAAK,EAAE,uBAAuB,EAC9B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,wBAAwB,EAChC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,oBAAoB,CAAC,EAAE,WAAW,EAAE,EACpC,mBAAmB,GAAE,mBAA8B,EACnD,iBAAiB,CAAC,EAAE,WAAW,EAC/B,iBAAiB,CAAC,EAAE,MAAM,EAC1B,aAAa,CAAC,EAAE,MAAM,EACtB,yBAAyB,CAAC,EAAE,OAAO,EACnC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,UAAU,CAAC;IAsMtB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,2BAA2B;IA0DnC;;;;;;OAMG;IACH,OAAO,CAAC,mCAAmC;IA0C3C;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAc9B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+C9B,OAAO,CAAC,iBAAiB;IAqCzB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAoJ;IAEpM;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAsB;IAEtE;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,sBAAsB;IA0B9B;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IA0BlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA4C7B;;OAEG;YACW,4BAA4B;IA8L1C;;OAEG;IACH;;;OAGG;IACH,OAAO,CAAC,mBAAmB;YA8Bb,eAAe;IAO7B;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IA6C9B;;;OAGG;YACW,oBAAoB;IAmDlC;;;;;OAKG;YACW,oBAAoB;IAsFlC;;;OAGG;YACW,yBAAyB;IAoDvC;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAuBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA2CjC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+B9B;;;;;;;;;OASG;YACW,8BAA8B;IAmI5C;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAcvB;;;;;;;;;;;OAWG;YACW,iBAAiB;IAsC/B;;;;;;;;OAQG;YACW,iBAAiB;IA8G/B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;OAEG;YACW,qBAAqB;IAkGnC;;OAEG;YACW,eAAe;IAoP7B;;;;;;;OAOG;IAIH;;;;;;;;;;OAUG;IACH,SAAS,CAAC,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,qBAAqB;IAU3F;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,qBAAqB,CAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,qBAAqB,EAC7B,aAAa,EAAE,MAAM,GACpB,OAAO;IA6BV;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,sBAAsB,CAC9B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GACvB,MAAM;IAaT;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,wBAAwB,CAChC,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,EAC3C,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,EACrD,aAAa,EAAE,oBAAoB,EAAE,EACrC,cAAc,EAAE,eAAe,EAAE,GAChC,oBAAoB,EAAE;IAgIzB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,kBAAkB,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,OAAO,GACjB,IAAI;CA6BR"}
|
package/dist/AIPromptRunner.js
CHANGED
|
@@ -6,7 +6,6 @@ import { CleanJSON, MJGlobal, JSONValidator, ValidationResult, ValidationErrorIn
|
|
|
6
6
|
import { CredentialEngine } from '@memberjunction/credentials';
|
|
7
7
|
import { TemplateEngineServer } from '@memberjunction/templates';
|
|
8
8
|
import { ExecutionPlanner } from './ExecutionPlanner.js';
|
|
9
|
-
import { ParallelExecutionCoordinator } from './ParallelExecutionCoordinator.js';
|
|
10
9
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
11
10
|
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
12
11
|
import { SystemPlaceholderManager } from '@memberjunction/ai-core-plus';
|
|
@@ -67,10 +66,36 @@ export class AIPromptRunner {
|
|
|
67
66
|
this._metadata = this._provider ?? new Metadata();
|
|
68
67
|
this._templateEngine = TemplateEngineServer.Instance;
|
|
69
68
|
this._executionPlanner = new ExecutionPlanner();
|
|
70
|
-
this._parallelCoordinator = new ParallelExecutionCoordinator();
|
|
71
69
|
this._jsonValidator = new JSONValidator();
|
|
72
70
|
this._modelRunner = new AIModelRunner();
|
|
73
71
|
}
|
|
72
|
+
/** ClassFactory key the parallel coordinator self-registers under (see {@link ParallelCoordinator}). */
|
|
73
|
+
static { this.PARALLEL_COORDINATOR_KEY = 'ParallelExecutionCoordinator'; }
|
|
74
|
+
/**
|
|
75
|
+
* Lazily resolves the parallel execution coordinator.
|
|
76
|
+
*
|
|
77
|
+
* The coordinator is a SUBCLASS of AIPromptRunner — it inherits {@link executeModel} and the rest
|
|
78
|
+
* of the battle-tested execution path so there is a single source of truth for credential / driver
|
|
79
|
+
* / ChatParams / streaming resolution. That subclass relationship means the base cannot statically
|
|
80
|
+
* `new` it without a hard circular import, so we resolve it through the ClassFactory instead (the
|
|
81
|
+
* coordinator self-registers via `@RegisterClass(AIPromptRunner, PARALLEL_COORDINATOR_KEY)`).
|
|
82
|
+
* Created once per runner and reused; the runner's Provider override is propagated. Throws a clear
|
|
83
|
+
* error if the coordinator class was never loaded/registered — otherwise ClassFactory would silently
|
|
84
|
+
* fall back to a plain AIPromptRunner that lacks the parallel methods.
|
|
85
|
+
*/
|
|
86
|
+
get ParallelCoordinator() {
|
|
87
|
+
if (!this._parallelCoordinator) {
|
|
88
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(AIPromptRunner, AIPromptRunner.PARALLEL_COORDINATOR_KEY);
|
|
89
|
+
if (!instance || typeof instance.executeTasksInParallel !== 'function') {
|
|
90
|
+
throw new Error(`ParallelExecutionCoordinator is not registered with the ClassFactory. Ensure ` +
|
|
91
|
+
`'@memberjunction/ai-prompts' is fully loaded (it is exported from the package index and ` +
|
|
92
|
+
`picked up by the class-registration manifest).`);
|
|
93
|
+
}
|
|
94
|
+
instance.Provider = this.Provider;
|
|
95
|
+
this._parallelCoordinator = instance;
|
|
96
|
+
}
|
|
97
|
+
return this._parallelCoordinator;
|
|
98
|
+
}
|
|
74
99
|
/**
|
|
75
100
|
* Access the underlying AIModelRunner for embedding and other non-LLM model calls.
|
|
76
101
|
* Use this when you need tracked embedding execution with AIPromptRun record creation.
|
|
@@ -457,25 +482,20 @@ export class AIPromptRunner {
|
|
|
457
482
|
let renderedPromptText = '';
|
|
458
483
|
// For hierarchical prompts, we need to create the parent prompt run first to get its ID
|
|
459
484
|
let parentPromptRun;
|
|
460
|
-
let selectedModel;
|
|
461
485
|
let childTemplateRenderingResult;
|
|
462
|
-
let
|
|
486
|
+
let selection;
|
|
463
487
|
// Handle different prompt execution modes
|
|
464
488
|
if (params.childPrompts && params.childPrompts.length > 0) {
|
|
465
489
|
// Hierarchical template composition mode - render child templates first, then compose
|
|
466
|
-
//this.logStatus(`🌳 Composing prompt with ${params.childPrompts.length} child templates in hierarchical mode`, true, params);
|
|
467
490
|
// Determine which prompt to use for model selection
|
|
468
491
|
let modelSelectionPrompt = prompt;
|
|
469
492
|
if (params.modelSelectionPrompt) {
|
|
470
493
|
modelSelectionPrompt = params.modelSelectionPrompt;
|
|
471
|
-
//this.logStatus(`🎯 Using prompt "${modelSelectionPrompt.Name}" for model selection instead of parent prompt`, true, params);
|
|
472
494
|
}
|
|
473
|
-
// Select model using the appropriate prompt
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (!selectedModel) {
|
|
478
|
-
throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
|
|
495
|
+
// Select model using the appropriate prompt — capture the FULL result
|
|
496
|
+
selection = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
|
|
497
|
+
if (!selection.model) {
|
|
498
|
+
throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, selection.selectionInfo));
|
|
479
499
|
}
|
|
480
500
|
// Check if we have a system prompt override
|
|
481
501
|
if (params.systemPromptOverride) {
|
|
@@ -490,7 +510,7 @@ export class AIPromptRunner {
|
|
|
490
510
|
renderedPromptText = await this.renderPromptWithChildTemplates(prompt, params, childTemplateRenderingResult.renderedTemplates);
|
|
491
511
|
}
|
|
492
512
|
// Create parent prompt run for the final composed prompt execution
|
|
493
|
-
parentPromptRun = await this.createPromptRun(prompt,
|
|
513
|
+
parentPromptRun = await this.createPromptRun(prompt, selection.model, params, renderedPromptText, startTime, params.override?.vendorId, selection.selectionInfo);
|
|
494
514
|
}
|
|
495
515
|
else if (prompt.TemplateID && (!params.conversationMessages || params.templateMessageRole !== 'none')) {
|
|
496
516
|
// Check if we have a system prompt override
|
|
@@ -520,30 +540,28 @@ export class AIPromptRunner {
|
|
|
520
540
|
if (params.cancellationToken?.aborted) {
|
|
521
541
|
throw new Error('Prompt execution was cancelled during template rendering');
|
|
522
542
|
}
|
|
523
|
-
// If no model was selected yet (
|
|
524
|
-
if (!
|
|
543
|
+
// If no model was selected yet (non-hierarchical case), select one now — capture the FULL result
|
|
544
|
+
if (!selection?.model) {
|
|
525
545
|
let modelSelectionPrompt = prompt;
|
|
526
546
|
if (params.modelSelectionPrompt) {
|
|
527
547
|
modelSelectionPrompt = params.modelSelectionPrompt;
|
|
528
548
|
this.logStatus(`🎯 Using prompt "${modelSelectionPrompt.Name}" for model selection instead of main prompt`, true, params);
|
|
529
549
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (!selectedModel) {
|
|
534
|
-
throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
|
|
550
|
+
selection = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
|
|
551
|
+
if (!selection.model) {
|
|
552
|
+
throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, selection.selectionInfo));
|
|
535
553
|
}
|
|
536
554
|
}
|
|
537
555
|
// Check if we need parallel execution based on ParallelizationMode
|
|
538
556
|
const shouldUseParallelExecution = prompt.ParallelizationMode && prompt.ParallelizationMode !== 'None';
|
|
539
557
|
let result;
|
|
540
558
|
if (shouldUseParallelExecution) {
|
|
541
|
-
// Use parallel execution path
|
|
542
|
-
result = await this.executePromptInParallel(prompt, renderedPromptText, params, startTime, parentPromptRun,
|
|
559
|
+
// Use parallel execution path — pass full selection through
|
|
560
|
+
result = await this.executePromptInParallel(prompt, renderedPromptText, params, startTime, parentPromptRun, selection);
|
|
543
561
|
}
|
|
544
562
|
else {
|
|
545
|
-
// Use traditional single execution path
|
|
546
|
-
result = await this.executeSinglePrompt(prompt, renderedPromptText, params, startTime, parentPromptRun,
|
|
563
|
+
// Use traditional single execution path — pass full selection through
|
|
564
|
+
result = await this.executeSinglePrompt(prompt, renderedPromptText, params, startTime, parentPromptRun, selection);
|
|
547
565
|
}
|
|
548
566
|
// Note: With template composition, we only execute once so no rollup calculations needed
|
|
549
567
|
// The final composed prompt is executed as a single operation
|
|
@@ -613,33 +631,22 @@ export class AIPromptRunner {
|
|
|
613
631
|
* @param startTime - Execution start time
|
|
614
632
|
* @returns Promise<AIPromptRunResult<T>> - The execution result
|
|
615
633
|
*/
|
|
616
|
-
async executeSinglePrompt(prompt, renderedPromptText, params, startTime, existingPromptRun,
|
|
634
|
+
async executeSinglePrompt(prompt, renderedPromptText, params, startTime, existingPromptRun, existingSelection) {
|
|
617
635
|
// Check for cancellation before model selection
|
|
618
636
|
if (params.cancellationToken?.aborted) {
|
|
619
637
|
throw new Error('Prompt execution was cancelled before model selection');
|
|
620
638
|
}
|
|
621
|
-
// Use existing
|
|
622
|
-
let selectedModel =
|
|
623
|
-
let modelSelectionInfo =
|
|
624
|
-
let vendorDriverClass;
|
|
625
|
-
let vendorApiName;
|
|
626
|
-
let vendorSupportsEffortLevel;
|
|
627
|
-
let modelEffortLevel;
|
|
628
|
-
let allCandidates = [];
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const modelID = modelSelectionInfo.modelSelected.ID;
|
|
633
|
-
const modelVendor = AIEngine.Instance.ModelVendorsByModelID.get(NormalizeUUID(modelID))
|
|
634
|
-
?.find(mv => UUIDsEqual(mv.VendorID, vendorID));
|
|
635
|
-
if (modelVendor) {
|
|
636
|
-
vendorDriverClass = modelVendor.DriverClass;
|
|
637
|
-
vendorApiName = modelVendor.APIName;
|
|
638
|
-
vendorSupportsEffortLevel = modelVendor.SupportsEffortLevel;
|
|
639
|
-
}
|
|
640
|
-
// Extract valid candidates from selection info for retry logic
|
|
641
|
-
allCandidates = this.buildCandidatesFromSelectionInfo(modelSelectionInfo);
|
|
642
|
-
}
|
|
639
|
+
// Use existing selection if provided (hierarchical case) or select now
|
|
640
|
+
let selectedModel = existingSelection?.model ?? undefined;
|
|
641
|
+
let modelSelectionInfo = existingSelection?.selectionInfo;
|
|
642
|
+
let vendorDriverClass = existingSelection?.vendorDriverClass;
|
|
643
|
+
let vendorApiName = existingSelection?.vendorApiName;
|
|
644
|
+
let vendorSupportsEffortLevel = existingSelection?.vendorSupportsEffortLevel;
|
|
645
|
+
let modelEffortLevel = existingSelection?.modelEffortLevel;
|
|
646
|
+
let allCandidates = existingSelection?.allCandidates ?? [];
|
|
647
|
+
// Credential probes already done during selection — reused by failover so it doesn't
|
|
648
|
+
// recompute hasCredentialsAvailable for the prefix it walks before the selected candidate.
|
|
649
|
+
let credentialAvailability = existingSelection?.credentialAvailability;
|
|
643
650
|
if (!selectedModel) {
|
|
644
651
|
// Determine which prompt to use for model selection
|
|
645
652
|
let modelSelectionPrompt = prompt;
|
|
@@ -655,6 +662,7 @@ export class AIPromptRunner {
|
|
|
655
662
|
modelEffortLevel = modelResult.modelEffortLevel;
|
|
656
663
|
modelSelectionInfo = modelResult.selectionInfo;
|
|
657
664
|
allCandidates = modelResult.allCandidates || [];
|
|
665
|
+
credentialAvailability = modelResult.credentialAvailability;
|
|
658
666
|
if (!selectedModel) {
|
|
659
667
|
throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
|
|
660
668
|
}
|
|
@@ -670,7 +678,8 @@ export class AIPromptRunner {
|
|
|
670
678
|
throw new Error('Prompt execution was cancelled before model execution');
|
|
671
679
|
}
|
|
672
680
|
// Execute with retry logic for validation failures
|
|
673
|
-
const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel // Pass model-specific effort level
|
|
681
|
+
const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, // Pass model-specific effort level
|
|
682
|
+
credentialAvailability // Reuse credential probes from selection
|
|
674
683
|
);
|
|
675
684
|
// Calculate execution metrics
|
|
676
685
|
const endTime = new Date();
|
|
@@ -730,7 +739,7 @@ export class AIPromptRunner {
|
|
|
730
739
|
* @param startTime - Execution start time
|
|
731
740
|
* @returns Promise<AIPromptRunResult<T>> - The aggregated execution result
|
|
732
741
|
*/
|
|
733
|
-
async executePromptInParallel(prompt, renderedPromptText, params, startTime, existingPromptRun,
|
|
742
|
+
async executePromptInParallel(prompt, renderedPromptText, params, startTime, existingPromptRun, existingSelection) {
|
|
734
743
|
// Check for cancellation before starting parallel execution
|
|
735
744
|
if (params.cancellationToken?.aborted) {
|
|
736
745
|
throw new Error('Parallel execution was cancelled before starting');
|
|
@@ -738,21 +747,21 @@ export class AIPromptRunner {
|
|
|
738
747
|
// Load AI Engine to get models and prompt models
|
|
739
748
|
await AIEngine.Instance.Config(false, params.contextUser);
|
|
740
749
|
let executionTasks;
|
|
741
|
-
// If a model is already selected (from hierarchical template composition),
|
|
750
|
+
// If a model is already selected (from hierarchical template composition),
|
|
742
751
|
// create a single task with that model instead of using the planner
|
|
743
|
-
if (
|
|
752
|
+
if (existingSelection?.model) {
|
|
744
753
|
// Create a single execution task with the pre-selected model
|
|
745
754
|
executionTasks = [{
|
|
746
755
|
taskId: 'pre-selected',
|
|
747
|
-
model:
|
|
748
|
-
vendorDriverClass:
|
|
749
|
-
vendorApiName:
|
|
756
|
+
model: existingSelection.model,
|
|
757
|
+
vendorDriverClass: existingSelection.vendorDriverClass,
|
|
758
|
+
vendorApiName: existingSelection.vendorApiName,
|
|
750
759
|
messages: params.conversationMessages || [],
|
|
751
760
|
promptText: renderedPromptText,
|
|
752
761
|
templateMessageRole: params.templateMessageRole || 'system',
|
|
753
762
|
contextUser: params.contextUser
|
|
754
763
|
}];
|
|
755
|
-
this.logStatus(` Using pre-selected model "${
|
|
764
|
+
this.logStatus(` Using pre-selected model "${existingSelection.model.Name}" for parallel execution`, true, params);
|
|
756
765
|
}
|
|
757
766
|
else {
|
|
758
767
|
// Normal parallel execution path - let the planner decide
|
|
@@ -777,7 +786,7 @@ export class AIPromptRunner {
|
|
|
777
786
|
throw new Error('Parallel execution was cancelled before task execution');
|
|
778
787
|
}
|
|
779
788
|
// Execute tasks in parallel
|
|
780
|
-
const parallelResult = await this.
|
|
789
|
+
const parallelResult = await this.ParallelCoordinator.executeTasksInParallel(params, executionTasks, undefined, undefined, params.cancellationToken, undefined, params.agentRunId);
|
|
781
790
|
if (!parallelResult.success) {
|
|
782
791
|
throw new Error(`Parallel execution failed: ${parallelResult.errors.join(', ')}`);
|
|
783
792
|
}
|
|
@@ -793,7 +802,7 @@ export class AIPromptRunner {
|
|
|
793
802
|
method: 'PromptSelector',
|
|
794
803
|
selectorPromptId: prompt.ResultSelectorPromptID,
|
|
795
804
|
};
|
|
796
|
-
const aiSelectedResult = await this.
|
|
805
|
+
const aiSelectedResult = await this.ParallelCoordinator.selectBestResult(successfulResults, selectionConfig, undefined, params.cancellationToken);
|
|
797
806
|
if (aiSelectedResult) {
|
|
798
807
|
selectedResult = aiSelectedResult;
|
|
799
808
|
}
|
|
@@ -822,7 +831,7 @@ export class AIPromptRunner {
|
|
|
822
831
|
}
|
|
823
832
|
// Use existing prompt run if provided (hierarchical case) or create new one
|
|
824
833
|
// Use the model selection info if provided (from hierarchical execution)
|
|
825
|
-
const consolidatedPromptRun = existingPromptRun || await this.createPromptRun(prompt, selectedResult.task.model, params, renderedPromptText, startTime, params.override?.vendorId,
|
|
834
|
+
const consolidatedPromptRun = existingPromptRun || await this.createPromptRun(prompt, selectedResult.task.model, params, renderedPromptText, startTime, params.override?.vendorId, existingSelection?.selectionInfo);
|
|
826
835
|
// Update with parallel execution metadata
|
|
827
836
|
const endTime = new Date();
|
|
828
837
|
consolidatedPromptRun.CompletedAt = endTime;
|
|
@@ -944,11 +953,11 @@ export class AIPromptRunner {
|
|
|
944
953
|
modelInfo: {
|
|
945
954
|
modelId: selectedResult.task.model.ID,
|
|
946
955
|
modelName: selectedResult.task.model.Name,
|
|
947
|
-
vendorId:
|
|
956
|
+
vendorId: existingSelection?.selectionInfo?.vendorSelected?.ID, // VendorID not directly available on AIModel
|
|
948
957
|
vendorName: selectedResult.task.model.Vendor,
|
|
949
958
|
},
|
|
950
959
|
judgeMetadata: selectedResult.judgeMetadata,
|
|
951
|
-
modelSelectionInfo:
|
|
960
|
+
modelSelectionInfo: existingSelection?.selectionInfo, // Include model selection info if provided
|
|
952
961
|
};
|
|
953
962
|
}
|
|
954
963
|
/**
|
|
@@ -1275,7 +1284,7 @@ export class AIPromptRunner {
|
|
|
1275
1284
|
// });
|
|
1276
1285
|
// }
|
|
1277
1286
|
// Select the first candidate with available credentials and track all attempts
|
|
1278
|
-
const { selected, consideredModels } = await this.selectModelWithAPIKeyTracked(candidates, prompt.ID, params);
|
|
1287
|
+
const { selected, consideredModels, credentialAvailability } = await this.selectModelWithAPIKeyTracked(candidates, prompt.ID, params);
|
|
1279
1288
|
// Merge considered models into our tracking
|
|
1280
1289
|
modelsConsidered.push(...consideredModels);
|
|
1281
1290
|
if (!selected) {
|
|
@@ -1287,6 +1296,7 @@ export class AIPromptRunner {
|
|
|
1287
1296
|
vendorSupportsEffortLevel: undefined,
|
|
1288
1297
|
modelEffortLevel: undefined,
|
|
1289
1298
|
allCandidates: candidates,
|
|
1299
|
+
credentialAvailability,
|
|
1290
1300
|
selectionInfo: this.createSelectionInfo({
|
|
1291
1301
|
aiConfiguration: configuration,
|
|
1292
1302
|
modelsConsidered,
|
|
@@ -1331,6 +1341,7 @@ export class AIPromptRunner {
|
|
|
1331
1341
|
vendorSupportsEffortLevel: selected.supportsEffortLevel,
|
|
1332
1342
|
modelEffortLevel: selected.effortLevel, // Pass through model-specific effort level
|
|
1333
1343
|
allCandidates: candidates,
|
|
1344
|
+
credentialAvailability,
|
|
1334
1345
|
selectionInfo: this.createSelectionInfo({
|
|
1335
1346
|
aiConfiguration: configuration,
|
|
1336
1347
|
modelsConsidered,
|
|
@@ -1879,33 +1890,6 @@ export class AIPromptRunner {
|
|
|
1879
1890
|
Object.assign(info, data);
|
|
1880
1891
|
return info;
|
|
1881
1892
|
}
|
|
1882
|
-
/**
|
|
1883
|
-
* Converts model selection info into ModelVendorCandidate array for retry logic.
|
|
1884
|
-
* Extracts only the valid candidates (those with available API keys) from the selection info.
|
|
1885
|
-
*
|
|
1886
|
-
* @param selectionInfo - Model selection information containing considered models
|
|
1887
|
-
* @returns Array of valid model-vendor candidates sorted by priority
|
|
1888
|
-
*/
|
|
1889
|
-
buildCandidatesFromSelectionInfo(selectionInfo) {
|
|
1890
|
-
const validModels = selectionInfo.extractValidCandidates();
|
|
1891
|
-
return validModels.map(considered => {
|
|
1892
|
-
// Find matching model vendor for driver and API info
|
|
1893
|
-
const modelVendor = considered.vendor
|
|
1894
|
-
? considered.model.ModelVendors.find(mv => UUIDsEqual(mv.VendorID, considered.vendor.ID))
|
|
1895
|
-
: undefined;
|
|
1896
|
-
return {
|
|
1897
|
-
model: considered.model,
|
|
1898
|
-
vendorId: considered.vendor?.ID,
|
|
1899
|
-
vendorName: considered.vendor?.Name,
|
|
1900
|
-
driverClass: modelVendor?.DriverClass || considered.model.DriverClass,
|
|
1901
|
-
apiName: modelVendor?.APIName || considered.model.APIName,
|
|
1902
|
-
supportsEffortLevel: modelVendor?.SupportsEffortLevel ?? considered.model.SupportsEffortLevel ?? false,
|
|
1903
|
-
isPreferredVendor: false, // Can't determine from selection info alone
|
|
1904
|
-
priority: considered.priority,
|
|
1905
|
-
source: (selectionInfo.selectionStrategy === 'ByPower' ? 'power-rank' : 'model-type')
|
|
1906
|
-
};
|
|
1907
|
-
}).sort((a, b) => b.priority - a.priority); // Sort by priority descending
|
|
1908
|
-
}
|
|
1909
1893
|
/**
|
|
1910
1894
|
* Enhanced version of selectModelWithAPIKey that tracks all considered models
|
|
1911
1895
|
* for model selection reporting. Uses the hierarchical credential resolution
|
|
@@ -1997,7 +1981,7 @@ export class AIPromptRunner {
|
|
|
1997
1981
|
maxErrorLength: params?.maxErrorLength
|
|
1998
1982
|
});
|
|
1999
1983
|
}
|
|
2000
|
-
return { selected: selectedCandidate, consideredModels };
|
|
1984
|
+
return { selected: selectedCandidate, consideredModels, credentialAvailability: credentialCache };
|
|
2001
1985
|
}
|
|
2002
1986
|
/**
|
|
2003
1987
|
* Builds a descriptive error message when no model could be selected for a prompt.
|
|
@@ -2110,7 +2094,7 @@ export class AIPromptRunner {
|
|
|
2110
2094
|
promptRun.Status = 'Running';
|
|
2111
2095
|
promptRun.Cancelled = false;
|
|
2112
2096
|
promptRun.CacheHit = false;
|
|
2113
|
-
promptRun.StreamingEnabled =
|
|
2097
|
+
promptRun.StreamingEnabled = !!params.onStreaming;
|
|
2114
2098
|
promptRun.WasSelectedResult = false;
|
|
2115
2099
|
// Set model selection tracking fields
|
|
2116
2100
|
if (modelSelectionInfo) {
|
|
@@ -2353,7 +2337,7 @@ export class AIPromptRunner {
|
|
|
2353
2337
|
* - updatePromptRunWithFailoverFailure: Records failed failover metadata
|
|
2354
2338
|
* - createFailoverErrorResult: Creates standardized error response
|
|
2355
2339
|
*/
|
|
2356
|
-
async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
|
|
2340
|
+
async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability) {
|
|
2357
2341
|
// Get failover configuration (used for errorScope filtering)
|
|
2358
2342
|
const failoverConfig = this.getFailoverConfiguration(prompt);
|
|
2359
2343
|
// If no candidates provided or failover disabled, execute normally with first model
|
|
@@ -2363,10 +2347,45 @@ export class AIPromptRunner {
|
|
|
2363
2347
|
// Track failover attempts
|
|
2364
2348
|
const failoverAttempts = [];
|
|
2365
2349
|
let lastError = null;
|
|
2350
|
+
// Cache credential availability per driver:model:vendor for the duration of this failover
|
|
2351
|
+
// scan so we don't repeat env-var / binding lookups while walking the candidate list.
|
|
2352
|
+
//
|
|
2353
|
+
// PERF: seed it with the probes model SELECTION already performed (same key format). Selection
|
|
2354
|
+
// walks the priority list until it finds the first credentialed candidate, so this map holds
|
|
2355
|
+
// the prefix it rejected (known false) PLUS the selected candidate (known true) — which is
|
|
2356
|
+
// exactly the segment failover re-walks on the happy path. Reusing those results means the
|
|
2357
|
+
// common case (and any caller looping failover) does ZERO redundant hasCredentialsAvailable
|
|
2358
|
+
// calls. The not-evaluated tail is intentionally absent, so failover still lazily probes it
|
|
2359
|
+
// only if a real failure forces it to walk down there.
|
|
2360
|
+
const failoverCredentialCache = credentialAvailability
|
|
2361
|
+
? new Map(credentialAvailability)
|
|
2362
|
+
: new Map();
|
|
2363
|
+
const candidateHasCredentials = (c) => {
|
|
2364
|
+
const key = `${c.driverClass}:${c.model.ID}:${c.vendorId || 'default'}`;
|
|
2365
|
+
let has = failoverCredentialCache.get(key);
|
|
2366
|
+
if (has === undefined) {
|
|
2367
|
+
has = this.hasCredentialsAvailable(c.driverClass, prompt.ID, c.model.ID, c.vendorId, params);
|
|
2368
|
+
failoverCredentialCache.set(key, has);
|
|
2369
|
+
}
|
|
2370
|
+
return has;
|
|
2371
|
+
};
|
|
2372
|
+
let skippedForCredentials = 0;
|
|
2366
2373
|
// Iterate through all candidates in priority order with instant failover
|
|
2367
2374
|
for (let i = 0; i < allCandidates.length; i++) {
|
|
2368
2375
|
const candidate = allCandidates[i];
|
|
2369
2376
|
const attemptStartTime = Date.now();
|
|
2377
|
+
// Skip candidates with no credentials configured. `allCandidates` is intentionally the
|
|
2378
|
+
// FULL priority-ordered list (see the DECISION note in selectModelWithAPIKeyTracked),
|
|
2379
|
+
// so it can include vendors that have no API key in this environment. Firing a live
|
|
2380
|
+
// request at one of those produces a misleading "401 invalid API key" — and because an
|
|
2381
|
+
// Authentication error is treated as fatal, it would halt failover before any
|
|
2382
|
+
// credentialed candidate is ever reached. Skipping here makes failover land on the
|
|
2383
|
+
// first candidate that can actually authenticate (mirroring model selection's own
|
|
2384
|
+
// highest-priority-with-credentials rule).
|
|
2385
|
+
if (!candidateHasCredentials(candidate)) {
|
|
2386
|
+
skippedForCredentials++;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2370
2389
|
try {
|
|
2371
2390
|
// Log the attempt if not the first one
|
|
2372
2391
|
if (i > 0) {
|
|
@@ -2435,6 +2454,11 @@ export class AIPromptRunner {
|
|
|
2435
2454
|
if (promptRun && failoverAttempts.length > 0) {
|
|
2436
2455
|
this.updatePromptRunWithFailoverFailure(promptRun, failoverAttempts);
|
|
2437
2456
|
}
|
|
2457
|
+
// If every candidate was skipped for missing credentials we never attempted a call and
|
|
2458
|
+
// have no underlying error to report — surface an actionable message instead of null.
|
|
2459
|
+
if (!lastError && failoverAttempts.length === 0 && skippedForCredentials > 0) {
|
|
2460
|
+
lastError = new Error(`No API credentials configured for any of the ${skippedForCredentials} candidate model-vendor combination(s) for prompt "${prompt.Name}".`);
|
|
2461
|
+
}
|
|
2438
2462
|
return this.createFailoverErrorResult(lastError, failoverAttempts);
|
|
2439
2463
|
}
|
|
2440
2464
|
/**
|
|
@@ -2570,7 +2594,11 @@ export class AIPromptRunner {
|
|
|
2570
2594
|
};
|
|
2571
2595
|
}
|
|
2572
2596
|
/**
|
|
2573
|
-
* Executes the AI model with the rendered prompt
|
|
2597
|
+
* Executes the AI model with the rendered prompt.
|
|
2598
|
+
*
|
|
2599
|
+
* `protected` so the parallel coordinator subclass reuses this exact code path — credential
|
|
2600
|
+
* resolution, driver selection, ChatParams construction, prefill, media handling, and streaming
|
|
2601
|
+
* all live here ONCE. Do not duplicate this logic elsewhere.
|
|
2574
2602
|
*/
|
|
2575
2603
|
async executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
|
|
2576
2604
|
// define these variables here to ensure they're available in the catch block
|
|
@@ -2712,6 +2740,17 @@ export class AIPromptRunner {
|
|
|
2712
2740
|
this.stripUnsupportedMediaBlocks(llm, chatParams, model, verbose, params);
|
|
2713
2741
|
// Apply assistant prefill (native or fallback) based on prompt config and provider support
|
|
2714
2742
|
this.applyAssistantPrefill(chatParams, prompt, model, vendorId, llm);
|
|
2743
|
+
// Streaming: wire the prompt-level onStreaming callback into the LLM call. This is the SINGLE
|
|
2744
|
+
// place streaming is configured for prompt execution, so the single-model path and the parallel
|
|
2745
|
+
// path (which bridges its per-task callbacks into params.onStreaming) stream through identical
|
|
2746
|
+
// code — no second streaming implementation that can drift.
|
|
2747
|
+
if (params.onStreaming) {
|
|
2748
|
+
const onStreaming = params.onStreaming;
|
|
2749
|
+
chatParams.streaming = true;
|
|
2750
|
+
chatParams.streamingCallbacks = {
|
|
2751
|
+
OnContent: (chunk, isComplete) => onStreaming({ content: chunk, isComplete, modelName: model.Name }),
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2715
2754
|
// Execute the model with cancellation support
|
|
2716
2755
|
if (cancellationToken) {
|
|
2717
2756
|
// If cancellation token is provided, wrap the execution to handle cancellation
|
|
@@ -3091,7 +3130,7 @@ export class AIPromptRunner {
|
|
|
3091
3130
|
/**
|
|
3092
3131
|
* Executes the model with retry logic for validation failures
|
|
3093
3132
|
*/
|
|
3094
|
-
async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
|
|
3133
|
+
async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability) {
|
|
3095
3134
|
const validationAttempts = [];
|
|
3096
3135
|
const maxRetries = Math.max(0, prompt.MaxRetries || 0);
|
|
3097
3136
|
let lastError = null;
|
|
@@ -3111,7 +3150,8 @@ export class AIPromptRunner {
|
|
|
3111
3150
|
}
|
|
3112
3151
|
// Execute the AI model with failover support
|
|
3113
3152
|
const modelResult = await this.executeModelWithFailover(selectedModel, renderedPromptText, prompt, params, promptRun.VendorID, params.conversationMessages, params.templateMessageRole || 'system', params.cancellationToken, allCandidates, // Pass the candidates from initial selection
|
|
3114
|
-
promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel
|
|
3153
|
+
promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability // Reuse credential probes from selection
|
|
3154
|
+
);
|
|
3115
3155
|
// Check for fatal errors - don't attempt validation/retry on these
|
|
3116
3156
|
// Fatal errors (like ContextLengthExceeded when all models exhausted) cannot be resolved by retrying
|
|
3117
3157
|
if (!modelResult.success && modelResult.errorInfo?.severity === 'Fatal') {
|
|
@@ -3983,6 +4023,12 @@ export class AIPromptRunner {
|
|
|
3983
4023
|
*/
|
|
3984
4024
|
async updatePromptRun(promptRun, prompt, modelResult, parsedResult, endTime, executionTimeMS, validationAttempts, cumulativeTokens) {
|
|
3985
4025
|
try {
|
|
4026
|
+
// Ensure the initial 'Running' INSERT (and its BaseEntity.finalizeSave post-save reload, which does
|
|
4027
|
+
// init() + SetMany(insertedRow)) has fully landed BEFORE we mutate the final state. Mutating while the
|
|
4028
|
+
// INSERT is still in flight lets the reload revert these values, and the chained UPDATE then persists
|
|
4029
|
+
// the stale 'Running' row (the same race fixed in the agent-run-step queue and action-execution-log).
|
|
4030
|
+
// The model call between create and update almost always covers this; a fast-failing prompt could not.
|
|
4031
|
+
await this._promptRunSaveChains.get(promptRun);
|
|
3986
4032
|
promptRun.CompletedAt = endTime;
|
|
3987
4033
|
promptRun.ExecutionTimeMS = executionTimeMS;
|
|
3988
4034
|
// Determine what to save as the result
|