Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -63,15 +64,18 @@
* Optional SMSS keys:
* <ul>
* <li>{@code SYSTEM_PROMPT} — override the default classification instruction</li>
* <li>{@code BLOCKED_MESSAGE} — override the default message returned when UNSAFE</li>
* </ul>
*/
public class AggressiveSelfHarmGuardrailEngine extends AbstractGuardrailReactorFunctionEngine {

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"
Expand All @@ -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" };
Expand All @@ -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<>();
Expand All @@ -109,6 +127,9 @@ public void open(Properties smssProp) throws Exception {
public GuardrailNounMetadata execute(NounStore ns, GenRowStruct curRow) {
Map<String, String> 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);
Expand All @@ -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<String, Object> 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)
Expand All @@ -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());
}

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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());
}
}
Expand Down
20 changes: 18 additions & 2 deletions src/prerna/engine/impl/pipeline/PipelineInvocationHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Pipeline> pipelinesMap = new HashMap<>();
Expand Down Expand Up @@ -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;
Expand All @@ -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)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,17 @@ public NounMetadata execute() {
}
}

Map<String, Object> 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<String, Object> resultMap = createInterimResult(output, this.getClass().getName(), masked,
cannedResponse);

// Update the processedArguments with the interim result
processedArguments.put(PipelineReactorUtils.INTERIM_RESULT, resultMap);
Expand All @@ -206,13 +216,16 @@ public NounMetadata execute() {
* @return
*/
private Map<String, Object> createInterimResult(GuardrailNounMetadata results, String interceptorName,
boolean masked) {
boolean masked, String cannedResponse) {
Map<String, Object> 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;
}
Expand Down
1 change: 1 addition & 0 deletions src/prerna/reactor/interceptor/PipelineReactorUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading