Merge pull request #3700 from SillyTavern/reasoning-template

Reasoning templates
This commit is contained in:
Cohee
2025-03-18 01:49:06 +02:00
committed by GitHub
13 changed files with 273 additions and 22 deletions

View File

@@ -39,6 +39,7 @@ const CC_COMMANDS = [
'proxy',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
'tokenizer',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
'tokenizer': 'Tokenizer',
'stop-strings': 'Custom Stopping Strings',
'start-reply-with': 'Start Reply With',
'reasoning-template': 'Reasoning Template',
};
/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
* @property {string} [tokenizer] Tokenizer
* @property {string} [stop-strings] Custom Stopping Strings
* @property {string} [start-reply-with] Start Reply With
* @property {string} [reasoning-template] Reasoning Template
* @property {string[]} [exclude] Commands to exclude
*/

View File

@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
export {
loadPowerUserSettings,
@@ -257,6 +258,7 @@ let power_user = {
},
reasoning: {
name: DEFAULT_REASONING_TEMPLATE,
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
@@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
await loadInstructMode(data);
await loadContextSettings();
await loadSystemPrompts(data);
await loadReasoningTemplates(data);
loadMaxContextUnlocked();
switchWaifuMode();
switchSpoilerMode();

View File

@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js';
import { convertNovelPreset } from './nai-settings.js';
import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
import { openai_settings, openai_setting_names } from './openai.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
} from './textgen-settings.js';
import { download, parseJsonFile, waitUntilCondition } from './utils.js';
import { t } from './i18n.js';
import { reasoning_templates } from './reasoning.js';
const presetManagers = {};
@@ -168,6 +169,20 @@ class PresetManager {
},
isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
},
'reasoning': {
name: 'Reasoning Formatting',
getData: () => {
const manager = getPresetManager('reasoning');
const name = manager.getSelectedPresetName();
return manager.getPresetSettings(name);
},
setData: (data) => {
const manager = getPresetManager('reasoning');
const name = data.name;
return manager.savePreset(name, data);
},
isValid: (data) => PresetManager.isPossiblyReasoningData(data),
},
};
static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
}
static isPossiblyReasoningData(data) {
const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
}
/**
* Imports master settings from JSON data.
* @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
}
// 5. Reasoning Template
if (this.isPossiblyReasoningData(data)) {
toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
return await getPresetManager('reasoning').savePreset(data.name, data);
}
const validSections = [];
for (const [key, section] of Object.entries(this.masterSections)) {
if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
presets = system_prompts;
preset_names = system_prompts.map(x => x.name);
break;
case 'reasoning':
presets = reasoning_templates;
preset_names = reasoning_templates.map(x => x.name);
break;
default:
console.warn(`Unknown API ID ${api}`);
}
@@ -490,7 +520,7 @@ class PresetManager {
}
isAdvancedFormatting() {
return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
}
updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
sysprompt_preset['name'] = name || power_user.sysprompt.preset;
return sysprompt_preset;
}
case 'reasoning': {
const reasoning_preset = structuredClone(power_user.reasoning);
reasoning_preset['name'] = name || power_user.reasoning.preset;
return reasoning_preset;
}
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
@@ -599,6 +634,13 @@ class PresetManager {
'include_reasoning',
'global_banned_tokens',
'send_banned_tokens',
// Reasoning exclusions
'auto_parse',
'add_to_prompts',
'auto_expand',
'show_hidden',
'max_additions',
];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));

View File

@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
import { power_user } from './power-user.js';
import { performFuzzySearch, power_user } from './power-user.js';
import { getPresetManager } from './preset-manager.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
/**
* @typedef {object} ReasoningTemplate
* @property {string} name - The name of the template
* @property {string} prefix - Reasoning prefix
* @property {string} suffix - Reasoning suffix
* @property {string} separator - Reasoning separator
*/
/**
* @type {ReasoningTemplate[]} List of reasoning templates
*/
export const reasoning_templates = [];
export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
/**
* @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
* @readonly
*/
const UI = {
$select: $('#reasoning_select'),
$suffix: $('#reasoning_suffix'),
$prefix: $('#reasoning_prefix'),
$separator: $('#reasoning_separator'),
$autoParse: $('#reasoning_auto_parse'),
$autoExpand: $('#reasoning_auto_expand'),
$showHidden: $('#reasoning_show_hidden'),
$addToPrompts: $('#reasoning_add_to_prompts'),
$maxAdditions: $('#reasoning_max_additions'),
};
/**
* Enum representing the type of the reasoning for a message (where it came from)
@@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
mainApi = null,
ignoreShowThoughts = false,
textGenType = null,
chatCompletionSource = null
chatCompletionSource = null,
} = {}) {
switch (mainApi ?? main_api) {
case 'textgenerationwebui':
@@ -669,57 +701,102 @@ export class PromptReasoning {
}
function loadReasoningSettings() {
$('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
$('#reasoning_add_to_prompts').on('change', function () {
UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
UI.$addToPrompts.on('change', function () {
power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#reasoning_prefix').val(power_user.reasoning.prefix);
$('#reasoning_prefix').on('input', function () {
UI.$prefix.val(power_user.reasoning.prefix);
UI.$prefix.on('input', function () {
power_user.reasoning.prefix = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_suffix').val(power_user.reasoning.suffix);
$('#reasoning_suffix').on('input', function () {
UI.$suffix.val(power_user.reasoning.suffix);
UI.$suffix.on('input', function () {
power_user.reasoning.suffix = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_separator').val(power_user.reasoning.separator);
$('#reasoning_separator').on('input', function () {
UI.$separator.val(power_user.reasoning.separator);
UI.$separator.on('input', function () {
power_user.reasoning.separator = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_max_additions').val(power_user.reasoning.max_additions);
$('#reasoning_max_additions').on('input', function () {
UI.$maxAdditions.val(power_user.reasoning.max_additions);
UI.$maxAdditions.on('input', function () {
power_user.reasoning.max_additions = Number($(this).val());
saveSettingsDebounced();
});
$('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);
$('#reasoning_auto_parse').on('change', function () {
UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
UI.$autoParse.on('change', function () {
power_user.reasoning.auto_parse = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
$('#reasoning_auto_expand').on('change', function () {
UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
UI.$autoExpand.on('change', function () {
power_user.reasoning.auto_expand = !!$(this).prop('checked');
toggleReasoningAutoExpand();
saveSettingsDebounced();
});
toggleReasoningAutoExpand();
$('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);
$('#reasoning_show_hidden').on('change', function () {
UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
UI.$showHidden.on('change', function () {
power_user.reasoning.show_hidden = !!$(this).prop('checked');
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
saveSettingsDebounced();
});
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
UI.$select.on('change', async function () {
const name = String($(this).val());
const template = reasoning_templates.find(p => p.name === name);
if (!template) {
return;
}
UI.$prefix.val(template.prefix);
UI.$suffix.val(template.suffix);
UI.$separator.val(template.separator);
power_user.reasoning.name = name;
power_user.reasoning.prefix = template.prefix;
power_user.reasoning.suffix = template.suffix;
power_user.reasoning.separator = template.separator;
saveSettingsDebounced();
});
}
function selectReasoningTemplateCallback(args, name) {
if (!name) {
return power_user.reasoning.name ?? '';
}
const quiet = isTrueBoolean(args?.quiet);
const templateNames = reasoning_templates.map(preset => preset.name);
let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
if (!foundName) {
const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
if (result.length === 0) {
!quiet && toastr.warning(`Reasoning template "${name}" not found`);
return '';
}
foundName = result[0].item;
}
UI.$select.val(foundName).trigger('change');
!quiet && toastr.success(`Reasoning template "${foundName}" selected`);
return foundName;
}
function registerReasoningSlashCommands() {
@@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
: parsedReasoning.reasoning;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'reasoning-template',
aliases: ['reasoning-formatting', 'reasoning-preset'],
callback: selectReasoningTemplateCallback,
returns: 'template name',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'quiet',
description: 'Suppress the toast message on template change',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'false',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'reasoning template name',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
}),
],
helpString: `
<div>
Selects a reasoning template by name, using fuzzy search to find the closest match.
Gets the current template if no name is provided.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
</li>
</ul>
</div>
`,
}));
}
function registerReasoningMacros() {
@@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
}
}
/**
* Loads reasoning templates from the settings data.
* @param {object} data Settings data
* @param {ReasoningTemplate[]} data.reasoning Reasoning templates
* @returns {Promise<void>}
*/
export async function loadReasoningTemplates(data) {
if (data.reasoning !== undefined) {
reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
}
for (const template of reasoning_templates) {
$('<option>').val(template.name).text(template.name).appendTo(UI.$select);
}
// No template name, need to migrate
if (power_user.reasoning.name === undefined) {
const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
if (defaultTemplate) {
// If the reasoning settings were modified - migrate them to a custom template
if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
/** @type {ReasoningTemplate} */
const data = {
name: '[Migrated] Custom',
prefix: power_user.reasoning.prefix,
suffix: power_user.reasoning.suffix,
separator: power_user.reasoning.separator,
};
await getPresetManager('reasoning')?.savePreset(data.name, data);
power_user.reasoning.name = data.name;
} else {
power_user.reasoning.name = defaultTemplate.name;
}
} else {
// Template not found (deleted or content check skipped - leave blank)
power_user.reasoning.name = '';
}
saveSettingsDebounced();
}
UI.$select.val(power_user.reasoning.name);
}
/**
* Initializes reasoning settings and event handlers.
*/
export function initReasoning() {
loadReasoningSettings();
setReasoningEventHandlers();