Fix auto-parsing of continue from reasoning

Continues #3606
This commit is contained in:
Cohee
2025-03-08 12:58:26 +02:00
parent 91fe2841e3
commit 980ed76cc3
2 changed files with 82 additions and 17 deletions

View File

@ -3287,7 +3287,7 @@ class StreamingProcessor {
chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken; chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken;
// Update reasoning // Update reasoning
await this.reasoningHandler.process(messageId, mesChanged); await this.reasoningHandler.process(messageId, mesChanged, this.promptReasoning);
processedText = chat[messageId]['mes']; processedText = chat[messageId]['mes'];
// Token count update. // Token count update.
@ -5953,7 +5953,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
getMessage = trimToEndSentence(getMessage); getMessage = trimToEndSentence(getMessage);
} }
if (power_user.trim_spaces) { if (power_user.trim_spaces && !PromptReasoning.getLatestPrefix()) {
getMessage = getMessage.trim(); getMessage = getMessage.trim();
} }
@ -6147,13 +6147,17 @@ export function syncMesToSwipe(messageId = null) {
} }
const targetMessageId = messageId ?? chat.length - 1; const targetMessageId = messageId ?? chat.length - 1;
if (chat.length > targetMessageId || targetMessageId < 0) { if (targetMessageId >= chat.length || targetMessageId < 0) {
console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`); console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`);
return false; return false;
} }
const targetMessage = chat[targetMessageId]; const targetMessage = chat[targetMessageId];
if (!targetMessage) {
return false;
}
// No swipe data there yet, exit out // No swipe data there yet, exit out
if (typeof targetMessage.swipe_id !== 'number') { if (typeof targetMessage.swipe_id !== 'number') {
return false; return false;

View File

@ -196,7 +196,7 @@ export class ReasoningHandler {
*/ */
initContinue(promptReasoning) { initContinue(promptReasoning) {
this.reasoning = promptReasoning.prefixReasoning; this.reasoning = promptReasoning.prefixReasoning;
this.state = ReasoningState.Done; this.state = promptReasoning.prefixIncomplete ? ReasoningState.None : ReasoningState.Done;
this.startTime = this.initialTime; this.startTime = this.initialTime;
this.endTime = promptReasoning.prefixDuration ? new Date(this.initialTime.getTime() + promptReasoning.prefixDuration) : null; this.endTime = promptReasoning.prefixDuration ? new Date(this.initialTime.getTime() + promptReasoning.prefixDuration) : null;
} }
@ -324,10 +324,11 @@ export class ReasoningHandler {
* *
* @param {number} messageId - The ID of the message to process * @param {number} messageId - The ID of the message to process
* @param {boolean} mesChanged - Whether the message has changed * @param {boolean} mesChanged - Whether the message has changed
* @param {PromptReasoning} promptReasoning - Prompt reasoning object
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async process(messageId, mesChanged) { async process(messageId, mesChanged, promptReasoning) {
mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged); mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged, promptReasoning);
if (!this.reasoning && !this.#isHiddenReasoningModel) if (!this.reasoning && !this.#isHiddenReasoningModel)
return; return;
@ -345,7 +346,14 @@ export class ReasoningHandler {
} }
} }
#autoParseReasoningFromMessage(messageId, mesChanged) { /**
* Parse reasoning from a message during streaming.
* @param {number} messageId Message ID
* @param {boolean} mesChanged Whether the message has changed before reasoning parsing
* @param {PromptReasoning} promptReasoning Prompt reasoning object
* @returns {boolean} Whether the message has changed after reasoning parsing
*/
#autoParseReasoningFromMessage(messageId, mesChanged, promptReasoning) {
if (!power_user.reasoning.auto_parse) if (!power_user.reasoning.auto_parse)
return; return;
if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) if (!power_user.reasoning.prefix || !power_user.reasoning.suffix)
@ -355,15 +363,17 @@ export class ReasoningHandler {
const message = chat[messageId]; const message = chat[messageId];
if (!message) return mesChanged; if (!message) return mesChanged;
const parseTarget = promptReasoning?.prefixIncomplete ? (promptReasoning.prefixReasoningFormatted + message.mes) : message.mes;
// If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it. // If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it.
if (this.#parsingReasoningMesStartIndex) { if (this.#parsingReasoningMesStartIndex) {
message.mes = trimSpaces(message.mes.slice(this.#parsingReasoningMesStartIndex)); message.mes = trimSpaces(parseTarget.slice(this.#parsingReasoningMesStartIndex));
return mesChanged; return mesChanged;
} }
if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) { if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) {
// If streamed message starts with the opening, cut it out and put all inside reasoning // If streamed message starts with the opening, cut it out and put all inside reasoning
if (message.mes.startsWith(power_user.reasoning.prefix) && message.mes.length > power_user.reasoning.prefix.length) { if (parseTarget.startsWith(power_user.reasoning.prefix) && parseTarget.length > power_user.reasoning.prefix.length) {
this.#isParsingReasoning = true; this.#isParsingReasoning = true;
// Manually set starting state here, as we might already have received the ending suffix // Manually set starting state here, as we might already have received the ending suffix
@ -377,15 +387,14 @@ export class ReasoningHandler {
return mesChanged; return mesChanged;
// If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block // If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block
const originalMes = message.mes; this.reasoning = parseTarget.slice(power_user.reasoning.prefix.length);
this.reasoning = originalMes.slice(power_user.reasoning.prefix.length);
message.mes = ''; message.mes = '';
// If the reasoning contains the ending suffix, we cut that off and continue as message streaming // If the reasoning contains the ending suffix, we cut that off and continue as message streaming
if (this.reasoning.includes(power_user.reasoning.suffix)) { if (this.reasoning.includes(power_user.reasoning.suffix)) {
this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix)); this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix));
this.#parsingReasoningMesStartIndex = originalMes.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length; this.#parsingReasoningMesStartIndex = parseTarget.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length;
message.mes = trimSpaces(originalMes.slice(this.#parsingReasoningMesStartIndex)); message.mes = trimSpaces(parseTarget.slice(this.#parsingReasoningMesStartIndex));
this.#isParsingReasoning = false; this.#isParsingReasoning = false;
} }
@ -525,13 +534,56 @@ export class ReasoningHandler {
* Keeps track of the number of reasoning additions. * Keeps track of the number of reasoning additions.
*/ */
export class PromptReasoning { export class PromptReasoning {
/**
* An instance initiated during the latest prompt processing.
* @type {PromptReasoning}
* */
static #LATEST = null;
/**
* @readonly Zero-width space character used as a placeholder for reasoning.
* @type {string}
*/
static REASONING_PLACEHOLDER = '\u200B'; static REASONING_PLACEHOLDER = '\u200B';
/**
* Returns the latest formatted reasoning prefix if the prefix is incomplete.
* @returns {string} Formatted reasoning prefix
*/
static getLatestPrefix() {
if (!PromptReasoning.#LATEST) {
return '';
}
if (!PromptReasoning.#LATEST.prefixIncomplete) {
return '';
}
return PromptReasoning.#LATEST.prefixReasoningFormatted;
}
/**
* Free the latest reasoning instance.
* To be called when the generation has ended or stopped.
*/
static clearLatest() {
PromptReasoning.#LATEST = null;
}
constructor() { constructor() {
PromptReasoning.#LATEST = this;
/** @type {number} */
this.counter = 0; this.counter = 0;
/** @type {number} */
this.prefixLength = -1; this.prefixLength = -1;
/** @type {string} */
this.prefixReasoning = ''; this.prefixReasoning = '';
/** @type {string} */
this.prefixReasoningFormatted = '';
/** @type {number?} */
this.prefixDuration = null; this.prefixDuration = null;
/** @type {boolean} */
this.prefixIncomplete = false;
} }
/** /**
@ -578,8 +630,10 @@ export class PromptReasoning {
const formattedReasoning = `${prefix}${reasoning}`; const formattedReasoning = `${prefix}${reasoning}`;
if (isPrefix) { if (isPrefix) {
this.prefixReasoning = reasoning; this.prefixReasoning = reasoning;
this.prefixReasoningFormatted = formattedReasoning;
this.prefixLength = formattedReasoning.length; this.prefixLength = formattedReasoning.length;
this.prefixDuration = duration; this.prefixDuration = duration;
this.prefixIncomplete = true;
} }
return formattedReasoning; return formattedReasoning;
} }
@ -588,8 +642,10 @@ export class PromptReasoning {
const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`; const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`;
if (isPrefix) { if (isPrefix) {
this.prefixReasoning = reasoning; this.prefixReasoning = reasoning;
this.prefixReasoningFormatted = formattedReasoning;
this.prefixLength = formattedReasoning.length; this.prefixLength = formattedReasoning.length;
this.prefixDuration = duration; this.prefixDuration = duration;
this.prefixIncomplete = false;
} }
return `${formattedReasoning}${content}`; return `${formattedReasoning}${content}`;
} }
@ -1049,12 +1105,13 @@ function parseReasoningFromString(str, { strict = true } = {}) {
} }
function registerReasoningAppEvents() { function registerReasoningAppEvents() {
const eventHandler = (/** @type {number} */ idx) => { const eventHandler = (/** @type {string} */ type, /** @type {number} */ idx) => {
if (!power_user.reasoning.auto_parse) { if (!power_user.reasoning.auto_parse) {
return; return;
} }
console.debug('[Reasoning] Auto-parsing reasoning block for message', idx); console.debug('[Reasoning] Auto-parsing reasoning block for message', idx);
const prefix = type === event_types.MESSAGE_RECEIVED ? PromptReasoning.getLatestPrefix() : '';
const message = chat[idx]; const message = chat[idx];
if (!message) { if (!message) {
@ -1067,12 +1124,12 @@ function registerReasoningAppEvents() {
return null; return null;
} }
if (message.extra?.reasoning) { if (message.extra?.reasoning && !prefix) {
console.debug('[Reasoning] Message already has reasoning', idx); console.debug('[Reasoning] Message already has reasoning', idx);
return null; return null;
} }
const parsedReasoning = parseReasoningFromString(message.mes); const parsedReasoning = parseReasoningFromString(prefix + message.mes);
// No reasoning block found // No reasoning block found
if (!parsedReasoning) { if (!parsedReasoning) {
@ -1111,7 +1168,11 @@ function registerReasoningAppEvents() {
}; };
for (const event of [event_types.MESSAGE_RECEIVED, event_types.MESSAGE_UPDATED]) { for (const event of [event_types.MESSAGE_RECEIVED, event_types.MESSAGE_UPDATED]) {
eventSource.on(event, eventHandler); eventSource.on(event, (/** @type {number} */ idx) => eventHandler(event, idx));
}
for (const event of [event_types.GENERATION_STOPPED, event_types.GENERATION_ENDED, event_types.CHAT_CHANGED]) {
eventSource.on(event, () => PromptReasoning.clearLatest());
} }
} }