Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
248 lines
6.8 KiB
TypeScript
248 lines
6.8 KiB
TypeScript
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
import { NodeOperationError } from 'n8n-workflow';
|
|
|
|
import { runStageGuardrails } from '../helpers/base';
|
|
import { splitByComma } from '../helpers/common';
|
|
import { mapGuardrailErrorsToMessage, mapGuardrailResultToUserResult } from '../helpers/mappers';
|
|
import { createLLMCheckFn } from '../helpers/model';
|
|
import { applyPreflightModifications } from '../helpers/preflight';
|
|
import { createJailbreakCheckFn, JAILBREAK_PROMPT } from './checks/jailbreak';
|
|
import { createKeywordsCheckFn } from './checks/keywords';
|
|
import { createNSFWCheckFn, NSFW_SYSTEM_PROMPT } from './checks/nsfw';
|
|
import { createCustomRegexCheckFn, createPiiCheckFn } from './checks/pii';
|
|
import { createSecretKeysCheckFn } from './checks/secretKeys';
|
|
import {
|
|
createTopicalAlignmentCheckFn,
|
|
TOPICAL_ALIGNMENT_SYSTEM_PROMPT,
|
|
} from './checks/topicalAlignment';
|
|
import { createUrlsCheckFn } from './checks/urls';
|
|
import type {
|
|
GroupedGuardrailResults,
|
|
GuardrailsOptions,
|
|
GuardrailUserResult,
|
|
StageGuardRails,
|
|
} from './types';
|
|
|
|
interface Result {
|
|
checks: GuardrailUserResult[];
|
|
}
|
|
|
|
export async function process(
|
|
this: IExecuteFunctions,
|
|
itemIndex: number,
|
|
model: BaseChatModel | null,
|
|
): Promise<{
|
|
guardrailsInput: string;
|
|
passed: Result | null;
|
|
failed: Result | null;
|
|
}> {
|
|
const inputText = this.getNodeParameter('text', itemIndex) as string;
|
|
const operation = this.getNodeParameter('operation', 0) as 'classify' | 'sanitize';
|
|
const guardrails = this.getNodeParameter('guardrails', itemIndex) as GuardrailsOptions;
|
|
const customizeSystemMessage =
|
|
operation === 'classify' &&
|
|
(this.getNodeParameter('customizeSystemMessage', itemIndex, false) as boolean);
|
|
const systemMessage = customizeSystemMessage
|
|
? (this.getNodeParameter('systemMessage', itemIndex) as string)
|
|
: undefined;
|
|
const failedChecks: GuardrailUserResult[] = [];
|
|
const passedChecks: GuardrailUserResult[] = [];
|
|
|
|
const handleFailedResults = (results: GroupedGuardrailResults): GuardrailUserResult[] => {
|
|
const unexpectedError = results.failed.find(
|
|
(result) =>
|
|
result.status === 'rejected' ||
|
|
(result.status === 'fulfilled' && result.value.executionFailed),
|
|
);
|
|
|
|
if (results.failed.length && operation === 'sanitize') {
|
|
throw new NodeOperationError(this.getNode(), 'Failed to sanitize text', {
|
|
description: mapGuardrailErrorsToMessage(results.failed),
|
|
itemIndex,
|
|
});
|
|
}
|
|
if (unexpectedError && !this.continueOnFail()) {
|
|
const error =
|
|
unexpectedError.status === 'rejected'
|
|
? unexpectedError.reason
|
|
: unexpectedError.value.originalException;
|
|
throw new NodeOperationError(this.getNode(), error, {
|
|
description: error?.description || error?.message,
|
|
itemIndex,
|
|
});
|
|
}
|
|
return results.failed.map(mapGuardrailResultToUserResult);
|
|
};
|
|
|
|
const stageGuardrails: StageGuardRails = {
|
|
preflight: [],
|
|
input: [],
|
|
};
|
|
|
|
const checkModelAvailable = (model: BaseChatModel | null): model is BaseChatModel => {
|
|
if (!model) {
|
|
throw new NodeOperationError(this.getNode(), 'Chat Model is required');
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (guardrails.pii?.value) {
|
|
const { entities } = guardrails.pii.value;
|
|
stageGuardrails.preflight.push({
|
|
name: 'personalData',
|
|
check: createPiiCheckFn({
|
|
entities,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (guardrails.customRegex?.regex) {
|
|
stageGuardrails.preflight.push({
|
|
name: 'customRegex',
|
|
check: createCustomRegexCheckFn({
|
|
customRegex: guardrails.customRegex.regex,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (guardrails.secretKeys?.value) {
|
|
const { permissiveness } = guardrails.secretKeys.value;
|
|
stageGuardrails.preflight.push({
|
|
name: 'secretKeys',
|
|
check: createSecretKeysCheckFn({ threshold: permissiveness }),
|
|
});
|
|
}
|
|
|
|
if (guardrails.urls?.value) {
|
|
const { allowedUrls, allowedSchemes, blockUserinfo, allowSubdomains } = guardrails.urls.value;
|
|
stageGuardrails.preflight.push({
|
|
name: 'urls',
|
|
check: createUrlsCheckFn({
|
|
allowedUrls: splitByComma(allowedUrls),
|
|
allowedSchemes,
|
|
blockUserinfo,
|
|
allowSubdomains,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (operation === 'classify') {
|
|
if (guardrails.keywords) {
|
|
stageGuardrails.input.push({
|
|
name: 'keywords',
|
|
check: createKeywordsCheckFn({ keywords: splitByComma(guardrails.keywords) }),
|
|
});
|
|
}
|
|
|
|
if (guardrails.jailbreak?.value && checkModelAvailable(model)) {
|
|
const { prompt, threshold } = guardrails.jailbreak.value;
|
|
stageGuardrails.input.push({
|
|
name: 'jailbreak',
|
|
check: createJailbreakCheckFn({
|
|
model,
|
|
prompt: prompt?.trim() || JAILBREAK_PROMPT,
|
|
threshold,
|
|
systemMessage,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (guardrails.nsfw?.value && checkModelAvailable(model)) {
|
|
const { prompt, threshold } = guardrails.nsfw.value;
|
|
stageGuardrails.input.push({
|
|
name: 'nsfw',
|
|
check: createNSFWCheckFn({
|
|
model,
|
|
prompt: prompt?.trim() || NSFW_SYSTEM_PROMPT,
|
|
threshold,
|
|
systemMessage,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (guardrails.topicalAlignment?.value && checkModelAvailable(model)) {
|
|
const { prompt, threshold } = guardrails.topicalAlignment.value;
|
|
stageGuardrails.input.push({
|
|
name: 'topicalAlignment',
|
|
check: createTopicalAlignmentCheckFn({
|
|
model,
|
|
prompt: prompt?.trim() || TOPICAL_ALIGNMENT_SYSTEM_PROMPT,
|
|
systemMessage,
|
|
threshold,
|
|
}),
|
|
});
|
|
}
|
|
|
|
if (guardrails.custom?.guardrail && checkModelAvailable(model)) {
|
|
for (const customGuardrail of guardrails.custom.guardrail) {
|
|
const { prompt, threshold, name } = customGuardrail;
|
|
stageGuardrails.input.push({
|
|
name,
|
|
check: createLLMCheckFn(name, {
|
|
model,
|
|
prompt,
|
|
threshold,
|
|
systemMessage,
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const preflightResults = await runStageGuardrails({
|
|
inputText,
|
|
stageGuardrails,
|
|
stage: 'preflight',
|
|
failOnlyOnErrors: operation === 'sanitize',
|
|
});
|
|
|
|
if (preflightResults.failed.length > 0) {
|
|
failedChecks.push.apply(failedChecks, handleFailedResults(preflightResults));
|
|
return {
|
|
guardrailsInput: inputText,
|
|
passed: null,
|
|
failed: {
|
|
checks: failedChecks,
|
|
},
|
|
};
|
|
} else {
|
|
passedChecks.push.apply(
|
|
passedChecks,
|
|
preflightResults.passed.map(mapGuardrailResultToUserResult),
|
|
);
|
|
}
|
|
|
|
const modifiedInputText = applyPreflightModifications(
|
|
inputText,
|
|
preflightResults.passed.map((result) => result.value),
|
|
);
|
|
|
|
const inputResults = await runStageGuardrails({
|
|
inputText: modifiedInputText,
|
|
stageGuardrails,
|
|
stage: 'input',
|
|
failOnlyOnErrors: operation === 'sanitize',
|
|
});
|
|
if (inputResults.failed.length > 0) {
|
|
failedChecks.push.apply(failedChecks, handleFailedResults(inputResults));
|
|
return {
|
|
guardrailsInput: modifiedInputText,
|
|
passed: null,
|
|
failed: {
|
|
checks: failedChecks,
|
|
},
|
|
};
|
|
} else {
|
|
passedChecks.push.apply(passedChecks, inputResults.passed.map(mapGuardrailResultToUserResult));
|
|
}
|
|
|
|
return {
|
|
guardrailsInput: modifiedInputText,
|
|
passed: {
|
|
checks: passedChecks,
|
|
},
|
|
failed: null,
|
|
};
|
|
}
|