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.renderedBlocks)}
|
||||
${_renderDebugDetails("实际执行消息序列", promptBuild.executionMessages || promptBuild.privateTaskMessages || null)}
|
||||
${_renderDebugDetails("系统提示词(兼容视图)", promptBuild.systemPrompt || "")}
|
||||
${_renderDebugDetails("系统提示词(兼容视图,不含 atDepth 消息)", promptBuild.systemPrompt || "")}
|
||||
${_renderDebugDetails("世界书桶内容(诊断)", promptBuild.hostInjections)}
|
||||
${_renderDebugDetails("世界书块命中计划(诊断)", promptBuild.hostInjectionPlan || null)}
|
||||
${_renderDebugDetails("世界书调试", promptBuild.worldInfo?.debug || promptBuild.worldInfoResolution?.debug || null)}
|
||||
|
||||
@@ -969,6 +969,13 @@ function getBlockDiagnosticInjectionPosition(block = {}) {
|
||||
}
|
||||
|
||||
function profileRequiresWorldInfo(profile) {
|
||||
if (
|
||||
profile?.worldInfo === false ||
|
||||
profile?.metadata?.disableWorldInfo === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blocks = Array.isArray(profile?.blocks) ? profile.blocks : [];
|
||||
for (const block of blocks) {
|
||||
if (!block || block.enabled === false) continue;
|
||||
@@ -990,7 +997,10 @@ function profileRequiresWorldInfo(profile) {
|
||||
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 = {}) {
|
||||
|
||||
@@ -596,32 +596,27 @@ function normalizeRegexStageKey(stageKey = "") {
|
||||
export function normalizeTaskRegexStages(stages = {}) {
|
||||
const source =
|
||||
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(
|
||||
TASK_REGEX_STAGE_ALIAS_MAP,
|
||||
)) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(normalized, canonicalKey) &&
|
||||
Object.prototype.hasOwnProperty.call(normalized, legacyKey)
|
||||
) {
|
||||
normalized[canonicalKey] = Boolean(normalized[legacyKey]);
|
||||
}
|
||||
delete normalized[legacyKey];
|
||||
}
|
||||
|
||||
for (const [groupKey, stageKeys] of Object.entries(TASK_REGEX_STAGE_GROUPS)) {
|
||||
if (normalized[groupKey] === false) {
|
||||
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]);
|
||||
continue;
|
||||
}
|
||||
const allSpecificStagesFalse =
|
||||
stageKeys.length > 0 &&
|
||||
stageKeys.every((stageKey) => normalized[stageKey] === false);
|
||||
if (!allSpecificStagesFalse) {
|
||||
continue;
|
||||
}
|
||||
for (const stageKey of stageKeys) {
|
||||
delete normalized[stageKey];
|
||||
if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) {
|
||||
normalized[canonicalKey] = Boolean(source[canonicalKey]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,65 @@ try {
|
||||
const { applyTaskRegex, inspectTaskRegexReuse } = await import(
|
||||
"../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 = () => {
|
||||
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.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 partialBridgeCalls = [];
|
||||
const partialBridgeEntriesByWorldbook = {
|
||||
|
||||
Reference in New Issue
Block a user