mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #3386 from SillyTavern/reasoning-parse
Reasoning blocks auto-parsing
This commit is contained in:
@@ -254,6 +254,7 @@ let power_user = {
|
||||
},
|
||||
|
||||
reasoning: {
|
||||
auto_parse: false,
|
||||
add_to_prompts: false,
|
||||
prefix: '<think>\n',
|
||||
suffix: '\n</think>',
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
|
||||
import { chat, closeMessageEditor, event_types, eventSource, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
|
||||
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
|
||||
import { t } from './i18n.js';
|
||||
import { MacrosParser } from './macros.js';
|
||||
@@ -8,7 +8,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { copyText } from './utils.js';
|
||||
import { copyText, escapeRegex, isFalseBoolean } from './utils.js';
|
||||
|
||||
/**
|
||||
* Gets a message from a jQuery element.
|
||||
@@ -106,6 +106,12 @@ function loadReasoningSettings() {
|
||||
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 () {
|
||||
power_user.reasoning.auto_parse = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
}
|
||||
|
||||
function registerReasoningSlashCommands() {
|
||||
@@ -161,6 +167,49 @@ function registerReasoningSlashCommands() {
|
||||
return message.extra.reasoning;
|
||||
},
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'reasoning-parse',
|
||||
returns: 'reasoning string',
|
||||
helpString: t`Extracts the reasoning block from a string using the Reasoning Formatting settings.`,
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'regex',
|
||||
description: 'Whether to apply regex scripts to the reasoning content.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
isRequired: false,
|
||||
enumProvider: commonEnumProviders.boolean('trueFalse'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'input string',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
}),
|
||||
],
|
||||
callback: (args, value) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
|
||||
toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`);
|
||||
return String(value);
|
||||
}
|
||||
|
||||
const parsedReasoning = parseReasoningFromString(String(value));
|
||||
|
||||
if (!parsedReasoning) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const applyRegex = !isFalseBoolean(String(args.regex ?? ''));
|
||||
return applyRegex
|
||||
? getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING)
|
||||
: parsedReasoning.reasoning;
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function registerReasoningMacros() {
|
||||
@@ -290,9 +339,101 @@ function setReasoningEventHandlers(){
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses reasoning from a string using the power user reasoning settings.
|
||||
* @typedef {Object} ParsedReasoning
|
||||
* @property {string} reasoning Reasoning block
|
||||
* @property {string} content Message content
|
||||
* @param {string} str Content of the message
|
||||
* @returns {ParsedReasoning|null} Parsed reasoning block and message content
|
||||
*/
|
||||
function parseReasoningFromString(str) {
|
||||
// Both prefix and suffix must be defined
|
||||
if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const regex = new RegExp(`${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's');
|
||||
|
||||
let didReplace = false;
|
||||
let reasoning = '';
|
||||
let content = String(str).replace(regex, (_match, captureGroup) => {
|
||||
didReplace = true;
|
||||
reasoning = captureGroup;
|
||||
return '';
|
||||
});
|
||||
|
||||
if (didReplace && power_user.trim_spaces) {
|
||||
reasoning = reasoning.trim();
|
||||
content = content.trim();
|
||||
}
|
||||
|
||||
return { reasoning, content };
|
||||
} catch (error) {
|
||||
console.error('[Reasoning] Error parsing reasoning block', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function registerReasoningAppEvents() {
|
||||
eventSource.makeFirst(event_types.MESSAGE_RECEIVED, (/** @type {number} */ idx) => {
|
||||
if (!power_user.reasoning.auto_parse) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('[Reasoning] Auto-parsing reasoning block for message', idx);
|
||||
const message = chat[idx];
|
||||
|
||||
if (!message) {
|
||||
console.warn('[Reasoning] Message not found', idx);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!message.mes || message.mes === '...') {
|
||||
console.debug('[Reasoning] Message content is empty or a placeholder', idx);
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedReasoning = parseReasoningFromString(message.mes);
|
||||
|
||||
// No reasoning block found
|
||||
if (!parsedReasoning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the message has an extra object
|
||||
if (!message.extra || typeof message.extra !== 'object') {
|
||||
message.extra = {};
|
||||
}
|
||||
|
||||
const contentUpdated = !!parsedReasoning.reasoning || parsedReasoning.content !== message.mes;
|
||||
|
||||
// If reasoning was found, add it to the message
|
||||
if (parsedReasoning.reasoning) {
|
||||
message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
|
||||
}
|
||||
|
||||
// Update the message text if it was changed
|
||||
if (parsedReasoning.content !== message.mes) {
|
||||
message.mes = parsedReasoning.content;
|
||||
}
|
||||
|
||||
// Find if a message already exists in DOM and must be updated
|
||||
if (contentUpdated) {
|
||||
const messageRendered = document.querySelector(`.mes[mesid="${idx}"]`) !== null;
|
||||
if (messageRendered) {
|
||||
console.debug('[Reasoning] Updating message block', idx);
|
||||
updateMessageBlock(idx, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function initReasoning() {
|
||||
loadReasoningSettings();
|
||||
setReasoningEventHandlers();
|
||||
registerReasoningSlashCommands();
|
||||
registerReasoningMacros();
|
||||
registerReasoningAppEvents();
|
||||
}
|
||||
|
Reference in New Issue
Block a user