Add prompt injection filters
| @@ -2875,23 +2875,54 @@ function addPersonaDescriptionExtensionPrompt() { | ||
| 2875 | 2875 | } |
| 2876 | 2876 | } |
| 2877 | 2877 | |
| 2878 | -function getAllExtensionPrompts() { | |
| 2878 | +/** | |
| 2879 | - const value = Object | |
| 2879 | + * Returns all extension prompts combined. | |
| 2880 | - .values(extension_prompts) | |
| 2880 | + * @returns {Promise<string>} Combined extension prompts | |
| 2881 | - .filter(x => x.value) | |
| 2881 | + */ | |
| 2882 | - .map(x => x.value.trim()) | |
| 2882 | +async function getAllExtensionPrompts() { | |
| 2883 | - .join('\n'); | |
| 2883 | + const values = []; | |
| 2884 | 2884 | |
| 2885 | - return value.length ? substituteParams(value) : ''; | |
| 2885 | + for (const prompt of Object.values(extension_prompts)) { | |
| 2886 | + const value = prompt?.value?.trim(); | |
| 2887 | + | |
| 2888 | + if (!value) { | |
| 2889 | + continue; | |
| 2886 | 2890 | } |
| 2887 | 2891 | |
| 2888 | -// Wrapper to fetch extension prompts by module name | |
| 2892 | + const hasFilter = typeof prompt.filter === 'function'; | |
| 2889 | -export function getExtensionPromptByName(moduleName) { | |
| 2893 | + if (hasFilter && !await prompt.filter()) { | |
| 2890 | - if (moduleName) { | |
| 2894 | + continue; | |
| 2891 | - return substituteParams(extension_prompts[moduleName]?.value); | |
| 2892 | - } else { | |
| 2893 | - return; | |
| 2894 | 2895 | } |
| 2896 | + | |
| 2897 | + values.push(value); | |
| 2898 | + } | |
| 2899 | + | |
| 2900 | + return substituteParams(values.join('\n')); | |
| 2901 | +} | |
| 2902 | + | |
| 2903 | +/** | |
| 2904 | + * Wrapper to fetch extension prompts by module name | |
| 2905 | + * @param {string} moduleName Module name | |
| 2906 | + * @returns {Promise<string>} Extension prompt | |
| 2907 | + */ | |
| 2908 | +export async function getExtensionPromptByName(moduleName) { | |
| 2909 | + if (!moduleName) { | |
| 2910 | + return ''; | |
| 2911 | + } | |
| 2912 | + | |
| 2913 | + const prompt = extension_prompts[moduleName]; | |
| 2914 | + | |
| 2915 | + if (!prompt) { | |
| 2916 | + return ''; | |
| 2917 | + } | |
| 2918 | + | |
| 2919 | + const hasFilter = typeof prompt.filter === 'function'; | |
| 2920 | + | |
| 2921 | + if (hasFilter && !await prompt.filter()) { | |
| 2922 | + return ''; | |
| 2923 | + } | |
| 2924 | + | |
| 2925 | + return substituteParams(prompt.value); | |
| 2895 | 2926 | } |
| 2896 | 2927 | |
| 2897 | 2928 | /** |
| @@ -2902,27 +2933,36 @@ export function getExtensionPromptByName(moduleName) { | ||
| 2902 | 2933 | * @param {string} [separator] Separator for joining multiple prompts |
| 2903 | 2934 | * @param {number} [role] Role of the prompt |
| 2904 | 2935 | * @param {boolean} [wrap] Wrap start and end with a separator |
| 2905 | 2936 | * @returns {Promise<string>} Extension prompt |
| 2906 | 2937 | */ |
| 2907 | 2938 | export async function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined, wrap = true) { |
| 2908 | - let extension_prompt = Object.keys(extension_prompts) | |
| 2939 | + const filterByFunction = async (prompt) => { | |
| 2940 | + const hasFilter = typeof prompt.filter === 'function'; | |
| 2941 | + if (hasFilter && !await prompt.filter()) { | |
| 2942 | + return false; | |
| 2943 | + } | |
| 2944 | + return true; | |
| 2945 | + }; | |
| 2946 | + const promptPromises = Object.keys(extension_prompts) | |
| 2909 | 2947 | .sort() |
| 2910 | 2948 | .map((x) => extension_prompts[x]) |
| 2911 | 2949 | .filter(x => x.position == position && x.value) |
| 2912 | 2950 | .filter(x => depth === undefined || x.depth === undefined || x.depth === depth) |
| 2913 | 2951 | .filter(x => role === undefined || x.role === undefined || x.role === role) |
| 2914 | - .map(x => x.value.trim()) | |
| 2952 | + .filter(filterByFunction); | |
| 2915 | - .join(separator); | |
| 2953 | + const prompts = await Promise.all(promptPromises); | |
| 2916 | - if (wrap && extension_prompt.length && !extension_prompt.startsWith(separator)) { | |
| 2954 | + | |
| 2917 | - extension_prompt = separator + extension_prompt; | |
| 2955 | + let values = prompts.map(x => x.value.trim()).join(separator); | |
| 2956 | + if (wrap && values.length && !values.startsWith(separator)) { | |
| 2957 | + values = separator + values; | |
| 2918 | 2958 | } |
| 2919 | 2959 | if (wrap && extension_promptvalues.length && !extension_promptvalues.endsWith(separator)) { |
| 2920 | 2960 | extension_promptvalues = extension_promptvalues + separator; |
| 2921 | 2961 | } |
| 2922 | 2962 | if (extension_promptvalues.length) { |
| 2923 | 2963 | extension_promptvalues = substituteParams(extension_promptvalues); |
| 2924 | 2964 | } |
| 2925 | 2965 | return extension_promptvalues; |
| 2926 | 2966 | } |
| 2927 | 2967 | |
| 2928 | 2968 | export function baseChatReplace(value, name1, name2) { |
| @@ -3836,7 +3876,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3836 | 3876 | // Inject all Depth prompts. Chat Completion does it separately |
| 3837 | 3877 | let injectedIndices = []; |
| 3838 | 3878 | if (main_api !== 'openai') { |
| 3839 | 3879 | injectedIndices = await doChatInject(coreChat, isContinue); |
| 3840 | 3880 | } |
| 3841 | 3881 | |
| 3842 | 3882 | // Insert character jailbreak as the last user message (if exists, allowed, preferred, and not using Chat Completion) |
| @@ -3909,8 +3949,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3909 | 3949 | } |
| 3910 | 3950 | |
| 3911 | 3951 | // Call combined AN into Generate |
| 3912 | 3952 | const beforeScenarioAnchor = (await getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT)).trimStart(); |
| 3913 | 3953 | const afterScenarioAnchor = await getExtensionPrompt(extension_prompt_types.IN_PROMPT); |
| 3914 | 3954 | |
| 3915 | 3955 | const storyStringParams = { |
| 3916 | 3956 | description: description, |
| @@ -4473,7 +4513,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4473 | 4513 | ...thisPromptBits[currentArrayEntry], |
| 4474 | 4514 | rawPrompt: generate_data.prompt || generate_data.input, |
| 4475 | 4515 | mesId: getNextMessageId(type), |
| 4476 | 4516 | allAnchors: await getAllExtensionPrompts(), |
| 4477 | 4517 | chatInjects: injectedIndices?.map(index => arrMes[arrMes.length - index - 1])?.join('') || '', |
| 4478 | 4518 | summarizeString: (extension_prompts['1_memory']?.value || ''), |
| 4479 | 4519 | authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), |
| @@ -4742,9 +4782,9 @@ export function stopGeneration() { | ||
| 4742 | 4782 | * Injects extension prompts into chat messages. |
| 4743 | 4783 | * @param {object[]} messages Array of chat messages |
| 4744 | 4784 | * @param {boolean} isContinue Whether the generation is a continuation. If true, the extension prompts of depth 0 are injected at position 1. |
| 4745 | 4785 | * @returns {Promise<number[]>} Array of indices where the extension prompts were injected |
| 4746 | 4786 | */ |
| 4747 | 4787 | async function doChatInject(messages, isContinue) { |
| 4748 | 4788 | const injectedIndices = []; |
| 4749 | 4789 | let totalInsertedMessages = 0; |
| 4750 | 4790 | messages.reverse(); |
| @@ -4762,7 +4802,7 @@ function doChatInject(messages, isContinue) { | ||
| 4762 | 4802 | const wrap = false; |
| 4763 | 4803 | |
| 4764 | 4804 | for (const role of roles) { |
| 4765 | 4805 | const extensionPrompt = String(await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role, wrap)).trimStart(); |
| 4766 | 4806 | const isNarrator = role === extension_prompt_roles.SYSTEM; |
| 4767 | 4807 | const isUser = role === extension_prompt_roles.USER; |
| 4768 | 4808 | const name = names[role]; |
| @@ -7455,14 +7495,16 @@ function select_rm_characters() { | ||
| 7455 | 7495 | * @param {number} depth Insertion depth. 0 represets the last message in context. Expected values up to MAX_INJECTION_DEPTH. |
| 7456 | 7496 | * @param {number} role Extension prompt role. Defaults to SYSTEM. |
| 7457 | 7497 | * @param {boolean} scan Should the prompt be included in the world info scan. |
| 7498 | + * @param {(function(): Promise<boolean>|boolean)} filter Filter function to determine if the prompt should be injected. | |
| 7458 | 7499 | */ |
| 7459 | 7500 | export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM, filter = null) { |
| 7460 | 7501 | extension_prompts[key] = { |
| 7461 | 7502 | value: String(value), |
| 7462 | 7503 | position: Number(position), |
| 7463 | 7504 | depth: Number(depth), |
| 7464 | 7505 | scan: !!scan, |
| 7465 | 7506 | role: Number(role ?? extension_prompt_roles.SYSTEM), |
| 7507 | + filter: filter, | |
| 7466 | 7508 | }; |
| 7467 | 7509 | } |
| 7468 | 7510 | |
| @@ -611,8 +611,9 @@ function formatWorldInfo(value) { | ||
| 611 | 611 | * |
| 612 | 612 | * @param {Prompt[]} prompts - Array containing injection prompts. |
| 613 | 613 | * @param {Object[]} messages - Array containing all messages. |
| 614 | + * @returns {Promise<Object[]>} - Array containing all messages with injections. | |
| 614 | 615 | */ |
| 615 | 616 | async function populationInjectionPrompts(prompts, messages) { |
| 616 | 617 | let totalInsertedMessages = 0; |
| 617 | 618 | |
| 618 | 619 | const roleTypes = { |
| @@ -635,7 +636,7 @@ function populationInjectionPrompts(prompts, messages) { | ||
| 635 | 636 | // Get prompts for current role |
| 636 | 637 | const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join(separator); |
| 637 | 638 | // Get extension prompt |
| 638 | 639 | const extensionPrompt = await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap); |
| 639 | 640 | |
| 640 | 641 | const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator); |
| 641 | 642 | |
| @@ -1020,7 +1021,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | ||
| 1020 | 1021 | } |
| 1021 | 1022 | |
| 1022 | 1023 | // Add in-chat injections |
| 1023 | 1024 | messages = await populationInjectionPrompts(absolutePrompts, messages); |
| 1024 | 1025 | |
| 1025 | 1026 | // Decide whether dialogue examples should always be added |
| 1026 | 1027 | if (power_user.pin_examples) { |
| @@ -1051,9 +1052,9 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | ||
| 1051 | 1052 | * @param {string} options.systemPromptOverride |
| 1052 | 1053 | * @param {string} options.jailbreakPromptOverride |
| 1053 | 1054 | * @param {string} options.personaDescription |
| 1054 | 1055 | * @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts. |
| 1055 | 1056 | */ |
| 1056 | 1057 | async function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) { |
| 1057 | 1058 | const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : ''; |
| 1058 | 1059 | const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : ''; |
| 1059 | 1060 | const groupNudge = substituteParams(oai_settings.group_nudge_prompt); |
| @@ -1142,6 +1143,9 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor | ||
| 1142 | 1143 | if (!extensionPrompts[key].value) continue; |
| 1143 | 1144 | if (![extension_prompt_types.BEFORE_PROMPT, extension_prompt_types.IN_PROMPT].includes(prompt.position)) continue; |
| 1144 | 1145 | |
| 1146 | + const hasFilter = typeof prompt.filter === 'function'; | |
| 1147 | + if (hasFilter && !await prompt.filter()) continue; | |
| 1148 | + | |
| 1145 | 1149 | systemPrompts.push({ |
| 1146 | 1150 | identifier: key.replace(/\W/g, '_'), |
| 1147 | 1151 | position: getPromptPosition(prompt.position), |
| @@ -1252,7 +1256,7 @@ export async function prepareOpenAIMessages({ | ||
| 1252 | 1256 | |
| 1253 | 1257 | try { |
| 1254 | 1258 | // Merge markers and ordered user prompts with system prompts |
| 1255 | 1259 | const prompts = await preparePromptsForChatCompletion({ |
| 1256 | 1260 | Scenario, |
| 1257 | 1261 | charPersonality, |
| 1258 | 1262 | name2, |
| @@ -3721,7 +3721,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3721 | 3721 | // Put this code here since otherwise, the chat reference is modified |
| 3722 | 3722 | for (const key of Object.keys(context.extensionPrompts)) { |
| 3723 | 3723 | if (context.extensionPrompts[key]?.scan) { |
| 3724 | 3724 | const prompt = await getExtensionPromptByName(key); |
| 3725 | 3725 | if (prompt) { |
| 3726 | 3726 | buffer.addInject(prompt); |
| 3727 | 3727 | } |