From 507f3b15d3b13bf5bfcaa749269d71df2fd29743 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:40:20 +0800 Subject: [PATCH] Add MVU sanitize warning diagnostics --- prompt-builder.js | 93 +++++++++++++++++++++++++++++++++++- tests/prompt-builder-mvu.mjs | 47 ++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/prompt-builder.js b/prompt-builder.js index 004a88e..84cc14c 100644 --- a/prompt-builder.js +++ b/prompt-builder.js @@ -361,6 +361,30 @@ function joinStructuredPath(basePath = "", segment = "") { : `${basePath}.${normalizedSegment}`; } +function mergeSanitizeReasons(...reasonLists) { + const merged = new Set(); + for (const list of reasonLists) { + for (const reason of Array.isArray(list) ? list : []) { + if (reason) { + merged.add(String(reason)); + } + } + } + return [...merged]; +} + +function summarizeSanitizePreview(value, maxLength = 200) { + const rendered = stringifyInterpolatedValue(value) + .replace(/\s+/g, " ") + .trim(); + if (!rendered) { + return ""; + } + return rendered.length > maxLength + ? `${rendered.slice(0, maxLength)}...` + : rendered; +} + function looksLikeMvuStateContainer(value, seen = new WeakSet()) { if (!value || typeof value !== "object") { return false; @@ -446,12 +470,22 @@ function sanitizeStructuredPromptValue( omit: !String(sanitized.text || "").trim() && String(value || "").trim().length > 0, + dropped: Boolean(sanitized.dropped), + reasons: Array.isArray(sanitized.reasons) ? [...sanitized.reasons] : [], + blockedHitCount: Number(sanitized.blockedHitCount || 0), + artifactRemovedCount: Number(sanitized.artifactRemovedCount || 0), + rawPreview: summarizeSanitizePreview(value), + sanitizedPreview: summarizeSanitizePreview(sanitized.text), }; } if (Array.isArray(value)) { const sanitizedArray = []; let changed = false; + let dropped = false; + let blockedHitCount = 0; + let artifactRemovedCount = 0; + let reasons = []; for (let index = 0; index < value.length; index += 1) { const childResult = sanitizeStructuredPromptValue( settings, @@ -473,17 +507,31 @@ function sanitizeStructuredPromptValue( ); if (childResult.omit) { changed = true; + dropped = dropped || Boolean(childResult.dropped); + blockedHitCount += Number(childResult.blockedHitCount || 0); + artifactRemovedCount += Number(childResult.artifactRemovedCount || 0); + reasons = mergeSanitizeReasons(reasons, childResult.reasons); continue; } sanitizedArray.push(childResult.value); if (childResult.changed) { changed = true; } + dropped = dropped || Boolean(childResult.dropped); + blockedHitCount += Number(childResult.blockedHitCount || 0); + artifactRemovedCount += Number(childResult.artifactRemovedCount || 0); + reasons = mergeSanitizeReasons(reasons, childResult.reasons); } return { value: sanitizedArray, changed: changed || sanitizedArray.length !== value.length, omit: value.length > 0 && sanitizedArray.length === 0, + dropped, + reasons, + blockedHitCount, + artifactRemovedCount, + rawPreview: summarizeSanitizePreview(value), + sanitizedPreview: summarizeSanitizePreview(sanitizedArray), }; } @@ -493,6 +541,12 @@ function sanitizeStructuredPromptValue( value, changed: false, omit: false, + dropped: false, + reasons: [], + blockedHitCount: 0, + artifactRemovedCount: 0, + rawPreview: summarizeSanitizePreview(value), + sanitizedPreview: summarizeSanitizePreview(value), }; } seen.add(value); @@ -501,6 +555,10 @@ function sanitizeStructuredPromptValue( const sanitizedObject = {}; let changed = false; let keptEntries = 0; + let dropped = false; + let blockedHitCount = 0; + let artifactRemovedCount = 0; + let reasons = []; for (const [key, entryValue] of Object.entries(value)) { const stripReason = stripMvuContainers @@ -516,6 +574,8 @@ function sanitizeStructuredPromptValue( reasons: [stripReason], blockedHitCount: 0, }); + dropped = true; + reasons = mergeSanitizeReasons(reasons, [stripReason]); continue; } @@ -539,6 +599,10 @@ function sanitizeStructuredPromptValue( ); if (childResult.omit) { changed = true; + dropped = dropped || Boolean(childResult.dropped); + blockedHitCount += Number(childResult.blockedHitCount || 0); + artifactRemovedCount += Number(childResult.artifactRemovedCount || 0); + reasons = mergeSanitizeReasons(reasons, childResult.reasons); continue; } sanitizedObject[key] = childResult.value; @@ -546,12 +610,22 @@ function sanitizeStructuredPromptValue( if (childResult.changed) { changed = true; } + dropped = dropped || Boolean(childResult.dropped); + blockedHitCount += Number(childResult.blockedHitCount || 0); + artifactRemovedCount += Number(childResult.artifactRemovedCount || 0); + reasons = mergeSanitizeReasons(reasons, childResult.reasons); } return { value: sanitizedObject, changed, omit: originalLooksMvuContainer && keptEntries === 0, + dropped, + reasons, + blockedHitCount, + artifactRemovedCount, + rawPreview: summarizeSanitizePreview(value), + sanitizedPreview: summarizeSanitizePreview(sanitizedObject), }; } @@ -559,6 +633,12 @@ function sanitizeStructuredPromptValue( value, changed: false, omit: false, + dropped: false, + reasons: [], + blockedHitCount: 0, + artifactRemovedCount: 0, + rawPreview: summarizeSanitizePreview(value), + sanitizedPreview: summarizeSanitizePreview(value), }; } @@ -665,7 +745,18 @@ function sanitizePromptContextInputs( const rawLength = typeof value === "string" ? value.length : -1; console.warn( "[ST-BME] 关键任务输入字段被 MVU 策略清空", - { taskType, fieldName, mode: fieldMode, rawLength }, + { + taskType, + fieldName, + mode: fieldMode, + rawLength, + dropped: Boolean(sanitized.dropped), + reasons: Array.isArray(sanitized.reasons) ? sanitized.reasons : [], + artifactRemovedCount: Number(sanitized.artifactRemovedCount || 0), + blockedHitCount: Number(sanitized.blockedHitCount || 0), + rawPreview: String(sanitized.rawPreview || ""), + sanitizedPreview: String(sanitized.sanitizedPreview || ""), + }, ); } sanitizedContext[fieldName] = sanitized.omit diff --git a/tests/prompt-builder-mvu.mjs b/tests/prompt-builder-mvu.mjs index 30ba0d5..c47a63c 100644 --- a/tests/prompt-builder-mvu.mjs +++ b/tests/prompt-builder-mvu.mjs @@ -804,6 +804,53 @@ try { } } + // 测试 6c:warn 诊断字段包含 reasons 和 before/after preview + { + delete globalThis.__stBmeRuntimeDebugState; + const warnCalls = []; + const originalWarn = console.warn; + console.warn = (...args) => warnCalls.push(args); + try { + const s = buildMinimalExtractSettings(); + await buildTaskPrompt(s, "extract", { + recentMessages: + "{{get_message_variable::stat_data.hp}}\n{{get_message_variable::display_data.hp}}", + charDescription: "", + userPersona: "", + candidateText: "", + }); + const criticalDropWarn = warnCalls.find( + (args) => String(args[0] || "").includes("关键任务输入字段被 MVU 策略清空"), + ); + assert.ok(criticalDropWarn, "T6c: 清洗后为空时应触发关键字段 warn"); + assert.equal(criticalDropWarn[1]?.fieldName, "recentMessages", + "T6c: warn 应指向 recentMessages"); + assert.equal(criticalDropWarn[1]?.mode, "passive", + "T6c: recentMessages 应以 passive mode 清洗"); + assert.ok( + Array.isArray(criticalDropWarn[1]?.reasons) && + criticalDropWarn[1].reasons.includes("artifact_stripped"), + "T6c: warn 应携带 artifact_stripped reason", + ); + assert.match( + String(criticalDropWarn[1]?.rawPreview || ""), + /get_message_variable/, + "T6c: warn 应携带原始内容 preview", + ); + assert.equal( + String(criticalDropWarn[1]?.sanitizedPreview || ""), + "", + "T6c: 清洗为空时 sanitizedPreview 应为空串", + ); + assert.ok( + Number(criticalDropWarn[1]?.artifactRemovedCount || 0) >= 2, + "T6c: warn 应记录 artifactRemovedCount", + ); + } finally { + console.warn = originalWarn; + } + } + console.log("prompt-builder-mvu tests passed"); } finally { if (originalRequire === undefined) {