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
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
// Source: https://github.com/openai/openai-guardrails-js/blob/b9b99b4fb454f02a362c2836aec6285176ec40a8/src/checks/keywords.ts
|
|
import type { CreateCheckFn, GuardrailResult } from '../types';
|
|
|
|
interface KeywordsConfig {
|
|
keywords: string[];
|
|
}
|
|
|
|
// \p{L}|\p{N}|_ - any unicode letter, number, or underscore. Alternative to \b
|
|
const WORD_CHAR_CLASS = '[\\p{L}\\p{N}_]';
|
|
const isWordChar = (() => {
|
|
const wordCharRegex = new RegExp(WORD_CHAR_CLASS, 'u');
|
|
return (char: string | undefined): boolean => {
|
|
if (!char) return false;
|
|
return wordCharRegex.test(char);
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* Keywords-based content filtering guardrail.
|
|
*
|
|
* Checks if any of the configured keywords appear in the input text.
|
|
* Can be configured to trigger tripwires on matches or just report them.
|
|
*
|
|
* @param text Input text to check
|
|
* @param config Configuration specifying keywords and behavior
|
|
* @returns GuardrailResult indicating if tripwire was triggered
|
|
*/
|
|
const keywordsCheck = (text: string, config: KeywordsConfig): GuardrailResult => {
|
|
const { keywords } = config;
|
|
|
|
// Sanitize keywords by stripping trailing punctuation
|
|
const sanitizedKeywords = keywords.map((k: string) => k.replace(/[.,!?;:]+$/, ''));
|
|
|
|
const keywordEntries = sanitizedKeywords
|
|
.map((sanitized) => ({
|
|
sanitized,
|
|
escaped: sanitized.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
|
}))
|
|
.filter(({ sanitized }) => sanitized.length > 0);
|
|
|
|
if (keywordEntries.length === 0) {
|
|
return {
|
|
guardrailName: 'keywords',
|
|
tripwireTriggered: false,
|
|
info: {
|
|
matchedKeywords: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
// Apply unicode-aware word boundaries per keyword so tokens that start/end with punctuation still match.
|
|
const keywordPatterns = keywordEntries.map(({ sanitized, escaped }) => {
|
|
const keywordChars = Array.from(sanitized);
|
|
const firstChar = keywordChars[0];
|
|
const lastChar = keywordChars[keywordChars.length - 1];
|
|
const needsLeftBoundary = isWordChar(firstChar);
|
|
const needsRightBoundary = isWordChar(lastChar);
|
|
// not preceded by a word character
|
|
const leftBoundary = needsLeftBoundary ? `(?<!${WORD_CHAR_CLASS})` : '';
|
|
// not followed by a word character
|
|
const rightBoundary = needsRightBoundary ? `(?!${WORD_CHAR_CLASS})` : '';
|
|
return `${leftBoundary}${escaped}${rightBoundary}`;
|
|
});
|
|
|
|
const patternText = `(?:${keywordPatterns.join('|')})`;
|
|
const pattern = new RegExp(patternText, 'giu'); // case-insensitive, global, unicode aware
|
|
|
|
const matches: string[] = [];
|
|
let match;
|
|
const seen = new Set<string>();
|
|
|
|
// Find all matches and collect unique ones (case-insensitive)
|
|
while ((match = pattern.exec(text)) !== null) {
|
|
const matchedText = match[0];
|
|
if (!seen.has(matchedText.toLowerCase())) {
|
|
matches.push(matchedText);
|
|
seen.add(matchedText.toLowerCase());
|
|
}
|
|
}
|
|
|
|
const tripwireTriggered = matches.length > 0;
|
|
|
|
return {
|
|
guardrailName: 'keywords',
|
|
tripwireTriggered,
|
|
info: {
|
|
matchedKeywords: matches,
|
|
},
|
|
};
|
|
};
|
|
|
|
export const createKeywordsCheckFn: CreateCheckFn<KeywordsConfig> = (config) => (input: string) =>
|
|
keywordsCheck(input, config);
|