first commit
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
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
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user