Add prompt injection filters

d6f34f7b2c812462030be6af198b4b982808d895

Cohee <18619528+Cohee1207@users.noreply.github.com>

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