Merge pull request #3370 from SillyTavern/reasoning-regex

Add regex processing for reasoning blocks
This commit is contained in:
Cohee
2025-01-29 15:09:55 +02:00
committed by GitHub
5 changed files with 53 additions and 20 deletions

View File

@@ -1993,9 +1993,10 @@ export async function sendTextareaMessage() {
* @param {boolean} isUser If the message was sent by the user * @param {boolean} isUser If the message was sent by the user
* @param {number} messageId Message index in chat array * @param {number} messageId Message index in chat array
* @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides
* @param {boolean} [isReasoning] If the message is reasoning output
* @returns {string} HTML string * @returns {string} HTML string
*/ */
export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) { export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}, isReasoning = false) {
if (!mes) { if (!mes) {
return ''; return '';
} }
@@ -2029,6 +2030,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
if (!isSystem) { if (!isSystem) {
function getRegexPlacement() { function getRegexPlacement() {
try { try {
if (isReasoning) {
return regex_placement.REASONING;
}
if (isUser) { if (isUser) {
return regex_placement.USER_INPUT; return regex_placement.USER_INPUT;
} else if (chat[messageId]?.extra?.type === 'narrator') { } else if (chat[messageId]?.extra?.type === 'narrator') {
@@ -2250,8 +2254,8 @@ function getMessageFromTemplate({
export function updateMessageBlock(messageId, message) { export function updateMessageBlock(messageId, message) {
const messageElement = $(`#chat [mesid="${messageId}"]`); const messageElement = $(`#chat [mesid="${messageId}"]`);
const text = message?.extra?.display_text ?? message.mes; const text = message?.extra?.display_text ?? message.mes;
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId)); messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false));
messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, -1)); messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));
addCopyToCodeBlocks(messageElement); addCopyToCodeBlocks(messageElement);
appendMediaToMessage(message, messageElement); appendMediaToMessage(message, messageElement);
} }
@@ -2408,9 +2412,10 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
mes.is_user, mes.is_user,
chat.indexOf(mes), chat.indexOf(mes),
sanitizerOverrides, sanitizerOverrides,
false,
); );
const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1); const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false);
const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, -1); const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, chat.indexOf(mes), {}, true);
let bookmarkLink = mes?.extra?.bookmark_link ?? ''; let bookmarkLink = mes?.extra?.bookmark_link ?? '';
let params = { let params = {
@@ -3205,7 +3210,7 @@ class StreamingProcessor {
if (this.reasoning) { if (this.reasoning) {
chat[messageId]['extra']['reasoning'] = this.reasoning; chat[messageId]['extra']['reasoning'] = this.reasoning;
if (this.messageReasoningDom instanceof HTMLElement) { if (this.messageReasoningDom instanceof HTMLElement) {
const formattedReasoning = messageFormatting(this.reasoning, '', false, false, -1); const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
this.messageReasoningDom.innerHTML = formattedReasoning; this.messageReasoningDom.innerHTML = formattedReasoning;
} }
} }
@@ -3232,6 +3237,8 @@ class StreamingProcessor {
chat[messageId].is_system, chat[messageId].is_system,
chat[messageId].is_user, chat[messageId].is_user,
messageId, messageId,
{},
false,
); );
if (this.messageTextDom instanceof HTMLElement) { if (this.messageTextDom instanceof HTMLElement) {
this.messageTextDom.innerHTML = formattedText; this.messageTextDom.innerHTML = formattedText;
@@ -3383,7 +3390,7 @@ class StreamingProcessor {
if (logprobs) { if (logprobs) {
this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
} }
this.reasoning = state?.reasoning ?? ''; this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text)); await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
} }
@@ -3850,14 +3857,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
coreChat.pop(); coreChat.pop();
} }
const reasoning = new PromptReasoning();
for (let i = coreChat.length - 1; i >= 0; i--) {
if (reasoning.isLimitReached()) {
break;
}
coreChat[i] = { ...coreChat[i], mes: reasoning.addToMessage(coreChat[i].mes, coreChat[i].extra?.reasoning) };
}
coreChat = await Promise.all(coreChat.map(async (chatItem, index) => { coreChat = await Promise.all(coreChat.map(async (chatItem, index) => {
let message = chatItem.mes; let message = chatItem.mes;
let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT; let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;
@@ -3877,6 +3876,25 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
}; };
})); }));
const reasoning = new PromptReasoning();
for (let i = coreChat.length - 1; i >= 0; i--) {
if (reasoning.isLimitReached()) {
break;
}
const depth = coreChat.length - i - 1;
coreChat[i] = {
...coreChat[i],
mes: reasoning.addToMessage(
coreChat[i].mes,
getRegexedString(
String(coreChat[i].extra?.reasoning ?? ''),
regex_placement.REASONING,
{ isPrompt: true, depth: depth },
),
),
};
}
// Determine token limit // Determine token limit
let this_max_context = getMaxContextSize(); let this_max_context = getMaxContextSize();
@@ -4785,6 +4803,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
const swipes = extractMultiSwipes(data, type); const swipes = extractMultiSwipes(data, type);
messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);
reasoning = getRegexedString(reasoning, regex_placement.REASONING);
if (isContinue) { if (isContinue) {
getMessage = continue_mag + getMessage; getMessage = continue_mag + getMessage;
@@ -7190,9 +7209,11 @@ function messageEditAuto(div) {
mes.is_system, mes.is_system,
mes.is_user, mes.is_user,
this_edit_mes_id, this_edit_mes_id,
{},
false,
)); ));
mesBlock.find('.mes_bias').empty(); mesBlock.find('.mes_bias').empty();
mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1)); mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false));
saveChatDebounced(); saveChatDebounced();
} }
@@ -7214,10 +7235,12 @@ async function messageEditDone(div) {
mes.is_system, mes.is_system,
mes.is_user, mes.is_user,
this_edit_mes_id, this_edit_mes_id,
{},
false,
), ),
); );
mesBlock.find('.mes_bias').empty(); mesBlock.find('.mes_bias').empty();
mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1)); mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false));
appendMediaToMessage(mes, div.closest('.mes')); appendMediaToMessage(mes, div.closest('.mes'));
addCopyToCodeBlocks(div.closest('.mes')); addCopyToCodeBlocks(div.closest('.mes'));
@@ -10860,6 +10883,8 @@ jQuery(async function () {
chat[this_edit_mes_id].is_system, chat[this_edit_mes_id].is_system,
chat[this_edit_mes_id].is_user, chat[this_edit_mes_id].is_user,
this_edit_mes_id, this_edit_mes_id,
{},
false,
)); ));
appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes')); appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
addCopyToCodeBlocks($(this).closest('.mes')); addCopyToCodeBlocks($(this).closest('.mes'));

View File

@@ -94,6 +94,12 @@
<span data-i18n="World Info">World Info</span> <span data-i18n="World Info">World Info</span>
</label> </label>
</div> </div>
<div data-i18n="[title]ext_regex_reasoning_desc" title="Reasoning block contents. When 'Only Format Prompt' is checked, it will also affect the reasoning contents added to the prompt.">
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="6">
<span data-i18n="Reasoning">Reasoning</span>
</label>
</div>
<div class="flex-container wide100p marginTop5"> <div class="flex-container wide100p marginTop5">
<div class="flex1 flex-container flexNoGap"> <div class="flex1 flex-container flexNoGap">
<small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system."> <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">

View File

@@ -20,6 +20,7 @@ const regex_placement = {
SLASH_COMMAND: 3, SLASH_COMMAND: 3,
// 4 - sendAs (legacy) // 4 - sendAs (legacy)
WORLD_INFO: 5, WORLD_INFO: 5,
REASONING: 6,
}; };
export const substitute_find_regex = { export const substitute_find_regex = {
@@ -94,7 +95,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
// Script applies to Generate and input is Generate // Script applies to Generate and input is Generate
(script.promptOnly && isPrompt) || (script.promptOnly && isPrompt) ||
// Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand // Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand
(!script.markdownOnly && !script.promptOnly && !isMarkdown) (!script.markdownOnly && !script.promptOnly && !isMarkdown && !isPrompt)
) { ) {
if (isEdit && !script.runOnEdit) { if (isEdit && !script.runOnEdit) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`); console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`);

View File

@@ -18,7 +18,7 @@ import { t } from '../../i18n.js';
* @property {string} replaceString - The replace string * @property {string} replaceString - The replace string
* @property {string[]} trimStrings - The trim strings * @property {string[]} trimStrings - The trim strings
* @property {string?} findRegex - The find regex * @property {string?} findRegex - The find regex
* @property {string?} substituteRegex - The substitute regex * @property {number?} substituteRegex - The substitute regex
*/ */
/** /**

View File

@@ -1,4 +1,5 @@
import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { MacrosParser } from './macros.js'; import { MacrosParser } from './macros.js';
import { Popup } from './popup.js'; import { Popup } from './popup.js';
@@ -224,7 +225,7 @@ function setReasoningEventHandlers(){
} }
const textarea = messageBlock.find('.reasoning_edit_textarea'); const textarea = messageBlock.find('.reasoning_edit_textarea');
const reasoning = String(textarea.val()); const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
message.extra.reasoning = reasoning; message.extra.reasoning = reasoning;
await saveChatConditional(); await saveChatConditional();
updateMessageBlock(messageId, message); updateMessageBlock(messageId, message);