diff --git a/src/prerna/engine/impl/guardrail/AggressiveSelfHarmGuardrailEngine.java b/src/prerna/engine/impl/guardrail/AggressiveSelfHarmGuardrailEngine.java
index 5cc400aa55..0ae6a88705 100644
--- a/src/prerna/engine/impl/guardrail/AggressiveSelfHarmGuardrailEngine.java
+++ b/src/prerna/engine/impl/guardrail/AggressiveSelfHarmGuardrailEngine.java
@@ -46,6 +46,7 @@
import prerna.engine.impl.model.message.ResponseMessage;
import prerna.om.Insight;
import prerna.om.InsightStore;
+import prerna.om.ThreadStore;
import prerna.sablecc2.om.GenRowStruct;
import prerna.sablecc2.om.NounStore;
import prerna.sablecc2.om.nounmeta.GuardrailNounMetadata;
@@ -63,6 +64,7 @@
* Optional SMSS keys:
*
* - {@code SYSTEM_PROMPT} — override the default classification instruction
+ * - {@code BLOCKED_MESSAGE} — override the default message returned when UNSAFE
*
*/
public class AggressiveSelfHarmGuardrailEngine extends AbstractGuardrailReactorFunctionEngine {
@@ -70,8 +72,10 @@ public class AggressiveSelfHarmGuardrailEngine extends AbstractGuardrailReactorF
private static final Logger classLogger = LogManager.getLogger(AggressiveSelfHarmGuardrailEngine.class);
public static final String MODEL_ENGINE_ID_KEY = "MODEL_ENGINE_ID";
+ public static final String SYSTEM_PROMPT_KEY = "SYSTEM_PROMPT";
+ public static final String BLOCKED_MESSAGE_KEY = "BLOCKED_MESSAGE";
- private static final String SYSTEM_PROMPT =
+ private static final String DEFAULT_SYSTEM_PROMPT =
"You are a content safety classifier. Your sole task is to detect whether a user message "
+ "expresses first-person intent to harm themselves or others, suicidal ideation, or aggressive threats.\n\n"
+ "Respond with ONLY one word:\n"
@@ -82,7 +86,13 @@ public class AggressiveSelfHarmGuardrailEngine extends AbstractGuardrailReactorF
+ "third-party reports, or general medical questions\n\n"
+ "Do not explain your reasoning. Output only SAFE or UNSAFE.";
+ private static final String DEFAULT_BLOCKED_MESSAGE =
+ "I'm sorry, I'm not able to help with that. If you or someone you know is in crisis, "
+ + "please call or text 988 to reach the Suicide & Crisis Lifeline, or call 911 for immediate emergency assistance.";
+
private String modelEngineId;
+ private String systemPrompt;
+ private String blockedMessage;
public AggressiveSelfHarmGuardrailEngine() {
this.keysToGet = new String[] { "prompt" };
@@ -98,6 +108,14 @@ public void open(Properties smssProp) throws Exception {
}
this.modelEngineId = this.modelEngineId.trim();
+ String systemPromptProp = this.smssProp.getProperty(SYSTEM_PROMPT_KEY);
+ this.systemPrompt = (systemPromptProp != null && !systemPromptProp.trim().isEmpty())
+ ? systemPromptProp.trim() : DEFAULT_SYSTEM_PROMPT;
+
+ String blockedMessageProp = this.smssProp.getProperty(BLOCKED_MESSAGE_KEY);
+ this.blockedMessage = (blockedMessageProp != null && !blockedMessageProp.trim().isEmpty())
+ ? blockedMessageProp.trim() : DEFAULT_BLOCKED_MESSAGE;
+
this.functionDescription = "Detects aggressive or self-harm content by asking a configured LLM to classify "
+ "the prompt as SAFE or UNSAFE.";
this.parameters = new ArrayList<>();
@@ -109,6 +127,9 @@ public void open(Properties smssProp) throws Exception {
public GuardrailNounMetadata execute(NounStore ns, GenRowStruct curRow) {
Map keyValue = organizeKeys(ns, curRow);
String prompt = keyValue.get("prompt");
+ if (prompt == null) {
+ throw new IllegalArgumentException("No prompt has been defined");
+ }
classLogger.info("AggressiveSelfHarmGuardrail: classifying prompt (length={}) via model={}",
prompt.length(), this.modelEngineId);
@@ -121,13 +142,16 @@ public GuardrailNounMetadata execute(NounStore ns, GenRowStruct curRow) {
Insight classificationInsight = new Insight();
InsightStore.getInstance().put(classificationInsight);
+ String savedJobId = ThreadStore.getJobId();
+ ThreadStore.setJobId(null);
+
String classification;
try {
Room room = RoomUtils.createRoomIfNotExists(UUID.randomUUID().toString(), classificationInsight, modelEngine, prompt);
Map params = new HashMap<>();
params.put("use_history", false);
InputMessage msg = InputMessage.builder(room)
- .withSystemPrompt(SYSTEM_PROMPT)
+ .withSystemPrompt(this.systemPrompt)
.withText(prompt)
.withModelType(modelEngine.getModelType())
.withParamMap(params)
@@ -136,6 +160,7 @@ public GuardrailNounMetadata execute(NounStore ns, GenRowStruct curRow) {
Object responseObj = response.getModelEngineResponse().toMap().get("response");
classification = responseObj != null ? responseObj.toString().trim() : "";
} finally {
+ ThreadStore.setJobId(savedJobId);
InsightStore.getInstance().remove(classificationInsight.getInsightId());
}
@@ -147,11 +172,7 @@ public GuardrailNounMetadata execute(NounStore ns, GenRowStruct curRow) {
details.put("classification", classification);
details.put("modelEngineId", this.modelEngineId);
- String returnPrompt = pass ? prompt
- : "Respond only with the following message, word for word: "
- + "'I'm sorry, I'm not able to help with that. "
- + "If you or someone you know is in crisis, please call or text 988 to reach the Veterans Crisis Line, "
- + "or call 911 for immediate emergency assistance.'";
+ String returnPrompt = pass ? prompt : this.blockedMessage;
return new GuardrailNounMetadata(pass, returnPrompt, details);
}
diff --git a/src/prerna/engine/impl/guardrail/PolicyComplianceGuardrailEngine.java b/src/prerna/engine/impl/guardrail/PolicyComplianceGuardrailEngine.java
index a73f9a7134..1d141d70b6 100644
--- a/src/prerna/engine/impl/guardrail/PolicyComplianceGuardrailEngine.java
+++ b/src/prerna/engine/impl/guardrail/PolicyComplianceGuardrailEngine.java
@@ -47,6 +47,7 @@
import prerna.engine.impl.model.responses.AbstractModelEngineResponse;
import prerna.om.Insight;
import prerna.om.InsightStore;
+import prerna.om.ThreadStore;
import prerna.sablecc2.om.GenRowStruct;
import prerna.sablecc2.om.NounStore;
import prerna.sablecc2.om.nounmeta.GuardrailNounMetadata;
@@ -181,6 +182,8 @@ private String classify(String textToJudge, String policyDescription) throws Exc
// Room/model calls require an Insight to be registered, this one is never tied to a real user session.
Insight classificationInsight = new Insight();
InsightStore.getInstance().put(classificationInsight);
+ String savedJobId = ThreadStore.getJobId();
+ ThreadStore.setJobId(null);
try {
Room room = RoomUtils.createRoomIfNotExists(UUID.randomUUID().toString(), classificationInsight,
judgeEngine, textToJudge);
@@ -196,6 +199,7 @@ private String classify(String textToJudge, String policyDescription) throws Exc
Object responseObj = response.getModelEngineResponse().toMap().get("response");
return responseObj != null ? responseObj.toString().trim() : "";
} finally {
+ ThreadStore.setJobId(savedJobId);
InsightStore.getInstance().remove(classificationInsight.getInsightId());
}
}
diff --git a/src/prerna/engine/impl/pipeline/PipelineInvocationHandler.java b/src/prerna/engine/impl/pipeline/PipelineInvocationHandler.java
index 5007d862bd..101981099e 100644
--- a/src/prerna/engine/impl/pipeline/PipelineInvocationHandler.java
+++ b/src/prerna/engine/impl/pipeline/PipelineInvocationHandler.java
@@ -68,6 +68,8 @@
import prerna.engine.api.IEngine;
import prerna.engine.impl.model.Room;
import prerna.engine.impl.model.responses.AbstractModelEngineResponse;
+import prerna.engine.impl.model.responses.AskModelEngineResponse;
+import prerna.engine.impl.model.responses.AskStringModelEngineResponse;
import prerna.logging.IgnoreEngineLogging;
import prerna.logging.LoggingEngineSerializer;
import prerna.logging.LoggingIReactorSerializer;
@@ -129,6 +131,7 @@ public class PipelineInvocationHandler implements InvocationHandler {
// took
private static final String GUARDRAIL_ACTION_MASK = "MASK";
private static final String GUARDRAIL_ACTION_BLOCK = "BLOCK";
+ private static final String GUARDRAIL_ACTION_RESPOND = "RESPOND";
private final ZoneId UTC_ZONE_ID = ZoneId.of("UTC");
private final Map pipelinesMap = new HashMap<>();
@@ -315,9 +318,12 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
.get(PipelineReactorUtils.INTERIM_RESULT);
boolean pass = (boolean) resultMap.get(PipelineReactorUtils.PASS);
boolean masked = Boolean.TRUE.equals(resultMap.get(PipelineReactorUtils.MASKED));
+ String cannedResponse = (String) resultMap.get(PipelineReactorUtils.SHORT_CIRCUIT_RESPONSE);
// MASK when the guardrail neutralized content, BLOCK when it stopped the
- // request, null when it ran clean - queryable via the GUARDRAIL_ACTION column
- String guardrailAction = masked ? GUARDRAIL_ACTION_MASK : (!pass ? GUARDRAIL_ACTION_BLOCK : null);
+ // request, RESPOND when it supplied the answer itself, null when it ran clean
+ // - queryable via the GUARDRAIL_ACTION column
+ String guardrailAction = cannedResponse != null ? GUARDRAIL_ACTION_RESPOND
+ : masked ? GUARDRAIL_ACTION_MASK : (!pass ? GUARDRAIL_ACTION_BLOCK : null);
String request = null;
String response = null;
@@ -334,6 +340,16 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
logEngineCall(engineSpecificLogger, start, end, pass, request, response,
reactor.getClass().getSimpleName(), null, null, null, guardrailAction);
+ if (cannedResponse != null) {
+ if (!AskModelEngineResponse.class.isAssignableFrom(method.getReturnType())) {
+ throw new SemossPixelException(
+ "Unable to process this request due to content policy (guardrail input exception)");
+ }
+ classLogger.warn("Guardrail {} short-circuited the model call with a canned response",
+ reactor.getClass().getSimpleName());
+ return new AskStringModelEngineResponse(cannedResponse, 0, 0);
+ }
+
if (!pass) {
throw new SemossPixelException(
"Unable to process this request due to content policy (guardrail input exception)");
diff --git a/src/prerna/reactor/interceptor/GenericGuardrailInputReactor.java b/src/prerna/reactor/interceptor/GenericGuardrailInputReactor.java
index bfc24de8b9..09a00b863c 100644
--- a/src/prerna/reactor/interceptor/GenericGuardrailInputReactor.java
+++ b/src/prerna/reactor/interceptor/GenericGuardrailInputReactor.java
@@ -191,7 +191,17 @@ public NounMetadata execute() {
}
}
- Map resultMap = createInterimResult(output, this.getClass().getName(), masked);
+ // When configured to respond (rather than mask or block), hand the guardrail's
+ // message back as the model's answer. The real model call is skipped entirely,
+ // so no version of the prompt reaches the provider.
+ String cannedResponse = null;
+ Boolean respondWithGuardrailMessage = helper.getConfigParameter("respondWithGuardrailMessage", Boolean.class);
+ if (Boolean.TRUE.equals(respondWithGuardrailMessage) && !output.isPass()) {
+ cannedResponse = output.getReturnPrompt();
+ }
+
+ Map resultMap = createInterimResult(output, this.getClass().getName(), masked,
+ cannedResponse);
// Update the processedArguments with the interim result
processedArguments.put(PipelineReactorUtils.INTERIM_RESULT, resultMap);
@@ -206,13 +216,16 @@ public NounMetadata execute() {
* @return
*/
private Map createInterimResult(GuardrailNounMetadata results, String interceptorName,
- boolean masked) {
+ boolean masked, String cannedResponse) {
Map resultMap = new HashMap<>();
resultMap.put(PipelineReactorUtils.INTERCEPTOR, interceptorName);
// when we masked the input we neutralized the failure, so let it pass downstream
resultMap.put(PipelineReactorUtils.PASS, masked || results.isPass());
resultMap.put(PipelineReactorUtils.PASS_DETAILS, results.getValue());
resultMap.put(PipelineReactorUtils.MASKED, masked);
+ if (cannedResponse != null) {
+ resultMap.put(PipelineReactorUtils.SHORT_CIRCUIT_RESPONSE, cannedResponse);
+ }
return resultMap;
}
diff --git a/src/prerna/reactor/interceptor/PipelineReactorUtils.java b/src/prerna/reactor/interceptor/PipelineReactorUtils.java
index a947bae9b9..d34e6fed40 100644
--- a/src/prerna/reactor/interceptor/PipelineReactorUtils.java
+++ b/src/prerna/reactor/interceptor/PipelineReactorUtils.java
@@ -48,6 +48,7 @@ public final class PipelineReactorUtils {
public static final String PASS = "pass";
public static final String PASS_DETAILS = "passDetails";
public static final String MASKED = "masked";
+ public static final String SHORT_CIRCUIT_RESPONSE = "shortCircuitResponse";
private PipelineReactorUtils() {
// private constructor to prevent instantiation