mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix regex stage timing and implicit world info resolution
This commit is contained in:
2
panel.js
2
panel.js
@@ -3926,7 +3926,7 @@ function _renderTaskDebugPromptCard(taskType, promptBuild) {
|
|||||||
${_renderDebugDetails("实际投递路径", promptBuild.debug?.effectivePath || null)}
|
${_renderDebugDetails("实际投递路径", promptBuild.debug?.effectivePath || null)}
|
||||||
${_renderDebugDetails("渲染后的块(按配置顺序)", promptBuild.renderedBlocks)}
|
${_renderDebugDetails("渲染后的块(按配置顺序)", promptBuild.renderedBlocks)}
|
||||||
${_renderDebugDetails("实际执行消息序列", promptBuild.executionMessages || promptBuild.privateTaskMessages || null)}
|
${_renderDebugDetails("实际执行消息序列", promptBuild.executionMessages || promptBuild.privateTaskMessages || null)}
|
||||||
${_renderDebugDetails("系统提示词(兼容视图)", promptBuild.systemPrompt || "")}
|
${_renderDebugDetails("系统提示词(兼容视图,不含 atDepth 消息)", promptBuild.systemPrompt || "")}
|
||||||
${_renderDebugDetails("世界书桶内容(诊断)", promptBuild.hostInjections)}
|
${_renderDebugDetails("世界书桶内容(诊断)", promptBuild.hostInjections)}
|
||||||
${_renderDebugDetails("世界书块命中计划(诊断)", promptBuild.hostInjectionPlan || null)}
|
${_renderDebugDetails("世界书块命中计划(诊断)", promptBuild.hostInjectionPlan || null)}
|
||||||
${_renderDebugDetails("世界书调试", promptBuild.worldInfo?.debug || promptBuild.worldInfoResolution?.debug || null)}
|
${_renderDebugDetails("世界书调试", promptBuild.worldInfo?.debug || promptBuild.worldInfoResolution?.debug || null)}
|
||||||
|
|||||||
@@ -969,6 +969,13 @@ function getBlockDiagnosticInjectionPosition(block = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function profileRequiresWorldInfo(profile) {
|
function profileRequiresWorldInfo(profile) {
|
||||||
|
if (
|
||||||
|
profile?.worldInfo === false ||
|
||||||
|
profile?.metadata?.disableWorldInfo === true
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const blocks = Array.isArray(profile?.blocks) ? profile.blocks : [];
|
const blocks = Array.isArray(profile?.blocks) ? profile.blocks : [];
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
if (!block || block.enabled === false) continue;
|
if (!block || block.enabled === false) continue;
|
||||||
@@ -990,7 +997,10 @@ function profileRequiresWorldInfo(profile) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
// atDepth world info is implicit in the final message chain, so profiles
|
||||||
|
// without explicit before/after placeholders should still resolve lore.
|
||||||
|
return blocks.some((block) => block && block.enabled !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractWorldInfoChatMessages(context = {}) {
|
function extractWorldInfoChatMessages(context = {}) {
|
||||||
|
|||||||
@@ -596,32 +596,27 @@ function normalizeRegexStageKey(stageKey = "") {
|
|||||||
export function normalizeTaskRegexStages(stages = {}) {
|
export function normalizeTaskRegexStages(stages = {}) {
|
||||||
const source =
|
const source =
|
||||||
stages && typeof stages === "object" && !Array.isArray(stages) ? stages : {};
|
stages && typeof stages === "object" && !Array.isArray(stages) ? stages : {};
|
||||||
const normalized = { ...source };
|
const normalized = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(source)) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(TASK_REGEX_STAGE_ALIAS_MAP, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
normalized[key] = Boolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [legacyKey, canonicalKey] of Object.entries(
|
for (const [legacyKey, canonicalKey] of Object.entries(
|
||||||
TASK_REGEX_STAGE_ALIAS_MAP,
|
TASK_REGEX_STAGE_ALIAS_MAP,
|
||||||
)) {
|
)) {
|
||||||
if (
|
if (Object.prototype.hasOwnProperty.call(source, legacyKey)) {
|
||||||
!Object.prototype.hasOwnProperty.call(normalized, canonicalKey) &&
|
// Older exports may carry both legacy and canonical keys at the same
|
||||||
Object.prototype.hasOwnProperty.call(normalized, legacyKey)
|
// time. When that happens, keep the legacy intent instead of letting a
|
||||||
) {
|
// newer placeholder default silently flip stage timing.
|
||||||
normalized[canonicalKey] = Boolean(normalized[legacyKey]);
|
normalized[canonicalKey] = Boolean(source[legacyKey]);
|
||||||
}
|
|
||||||
delete normalized[legacyKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [groupKey, stageKeys] of Object.entries(TASK_REGEX_STAGE_GROUPS)) {
|
|
||||||
if (normalized[groupKey] === false) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const allSpecificStagesFalse =
|
if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) {
|
||||||
stageKeys.length > 0 &&
|
normalized[canonicalKey] = Boolean(source[canonicalKey]);
|
||||||
stageKeys.every((stageKey) => normalized[stageKey] === false);
|
|
||||||
if (!allSpecificStagesFalse) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const stageKey of stageKeys) {
|
|
||||||
delete normalized[stageKey];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,65 @@ try {
|
|||||||
const { applyTaskRegex, inspectTaskRegexReuse } = await import(
|
const { applyTaskRegex, inspectTaskRegexReuse } = await import(
|
||||||
"../task-regex.js"
|
"../task-regex.js"
|
||||||
);
|
);
|
||||||
|
const {
|
||||||
|
createDefaultTaskProfiles,
|
||||||
|
isTaskRegexStageEnabled,
|
||||||
|
normalizeTaskRegexStages,
|
||||||
|
} = await import("../prompt-profiles.js");
|
||||||
|
|
||||||
|
const normalizedLegacyStages = 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,
|
||||||
|
});
|
||||||
|
assert.equal(normalizedLegacyStages["input.finalPrompt"], true);
|
||||||
|
assert.equal(normalizedLegacyStages["input.userMessage"], false);
|
||||||
|
assert.equal(normalizedLegacyStages["input.recentMessages"], false);
|
||||||
|
assert.equal(normalizedLegacyStages["input.candidateText"], false);
|
||||||
|
assert.equal(normalizedLegacyStages["output.rawResponse"], false);
|
||||||
|
assert.equal(normalizedLegacyStages["output.beforeParse"], false);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(normalizedLegacyStages, "input.finalPrompt"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(normalizedLegacyStages, "input.userMessage"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(normalizedLegacyStages, "input.recentMessages"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(normalizedLegacyStages, "input.candidateText"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultProfiles = createDefaultTaskProfiles();
|
||||||
|
const defaultExtractStages =
|
||||||
|
defaultProfiles.extract?.profiles?.[0]?.regex?.stages || {};
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(defaultExtractStages, "input.finalPrompt"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(defaultExtractStages, "input.userMessage"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(defaultExtractStages, "input.recentMessages"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
isTaskRegexStageEnabled(defaultExtractStages, "input.candidateText"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
globalThis.getTavernRegexes = () => {
|
globalThis.getTavernRegexes = () => {
|
||||||
throw new Error("legacy global getter should not be used in regex tests");
|
throw new Error("legacy global getter should not be used in regex tests");
|
||||||
|
|||||||
@@ -456,6 +456,56 @@ try {
|
|||||||
assert.equal(promptBuild.additionalMessages[0].content, "这是一条 atDepth 消息。");
|
assert.equal(promptBuild.additionalMessages[0].content, "这是一条 atDepth 消息。");
|
||||||
assert.equal(promptBuild.debug.mvu.sanitizedFieldCount >= 0, true);
|
assert.equal(promptBuild.debug.mvu.sanitizedFieldCount >= 0, true);
|
||||||
|
|
||||||
|
const noWorldInfoBlockSettings = {
|
||||||
|
taskProfiles: {
|
||||||
|
recall: {
|
||||||
|
activeProfileId: "custom",
|
||||||
|
profiles: [
|
||||||
|
{
|
||||||
|
id: "custom",
|
||||||
|
name: "无世界书显式块",
|
||||||
|
taskType: "recall",
|
||||||
|
builtin: false,
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
id: "u1",
|
||||||
|
type: "custom",
|
||||||
|
content: "角色: {{charName}}",
|
||||||
|
role: "user",
|
||||||
|
enabled: true,
|
||||||
|
order: 0,
|
||||||
|
injectionMode: "append",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const atDepthOnlyPromptBuild = await buildTaskPrompt(
|
||||||
|
noWorldInfoBlockSettings,
|
||||||
|
"recall",
|
||||||
|
{
|
||||||
|
taskName: "recall",
|
||||||
|
userMessage: "继续调查",
|
||||||
|
recentMessages: "我们继续调查那条线索",
|
||||||
|
charName: "Alice",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(atDepthOnlyPromptBuild.debug.worldInfoRequested, true);
|
||||||
|
assert.equal(atDepthOnlyPromptBuild.debug.worldInfoAtDepthCount, 1);
|
||||||
|
assert.equal(atDepthOnlyPromptBuild.additionalMessages.length, 1);
|
||||||
|
assert.equal(
|
||||||
|
atDepthOnlyPromptBuild.additionalMessages[0].content,
|
||||||
|
"这是一条 atDepth 消息。",
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
atDepthOnlyPromptBuild.executionMessages.map((message) => message.role),
|
||||||
|
["user", "system"],
|
||||||
|
);
|
||||||
|
|
||||||
const { initializeHostAdapter } = await import("../host-adapter/index.js");
|
const { initializeHostAdapter } = await import("../host-adapter/index.js");
|
||||||
const partialBridgeCalls = [];
|
const partialBridgeCalls = [];
|
||||||
const partialBridgeEntriesByWorldbook = {
|
const partialBridgeEntriesByWorldbook = {
|
||||||
|
|||||||
Reference in New Issue
Block a user