Files
alighasami 3d5eaf9445
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
first commit
2026-03-17 16:22:57 +03:30

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,
};
}