diff --git a/default-task-profile-templates.js b/default-task-profile-templates.js index 36329b9..47ae4e2 100644 --- a/default-task-profile-templates.js +++ b/default-task-profile-templates.js @@ -189,13 +189,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -396,13 +393,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -579,13 +573,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -774,13 +765,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -981,13 +969,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -1200,13 +1185,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, diff --git a/extractor.js b/extractor.js index 5b4f946..930e5c8 100644 --- a/extractor.js +++ b/extractor.js @@ -411,6 +411,33 @@ export async function extractMemories({ systemPrompt, ); + // 诊断:追踪 promptPayload + { + const pm = Array.isArray(promptPayload.promptMessages) ? promptPayload.promptMessages : []; + const pmUser = pm.filter((m) => m?.role === "user"); + const am = Array.isArray(promptPayload.additionalMessages) ? promptPayload.additionalMessages : []; + console.log( + `[ST-BME][prompt-diag] resolveTaskPromptPayload: ` + + `promptMessages=${pm.length} (user=${pmUser.length}), ` + + `additionalMessages=${am.length}, ` + + `userPrompt length=${String(promptPayload.userPrompt || "").length}, ` + + `systemPrompt length=${String(promptPayload.systemPrompt || "").length}, ` + + `llmSystemPrompt length=${String(llmSystemPrompt || "").length}`, + ); + if (pmUser.length > 0) { + for (const m of pmUser) { + console.log( + `[ST-BME][prompt-diag] user msg: contentLen=${String(m.content || "").length}, ` + + `blockName="${m.blockName || ""}", preview="${String(m.content || "").slice(0, 60)}..."`, + ); + } + } else { + console.warn( + `[ST-BME][prompt-diag] NO user messages in promptMessages! Fallback userPrompt will be used.`, + ); + } + } + // 调用 LLM const result = await callLLMForJSON({ systemPrompt: llmSystemPrompt, diff --git a/llm.js b/llm.js index 0c8f406..d32054d 100644 --- a/llm.js +++ b/llm.js @@ -1583,10 +1583,43 @@ export async function callLLMForJSON({ additionalMessages, promptMessages, ); + { + const asmUser = assembledMessages.filter((m) => m?.role === "user"); + console.log( + `[ST-BME][prompt-diag] buildJsonAttemptMessages: ` + + `total=${assembledMessages.length}, user=${asmUser.length}, ` + + `roles=[${assembledMessages.map((m) => m?.role).join(",")}]`, + ); + for (const m of asmUser) { + console.log( + `[ST-BME][prompt-diag] assembled user: len=${String(m.content || "").length}, ` + + `preview="${String(m.content || "").slice(0, 80)}..."`, + ); + } + } const requestCleaning = applyTaskFinalInputRegex( taskType, assembledMessages, ); + { + const rcMsgs = Array.isArray(requestCleaning.messages) ? requestCleaning.messages : []; + const rcUser = rcMsgs.filter((m) => m?.role === "user"); + const dbg = requestCleaning.debug || {}; + console.log( + `[ST-BME][prompt-diag] applyTaskFinalInputRegex: ` + + `total=${rcMsgs.length}, user=${rcUser.length}, ` + + `changed=${dbg.changed}, applied=${dbg.applied}, ` + + `roles=[${rcMsgs.map((m) => m?.role).join(",")}]`, + ); + if (rcUser.length === 0 && assembledMessages.filter((m) => m?.role === "user").length > 0) { + console.warn( + `[ST-BME][prompt-diag] *** USER MESSAGES LOST during applyTaskFinalInputRegex! ***`, + ); + for (const rule of dbg.appliedRules || []) { + console.warn(`[ST-BME][prompt-diag] applied rule: ${JSON.stringify(rule)}`); + } + } + } const promptExecutionSnapshot = attachRequestCleaningToPromptExecution( promptExecutionSummary, requestCleaning.debug, diff --git a/prompt-builder.js b/prompt-builder.js index ed727b4..c19f257 100644 --- a/prompt-builder.js +++ b/prompt-builder.js @@ -1098,6 +1098,12 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { let assistantRoleBlockCount = 0; let systemRoleBlockCount = 0; + console.log( + `[ST-BME][prompt-diag] buildTaskPrompt: taskType=${taskType}, ` + + `total blocks=${blocks.length}, ` + + `block roles=[${blocks.map((b) => `${b.name}(${b.role},${b.enabled !== false ? "on" : "off"})`).join(", ")}]`, + ); + for (const block of blocks) { if (!block || block.enabled === false) continue; @@ -1118,6 +1124,15 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { content = interpolateVariables(block.content || "", resolvedContext); } + if (role === "user") { + console.log( + `[ST-BME][prompt-diag] user block "${block.name || block.id}": ` + + `type=${block.type}, contentLen=${String(content || "").length}, ` + + `rawContentLen=${String(block.content || "").length}, ` + + `blockedContentsCount=${worldInfoRuntimeBlockedContents.length}`, + ); + } + const sanitizedBlockContent = sanitizeTaskPromptText( settings, taskType, @@ -1137,7 +1152,19 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { } content = sanitizedBlockContent.text; - if (!String(content || "").trim()) continue; + if (!String(content || "").trim()) { + if (role === "user" && String(block.content || "").trim()) { + console.warn( + `[ST-BME] buildTaskPrompt: user block "${block.name || block.id}" ` + + `content emptied during sanitization! ` + + `original length=${String(block.content || "").length}, ` + + `dropped=${sanitizedBlockContent.dropped}, ` + + `reasons=[${(sanitizedBlockContent.reasons || []).join(", ")}], ` + + `blockedHitCount=${sanitizedBlockContent.blockedHitCount}`, + ); + } + continue; + } const mode = normalizeInjectionMode(block.injectionMode); renderedBlocks.push({ @@ -1193,6 +1220,13 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { } } + console.log( + `[ST-BME][prompt-diag] buildTaskPrompt done: ` + + `executionMessages=${executionMessages.length}, ` + + `userBlocks=${userRoleBlockCount}, systemBlocks=${systemRoleBlockCount}, ` + + `customMessages=${customMessages.length}`, + ); + for (const message of worldInfoResolution.additionalMessages || []) { const executionMessage = createExecutionMessage( message.role, @@ -1361,6 +1395,39 @@ export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "") const hasUserMessage = executionMessages.some( (message) => message.role === "user", ); + if (!hasUserMessage && rawExecutionMessages.length > 0) { + const userBlocksBefore = (promptBuild?.executionMessages || []).filter( + (m) => m?.role === "user", + ); + const userBlocksAfterRaw = rawExecutionMessages.filter( + (m) => m?.role === "user", + ); + const userBlocksAfterSanitize = executionMessages.filter( + (m) => m?.role === "user", + ); + console.warn( + `[ST-BME] buildTaskLlmPayload fallback triggered: ` + + `user blocks in promptBuild=${userBlocksBefore.length}, ` + + `after recreate=${userBlocksAfterRaw.length}, ` + + `after sanitize=${userBlocksAfterSanitize.length}, ` + + `blockedContents count=${blockedContents.length}, ` + + `total executionMessages=${executionMessages.length}`, + ); + if (userBlocksBefore.length > 0) { + for (const block of userBlocksBefore) { + console.warn( + `[ST-BME] user block "${block.blockName || block.blockId}": ` + + `content length=${String(block.content || "").length}, ` + + `content preview="${String(block.content || "").slice(0, 80)}..."`, + ); + } + } + if (blockedContents.length > 0) { + console.warn( + `[ST-BME] blockedContents lengths: [${blockedContents.map((c) => String(c || "").length).join(", ")}]`, + ); + } + } const sanitizedFallbackUserPrompt = sanitizeTaskPromptText( {}, promptBuild?.debug?.taskType || "", diff --git a/prompt-profiles.js b/prompt-profiles.js index 695997e..aece4cc 100644 --- a/prompt-profiles.js +++ b/prompt-profiles.js @@ -637,15 +637,13 @@ export function normalizeTaskRegexStages(stages = {}) { for (const [legacyKey, canonicalKey] of Object.entries( TASK_REGEX_STAGE_ALIAS_MAP, )) { - if (Object.prototype.hasOwnProperty.call(source, legacyKey)) { - // Older exports may carry both legacy and canonical keys at the same - // time. When that happens, keep the legacy intent instead of letting a - // newer placeholder default silently flip stage timing. - normalized[canonicalKey] = Boolean(source[legacyKey]); + if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) { + // Respect an explicitly stored canonical key when both forms are + // present. Legacy aliases should only backfill older exports. continue; } - if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) { - normalized[canonicalKey] = Boolean(source[canonicalKey]); + if (Object.prototype.hasOwnProperty.call(source, legacyKey)) { + normalized[canonicalKey] = Boolean(source[legacyKey]); } } @@ -893,13 +891,10 @@ function createFallbackDefaultTaskProfile(taskType) { character: true, }, stages: normalizeTaskRegexStages({ - finalPrompt: true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - rawResponse: false, - beforeParse: false, "output.rawResponse": false, "output.beforeParse": false, }), @@ -954,10 +949,10 @@ export function createDefaultTaskProfile(taskType) { ...fallback.regex.sources, ...(template?.regex?.sources || {}), }, - stages: normalizeTaskRegexStages({ - ...fallback.regex.stages, - ...(template?.regex?.stages || {}), - }), + stages: { + ...normalizeTaskRegexStages(fallback.regex.stages || {}), + ...normalizeTaskRegexStages(template?.regex?.stages || {}), + }, localRules: Array.isArray(template?.regex?.localRules) ? template.regex.localRules.map((rule, index) => normalizeRegexLocalRule(rule, taskType, index), @@ -1148,10 +1143,10 @@ export function normalizeTaskProfile(taskType, profile = {}, settings = {}) { ...base.regex.sources, ...(profile?.regex?.sources || {}), }, - stages: normalizeTaskRegexStages({ - ...base.regex.stages, - ...(profile?.regex?.stages || {}), - }), + stages: { + ...normalizeTaskRegexStages(base.regex.stages || {}), + ...normalizeTaskRegexStages(profile?.regex?.stages || {}), + }, localRules: Array.isArray(profile?.regex?.localRules) ? profile.regex.localRules.map((rule, index) => normalizeRegexLocalRule(rule, taskType, index), diff --git a/tests/task-regex.mjs b/tests/task-regex.mjs index eba3653..8bad23c 100644 --- a/tests/task-regex.mjs +++ b/tests/task-regex.mjs @@ -156,6 +156,7 @@ try { const { createDefaultTaskProfiles, isTaskRegexStageEnabled, + normalizeTaskProfile, normalizeTaskRegexStages, } = await import("../prompt-profiles.js"); @@ -170,7 +171,7 @@ try { "output.rawResponse": false, "output.beforeParse": false, }); - assert.equal(normalizedLegacyStages["input.finalPrompt"], true); + assert.equal(normalizedLegacyStages["input.finalPrompt"], false); assert.equal(normalizedLegacyStages["input.userMessage"], false); assert.equal(normalizedLegacyStages["input.recentMessages"], false); assert.equal(normalizedLegacyStages["input.candidateText"], false); @@ -178,7 +179,7 @@ try { assert.equal(normalizedLegacyStages["output.beforeParse"], false); assert.equal( isTaskRegexStageEnabled(normalizedLegacyStages, "input.finalPrompt"), - true, + false, ); assert.equal( isTaskRegexStageEnabled(normalizedLegacyStages, "input.userMessage"), @@ -198,7 +199,7 @@ try { defaultProfiles.extract?.profiles?.[0]?.regex?.stages || {}; assert.equal( isTaskRegexStageEnabled(defaultExtractStages, "input.finalPrompt"), - true, + false, ); assert.equal( isTaskRegexStageEnabled(defaultExtractStages, "input.userMessage"), @@ -213,6 +214,27 @@ try { false, ); + const normalizedLegacyOnlyProfile = normalizeTaskProfile( + "extract", + { + id: "legacy-only-profile", + name: "legacy only", + regex: { + stages: { + finalPrompt: true, + }, + }, + }, + {}, + ); + assert.equal( + isTaskRegexStageEnabled( + normalizedLegacyOnlyProfile.regex?.stages || {}, + "input.finalPrompt", + ), + true, + ); + globalThis.getTavernRegexes = () => { throw new Error("legacy global getter should not be used in regex tests"); };