@prompd/cli 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +48 -20
- package/dist/commands/workflow.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/commandExecutor.d.ts.map +1 -1
- package/dist/lib/commandExecutor.js +45 -25
- package/dist/lib/commandExecutor.js.map +1 -1
- package/dist/lib/executor.d.ts.map +1 -1
- package/dist/lib/executor.js +39 -5
- package/dist/lib/executor.js.map +1 -1
- package/dist/lib/workflowExecutor.d.ts.map +1 -1
- package/dist/lib/workflowExecutor.js +180 -21
- package/dist/lib/workflowExecutor.js.map +1 -1
- package/dist/lib/workflowTypes.d.ts +31 -0
- package/dist/lib/workflowTypes.d.ts.map +1 -1
- package/dist/lib/workflowTypes.js.map +1 -1
- package/package.json +1 -1
|
@@ -1239,6 +1239,103 @@ async function executePromptNode(node, context, options, trace, state, workflowF
|
|
|
1239
1239
|
},
|
|
1240
1240
|
};
|
|
1241
1241
|
await emitAgentCheckpoint(afterEvent, options, workflowFile);
|
|
1242
|
+
// Apply guardrail validation if configured
|
|
1243
|
+
if (data.guardrail) {
|
|
1244
|
+
const guardrail = data.guardrail;
|
|
1245
|
+
let isRejected = false;
|
|
1246
|
+
// Evaluate rejection condition
|
|
1247
|
+
if (guardrail.rejectionExpression) {
|
|
1248
|
+
// Advanced: Custom rejection expression
|
|
1249
|
+
try {
|
|
1250
|
+
// Extend context with response in nodeOutputs for guardrail expression evaluation
|
|
1251
|
+
const guardContext = {
|
|
1252
|
+
nodeOutputs: { ...context.nodeOutputs, response: result },
|
|
1253
|
+
variables: context.variables,
|
|
1254
|
+
workflow: context.workflow,
|
|
1255
|
+
previous_output: context.previous_output,
|
|
1256
|
+
};
|
|
1257
|
+
const evalResult = evaluateExpression(guardrail.rejectionExpression, guardContext);
|
|
1258
|
+
isRejected = Boolean(evalResult);
|
|
1259
|
+
}
|
|
1260
|
+
catch (error) {
|
|
1261
|
+
addTraceEntry(trace, {
|
|
1262
|
+
type: 'debug_step',
|
|
1263
|
+
nodeId: node.id,
|
|
1264
|
+
nodeName: data.label,
|
|
1265
|
+
message: `Guardrail expression evaluation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1266
|
+
}, options);
|
|
1267
|
+
isRejected = false; // Default to not rejected on expression error
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
else if (guardrail.rejectionField && guardrail.expectedFormat === 'json') {
|
|
1271
|
+
// Simple: Field-based check for JSON responses
|
|
1272
|
+
try {
|
|
1273
|
+
const parsed = typeof result === 'string' ? JSON.parse(result) : result;
|
|
1274
|
+
const fieldValue = parsed?.[guardrail.rejectionField];
|
|
1275
|
+
if (guardrail.passWhen === 'true') {
|
|
1276
|
+
// Pass when field is true, reject when false
|
|
1277
|
+
isRejected = !fieldValue;
|
|
1278
|
+
}
|
|
1279
|
+
else if (guardrail.passWhen === 'false') {
|
|
1280
|
+
// Pass when field is false, reject when true
|
|
1281
|
+
isRejected = Boolean(fieldValue);
|
|
1282
|
+
}
|
|
1283
|
+
addTraceEntry(trace, {
|
|
1284
|
+
type: 'debug_step',
|
|
1285
|
+
nodeId: node.id,
|
|
1286
|
+
nodeName: data.label,
|
|
1287
|
+
message: `Guardrail field check: ${guardrail.rejectionField}=${fieldValue}, passWhen=${guardrail.passWhen}, rejected=${isRejected}`,
|
|
1288
|
+
}, options);
|
|
1289
|
+
}
|
|
1290
|
+
catch (error) {
|
|
1291
|
+
addTraceEntry(trace, {
|
|
1292
|
+
type: 'debug_step',
|
|
1293
|
+
nodeId: node.id,
|
|
1294
|
+
nodeName: data.label,
|
|
1295
|
+
message: `Guardrail JSON parse failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1296
|
+
}, options);
|
|
1297
|
+
isRejected = false; // Default to not rejected on parse error
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// Handle rejection
|
|
1301
|
+
if (isRejected) {
|
|
1302
|
+
const failAction = guardrail.failAction || 'error';
|
|
1303
|
+
const rejectMessage = guardrail.customRejectMessage || 'Content rejected by guardrail';
|
|
1304
|
+
addTraceEntry(trace, {
|
|
1305
|
+
type: 'debug_step',
|
|
1306
|
+
nodeId: node.id,
|
|
1307
|
+
nodeName: data.label,
|
|
1308
|
+
message: `Guardrail REJECTED content, failAction=${failAction}`,
|
|
1309
|
+
data: { rejectMessage, result },
|
|
1310
|
+
}, options);
|
|
1311
|
+
if (failAction === 'error' || failAction === 'stop') {
|
|
1312
|
+
throw new Error(rejectMessage);
|
|
1313
|
+
}
|
|
1314
|
+
else if (failAction === 'continue') {
|
|
1315
|
+
// Return reject message and continue workflow
|
|
1316
|
+
return rejectMessage;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
// Guardrail passed - determine output based on outputMode
|
|
1321
|
+
const outputMode = guardrail.outputMode || 'passthrough';
|
|
1322
|
+
addTraceEntry(trace, {
|
|
1323
|
+
type: 'debug_step',
|
|
1324
|
+
nodeId: node.id,
|
|
1325
|
+
nodeName: data.label,
|
|
1326
|
+
message: `Guardrail PASSED content, outputMode=${outputMode}`,
|
|
1327
|
+
}, options);
|
|
1328
|
+
if (outputMode === 'original') {
|
|
1329
|
+
// Return the original input that was sent to the guardrail prompt
|
|
1330
|
+
result = context.previous_output;
|
|
1331
|
+
}
|
|
1332
|
+
else if (outputMode === 'reject-message') {
|
|
1333
|
+
// Return custom message even on pass (useful for custom routing)
|
|
1334
|
+
result = guardrail.customRejectMessage || result;
|
|
1335
|
+
}
|
|
1336
|
+
// 'passthrough' - keep result as-is (default)
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1242
1339
|
// Apply output mapping if defined
|
|
1243
1340
|
if (data.outputMapping) {
|
|
1244
1341
|
const mapped = {};
|
|
@@ -3863,8 +3960,36 @@ Analyze the input above. Return a JSON object:
|
|
|
3863
3960
|
if (jsonMatch) {
|
|
3864
3961
|
try {
|
|
3865
3962
|
const parsed = JSON.parse(jsonMatch[1] || jsonMatch[0]);
|
|
3866
|
-
//
|
|
3867
|
-
if (data.
|
|
3963
|
+
// Priority 1: Custom rejection expression (new field)
|
|
3964
|
+
if (data.guardrailRejectionExpression) {
|
|
3965
|
+
const exprContext = { ...context, response: parsed, previous_output: parsed, input: parsed };
|
|
3966
|
+
const exprResult = evaluateExpression(data.guardrailRejectionExpression, exprContext);
|
|
3967
|
+
guardrailResult = {
|
|
3968
|
+
rejected: Boolean(exprResult), // Expression returns true for REJECT
|
|
3969
|
+
score: typeof parsed.score === 'number' ? parsed.score : undefined,
|
|
3970
|
+
analysis: parsed.analysis,
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
// Priority 2: Field-based check (new fields)
|
|
3974
|
+
else if (data.guardrailRejectionField && data.guardrailExpectedFormat === 'json') {
|
|
3975
|
+
const fieldValue = parsed?.[data.guardrailRejectionField];
|
|
3976
|
+
if (data.guardrailPassWhen === 'true') {
|
|
3977
|
+
// Pass when field is true, reject when false
|
|
3978
|
+
guardrailResult.rejected = !fieldValue;
|
|
3979
|
+
}
|
|
3980
|
+
else if (data.guardrailPassWhen === 'false') {
|
|
3981
|
+
// Pass when field is false, reject when true
|
|
3982
|
+
guardrailResult.rejected = Boolean(fieldValue);
|
|
3983
|
+
}
|
|
3984
|
+
else {
|
|
3985
|
+
// Default: check parsed.rejected field
|
|
3986
|
+
guardrailResult.rejected = parsed.rejected === true;
|
|
3987
|
+
}
|
|
3988
|
+
guardrailResult.score = typeof parsed.score === 'number' ? parsed.score : undefined;
|
|
3989
|
+
guardrailResult.analysis = parsed.analysis;
|
|
3990
|
+
}
|
|
3991
|
+
// Priority 3: Legacy passExpression
|
|
3992
|
+
else if (data.guardrailPassExpression) {
|
|
3868
3993
|
const exprContext = { ...context, previous_output: parsed, input: parsed };
|
|
3869
3994
|
const exprResult = evaluateExpression(data.guardrailPassExpression, exprContext);
|
|
3870
3995
|
guardrailResult = {
|
|
@@ -3873,8 +3998,8 @@ Analyze the input above. Return a JSON object:
|
|
|
3873
3998
|
analysis: parsed.analysis,
|
|
3874
3999
|
};
|
|
3875
4000
|
}
|
|
4001
|
+
// Priority 4: Fallback to checking parsed.rejected field
|
|
3876
4002
|
else {
|
|
3877
|
-
// Fallback to checking parsed.rejected field
|
|
3878
4003
|
guardrailResult = {
|
|
3879
4004
|
rejected: parsed.rejected === true,
|
|
3880
4005
|
score: typeof parsed.score === 'number' ? parsed.score : undefined,
|
|
@@ -3884,16 +4009,22 @@ Analyze the input above. Return a JSON object:
|
|
|
3884
4009
|
}
|
|
3885
4010
|
catch {
|
|
3886
4011
|
// If JSON parsing fails, try expression evaluation with raw response
|
|
3887
|
-
if (data.
|
|
4012
|
+
if (data.guardrailRejectionExpression) {
|
|
4013
|
+
const exprContext = { ...context, response: responseStr, guardrail_response: responseStr };
|
|
4014
|
+
const exprResult = evaluateExpression(data.guardrailRejectionExpression, exprContext);
|
|
4015
|
+
guardrailResult.rejected = Boolean(exprResult);
|
|
4016
|
+
}
|
|
4017
|
+
else if (data.guardrailPassExpression) {
|
|
3888
4018
|
const exprContext = { ...context, guardrail_response: responseStr };
|
|
3889
4019
|
const exprResult = evaluateExpression(data.guardrailPassExpression, exprContext);
|
|
3890
4020
|
guardrailResult.rejected = !exprResult;
|
|
3891
4021
|
}
|
|
3892
4022
|
}
|
|
3893
4023
|
}
|
|
3894
|
-
// Apply score threshold ONLY if no passExpression is configured
|
|
3895
|
-
// This ensures
|
|
3896
|
-
if (!data.
|
|
4024
|
+
// Apply score threshold ONLY if no passExpression/rejectionExpression/fieldCheck is configured
|
|
4025
|
+
// This ensures explicit conditions take precedence
|
|
4026
|
+
if (!data.guardrailRejectionExpression && !data.guardrailPassExpression && !data.guardrailRejectionField &&
|
|
4027
|
+
data.guardrailScoreThreshold !== undefined && guardrailResult.score !== undefined) {
|
|
3897
4028
|
guardrailResult.rejected = guardrailResult.score < data.guardrailScoreThreshold;
|
|
3898
4029
|
}
|
|
3899
4030
|
// Emit afterGuardrail checkpoint
|
|
@@ -3905,26 +4036,54 @@ Analyze the input above. Return a JSON object:
|
|
|
3905
4036
|
return { cancelled: true, stage: 'afterGuardrail', guardrailResult };
|
|
3906
4037
|
}
|
|
3907
4038
|
if (guardrailResult.rejected) {
|
|
4039
|
+
const failAction = data.guardrailFailAction || 'error';
|
|
4040
|
+
const rejectMessage = data.guardrailCustomRejectMessage || 'Input rejected by guardrail';
|
|
3908
4041
|
addTraceEntry(trace, {
|
|
3909
4042
|
type: 'debug_step',
|
|
3910
4043
|
nodeId: node.id,
|
|
3911
4044
|
nodeName: data.label,
|
|
3912
|
-
message:
|
|
3913
|
-
data: { guardrailResult },
|
|
4045
|
+
message: `Guardrail rejected input, failAction=${failAction}`,
|
|
4046
|
+
data: { guardrailResult, rejectMessage },
|
|
3914
4047
|
}, options);
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4048
|
+
if (failAction === 'error' || failAction === 'stop') {
|
|
4049
|
+
// Return rejection result for branching (original behavior)
|
|
4050
|
+
return {
|
|
4051
|
+
rejected: true,
|
|
4052
|
+
guardrailResult,
|
|
4053
|
+
input: currentInput,
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
else if (failAction === 'continue') {
|
|
4057
|
+
// Continue with reject message as the input
|
|
4058
|
+
currentInput = rejectMessage;
|
|
4059
|
+
addTraceEntry(trace, {
|
|
4060
|
+
type: 'debug_step',
|
|
4061
|
+
nodeId: node.id,
|
|
4062
|
+
nodeName: data.label,
|
|
4063
|
+
message: 'Continuing with reject message as input',
|
|
4064
|
+
}, options);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
else {
|
|
4068
|
+
// Guardrail passed - handle outputMode
|
|
4069
|
+
const outputMode = data.guardrailOutputMode || 'passthrough';
|
|
4070
|
+
addTraceEntry(trace, {
|
|
4071
|
+
type: 'debug_step',
|
|
4072
|
+
nodeId: node.id,
|
|
4073
|
+
nodeName: data.label,
|
|
4074
|
+
message: `Guardrail passed, outputMode=${outputMode}`,
|
|
4075
|
+
data: { score: guardrailResult.score },
|
|
4076
|
+
}, options);
|
|
4077
|
+
if (outputMode === 'original') {
|
|
4078
|
+
// Keep currentInput as-is (original input that was validated)
|
|
4079
|
+
// No change needed
|
|
4080
|
+
}
|
|
4081
|
+
else if (outputMode === 'reject-message') {
|
|
4082
|
+
// Use custom message even on pass
|
|
4083
|
+
currentInput = data.guardrailCustomRejectMessage || currentInput;
|
|
4084
|
+
}
|
|
4085
|
+
// 'passthrough' - keep currentInput as-is (default)
|
|
3920
4086
|
}
|
|
3921
|
-
addTraceEntry(trace, {
|
|
3922
|
-
type: 'debug_step',
|
|
3923
|
-
nodeId: node.id,
|
|
3924
|
-
nodeName: data.label,
|
|
3925
|
-
message: 'Guardrail passed',
|
|
3926
|
-
data: { score: guardrailResult.score },
|
|
3927
|
-
}, options);
|
|
3928
4087
|
}
|
|
3929
4088
|
catch (error) {
|
|
3930
4089
|
addTraceEntry(trace, {
|