@memberjunction/ai-prompts 2.44.0 → 2.45.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/README.md +937 -9
- package/dist/AIPromptCategoryExtended.d.ts +1 -1
- package/dist/AIPromptCategoryExtended.d.ts.map +1 -1
- package/dist/AIPromptCategoryExtended.js +1 -1
- package/dist/AIPromptCategoryExtended.js.map +1 -1
- package/dist/AIPromptRunner.d.ts +83 -5
- package/dist/AIPromptRunner.d.ts.map +1 -1
- package/dist/AIPromptRunner.js +561 -60
- package/dist/AIPromptRunner.js.map +1 -1
- package/dist/ExecutionPlanner.d.ts +6 -4
- package/dist/ExecutionPlanner.d.ts.map +1 -1
- package/dist/ExecutionPlanner.js +39 -36
- package/dist/ExecutionPlanner.js.map +1 -1
- package/dist/ParallelExecution.d.ts +96 -6
- package/dist/ParallelExecution.d.ts.map +1 -1
- package/dist/ParallelExecutionCoordinator.d.ts +22 -3
- package/dist/ParallelExecutionCoordinator.d.ts.map +1 -1
- package/dist/ParallelExecutionCoordinator.js +490 -37
- package/dist/ParallelExecutionCoordinator.js.map +1 -1
- package/package.json +8 -7
package/dist/AIPromptRunner.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.LoadAIPromptRunner = exports.AIPromptRunner = exports.AIPromptRunResult = exports.AIPromptParams = void 0;
|
|
4
7
|
const ai_1 = require("@memberjunction/ai");
|
|
@@ -8,6 +11,7 @@ const templates_1 = require("@memberjunction/templates");
|
|
|
8
11
|
const ExecutionPlanner_1 = require("./ExecutionPlanner");
|
|
9
12
|
const ParallelExecutionCoordinator_1 = require("./ParallelExecutionCoordinator");
|
|
10
13
|
const aiengine_1 = require("@memberjunction/aiengine");
|
|
14
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
11
15
|
class AIPromptParams {
|
|
12
16
|
}
|
|
13
17
|
exports.AIPromptParams = AIPromptParams;
|
|
@@ -20,10 +24,34 @@ class AIPromptRunner {
|
|
|
20
24
|
this._templateEngine = templates_1.TemplateEngineServer.Instance;
|
|
21
25
|
this._executionPlanner = new ExecutionPlanner_1.ExecutionPlanner();
|
|
22
26
|
this._parallelCoordinator = new ParallelExecutionCoordinator_1.ParallelExecutionCoordinator();
|
|
27
|
+
if (!AIPromptRunner._ajvInstance) {
|
|
28
|
+
AIPromptRunner._ajvInstance = new ajv_1.default({ allErrors: true, verbose: true });
|
|
29
|
+
}
|
|
30
|
+
this._ajv = AIPromptRunner._ajvInstance;
|
|
31
|
+
}
|
|
32
|
+
static clearSchemaCache() {
|
|
33
|
+
AIPromptRunner._schemaCache.clear();
|
|
34
|
+
(0, core_1.LogStatus)('Cleared global JSON schema cache');
|
|
35
|
+
}
|
|
36
|
+
static getSchemaCacheStats() {
|
|
37
|
+
return {
|
|
38
|
+
size: AIPromptRunner._schemaCache.size,
|
|
39
|
+
keys: Array.from(AIPromptRunner._schemaCache.keys())
|
|
40
|
+
};
|
|
23
41
|
}
|
|
24
42
|
async ExecutePrompt(params) {
|
|
25
43
|
const startTime = new Date();
|
|
26
|
-
|
|
44
|
+
const promptRun = null;
|
|
45
|
+
if (params.cancellationToken?.aborted) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
status: 'cancelled',
|
|
49
|
+
cancelled: true,
|
|
50
|
+
cancellationReason: 'user_requested',
|
|
51
|
+
errorMessage: 'Prompt execution was cancelled before starting',
|
|
52
|
+
executionTimeMS: 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
27
55
|
try {
|
|
28
56
|
const prompt = params.prompt;
|
|
29
57
|
if (!prompt) {
|
|
@@ -32,21 +60,28 @@ class AIPromptRunner {
|
|
|
32
60
|
if (prompt.Status !== 'Active') {
|
|
33
61
|
throw new Error(`Prompt ${prompt.Name} is not active (Status: ${prompt.Status})`);
|
|
34
62
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
let renderedPromptText = '';
|
|
64
|
+
if (prompt.TemplateID && (!params.conversationMessages || params.templateMessageRole !== 'none')) {
|
|
65
|
+
await this._templateEngine.Config(false, params.contextUser);
|
|
66
|
+
const template = await this.loadTemplate(prompt.TemplateID, params.contextUser);
|
|
67
|
+
if (!template) {
|
|
68
|
+
throw new Error(`Template with ID ${prompt.TemplateID} not found for prompt ${prompt.Name}`);
|
|
69
|
+
}
|
|
70
|
+
const renderedPrompt = await this.renderPromptTemplate(template, params.data, params.templateData);
|
|
71
|
+
if (!renderedPrompt.Success) {
|
|
72
|
+
throw new Error(`Failed to render template for prompt ${prompt.Name}: ${renderedPrompt.Message}`);
|
|
73
|
+
}
|
|
74
|
+
renderedPromptText = renderedPrompt.Output;
|
|
39
75
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new Error(`Failed to render template for prompt ${prompt.Name}: ${renderedPrompt.Message}`);
|
|
76
|
+
if (params.cancellationToken?.aborted) {
|
|
77
|
+
throw new Error('Prompt execution was cancelled during template rendering');
|
|
43
78
|
}
|
|
44
79
|
const shouldUseParallelExecution = prompt.ParallelizationMode && prompt.ParallelizationMode !== 'None';
|
|
45
80
|
if (shouldUseParallelExecution) {
|
|
46
|
-
return await this.executePromptInParallel(prompt,
|
|
81
|
+
return await this.executePromptInParallel(prompt, renderedPromptText, params, startTime);
|
|
47
82
|
}
|
|
48
83
|
else {
|
|
49
|
-
return await this.executeSinglePrompt(prompt,
|
|
84
|
+
return await this.executeSinglePrompt(prompt, renderedPromptText, params, startTime);
|
|
50
85
|
}
|
|
51
86
|
}
|
|
52
87
|
catch (error) {
|
|
@@ -66,21 +101,29 @@ class AIPromptRunner {
|
|
|
66
101
|
success: false,
|
|
67
102
|
errorMessage: error.message,
|
|
68
103
|
promptRun,
|
|
69
|
-
executionTimeMS
|
|
104
|
+
executionTimeMS,
|
|
70
105
|
};
|
|
71
106
|
}
|
|
72
107
|
}
|
|
73
108
|
async executeSinglePrompt(prompt, renderedPromptText, params, startTime) {
|
|
109
|
+
if (params.cancellationToken?.aborted) {
|
|
110
|
+
throw new Error('Prompt execution was cancelled before model selection');
|
|
111
|
+
}
|
|
74
112
|
const selectedModel = await this.selectModel(prompt, params.modelId, params.contextUser, params.configurationId, params.vendorId);
|
|
75
113
|
if (!selectedModel) {
|
|
76
114
|
throw new Error(`No suitable model found for prompt ${prompt.Name}`);
|
|
77
115
|
}
|
|
116
|
+
if (params.cancellationToken?.aborted) {
|
|
117
|
+
throw new Error('Prompt execution was cancelled after model selection');
|
|
118
|
+
}
|
|
78
119
|
const promptRun = await this.createPromptRun(prompt, selectedModel, params, startTime, params.vendorId);
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
if (params.cancellationToken?.aborted) {
|
|
121
|
+
throw new Error('Prompt execution was cancelled before model execution');
|
|
122
|
+
}
|
|
123
|
+
const { modelResult, parsedResult, validationAttempts } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun);
|
|
81
124
|
const endTime = new Date();
|
|
82
125
|
const executionTimeMS = endTime.getTime() - startTime.getTime();
|
|
83
|
-
await this.updatePromptRun(promptRun, modelResult, parsedResult, endTime, executionTimeMS);
|
|
126
|
+
await this.updatePromptRun(promptRun, modelResult, parsedResult, endTime, executionTimeMS, validationAttempts);
|
|
84
127
|
const chatResult = modelResult;
|
|
85
128
|
return {
|
|
86
129
|
success: true,
|
|
@@ -89,23 +132,30 @@ class AIPromptRunner {
|
|
|
89
132
|
promptRun,
|
|
90
133
|
executionTimeMS,
|
|
91
134
|
tokensUsed: chatResult.data?.usage?.totalTokens,
|
|
92
|
-
validationResult: parsedResult.validationResult
|
|
135
|
+
validationResult: parsedResult.validationResult,
|
|
136
|
+
validationAttempts,
|
|
93
137
|
};
|
|
94
138
|
}
|
|
95
139
|
async executePromptInParallel(prompt, renderedPromptText, params, startTime) {
|
|
140
|
+
if (params.cancellationToken?.aborted) {
|
|
141
|
+
throw new Error('Parallel execution was cancelled before starting');
|
|
142
|
+
}
|
|
96
143
|
await aiengine_1.AIEngine.Instance.Config(false, params.contextUser);
|
|
97
|
-
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
|
|
144
|
+
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter((pm) => pm.PromptID === prompt.ID &&
|
|
98
145
|
(pm.Status === 'Active' || pm.Status === 'Preview') &&
|
|
99
146
|
(!params.configurationId || !pm.ConfigurationID || pm.ConfigurationID === params.configurationId));
|
|
100
|
-
const executionTasks = this._executionPlanner.createExecutionPlan(prompt, promptModels, aiengine_1.AIEngine.Instance.Models, renderedPromptText, params.contextUser, params.configurationId);
|
|
147
|
+
const executionTasks = this._executionPlanner.createExecutionPlan(prompt, promptModels, aiengine_1.AIEngine.Instance.Models, renderedPromptText, params.contextUser, params.configurationId, params.conversationMessages, params.templateMessageRole || 'system');
|
|
101
148
|
if (executionTasks.length === 0) {
|
|
102
149
|
throw new Error(`No execution tasks created for parallel execution of prompt ${prompt.Name}`);
|
|
103
150
|
}
|
|
104
|
-
|
|
151
|
+
if (params.cancellationToken?.aborted) {
|
|
152
|
+
throw new Error('Parallel execution was cancelled before task execution');
|
|
153
|
+
}
|
|
154
|
+
const parallelResult = await this._parallelCoordinator.executeTasksInParallel(executionTasks, undefined, undefined, params.cancellationToken);
|
|
105
155
|
if (!parallelResult.success) {
|
|
106
156
|
throw new Error(`Parallel execution failed: ${parallelResult.errors.join(', ')}`);
|
|
107
157
|
}
|
|
108
|
-
const successfulResults = parallelResult.taskResults.filter(r => r.success);
|
|
158
|
+
const successfulResults = parallelResult.taskResults.filter((r) => r.success);
|
|
109
159
|
if (successfulResults.length === 0) {
|
|
110
160
|
throw new Error(`No successful results from parallel execution`);
|
|
111
161
|
}
|
|
@@ -113,9 +163,9 @@ class AIPromptRunner {
|
|
|
113
163
|
if (successfulResults.length > 1 && prompt.ResultSelectorPromptID) {
|
|
114
164
|
const selectionConfig = {
|
|
115
165
|
method: 'PromptSelector',
|
|
116
|
-
selectorPromptId: prompt.ResultSelectorPromptID
|
|
166
|
+
selectorPromptId: prompt.ResultSelectorPromptID,
|
|
117
167
|
};
|
|
118
|
-
const aiSelectedResult = await this._parallelCoordinator.selectBestResult(successfulResults, selectionConfig);
|
|
168
|
+
const aiSelectedResult = await this._parallelCoordinator.selectBestResult(successfulResults, selectionConfig, undefined, params.cancellationToken);
|
|
119
169
|
if (aiSelectedResult) {
|
|
120
170
|
selectedResult = aiSelectedResult;
|
|
121
171
|
}
|
|
@@ -132,27 +182,68 @@ class AIPromptRunner {
|
|
|
132
182
|
successfulTasks: parallelResult.successCount,
|
|
133
183
|
failedTasks: parallelResult.failureCount,
|
|
134
184
|
selectedTaskId: selectedResult.task.taskId,
|
|
135
|
-
executionGroups: Array.from(parallelResult.groupResults.keys())
|
|
185
|
+
executionGroups: Array.from(parallelResult.groupResults.keys()),
|
|
136
186
|
};
|
|
137
187
|
if (params.data || params.templateData || parallelMetadata) {
|
|
138
188
|
consolidatedPromptRun.Messages = JSON.stringify({
|
|
139
189
|
data: params.data,
|
|
140
190
|
templateData: params.templateData,
|
|
141
|
-
parallelExecution: parallelMetadata
|
|
191
|
+
parallelExecution: parallelMetadata,
|
|
142
192
|
});
|
|
143
193
|
}
|
|
144
194
|
const saveResult = await consolidatedPromptRun.Save();
|
|
145
195
|
if (!saveResult) {
|
|
146
196
|
(0, core_1.LogError)(`Failed to save consolidated AIPromptRun: ${consolidatedPromptRun.LatestResult?.Message || 'Unknown error'}`);
|
|
147
197
|
}
|
|
198
|
+
const additionalResults = [];
|
|
199
|
+
const sortedResults = successfulResults.sort((a, b) => {
|
|
200
|
+
if (a.ranking && b.ranking) {
|
|
201
|
+
return a.ranking - b.ranking;
|
|
202
|
+
}
|
|
203
|
+
return 0;
|
|
204
|
+
});
|
|
205
|
+
for (const result of sortedResults) {
|
|
206
|
+
if (result.task.taskId !== selectedResult.task.taskId) {
|
|
207
|
+
const { result: parsedResultData, validationResult, validationErrors } = await this.parseAndValidateResultEnhanced(result.modelResult, prompt, params.skipValidation);
|
|
208
|
+
const parsedResult = { result: parsedResultData, validationResult };
|
|
209
|
+
additionalResults.push({
|
|
210
|
+
success: result.success,
|
|
211
|
+
rawResult: result.rawResult,
|
|
212
|
+
result: parsedResult.result,
|
|
213
|
+
executionTimeMS: result.executionTimeMS,
|
|
214
|
+
tokensUsed: result.tokensUsed,
|
|
215
|
+
validationResult: parsedResult.validationResult,
|
|
216
|
+
ranking: result.ranking,
|
|
217
|
+
judgeRationale: result.judgeRationale,
|
|
218
|
+
modelInfo: {
|
|
219
|
+
modelId: result.task.model.ID,
|
|
220
|
+
modelName: result.task.model.Name,
|
|
221
|
+
vendorId: undefined,
|
|
222
|
+
vendorName: result.task.model.Vendor,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const { result: selectedResultData, validationResult: selectedValidationResult } = await this.parseAndValidateResultEnhanced(selectedResult.modelResult, prompt, params.skipValidation);
|
|
228
|
+
const selectedParsedResult = { result: selectedResultData, validationResult: selectedValidationResult };
|
|
148
229
|
return {
|
|
149
230
|
success: true,
|
|
150
231
|
rawResult: selectedResult.rawResult,
|
|
151
|
-
result:
|
|
232
|
+
result: selectedParsedResult.result,
|
|
152
233
|
promptRun: consolidatedPromptRun,
|
|
153
234
|
executionTimeMS: parallelResult.totalExecutionTimeMS,
|
|
154
235
|
tokensUsed: parallelResult.totalTokensUsed,
|
|
155
|
-
validationResult:
|
|
236
|
+
validationResult: selectedParsedResult.validationResult,
|
|
237
|
+
additionalResults: additionalResults.length > 0 ? additionalResults : undefined,
|
|
238
|
+
ranking: selectedResult.ranking || 1,
|
|
239
|
+
judgeRationale: selectedResult.judgeRationale,
|
|
240
|
+
modelInfo: {
|
|
241
|
+
modelId: selectedResult.task.model.ID,
|
|
242
|
+
modelName: selectedResult.task.model.Name,
|
|
243
|
+
vendorId: undefined,
|
|
244
|
+
vendorName: selectedResult.task.model.Vendor,
|
|
245
|
+
},
|
|
246
|
+
judgeMetadata: selectedResult.judgeMetadata,
|
|
156
247
|
};
|
|
157
248
|
}
|
|
158
249
|
async loadTemplate(templateId, _contextUser) {
|
|
@@ -175,7 +266,7 @@ class AIPromptRunner {
|
|
|
175
266
|
const vendorResult = await rv.RunView({
|
|
176
267
|
EntityName: 'AI Vendors',
|
|
177
268
|
ExtraFilter: `ID='${vendorId}'`,
|
|
178
|
-
ResultType: 'entity_object'
|
|
269
|
+
ResultType: 'entity_object',
|
|
179
270
|
});
|
|
180
271
|
if (vendorResult.Results && vendorResult.Results.length > 0) {
|
|
181
272
|
vendorName = vendorResult.Results[0].Name;
|
|
@@ -189,33 +280,29 @@ class AIPromptRunner {
|
|
|
189
280
|
}
|
|
190
281
|
}
|
|
191
282
|
if (explicitModelId) {
|
|
192
|
-
const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === explicitModelId &&
|
|
193
|
-
(!vendorName || m.Vendor === vendorName));
|
|
283
|
+
const model = aiengine_1.AIEngine.Instance.Models.find((m) => m.ID === explicitModelId && (!vendorName || m.Vendor === vendorName));
|
|
194
284
|
if (model && model.IsActive) {
|
|
195
285
|
return model;
|
|
196
286
|
}
|
|
197
287
|
(0, core_1.LogError)(`Explicit model ${explicitModelId} not found, inactive, or not from specified vendor, using prompt model selection`);
|
|
198
288
|
}
|
|
199
|
-
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
|
|
200
|
-
(pm.Status === 'Active' ||
|
|
201
|
-
pm.Status === 'Preview') &&
|
|
289
|
+
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter((pm) => pm.PromptID === prompt.ID &&
|
|
290
|
+
(pm.Status === 'Active' || pm.Status === 'Preview') &&
|
|
202
291
|
(!configurationId || !pm.ConfigurationID || pm.ConfigurationID === configurationId));
|
|
203
292
|
let candidateModels = [];
|
|
204
293
|
if (promptModels.length > 0) {
|
|
205
294
|
candidateModels = promptModels
|
|
206
|
-
.map(pm => aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID))
|
|
207
|
-
.filter(m => m &&
|
|
208
|
-
m.IsActive &&
|
|
209
|
-
(!vendorName || m.Vendor === vendorName));
|
|
295
|
+
.map((pm) => aiengine_1.AIEngine.Instance.Models.find((m) => m.ID === pm.ModelID))
|
|
296
|
+
.filter((m) => m && m.IsActive && (!vendorName || m.Vendor === vendorName));
|
|
210
297
|
candidateModels.sort((a, b) => {
|
|
211
|
-
const aPriority = promptModels.find(pm => pm.ModelID === a.ID)?.Priority || 0;
|
|
212
|
-
const bPriority = promptModels.find(pm => pm.ModelID === b.ID)?.Priority || 0;
|
|
298
|
+
const aPriority = promptModels.find((pm) => pm.ModelID === a.ID)?.Priority || 0;
|
|
299
|
+
const bPriority = promptModels.find((pm) => pm.ModelID === b.ID)?.Priority || 0;
|
|
213
300
|
return bPriority - aPriority;
|
|
214
301
|
});
|
|
215
302
|
}
|
|
216
303
|
else {
|
|
217
304
|
const minPowerRank = prompt.MinPowerRank || 0;
|
|
218
|
-
candidateModels = aiengine_1.AIEngine.Instance.Models.filter(m => m.IsActive &&
|
|
305
|
+
candidateModels = aiengine_1.AIEngine.Instance.Models.filter((m) => m.IsActive &&
|
|
219
306
|
m.PowerRank >= minPowerRank &&
|
|
220
307
|
(!prompt.AIModelTypeID || m.AIModelTypeID === prompt.AIModelTypeID) &&
|
|
221
308
|
(!vendorName || m.Vendor === vendorName));
|
|
@@ -253,8 +340,7 @@ class AIPromptRunner {
|
|
|
253
340
|
promptRun.VendorID = vendorId;
|
|
254
341
|
}
|
|
255
342
|
else {
|
|
256
|
-
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.ModelID === model.ID)
|
|
257
|
-
.sort((a, b) => b.Priority - a.Priority);
|
|
343
|
+
const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter((pm) => pm.ModelID === model.ID).sort((a, b) => b.Priority - a.Priority);
|
|
258
344
|
if (promptModels.length > 0) {
|
|
259
345
|
const highestPriorityModel = promptModels[0];
|
|
260
346
|
promptRun.VendorID = highestPriorityModel.VendorID;
|
|
@@ -265,7 +351,7 @@ class AIPromptRunner {
|
|
|
265
351
|
if (params.data || params.templateData) {
|
|
266
352
|
promptRun.Messages = JSON.stringify({
|
|
267
353
|
data: params.data,
|
|
268
|
-
templateData: params.templateData
|
|
354
|
+
templateData: params.templateData,
|
|
269
355
|
});
|
|
270
356
|
}
|
|
271
357
|
const saveResult = await promptRun.Save();
|
|
@@ -295,27 +381,408 @@ class AIPromptRunner {
|
|
|
295
381
|
throw error;
|
|
296
382
|
}
|
|
297
383
|
}
|
|
298
|
-
async executeModel(model, renderedPrompt, prompt, _contextUser) {
|
|
384
|
+
async executeModel(model, renderedPrompt, prompt, _contextUser, conversationMessages, templateMessageRole = 'system', cancellationToken) {
|
|
299
385
|
try {
|
|
300
386
|
const apiKey = (0, ai_1.GetAIAPIKey)(model.DriverClass);
|
|
301
387
|
const llm = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(ai_1.BaseLLM, model.DriverClass, apiKey);
|
|
302
388
|
const params = new ai_1.ChatParams();
|
|
303
389
|
params.model = model.APIName;
|
|
304
|
-
params.
|
|
305
|
-
|
|
306
|
-
role: ai_1.ChatMessageRole.user,
|
|
307
|
-
content: renderedPrompt
|
|
308
|
-
}
|
|
309
|
-
];
|
|
390
|
+
params.cancellationToken = cancellationToken;
|
|
391
|
+
params.messages = this.buildMessageArray(renderedPrompt, conversationMessages, templateMessageRole);
|
|
310
392
|
if (prompt.ResponseFormat && prompt.ResponseFormat !== 'Any') {
|
|
311
393
|
}
|
|
312
|
-
|
|
394
|
+
if (cancellationToken) {
|
|
395
|
+
return await Promise.race([
|
|
396
|
+
llm.ChatCompletion(params),
|
|
397
|
+
new Promise((_, reject) => {
|
|
398
|
+
if (cancellationToken.aborted) {
|
|
399
|
+
reject(new Error('Chat completion was cancelled'));
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
cancellationToken.addEventListener('abort', () => {
|
|
403
|
+
reject(new Error('Chat completion was cancelled'));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}),
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
return await llm.ChatCompletion(params);
|
|
411
|
+
}
|
|
313
412
|
}
|
|
314
413
|
catch (error) {
|
|
315
414
|
(0, core_1.LogError)(`Error executing model ${model.Name}: ${error.message}`);
|
|
316
415
|
throw error;
|
|
317
416
|
}
|
|
318
417
|
}
|
|
418
|
+
buildMessageArray(renderedPrompt, conversationMessages, templateMessageRole = 'system') {
|
|
419
|
+
const messages = [];
|
|
420
|
+
if (renderedPrompt && templateMessageRole !== 'none') {
|
|
421
|
+
messages.push({
|
|
422
|
+
role: templateMessageRole === 'system' ? ai_1.ChatMessageRole.system : ai_1.ChatMessageRole.user,
|
|
423
|
+
content: renderedPrompt,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (conversationMessages && conversationMessages.length > 0) {
|
|
427
|
+
messages.push(...conversationMessages);
|
|
428
|
+
}
|
|
429
|
+
if ((!conversationMessages || conversationMessages.length === 0) && templateMessageRole !== 'user' && renderedPrompt) {
|
|
430
|
+
if (templateMessageRole === 'system') {
|
|
431
|
+
messages.push({
|
|
432
|
+
role: ai_1.ChatMessageRole.user,
|
|
433
|
+
content: 'Please proceed with the above instructions.',
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else if ((!conversationMessages || conversationMessages.length === 0) && !renderedPrompt) {
|
|
438
|
+
messages.push({
|
|
439
|
+
role: ai_1.ChatMessageRole.user,
|
|
440
|
+
content: 'Hello',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return messages;
|
|
444
|
+
}
|
|
445
|
+
async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun) {
|
|
446
|
+
const validationAttempts = [];
|
|
447
|
+
const maxRetries = Math.max(0, prompt.MaxRetries || 0);
|
|
448
|
+
let lastError = null;
|
|
449
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
450
|
+
try {
|
|
451
|
+
if (params.cancellationToken?.aborted) {
|
|
452
|
+
throw new Error('Execution was cancelled during validation retries');
|
|
453
|
+
}
|
|
454
|
+
if (attempt > 0) {
|
|
455
|
+
(0, core_1.LogStatus)(`🔄 Retrying execution due to validation failure, attempt ${attempt + 1}/${maxRetries + 1}`);
|
|
456
|
+
await this.applyRetryDelay(prompt, attempt);
|
|
457
|
+
}
|
|
458
|
+
const modelResult = await this.executeModel(selectedModel, renderedPromptText, prompt, params.contextUser, params.conversationMessages, params.templateMessageRole || 'system', params.cancellationToken);
|
|
459
|
+
const { result, validationResult, validationErrors } = await this.parseAndValidateResultEnhanced(modelResult, prompt, params.skipValidation);
|
|
460
|
+
const validationAttempt = {
|
|
461
|
+
attemptNumber: attempt + 1,
|
|
462
|
+
success: validationResult?.Success || false,
|
|
463
|
+
errorMessage: validationErrors?.length ? validationErrors.map(e => e.Message).join('; ') : undefined,
|
|
464
|
+
validationErrors,
|
|
465
|
+
rawOutput: modelResult.data?.choices?.[0]?.message?.content || '',
|
|
466
|
+
parsedOutput: result,
|
|
467
|
+
timestamp: new Date(),
|
|
468
|
+
};
|
|
469
|
+
validationAttempts.push(validationAttempt);
|
|
470
|
+
await this.updatePromptRunWithValidationAttempt(promptRun, validationAttempt, attempt + 1, maxRetries + 1);
|
|
471
|
+
if (validationResult?.Success !== false) {
|
|
472
|
+
(0, core_1.LogStatus)(`✅ Validation succeeded on attempt ${attempt + 1}/${maxRetries + 1}`);
|
|
473
|
+
return {
|
|
474
|
+
modelResult,
|
|
475
|
+
parsedResult: { result, validationResult },
|
|
476
|
+
validationAttempts,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (prompt.ValidationBehavior === 'Strict' && attempt < maxRetries) {
|
|
480
|
+
lastError = new Error(`Validation failed: ${validationErrors?.map(e => e.Message).join('; ')}`);
|
|
481
|
+
(0, core_1.LogStatus)(`❌ Validation failed on attempt ${attempt + 1}, will retry (Strict mode)`);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const reason = prompt.ValidationBehavior !== 'Strict'
|
|
486
|
+
? `${prompt.ValidationBehavior} mode - continuing with invalid output`
|
|
487
|
+
: 'max retries exceeded';
|
|
488
|
+
(0, core_1.LogStatus)(`⚠️ Validation failed on attempt ${attempt + 1}, stopping retries (${reason})`);
|
|
489
|
+
return {
|
|
490
|
+
modelResult,
|
|
491
|
+
parsedResult: { result, validationResult },
|
|
492
|
+
validationAttempts,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
lastError = error;
|
|
498
|
+
(0, core_1.LogError)(`Execution attempt ${attempt + 1} failed: ${error.message}`);
|
|
499
|
+
const validationAttempt = {
|
|
500
|
+
attemptNumber: attempt + 1,
|
|
501
|
+
success: false,
|
|
502
|
+
errorMessage: error.message,
|
|
503
|
+
rawOutput: '',
|
|
504
|
+
timestamp: new Date(),
|
|
505
|
+
};
|
|
506
|
+
validationAttempts.push(validationAttempt);
|
|
507
|
+
await this.updatePromptRunWithValidationAttempt(promptRun, validationAttempt, attempt + 1, maxRetries + 1);
|
|
508
|
+
if (attempt === maxRetries) {
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
throw lastError || new Error('Execution failed after all retry attempts');
|
|
514
|
+
}
|
|
515
|
+
async applyRetryDelay(prompt, attemptNumber) {
|
|
516
|
+
const baseDelay = prompt.RetryDelayMS || 1000;
|
|
517
|
+
let delay = baseDelay;
|
|
518
|
+
switch (prompt.RetryStrategy) {
|
|
519
|
+
case 'Fixed':
|
|
520
|
+
delay = baseDelay;
|
|
521
|
+
break;
|
|
522
|
+
case 'Linear':
|
|
523
|
+
delay = baseDelay * attemptNumber;
|
|
524
|
+
break;
|
|
525
|
+
case 'Exponential':
|
|
526
|
+
delay = baseDelay * Math.pow(2, attemptNumber - 1);
|
|
527
|
+
break;
|
|
528
|
+
default:
|
|
529
|
+
delay = baseDelay;
|
|
530
|
+
}
|
|
531
|
+
(0, core_1.LogStatus)(`Applying retry delay: ${delay}ms (strategy: ${prompt.RetryStrategy})`);
|
|
532
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
533
|
+
}
|
|
534
|
+
async updatePromptRunWithValidationAttempt(promptRun, attempt, currentAttempt, totalAttempts) {
|
|
535
|
+
try {
|
|
536
|
+
const currentMessages = promptRun.Messages ? JSON.parse(promptRun.Messages) : {};
|
|
537
|
+
if (!currentMessages.validationAttempts) {
|
|
538
|
+
currentMessages.validationAttempts = [];
|
|
539
|
+
}
|
|
540
|
+
currentMessages.validationAttempts.push({
|
|
541
|
+
attempt: currentAttempt,
|
|
542
|
+
totalAttempts,
|
|
543
|
+
success: attempt.success,
|
|
544
|
+
timestamp: attempt.timestamp.toISOString(),
|
|
545
|
+
errorMessage: attempt.errorMessage,
|
|
546
|
+
validationErrorCount: attempt.validationErrors?.length || 0,
|
|
547
|
+
rawOutput: attempt.rawOutput?.substring(0, 500) + (attempt.rawOutput?.length > 500 ? '...' : ''),
|
|
548
|
+
parsedOutput: attempt.parsedOutput ? JSON.stringify(attempt.parsedOutput).substring(0, 500) : undefined,
|
|
549
|
+
validationErrors: attempt.validationErrors?.map(e => ({
|
|
550
|
+
source: e.Source,
|
|
551
|
+
message: e.Message,
|
|
552
|
+
type: e.Type,
|
|
553
|
+
value: typeof e.Value === 'string' ? e.Value.substring(0, 100) : e.Value
|
|
554
|
+
})) || []
|
|
555
|
+
});
|
|
556
|
+
promptRun.Messages = JSON.stringify(currentMessages);
|
|
557
|
+
(0, core_1.LogStatus)(`Recorded validation attempt ${currentAttempt}/${totalAttempts} for prompt run ${promptRun.ID}`);
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
(0, core_1.LogError)(`Error updating prompt run with validation attempt: ${error.message}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
getValidationDecisionDescription(finalSuccess, totalAttempts, validationBehavior) {
|
|
564
|
+
if (finalSuccess) {
|
|
565
|
+
return totalAttempts === 1
|
|
566
|
+
? 'Validation passed on first attempt'
|
|
567
|
+
: `Validation passed after ${totalAttempts} attempts`;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
switch (validationBehavior) {
|
|
571
|
+
case 'Strict':
|
|
572
|
+
return `Validation failed after ${totalAttempts} attempts - execution marked as failed (Strict mode)`;
|
|
573
|
+
case 'Warn':
|
|
574
|
+
return `Validation failed after ${totalAttempts} attempts - warning logged, execution continued (Warn mode)`;
|
|
575
|
+
case 'None':
|
|
576
|
+
return `Validation skipped or ignored (None mode)`;
|
|
577
|
+
default:
|
|
578
|
+
return `Validation failed after ${totalAttempts} attempts - behavior: ${validationBehavior}`;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
generateSchemaFromExample(example) {
|
|
583
|
+
if (typeof example !== 'object' || example === null) {
|
|
584
|
+
return { type: 'object' };
|
|
585
|
+
}
|
|
586
|
+
const schema = {
|
|
587
|
+
type: 'object',
|
|
588
|
+
properties: {},
|
|
589
|
+
required: [],
|
|
590
|
+
additionalProperties: false,
|
|
591
|
+
};
|
|
592
|
+
for (const [key, value] of Object.entries(example)) {
|
|
593
|
+
schema.properties[key] = this.generateSchemaForValue(value);
|
|
594
|
+
schema.required.push(key);
|
|
595
|
+
}
|
|
596
|
+
return schema;
|
|
597
|
+
}
|
|
598
|
+
generateSchemaForValue(value) {
|
|
599
|
+
if (value === null) {
|
|
600
|
+
return { type: 'null' };
|
|
601
|
+
}
|
|
602
|
+
switch (typeof value) {
|
|
603
|
+
case 'string':
|
|
604
|
+
return { type: 'string' };
|
|
605
|
+
case 'number':
|
|
606
|
+
return { type: 'number' };
|
|
607
|
+
case 'boolean':
|
|
608
|
+
return { type: 'boolean' };
|
|
609
|
+
case 'object':
|
|
610
|
+
if (Array.isArray(value)) {
|
|
611
|
+
if (value.length > 0) {
|
|
612
|
+
return {
|
|
613
|
+
type: 'array',
|
|
614
|
+
items: this.generateSchemaForValue(value[0]),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
return { type: 'array' };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
return this.generateSchemaFromExample(value);
|
|
623
|
+
}
|
|
624
|
+
default:
|
|
625
|
+
return { type: 'string' };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async parseAndValidateResultEnhanced(modelResult, prompt, skipValidation = false) {
|
|
629
|
+
const validationErrors = [];
|
|
630
|
+
try {
|
|
631
|
+
if (!modelResult.success) {
|
|
632
|
+
throw new Error(`Model execution failed: ${modelResult.errorMessage}`);
|
|
633
|
+
}
|
|
634
|
+
const rawOutput = modelResult.data?.choices?.[0]?.message?.content;
|
|
635
|
+
if (!rawOutput) {
|
|
636
|
+
throw new Error('No output received from model');
|
|
637
|
+
}
|
|
638
|
+
let parsedResult = rawOutput;
|
|
639
|
+
try {
|
|
640
|
+
switch (prompt.OutputType) {
|
|
641
|
+
case 'string':
|
|
642
|
+
parsedResult = rawOutput.toString();
|
|
643
|
+
break;
|
|
644
|
+
case 'number': {
|
|
645
|
+
const numberResult = parseFloat(rawOutput);
|
|
646
|
+
if (isNaN(numberResult)) {
|
|
647
|
+
const error = new core_1.ValidationErrorInfo('output', `Expected number output but got: ${rawOutput}`, rawOutput, core_1.ValidationErrorType.Failure);
|
|
648
|
+
validationErrors.push(error);
|
|
649
|
+
throw new Error(error.Message);
|
|
650
|
+
}
|
|
651
|
+
parsedResult = numberResult;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case 'boolean': {
|
|
655
|
+
const lowerOutput = rawOutput.toLowerCase().trim();
|
|
656
|
+
if (['true', 'yes', '1'].includes(lowerOutput)) {
|
|
657
|
+
parsedResult = true;
|
|
658
|
+
}
|
|
659
|
+
else if (['false', 'no', '0'].includes(lowerOutput)) {
|
|
660
|
+
parsedResult = false;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const error = new core_1.ValidationErrorInfo('output', `Expected boolean output but got: ${rawOutput}`, rawOutput, core_1.ValidationErrorType.Failure);
|
|
664
|
+
validationErrors.push(error);
|
|
665
|
+
throw new Error(error.Message);
|
|
666
|
+
}
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
case 'date': {
|
|
670
|
+
const dateResult = new Date(rawOutput);
|
|
671
|
+
if (isNaN(dateResult.getTime())) {
|
|
672
|
+
const error = new core_1.ValidationErrorInfo('output', `Expected date output but got: ${rawOutput}`, rawOutput, core_1.ValidationErrorType.Failure);
|
|
673
|
+
validationErrors.push(error);
|
|
674
|
+
throw new Error(error.Message);
|
|
675
|
+
}
|
|
676
|
+
parsedResult = dateResult;
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
case 'object':
|
|
680
|
+
try {
|
|
681
|
+
parsedResult = JSON.parse(rawOutput);
|
|
682
|
+
}
|
|
683
|
+
catch (jsonError) {
|
|
684
|
+
const error = new core_1.ValidationErrorInfo('output', `Expected JSON object but got invalid JSON: ${rawOutput}`, rawOutput, core_1.ValidationErrorType.Failure);
|
|
685
|
+
validationErrors.push(error);
|
|
686
|
+
throw new Error(error.Message);
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
default:
|
|
690
|
+
parsedResult = rawOutput;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (parseError) {
|
|
694
|
+
const validationResult = new core_1.ValidationResult();
|
|
695
|
+
validationResult.Success = false;
|
|
696
|
+
validationResult.Errors = validationErrors;
|
|
697
|
+
return { result: rawOutput, validationResult, validationErrors };
|
|
698
|
+
}
|
|
699
|
+
if (!skipValidation && prompt.OutputExample && prompt.OutputType === 'object' && parsedResult) {
|
|
700
|
+
try {
|
|
701
|
+
const schemaValidationErrors = await this.validateAgainstSchema(parsedResult, prompt.OutputExample, prompt.ID);
|
|
702
|
+
validationErrors.push(...schemaValidationErrors);
|
|
703
|
+
}
|
|
704
|
+
catch (schemaError) {
|
|
705
|
+
const error = new core_1.ValidationErrorInfo('schema', `Schema validation failed: ${schemaError.message}`, undefined, core_1.ValidationErrorType.Failure);
|
|
706
|
+
validationErrors.push(error);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const validationResult = new core_1.ValidationResult();
|
|
710
|
+
validationResult.Success = validationErrors.length === 0;
|
|
711
|
+
validationResult.Errors = validationErrors;
|
|
712
|
+
return { result: parsedResult, validationResult, validationErrors };
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
(0, core_1.LogError)(`Error parsing/validating result: ${error.message}`);
|
|
716
|
+
const validationResult = new core_1.ValidationResult();
|
|
717
|
+
validationResult.Success = false;
|
|
718
|
+
validationResult.Errors = validationErrors.length > 0 ? validationErrors : [
|
|
719
|
+
new core_1.ValidationErrorInfo('general', error.message, undefined, core_1.ValidationErrorType.Failure)
|
|
720
|
+
];
|
|
721
|
+
switch (prompt.ValidationBehavior) {
|
|
722
|
+
case 'Strict':
|
|
723
|
+
return { result: undefined, validationResult, validationErrors: validationResult.Errors };
|
|
724
|
+
case 'Warn':
|
|
725
|
+
(0, core_1.LogError)(`Validation warning for prompt ${prompt.Name}: ${error.message}`);
|
|
726
|
+
return { result: modelResult.data?.choices?.[0]?.message?.content, validationResult, validationErrors: validationResult.Errors };
|
|
727
|
+
case 'None':
|
|
728
|
+
default:
|
|
729
|
+
validationResult.Success = true;
|
|
730
|
+
return { result: modelResult.data?.choices?.[0]?.message?.content, validationResult, validationErrors: [] };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async validateAgainstSchema(parsedResult, outputExample, promptId) {
|
|
735
|
+
const validationErrors = [];
|
|
736
|
+
try {
|
|
737
|
+
let validator = AIPromptRunner._schemaCache.get(promptId);
|
|
738
|
+
if (!validator) {
|
|
739
|
+
let exampleObject;
|
|
740
|
+
try {
|
|
741
|
+
exampleObject = JSON.parse(outputExample);
|
|
742
|
+
}
|
|
743
|
+
catch (parseError) {
|
|
744
|
+
const error = new core_1.ValidationErrorInfo('outputExample', `Invalid OutputExample JSON: ${parseError.message}`, outputExample, core_1.ValidationErrorType.Failure);
|
|
745
|
+
validationErrors.push(error);
|
|
746
|
+
return validationErrors;
|
|
747
|
+
}
|
|
748
|
+
const schema = this.generateSchemaFromExample(exampleObject);
|
|
749
|
+
try {
|
|
750
|
+
validator = this._ajv.compile(schema);
|
|
751
|
+
AIPromptRunner._schemaCache.set(promptId, validator);
|
|
752
|
+
const cacheStats = AIPromptRunner.getSchemaCacheStats();
|
|
753
|
+
(0, core_1.LogStatus)(`📋 Compiled and cached JSON schema for prompt ${promptId} (global cache size: ${cacheStats.size})`);
|
|
754
|
+
}
|
|
755
|
+
catch (compileError) {
|
|
756
|
+
const error = new core_1.ValidationErrorInfo('schema', `Failed to compile schema: ${compileError.message}`, schema, core_1.ValidationErrorType.Failure);
|
|
757
|
+
validationErrors.push(error);
|
|
758
|
+
return validationErrors;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const isValid = validator(parsedResult);
|
|
762
|
+
if (!isValid && validator.errors) {
|
|
763
|
+
for (const ajvError of validator.errors) {
|
|
764
|
+
const fieldPath = ajvError.instancePath || ajvError.schemaPath || 'root';
|
|
765
|
+
const message = `${ajvError.instancePath || 'root'}: ${ajvError.message}`;
|
|
766
|
+
const error = new core_1.ValidationErrorInfo(fieldPath, message, ajvError.data, core_1.ValidationErrorType.Failure);
|
|
767
|
+
validationErrors.push(error);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (validationErrors.length === 0) {
|
|
771
|
+
(0, core_1.LogStatus)(`✅ Schema validation passed for prompt ${promptId}`);
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
(0, core_1.LogStatus)(`❌ Schema validation found ${validationErrors.length} errors for prompt ${promptId}:`);
|
|
775
|
+
validationErrors.forEach((error, index) => {
|
|
776
|
+
(0, core_1.LogStatus)(` ${index + 1}. ${error.Source}: ${error.Message}`);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
const validationError = new core_1.ValidationErrorInfo('validation', `Unexpected validation error: ${error.message}`, undefined, core_1.ValidationErrorType.Failure);
|
|
782
|
+
validationErrors.push(validationError);
|
|
783
|
+
}
|
|
784
|
+
return validationErrors;
|
|
785
|
+
}
|
|
319
786
|
async parseAndValidateResult(modelResult, prompt, skipValidation = false) {
|
|
320
787
|
try {
|
|
321
788
|
if (!modelResult.success) {
|
|
@@ -330,13 +797,15 @@ class AIPromptRunner {
|
|
|
330
797
|
case 'string':
|
|
331
798
|
parsedResult = rawOutput.toString();
|
|
332
799
|
break;
|
|
333
|
-
case 'number':
|
|
334
|
-
|
|
335
|
-
if (isNaN(
|
|
800
|
+
case 'number': {
|
|
801
|
+
const numberResult = parseFloat(rawOutput);
|
|
802
|
+
if (isNaN(numberResult)) {
|
|
336
803
|
throw new Error(`Expected number output but got: ${rawOutput}`);
|
|
337
804
|
}
|
|
805
|
+
parsedResult = numberResult;
|
|
338
806
|
break;
|
|
339
|
-
|
|
807
|
+
}
|
|
808
|
+
case 'boolean': {
|
|
340
809
|
const lowerOutput = rawOutput.toLowerCase().trim();
|
|
341
810
|
if (['true', 'yes', '1'].includes(lowerOutput)) {
|
|
342
811
|
parsedResult = true;
|
|
@@ -348,12 +817,15 @@ class AIPromptRunner {
|
|
|
348
817
|
throw new Error(`Expected boolean output but got: ${rawOutput}`);
|
|
349
818
|
}
|
|
350
819
|
break;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
820
|
+
}
|
|
821
|
+
case 'date': {
|
|
822
|
+
const dateResult = new Date(rawOutput);
|
|
823
|
+
if (isNaN(dateResult.getTime())) {
|
|
354
824
|
throw new Error(`Expected date output but got: ${rawOutput}`);
|
|
355
825
|
}
|
|
826
|
+
parsedResult = dateResult;
|
|
356
827
|
break;
|
|
828
|
+
}
|
|
357
829
|
case 'object':
|
|
358
830
|
try {
|
|
359
831
|
parsedResult = JSON.parse(rawOutput);
|
|
@@ -385,18 +857,45 @@ class AIPromptRunner {
|
|
|
385
857
|
}
|
|
386
858
|
}
|
|
387
859
|
}
|
|
388
|
-
async updatePromptRun(promptRun, modelResult, parsedResult, endTime, executionTimeMS) {
|
|
860
|
+
async updatePromptRun(promptRun, modelResult, parsedResult, endTime, executionTimeMS, validationAttempts) {
|
|
389
861
|
try {
|
|
390
862
|
promptRun.CompletedAt = endTime;
|
|
391
863
|
promptRun.ExecutionTimeMS = executionTimeMS;
|
|
392
|
-
promptRun.Result = typeof parsedResult.result === 'string'
|
|
393
|
-
? parsedResult.result
|
|
394
|
-
: JSON.stringify(parsedResult.result);
|
|
864
|
+
promptRun.Result = typeof parsedResult.result === 'string' ? parsedResult.result : JSON.stringify(parsedResult.result);
|
|
395
865
|
if (modelResult.data?.usage) {
|
|
396
866
|
promptRun.TokensUsed = modelResult.data.usage.totalTokens;
|
|
397
867
|
promptRun.TokensPrompt = modelResult.data.usage.promptTokens;
|
|
398
868
|
promptRun.TokensCompletion = modelResult.data.usage.completionTokens;
|
|
399
869
|
}
|
|
870
|
+
if (validationAttempts && validationAttempts.length > 0) {
|
|
871
|
+
try {
|
|
872
|
+
const currentMessages = promptRun.Messages ? JSON.parse(promptRun.Messages) : {};
|
|
873
|
+
currentMessages.validationSummary = {
|
|
874
|
+
totalAttempts: validationAttempts.length,
|
|
875
|
+
successfulAttempts: validationAttempts.filter(a => a.success).length,
|
|
876
|
+
finalValidationSuccess: parsedResult.validationResult?.Success || false,
|
|
877
|
+
validationBehavior: promptRun.Messages ? 'Logged during execution' : 'Updated at completion',
|
|
878
|
+
outputType: currentMessages.data?.prompt?.OutputType || 'unknown',
|
|
879
|
+
hasOutputExample: !!(currentMessages.data?.prompt?.OutputExample),
|
|
880
|
+
schemaValidationUsed: !!(currentMessages.data?.prompt?.OutputExample && currentMessages.data?.prompt?.OutputType === 'object'),
|
|
881
|
+
retryStrategy: currentMessages.data?.prompt?.RetryStrategy || 'Fixed',
|
|
882
|
+
maxRetries: currentMessages.data?.prompt?.MaxRetries || 0,
|
|
883
|
+
finalValidationErrors: parsedResult.validationResult?.Errors?.map(e => ({
|
|
884
|
+
source: e.Source,
|
|
885
|
+
message: e.Message,
|
|
886
|
+
type: e.Type,
|
|
887
|
+
value: e.Value
|
|
888
|
+
})) || [],
|
|
889
|
+
validationDecision: this.getValidationDecisionDescription(parsedResult.validationResult?.Success || false, validationAttempts.length, currentMessages.data?.prompt?.ValidationBehavior || 'Warn')
|
|
890
|
+
};
|
|
891
|
+
promptRun.Messages = JSON.stringify(currentMessages);
|
|
892
|
+
(0, core_1.LogStatus)(`Updated prompt run ${promptRun.ID} with ${validationAttempts.length} validation attempts`);
|
|
893
|
+
}
|
|
894
|
+
catch (jsonError) {
|
|
895
|
+
(0, core_1.LogError)(`Error updating Messages field with validation info: ${jsonError.message}`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
promptRun.Success = modelResult.success && (parsedResult.validationResult?.Success !== false);
|
|
400
899
|
const saveResult = await promptRun.Save();
|
|
401
900
|
if (!saveResult) {
|
|
402
901
|
(0, core_1.LogError)(`Failed to update AIPromptRun with results: ${promptRun.LatestResult?.Message || 'Unknown error'}`);
|
|
@@ -408,6 +907,8 @@ class AIPromptRunner {
|
|
|
408
907
|
}
|
|
409
908
|
}
|
|
410
909
|
exports.AIPromptRunner = AIPromptRunner;
|
|
910
|
+
AIPromptRunner._schemaCache = new Map();
|
|
911
|
+
AIPromptRunner._ajvInstance = null;
|
|
411
912
|
function LoadAIPromptRunner() {
|
|
412
913
|
}
|
|
413
914
|
exports.LoadAIPromptRunner = LoadAIPromptRunner;
|