Merge branch 'staging' into tc-split-generic

This commit is contained in:
Cohee
2024-12-14 15:24:10 +02:00
6 changed files with 157 additions and 52 deletions

View File

@@ -431,7 +431,8 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
}
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE);
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS;
const includeGroupNames = selected_group && [names_behavior_types.ALWAYS, names_behavior_types.FORCE].includes(power_user.instruct.names_behavior);
let inputPrefix = power_user.instruct.input_sequence || '';
let outputPrefix = power_user.instruct.output_sequence || '';
@@ -463,7 +464,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
for (const item of mesExamplesArray) {
const cleanedItem = item.replace(/<START>/i, '{Example Dialogue:}').replace(/\r/gm, '');
const blockExamples = parseExampleIntoIndividual(cleanedItem);
const blockExamples = parseExampleIntoIndividual(cleanedItem, includeGroupNames);
if (blockExamples.length === 0) {
continue;
@@ -474,8 +475,9 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
}
for (const example of blockExamples) {
// If group names were already included, we don't want to add an additional prefix
// If force group/persona names is set, we should override the include names for the user placeholder
const includeThisName = includeNames || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
const includeThisName = (includeNames && !includeGroupNames) || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
@@ -489,7 +491,6 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
if (formattedExamples.length === 0) {
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
}
return formattedExamples;
}

View File

@@ -611,8 +611,9 @@ function formatWorldInfo(value) {
*
* @param {Prompt[]} prompts - Array containing injection prompts.
* @param {Object[]} messages - Array containing all messages.
* @returns {Promise<Object[]>} - Array containing all messages with injections.
*/
function populationInjectionPrompts(prompts, messages) {
async function populationInjectionPrompts(prompts, messages) {
let totalInsertedMessages = 0;
const roleTypes = {
@@ -635,7 +636,7 @@ function populationInjectionPrompts(prompts, messages) {
// Get prompts for current role
const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join(separator);
// Get extension prompt
const extensionPrompt = getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap);
const extensionPrompt = await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap);
const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);
@@ -1020,7 +1021,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
}
// Add in-chat injections
messages = populationInjectionPrompts(absolutePrompts, messages);
messages = await populationInjectionPrompts(absolutePrompts, messages);
// Decide whether dialogue examples should always be added
if (power_user.pin_examples) {
@@ -1051,9 +1052,9 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
* @param {string} options.systemPromptOverride
* @param {string} options.jailbreakPromptOverride
* @param {string} options.personaDescription
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
* @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts.
*/
function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
async function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : '';
const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : '';
const groupNudge = substituteParams(oai_settings.group_nudge_prompt);
@@ -1142,6 +1143,9 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
if (!extensionPrompts[key].value) continue;
if (![extension_prompt_types.BEFORE_PROMPT, extension_prompt_types.IN_PROMPT].includes(prompt.position)) continue;
const hasFilter = typeof prompt.filter === 'function';
if (hasFilter && !await prompt.filter()) continue;
systemPrompts.push({
identifier: key.replace(/\W/g, '_'),
position: getPromptPosition(prompt.position),
@@ -1254,7 +1258,7 @@ export async function prepareOpenAIMessages({
try {
// Merge markers and ordered user prompts with system prompts
const prompts = preparePromptsForChatCompletion({
const prompts = await preparePromptsForChatCompletion({
Scenario,
charPersonality,
name2,

View File

@@ -84,6 +84,25 @@ export const parser = new SlashCommandParser();
const registerSlashCommand = SlashCommandParser.addCommand.bind(SlashCommandParser);
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
/**
* Converts a SlashCommandClosure to a filter function that returns a boolean.
* @param {SlashCommandClosure} closure
* @returns {() => Promise<boolean>}
*/
function closureToFilter(closure) {
return async () => {
try {
const localClosure = closure.getCopy();
localClosure.onProgress = () => { };
const result = await localClosure.execute();
return isTrueBoolean(result.pipe);
} catch (e) {
console.error('Error executing filter closure', e);
return false;
}
};
}
export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: '?',
@@ -1611,6 +1630,13 @@ export function initDefaultSlashCommands() {
new SlashCommandNamedArgument(
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
),
SlashCommandNamedArgument.fromProps({
name: 'filter',
description: 'if a filter is defined, an injection will only be performed if the closure returns true',
typeList: [ARGUMENT_TYPE.CLOSURE],
isRequired: false,
acceptsMultiple: false,
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@@ -1901,6 +1927,11 @@ const NARRATOR_NAME_DEFAULT = 'System';
export const COMMENT_NAME_DEFAULT = 'Note';
const SCRIPT_PROMPT_KEY = 'script_inject_';
/**
* Adds a new script injection to the chat.
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value Unnamed argument
*/
function injectCallback(args, value) {
const positions = {
'before': extension_prompt_types.BEFORE_PROMPT,
@@ -1914,8 +1945,8 @@ function injectCallback(args, value) {
'assistant': extension_prompt_roles.ASSISTANT,
};
const id = args?.id;
const ephemeral = isTrueBoolean(args?.ephemeral);
const id = String(args?.id);
const ephemeral = isTrueBoolean(String(args?.ephemeral));
if (!id) {
console.warn('WARN: No ID provided for /inject command');
@@ -1931,9 +1962,15 @@ function injectCallback(args, value) {
const depth = isNaN(depthValue) ? defaultDepth : depthValue;
const roleValue = typeof args?.role === 'string' ? args.role.toLowerCase().trim() : Number(args?.role ?? extension_prompt_roles.SYSTEM);
const role = roles[roleValue] ?? roles[extension_prompt_roles.SYSTEM];
const scan = isTrueBoolean(args?.scan);
const scan = isTrueBoolean(String(args?.scan));
const filter = args?.filter instanceof SlashCommandClosure ? args.filter.rawText : null;
const filterFunction = args?.filter instanceof SlashCommandClosure ? closureToFilter(args.filter) : null;
value = value || '';
if (args?.filter && !String(filter ?? '').trim()) {
throw new Error('Failed to parse the filter argument. Make sure it is a valid non-empty closure.');
}
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
if (!chat_metadata.script_injects) {
@@ -1941,13 +1978,13 @@ function injectCallback(args, value) {
}
if (value) {
const inject = { value, position, depth, scan, role };
const inject = { value, position, depth, scan, role, filter };
chat_metadata.script_injects[id] = inject;
} else {
delete chat_metadata.script_injects[id];
}
setExtensionPrompt(prefixedId, value, position, depth, scan, role);
setExtensionPrompt(prefixedId, String(value), position, depth, scan, role, filterFunction);
saveMetadataDebounced();
if (ephemeral) {
@@ -1958,7 +1995,7 @@ function injectCallback(args, value) {
}
console.log('Removing ephemeral script injection', id);
delete chat_metadata.script_injects[id];
setExtensionPrompt(prefixedId, '', position, depth, scan, role);
setExtensionPrompt(prefixedId, '', position, depth, scan, role, filterFunction);
saveMetadataDebounced();
deleted = true;
};
@@ -2053,9 +2090,28 @@ export function processChatSlashCommands() {
}
for (const [id, inject] of Object.entries(context.chatMetadata.script_injects)) {
/**
* Rehydrates a filter closure from a string.
* @returns {SlashCommandClosure | null}
*/
function reviveFilterClosure() {
if (!inject.filter) {
return null;
}
try {
return new SlashCommandParser().parse(inject.filter, true);
} catch (error) {
console.warn('Failed to revive filter closure for script injection', id, error);
return null;
}
}
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
const filterClosure = reviveFilterClosure();
const filter = filterClosure ? closureToFilter(filterClosure) : null;
console.log('Adding script injection', id);
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role);
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role, filter);
}
}

View File

@@ -63,7 +63,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { tag_map, tags } from './tags.js';
import { textgenerationwebui_settings } from './textgen-settings.js';
import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { ToolManager } from './tool-calling.js';
import { timestampToMoment } from './utils.js';
@@ -95,6 +95,8 @@ export function getContext() {
sendStreamingRequest,
sendGenerationRequest,
stopGeneration,
tokenizers,
getTextTokens,
/** @deprecated Use getTokenCountAsync instead */
getTokenCount,
getTokenCountAsync,

View File

@@ -3760,7 +3760,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
// Put this code here since otherwise, the chat reference is modified
for (const key of Object.keys(context.extensionPrompts)) {
if (context.extensionPrompts[key]?.scan) {
const prompt = getExtensionPromptByName(key);
const prompt = await getExtensionPromptByName(key);
if (prompt) {
buffer.addInject(prompt);
}