mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix task regex final input pipeline
This commit is contained in:
166
llm.js
166
llm.js
@@ -215,6 +215,104 @@ function applyTaskOutputRegexStages(taskType, text) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyTaskFinalInputRegex(taskType, messages = []) {
|
||||||
|
const normalizedMessages = (Array.isArray(messages) ? messages : [])
|
||||||
|
.map((message) => {
|
||||||
|
if (!message || typeof message !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const role = String(message.role || "").trim().toLowerCase();
|
||||||
|
if (!["system", "user", "assistant"].includes(role)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
role,
|
||||||
|
content: String(message.content || ""),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
const normalizedTaskType = String(taskType || "").trim();
|
||||||
|
|
||||||
|
if (!normalizedTaskType || normalizedMessages.length === 0) {
|
||||||
|
const cleanedMessages = normalizedMessages.filter((message) =>
|
||||||
|
String(message.content || "").trim(),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
messages: cleanedMessages,
|
||||||
|
debug: {
|
||||||
|
stage: "input.finalPrompt",
|
||||||
|
changed: cleanedMessages.length !== normalizedMessages.length,
|
||||||
|
applied: false,
|
||||||
|
rawMessageCount: normalizedMessages.length,
|
||||||
|
cleanedMessageCount: cleanedMessages.length,
|
||||||
|
droppedMessageCount: normalizedMessages.length - cleanedMessages.length,
|
||||||
|
stages: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = extension_settings[MODULE_NAME] || {};
|
||||||
|
const regexDebug = { entries: [] };
|
||||||
|
let changed = false;
|
||||||
|
let droppedMessageCount = 0;
|
||||||
|
const cleanedMessages = normalizedMessages
|
||||||
|
.map((message) => {
|
||||||
|
const originalContent = String(message.content || "");
|
||||||
|
const cleanedContent = applyTaskRegex(
|
||||||
|
settings,
|
||||||
|
normalizedTaskType,
|
||||||
|
"input.finalPrompt",
|
||||||
|
originalContent,
|
||||||
|
regexDebug,
|
||||||
|
message.role,
|
||||||
|
);
|
||||||
|
if (cleanedContent !== originalContent) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (!String(cleanedContent || "").trim()) {
|
||||||
|
droppedMessageCount += 1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
content: cleanedContent,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
const normalizedEntries = normalizeRegexDebugEntries(regexDebug);
|
||||||
|
const applied = normalizedEntries.some(
|
||||||
|
(entry) => entry.appliedRules.length > 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: cleanedMessages,
|
||||||
|
debug: {
|
||||||
|
stage: "input.finalPrompt",
|
||||||
|
changed: changed || droppedMessageCount > 0,
|
||||||
|
applied,
|
||||||
|
rawMessageCount: normalizedMessages.length,
|
||||||
|
cleanedMessageCount: cleanedMessages.length,
|
||||||
|
droppedMessageCount,
|
||||||
|
stages: normalizedEntries,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachRequestCleaningToPromptExecution(
|
||||||
|
promptExecutionSummary,
|
||||||
|
requestCleaning,
|
||||||
|
) {
|
||||||
|
const base =
|
||||||
|
promptExecutionSummary && typeof promptExecutionSummary === "object"
|
||||||
|
? cloneRuntimeDebugValue(promptExecutionSummary, {})
|
||||||
|
: {};
|
||||||
|
if (requestCleaning && typeof requestCleaning === "object") {
|
||||||
|
base.requestCleaning = cloneRuntimeDebugValue(requestCleaning, null);
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
function buildEffectiveLlmRoute(
|
function buildEffectiveLlmRoute(
|
||||||
hasDedicatedConfig,
|
hasDedicatedConfig,
|
||||||
privateRequestSource,
|
privateRequestSource,
|
||||||
@@ -1477,7 +1575,7 @@ export async function callLLMForJSON({
|
|||||||
|
|
||||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
const messages = buildJsonAttemptMessages(
|
const assembledMessages = buildJsonAttemptMessages(
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
userPrompt,
|
userPrompt,
|
||||||
attempt,
|
attempt,
|
||||||
@@ -1485,7 +1583,25 @@ export async function callLLMForJSON({
|
|||||||
additionalMessages,
|
additionalMessages,
|
||||||
promptMessages,
|
promptMessages,
|
||||||
);
|
);
|
||||||
const response = await callDedicatedOpenAICompatible(messages, {
|
const requestCleaning = applyTaskFinalInputRegex(
|
||||||
|
taskType,
|
||||||
|
assembledMessages,
|
||||||
|
);
|
||||||
|
const promptExecutionSnapshot = attachRequestCleaningToPromptExecution(
|
||||||
|
promptExecutionSummary,
|
||||||
|
requestCleaning.debug,
|
||||||
|
);
|
||||||
|
recordTaskLlmRequest(
|
||||||
|
taskType || privateRequestSource,
|
||||||
|
{
|
||||||
|
requestCleaning: requestCleaning.debug,
|
||||||
|
promptExecution: promptExecutionSnapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
merge: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const response = await callDedicatedOpenAICompatible(requestCleaning.messages, {
|
||||||
signal,
|
signal,
|
||||||
jsonMode: true,
|
jsonMode: true,
|
||||||
taskType,
|
taskType,
|
||||||
@@ -1500,8 +1616,9 @@ export async function callLLMForJSON({
|
|||||||
recordTaskLlmRequest(
|
recordTaskLlmRequest(
|
||||||
taskType || privateRequestSource,
|
taskType || privateRequestSource,
|
||||||
{
|
{
|
||||||
|
requestCleaning: requestCleaning.debug,
|
||||||
responseCleaning: outputCleanup.debug,
|
responseCleaning: outputCleanup.debug,
|
||||||
promptExecution: promptExecutionSummary,
|
promptExecution: promptExecutionSnapshot,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
merge: true,
|
merge: true,
|
||||||
@@ -1592,19 +1709,48 @@ export async function callLLM(systemPrompt, userPrompt, options = {}) {
|
|||||||
return await override(systemPrompt, userPrompt, options);
|
return await override(systemPrompt, userPrompt, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = [
|
const taskType = String(options.taskType || "").trim();
|
||||||
|
const privateRequestSource = resolvePrivateRequestSource(
|
||||||
|
taskType,
|
||||||
|
options.requestSource || options.source || "diagnostic:call-llm",
|
||||||
|
{ allowAnonymous: true },
|
||||||
|
);
|
||||||
|
const promptExecutionSummary = buildPromptExecutionSummary(
|
||||||
|
options.debugContext || null,
|
||||||
|
);
|
||||||
|
const assembledMessages = [
|
||||||
{ role: "system", content: systemPrompt },
|
{ role: "system", content: systemPrompt },
|
||||||
{ role: "user", content: userPrompt },
|
{ role: "user", content: userPrompt },
|
||||||
];
|
];
|
||||||
|
const requestCleaning = applyTaskFinalInputRegex(taskType, assembledMessages);
|
||||||
|
const promptExecutionSnapshot = attachRequestCleaningToPromptExecution(
|
||||||
|
promptExecutionSummary,
|
||||||
|
requestCleaning.debug,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await callDedicatedOpenAICompatible(messages, {
|
recordTaskLlmRequest(taskType || privateRequestSource, {
|
||||||
signal: options.signal,
|
requestCleaning: requestCleaning.debug,
|
||||||
taskType: options.taskType || "",
|
promptExecution: promptExecutionSnapshot,
|
||||||
requestSource:
|
}, {
|
||||||
options.requestSource || options.source || "diagnostic:call-llm",
|
merge: true,
|
||||||
});
|
});
|
||||||
return response?.content || null;
|
const response = await callDedicatedOpenAICompatible(requestCleaning.messages, {
|
||||||
|
signal: options.signal,
|
||||||
|
taskType,
|
||||||
|
requestSource: privateRequestSource,
|
||||||
|
});
|
||||||
|
const responseText =
|
||||||
|
typeof response?.content === "string" ? response.content : "";
|
||||||
|
const outputCleanup = applyTaskOutputRegexStages(taskType, responseText);
|
||||||
|
recordTaskLlmRequest(taskType || privateRequestSource, {
|
||||||
|
requestCleaning: requestCleaning.debug,
|
||||||
|
responseCleaning: outputCleanup.debug,
|
||||||
|
promptExecution: promptExecutionSnapshot,
|
||||||
|
}, {
|
||||||
|
merge: true,
|
||||||
|
});
|
||||||
|
return outputCleanup.cleanedText || null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[ST-BME] LLM 调用失败:", e);
|
console.error("[ST-BME] LLM 调用失败:", e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
68
panel.js
68
panel.js
@@ -20,6 +20,8 @@ import {
|
|||||||
getLegacyPromptFieldForTask,
|
getLegacyPromptFieldForTask,
|
||||||
getTaskTypeOptions,
|
getTaskTypeOptions,
|
||||||
importTaskProfile as parseImportedTaskProfile,
|
importTaskProfile as parseImportedTaskProfile,
|
||||||
|
isTaskRegexStageEnabled,
|
||||||
|
normalizeTaskRegexStages,
|
||||||
restoreDefaultTaskProfile,
|
restoreDefaultTaskProfile,
|
||||||
setActiveTaskProfileId,
|
setActiveTaskProfileId,
|
||||||
upsertTaskProfile,
|
upsertTaskProfile,
|
||||||
@@ -143,8 +145,46 @@ const TASK_PROFILE_GENERATION_GROUPS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const TASK_PROFILE_REGEX_STAGES = [
|
const TASK_PROFILE_REGEX_STAGES = [
|
||||||
{ key: "input", label: "输入阶段", desc: "对发送给 LLM 的 prompt 执行正则替换。" },
|
{
|
||||||
{ key: "output", label: "输出阶段", desc: "对 LLM 返回的结果执行正则替换。" },
|
key: "input",
|
||||||
|
label: "输入总开关",
|
||||||
|
desc: "控制全部输入阶段;未单独覆写的细分阶段会跟随它。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "input.userMessage",
|
||||||
|
label: "输入: 用户消息",
|
||||||
|
desc: "处理当前 userMessage。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "input.recentMessages",
|
||||||
|
label: "输入: 最近上下文",
|
||||||
|
desc: "处理 recentMessages、chatMessages、dialogueText。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "input.candidateText",
|
||||||
|
label: "输入: 候选与摘要",
|
||||||
|
desc: "处理 candidateText、candidateNodes、nodeContent 和各类摘要。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "input.finalPrompt",
|
||||||
|
label: "输入: 发送前最终消息",
|
||||||
|
desc: "在最终 messages 全部组装完成、真正发送给 LLM 前统一清洗。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "output",
|
||||||
|
label: "输出总开关",
|
||||||
|
desc: "控制全部输出阶段;未单独覆写的细分阶段会跟随它。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "output.rawResponse",
|
||||||
|
label: "输出: 原始响应",
|
||||||
|
desc: "LLM 原始文本到手后先清洗一次。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "output.beforeParse",
|
||||||
|
label: "输出: 解析前",
|
||||||
|
desc: "在 JSON 提取/解析前再清洗一次。",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let panelEl = null;
|
let panelEl = null;
|
||||||
@@ -3342,6 +3382,7 @@ function _renderTaskGenerationTab(state) {
|
|||||||
|
|
||||||
function _renderTaskRegexTab(state) {
|
function _renderTaskRegexTab(state) {
|
||||||
const regex = state.profile.regex || {};
|
const regex = state.profile.regex || {};
|
||||||
|
const normalizedStages = normalizeTaskRegexStages(regex.stages || {});
|
||||||
return `
|
return `
|
||||||
<div class="bme-task-tab-body">
|
<div class="bme-task-tab-body">
|
||||||
<div class="bme-task-regex-top">
|
<div class="bme-task-regex-top">
|
||||||
@@ -3415,14 +3456,14 @@ function _renderTaskRegexTab(state) {
|
|||||||
<span class="bme-toggle-title">${_escHtml(stage.label)}</span>
|
<span class="bme-toggle-title">${_escHtml(stage.label)}</span>
|
||||||
<span class="bme-toggle-desc">${_escHtml(stage.desc)}</span>
|
<span class="bme-toggle-desc">${_escHtml(stage.desc)}</span>
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
data-regex-stage="${_escAttr(stage.key)}"
|
data-regex-stage="${_escAttr(stage.key)}"
|
||||||
${(regex.stages?.[stage.key] ?? true) ? "checked" : ""}
|
${isTaskRegexStageEnabled(normalizedStages, stage.key) ? "checked" : ""}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
`,
|
`,
|
||||||
).join("")}
|
).join("")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -3754,8 +3795,13 @@ function _renderTaskDebugLlmCard(taskType, llmRequest) {
|
|||||||
<span class="bme-debug-kv-key">输出清洗</span>
|
<span class="bme-debug-kv-key">输出清洗</span>
|
||||||
<span class="bme-debug-kv-value">${_escHtml(llmRequest.responseCleaning?.applied ? "已生效" : "未生效")}</span>
|
<span class="bme-debug-kv-value">${_escHtml(llmRequest.responseCleaning?.applied ? "已生效" : "未生效")}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bme-debug-kv-item">
|
||||||
|
<span class="bme-debug-kv-key">发送前输入清洗</span>
|
||||||
|
<span class="bme-debug-kv-value">${_escHtml(llmRequest.requestCleaning?.applied ? "已生效" : "未生效")}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${_renderDebugDetails("提示词执行摘要", llmRequest.promptExecution || null)}
|
${_renderDebugDetails("提示词执行摘要", llmRequest.promptExecution || null)}
|
||||||
|
${_renderDebugDetails("发送前输入清洗", llmRequest.requestCleaning || null)}
|
||||||
${_renderDebugDetails("实际请求路径", llmRequest.effectiveRoute || null)}
|
${_renderDebugDetails("实际请求路径", llmRequest.effectiveRoute || null)}
|
||||||
${_renderDebugDetails("输出清洗", llmRequest.responseCleaning || null)}
|
${_renderDebugDetails("输出清洗", llmRequest.responseCleaning || null)}
|
||||||
${_renderDebugDetails("实际保留参数", llmRequest.filteredGeneration || {})}
|
${_renderDebugDetails("实际保留参数", llmRequest.filteredGeneration || {})}
|
||||||
@@ -4623,7 +4669,7 @@ function _normalizeTaskProfileDraft(profile = {}) {
|
|||||||
stages: {
|
stages: {
|
||||||
input: true,
|
input: true,
|
||||||
output: true,
|
output: true,
|
||||||
...(draft.regex?.stages || {}),
|
...normalizeTaskRegexStages(draft.regex?.stages || {}),
|
||||||
},
|
},
|
||||||
localRules: Array.isArray(draft.regex?.localRules)
|
localRules: Array.isArray(draft.regex?.localRules)
|
||||||
? draft.regex.localRules.map((rule) => ({
|
? draft.regex.localRules.map((rule) => ({
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ function sanitizePromptMessages(
|
|||||||
messages = [],
|
messages = [],
|
||||||
{
|
{
|
||||||
blockedContents = [],
|
blockedContents = [],
|
||||||
regexStage = "input.finalPrompt",
|
regexStage = "",
|
||||||
debugState = null,
|
debugState = null,
|
||||||
regexCollector = null,
|
regexCollector = null,
|
||||||
} = {},
|
} = {},
|
||||||
@@ -646,7 +646,7 @@ function sanitizeWorldInfoEntries(
|
|||||||
{
|
{
|
||||||
mode: "aggressive",
|
mode: "aggressive",
|
||||||
blockedContents,
|
blockedContents,
|
||||||
regexStage: "input.finalPrompt",
|
regexStage: "",
|
||||||
role: entry?.role || "system",
|
role: entry?.role || "system",
|
||||||
regexCollector,
|
regexCollector,
|
||||||
},
|
},
|
||||||
@@ -728,7 +728,7 @@ function sanitizeWorldInfoContext(
|
|||||||
{
|
{
|
||||||
mode: "aggressive",
|
mode: "aggressive",
|
||||||
blockedContents: runtimeBlockedContents,
|
blockedContents: runtimeBlockedContents,
|
||||||
regexStage: "input.finalPrompt",
|
regexStage: "",
|
||||||
role: message?.role || "system",
|
role: message?.role || "system",
|
||||||
regexCollector,
|
regexCollector,
|
||||||
},
|
},
|
||||||
@@ -1107,7 +1107,7 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
|
|||||||
{
|
{
|
||||||
mode: "final-safe",
|
mode: "final-safe",
|
||||||
blockedContents: worldInfoRuntimeBlockedContents,
|
blockedContents: worldInfoRuntimeBlockedContents,
|
||||||
regexStage: "input.finalPrompt",
|
regexStage: "",
|
||||||
role,
|
role,
|
||||||
regexCollector: promptRegexInput,
|
regexCollector: promptRegexInput,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -569,6 +569,88 @@ function normalizeRegexLocalRule(rule = {}, taskType = "task", index = 0) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TASK_REGEX_STAGE_ALIAS_MAP = Object.freeze({
|
||||||
|
finalPrompt: "input.finalPrompt",
|
||||||
|
rawResponse: "output.rawResponse",
|
||||||
|
beforeParse: "output.beforeParse",
|
||||||
|
});
|
||||||
|
|
||||||
|
const TASK_REGEX_STAGE_GROUPS = Object.freeze({
|
||||||
|
input: Object.freeze([
|
||||||
|
"input.userMessage",
|
||||||
|
"input.recentMessages",
|
||||||
|
"input.candidateText",
|
||||||
|
"input.finalPrompt",
|
||||||
|
]),
|
||||||
|
output: Object.freeze([
|
||||||
|
"output.rawResponse",
|
||||||
|
"output.beforeParse",
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeRegexStageKey(stageKey = "") {
|
||||||
|
const normalized = String(stageKey || "").trim();
|
||||||
|
return TASK_REGEX_STAGE_ALIAS_MAP[normalized] || normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTaskRegexStages(stages = {}) {
|
||||||
|
const source =
|
||||||
|
stages && typeof stages === "object" && !Array.isArray(stages) ? stages : {};
|
||||||
|
const normalized = { ...source };
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const allSpecificStagesFalse =
|
||||||
|
stageKeys.length > 0 &&
|
||||||
|
stageKeys.every((stageKey) => normalized[stageKey] === false);
|
||||||
|
if (!allSpecificStagesFalse) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const stageKey of stageKeys) {
|
||||||
|
delete normalized[stageKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTaskRegexStageEnabled(stages = {}, stageKey = "") {
|
||||||
|
const normalizedStages = normalizeTaskRegexStages(stages);
|
||||||
|
const normalizedStageKey = normalizeRegexStageKey(stageKey);
|
||||||
|
|
||||||
|
if (!normalizedStageKey) {
|
||||||
|
return normalizedStages.input !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(normalizedStages, normalizedStageKey)) {
|
||||||
|
return normalizedStages[normalizedStageKey] !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedStageKey.startsWith("input.")) {
|
||||||
|
return normalizedStages.input !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedStageKey.startsWith("output.")) {
|
||||||
|
return normalizedStages.output !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedStages[normalizedStageKey] !== false;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTaskProfilesState(taskProfiles = {}) {
|
function normalizeTaskProfilesState(taskProfiles = {}) {
|
||||||
return ensureTaskProfiles({ taskProfiles });
|
return ensureTaskProfiles({ taskProfiles });
|
||||||
}
|
}
|
||||||
@@ -741,7 +823,7 @@ function createFallbackDefaultTaskProfile(taskType) {
|
|||||||
preset: true,
|
preset: true,
|
||||||
character: true,
|
character: true,
|
||||||
},
|
},
|
||||||
stages: {
|
stages: normalizeTaskRegexStages({
|
||||||
finalPrompt: true,
|
finalPrompt: true,
|
||||||
"input.userMessage": false,
|
"input.userMessage": false,
|
||||||
"input.recentMessages": false,
|
"input.recentMessages": false,
|
||||||
@@ -751,7 +833,7 @@ function createFallbackDefaultTaskProfile(taskType) {
|
|||||||
beforeParse: false,
|
beforeParse: false,
|
||||||
"output.rawResponse": false,
|
"output.rawResponse": false,
|
||||||
"output.beforeParse": false,
|
"output.beforeParse": false,
|
||||||
},
|
}),
|
||||||
localRules: [],
|
localRules: [],
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -799,10 +881,10 @@ export function createDefaultTaskProfile(taskType) {
|
|||||||
...fallback.regex.sources,
|
...fallback.regex.sources,
|
||||||
...(template?.regex?.sources || {}),
|
...(template?.regex?.sources || {}),
|
||||||
},
|
},
|
||||||
stages: {
|
stages: normalizeTaskRegexStages({
|
||||||
...fallback.regex.stages,
|
...fallback.regex.stages,
|
||||||
...(template?.regex?.stages || {}),
|
...(template?.regex?.stages || {}),
|
||||||
},
|
}),
|
||||||
localRules: Array.isArray(template?.regex?.localRules)
|
localRules: Array.isArray(template?.regex?.localRules)
|
||||||
? template.regex.localRules.map((rule, index) =>
|
? template.regex.localRules.map((rule, index) =>
|
||||||
normalizeRegexLocalRule(rule, taskType, index),
|
normalizeRegexLocalRule(rule, taskType, index),
|
||||||
@@ -978,10 +1060,10 @@ export function normalizeTaskProfile(taskType, profile = {}, settings = {}) {
|
|||||||
...base.regex.sources,
|
...base.regex.sources,
|
||||||
...(profile?.regex?.sources || {}),
|
...(profile?.regex?.sources || {}),
|
||||||
},
|
},
|
||||||
stages: {
|
stages: normalizeTaskRegexStages({
|
||||||
...base.regex.stages,
|
...base.regex.stages,
|
||||||
...(profile?.regex?.stages || {}),
|
...(profile?.regex?.stages || {}),
|
||||||
},
|
}),
|
||||||
localRules: Array.isArray(profile?.regex?.localRules)
|
localRules: Array.isArray(profile?.regex?.localRules)
|
||||||
? profile.regex.localRules.map((rule, index) =>
|
? profile.regex.localRules.map((rule, index) =>
|
||||||
normalizeRegexLocalRule(rule, taskType, index),
|
normalizeRegexLocalRule(rule, taskType, index),
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
import { extension_settings, getContext } from "../../../extensions.js";
|
import { extension_settings, getContext } from "../../../extensions.js";
|
||||||
import { getHostAdapter } from "./host-adapter/index.js";
|
import { getHostAdapter } from "./host-adapter/index.js";
|
||||||
import { getActiveTaskProfile } from "./prompt-profiles.js";
|
import {
|
||||||
|
getActiveTaskProfile,
|
||||||
|
isTaskRegexStageEnabled,
|
||||||
|
normalizeTaskRegexStages,
|
||||||
|
} from "./prompt-profiles.js";
|
||||||
|
|
||||||
const HTML_TAG_PATTERN =
|
const HTML_TAG_PATTERN =
|
||||||
/<\/?(?:div|span|p|br|hr|img|details|summary|section|article|aside|header|footer|nav|ul|ol|li|table|tr|td|th|h[1-6]|a|em|strong|blockquote|pre|code|svg|path)\b/i;
|
/<\/?(?:div|span|p|br|hr|img|details|summary|section|article|aside|header|footer|nav|ul|ol|li|table|tr|td|th|h[1-6]|a|em|strong|blockquote|pre|code|svg|path)\b/i;
|
||||||
@@ -320,26 +324,19 @@ function collectLocalRules(regexConfig = {}) {
|
|||||||
|
|
||||||
function shouldApplyRuleForStage(rule, stage = "", stagesConfig = {}) {
|
function shouldApplyRuleForStage(rule, stage = "", stagesConfig = {}) {
|
||||||
const normalizedStage = String(stage || "").trim();
|
const normalizedStage = String(stage || "").trim();
|
||||||
if (
|
if (rule.destinationFlags.prompt === false) {
|
||||||
normalizedStage &&
|
return false;
|
||||||
Object.prototype.hasOwnProperty.call(stagesConfig, normalizedStage)
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
stagesConfig[normalizedStage] !== false &&
|
|
||||||
rule.destinationFlags.prompt !== false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (PROMPT_STAGES.has(normalizedStage)) {
|
|
||||||
return (
|
if (!normalizedStage) {
|
||||||
stagesConfig.input !== false && rule.destinationFlags.prompt !== false
|
return isTaskRegexStageEnabled(stagesConfig, "input");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (OUTPUT_STAGES.has(normalizedStage)) {
|
|
||||||
return (
|
if (PROMPT_STAGES.has(normalizedStage) || OUTPUT_STAGES.has(normalizedStage)) {
|
||||||
stagesConfig.output !== false && rule.destinationFlags.prompt !== false
|
return isTaskRegexStageEnabled(stagesConfig, normalizedStage);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return stagesConfig.input !== false && rule.destinationFlags.prompt !== false;
|
|
||||||
|
return isTaskRegexStageEnabled(stagesConfig, normalizedStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldApplyRuleForRole(rule, role = "system") {
|
function shouldApplyRuleForRole(rule, role = "system") {
|
||||||
@@ -398,7 +395,7 @@ export function applyTaskRegex(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 阶段检查已移到 shouldApplyRuleForStage 中,无需单独 gate
|
// 阶段检查已移到 shouldApplyRuleForStage 中,无需单独 gate
|
||||||
const stagesConfig = regexConfig?.stages || {};
|
const stagesConfig = normalizeTaskRegexStages(regexConfig?.stages || {});
|
||||||
|
|
||||||
const tavernRules = collectTavernRules(regexConfig);
|
const tavernRules = collectTavernRules(regexConfig);
|
||||||
const localRules = collectLocalRules(regexConfig);
|
const localRules = collectLocalRules(regexConfig);
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ try {
|
|||||||
|
|
||||||
assert.match(promptBuild.systemPrompt, /GOOD_RECENT/);
|
assert.match(promptBuild.systemPrompt, /GOOD_RECENT/);
|
||||||
assert.match(JSON.stringify(promptBuild.executionMessages), /GOOD_CANDIDATE/);
|
assert.match(JSON.stringify(promptBuild.executionMessages), /GOOD_CANDIDATE/);
|
||||||
assert.match(promptBuild.systemPrompt, /FINAL_GOOD/);
|
assert.match(promptBuild.systemPrompt, /FINAL_BAD/);
|
||||||
|
assert.doesNotMatch(promptBuild.systemPrompt, /FINAL_GOOD/);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
promptBuild.debug.mvu.sanitizedFields.some((entry) => entry.name === "userMessage"),
|
promptBuild.debug.mvu.sanitizedFields.some((entry) => entry.name === "userMessage"),
|
||||||
true,
|
true,
|
||||||
@@ -454,6 +455,8 @@ try {
|
|||||||
|
|
||||||
const payload = buildTaskLlmPayload(promptBuild, "unused fallback");
|
const payload = buildTaskLlmPayload(promptBuild, "unused fallback");
|
||||||
assert.equal(payload.systemPrompt, "");
|
assert.equal(payload.systemPrompt, "");
|
||||||
|
assert.match(JSON.stringify(payload.promptMessages), /FINAL_BAD/);
|
||||||
|
assert.doesNotMatch(JSON.stringify(payload.promptMessages), /FINAL_GOOD/);
|
||||||
const result = await llm.callLLMForJSON({
|
const result = await llm.callLLMForJSON({
|
||||||
systemPrompt: payload.systemPrompt,
|
systemPrompt: payload.systemPrompt,
|
||||||
userPrompt: payload.userPrompt,
|
userPrompt: payload.userPrompt,
|
||||||
@@ -466,6 +469,8 @@ try {
|
|||||||
|
|
||||||
assert.deepEqual(result, { ok: true });
|
assert.deepEqual(result, { ok: true });
|
||||||
assert.equal(capturedBodies.length, 1);
|
assert.equal(capturedBodies.length, 1);
|
||||||
|
assert.match(JSON.stringify(capturedBodies[0].messages), /FINAL_GOOD/);
|
||||||
|
assert.doesNotMatch(JSON.stringify(capturedBodies[0].messages), /FINAL_BAD/);
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
JSON.stringify(capturedBodies[0].messages),
|
JSON.stringify(capturedBodies[0].messages),
|
||||||
/status_current_variable|updatevariable|StatusPlaceHolderImpl|stat_data|display_data|delta_data|get_message_variable/i,
|
/status_current_variable|updatevariable|StatusPlaceHolderImpl|stat_data|display_data|delta_data|get_message_variable/i,
|
||||||
@@ -478,6 +483,18 @@ try {
|
|||||||
|
|
||||||
assert.ok(runtimePromptBuild);
|
assert.ok(runtimePromptBuild);
|
||||||
assert.ok(runtimeLlmRequest);
|
assert.ok(runtimeLlmRequest);
|
||||||
|
assert.match(JSON.stringify(runtimeLlmRequest.messages), /FINAL_GOOD/);
|
||||||
|
assert.equal(runtimeLlmRequest.requestCleaning?.applied, true);
|
||||||
|
assert.equal(
|
||||||
|
runtimeLlmRequest.requestCleaning?.stages?.length > 0,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
runtimeLlmRequest.requestCleaning?.stages?.every(
|
||||||
|
(entry) => entry.stage === "input.finalPrompt",
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
JSON.stringify(runtimePromptBuild.executionMessages),
|
JSON.stringify(runtimePromptBuild.executionMessages),
|
||||||
/status_current_variable|updatevariable|StatusPlaceHolderImpl|stat_data|display_data|delta_data|get_message_variable/i,
|
/status_current_variable|updatevariable|StatusPlaceHolderImpl|stat_data|display_data|delta_data|get_message_variable/i,
|
||||||
|
|||||||
@@ -365,6 +365,79 @@ try {
|
|||||||
assert.equal(exactStageResult, "JSON");
|
assert.equal(exactStageResult, "JSON");
|
||||||
assert.deepEqual(exactStageDebug.entries[0].appliedRules, []);
|
assert.deepEqual(exactStageDebug.entries[0].appliedRules, []);
|
||||||
|
|
||||||
|
const legacyStageCompatibilitySettings = {
|
||||||
|
taskProfilesVersion: 1,
|
||||||
|
taskProfiles: {
|
||||||
|
extract: {
|
||||||
|
activeProfileId: "legacy-stage-compat",
|
||||||
|
profiles: [
|
||||||
|
{
|
||||||
|
id: "legacy-stage-compat",
|
||||||
|
taskType: "extract",
|
||||||
|
regex: {
|
||||||
|
enabled: true,
|
||||||
|
inheritStRegex: false,
|
||||||
|
sources: {
|
||||||
|
global: false,
|
||||||
|
preset: false,
|
||||||
|
character: false,
|
||||||
|
},
|
||||||
|
stages: {
|
||||||
|
input: true,
|
||||||
|
output: true,
|
||||||
|
"input.userMessage": false,
|
||||||
|
"input.recentMessages": false,
|
||||||
|
"input.candidateText": false,
|
||||||
|
"input.finalPrompt": false,
|
||||||
|
"output.rawResponse": false,
|
||||||
|
"output.beforeParse": false,
|
||||||
|
},
|
||||||
|
localRules: [
|
||||||
|
createRule("legacy-input-user", "/Alpha/g", "A1"),
|
||||||
|
createRule("legacy-output-raw", "/Omega/g", "O1", {
|
||||||
|
source: {
|
||||||
|
user_input: false,
|
||||||
|
ai_output: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyStageInputDebug = { entries: [] };
|
||||||
|
const legacyStageInputResult = applyTaskRegex(
|
||||||
|
legacyStageCompatibilitySettings,
|
||||||
|
"extract",
|
||||||
|
"input.userMessage",
|
||||||
|
"Alpha",
|
||||||
|
legacyStageInputDebug,
|
||||||
|
"user",
|
||||||
|
);
|
||||||
|
assert.equal(legacyStageInputResult, "A1");
|
||||||
|
assert.deepEqual(
|
||||||
|
legacyStageInputDebug.entries[0].appliedRules.map((item) => item.id),
|
||||||
|
["legacy-input-user"],
|
||||||
|
);
|
||||||
|
|
||||||
|
const legacyStageOutputDebug = { entries: [] };
|
||||||
|
const legacyStageOutputResult = applyTaskRegex(
|
||||||
|
legacyStageCompatibilitySettings,
|
||||||
|
"extract",
|
||||||
|
"output.rawResponse",
|
||||||
|
"Omega",
|
||||||
|
legacyStageOutputDebug,
|
||||||
|
"assistant",
|
||||||
|
);
|
||||||
|
assert.equal(legacyStageOutputResult, "O1");
|
||||||
|
assert.deepEqual(
|
||||||
|
legacyStageOutputDebug.entries[0].appliedRules.map((item) => item.id),
|
||||||
|
["legacy-output-raw"],
|
||||||
|
);
|
||||||
|
|
||||||
console.log("task-regex tests passed");
|
console.log("task-regex tests passed");
|
||||||
} finally {
|
} finally {
|
||||||
if (originalSillyTavern === undefined) {
|
if (originalSillyTavern === undefined) {
|
||||||
|
|||||||
Reference in New Issue
Block a user