refactor(extraction): remove legacy extract prompt path

This commit is contained in:
youzini
2026-06-09 06:41:33 +00:00
parent 049300ae09
commit 1e32bd6499
19 changed files with 406 additions and 1034 deletions

View File

@@ -109,12 +109,11 @@ assert.equal(defaultSettings.loadNativeHydrateThresholdRecords, 30000);
assert.equal(defaultSettings.nativeRolloutVersion, 2);
assert.equal(defaultSettings.nativeEngineFailOpen, true);
assert.equal(defaultSettings.graphNativeForceDisable, false);
assert.equal(defaultSettings.extractPipelineVersion, "split-v1");
assert.equal(defaultSettings.taskProfilesVersion, 3);
assert.equal(defaultSettings.extractObjectivePrompt, "");
assert.equal(defaultSettings.extractSubjectivePrompt, "");
assert.ok(defaultSettings.taskProfiles);
assert.ok(defaultSettings.taskProfiles.extract);
assert.equal(defaultSettings.taskProfiles.extract, undefined);
assert.ok(defaultSettings.taskProfiles.extract_objective);
assert.ok(defaultSettings.taskProfiles.extract_subjective);
assert.ok(defaultSettings.taskProfiles.recall);

View File

@@ -194,13 +194,14 @@ function characterKnowledgeEntries(graph) {
async function captureTaskTypesForExtract(settings, options = {}) {
const graph = createGraphWithCharacter();
const capturedTaskTypes = [];
const capturedPayloads = [];
const restore = setTestOverrides({
llm: {
async callLLMForJSON(payload = {}) {
capturedTaskTypes.push(payload.taskType);
capturedPayloads.push(payload);
if (payload.taskType === "extract_objective") return objectivePayload();
if (payload.taskType === "extract_subjective") return subjectivePayload();
if (payload.taskType === "extract") return { operations: [], cognitionUpdates: [], regionUpdates: {} };
return { operations: [], cognitionUpdates: [], regionUpdates: {} };
},
},
@@ -215,53 +216,12 @@ async function captureTaskTypesForExtract(settings, options = {}) {
params.settings = settings;
}
const result = await extractMemories(params);
return { graph, result, capturedTaskTypes };
return { graph, result, capturedTaskTypes, capturedPayloads };
} finally {
restore();
}
}
function cloneJson(value) {
return JSON.parse(JSON.stringify(value));
}
function createCustomizedLegacyExtractProfileSettings() {
const taskProfiles = cloneJson(defaultSettings.taskProfiles);
const baseProfile = taskProfiles.extract.profiles[0];
const customProfile = {
...baseProfile,
id: "custom-legacy-extract-profile",
name: "Custom legacy extract profile",
builtin: false,
blocks: (Array.isArray(baseProfile.blocks) ? baseProfile.blocks : []).map((block, index) =>
index === 0
? { ...block, content: `${String(block.content || "")}\nCUSTOM_LEGACY_EXTRACT_SENTINEL` }
: { ...block },
),
};
taskProfiles.extract = {
activeProfileId: customProfile.id,
profiles: [baseProfile, customProfile],
};
return {
...defaultSettings,
extractPipelineVersion: "split-v1",
taskProfiles,
};
}
function createDefaultExtractProfileSettings(mutator) {
const taskProfiles = cloneJson(defaultSettings.taskProfiles);
const extractProfiles = taskProfiles.extract.profiles || [];
const defaultProfile = extractProfiles.find((profile) => profile.id === "default") || extractProfiles[0];
mutator?.(defaultProfile, taskProfiles.extract);
return {
...defaultSettings,
extractPipelineVersion: "split-v1",
taskProfiles,
};
}
// Phase 4 default switch: omitting settings should use the split pipeline by default.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract(undefined, {
@@ -276,19 +236,68 @@ function createDefaultExtractProfileSettings(mutator) {
);
}
// Phase 4 default switch: the default settings object should request split-v1.
// The default settings object should always use objective+subjective split extraction.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract({
const { result, capturedTaskTypes, capturedPayloads } = await captureTaskTypesForExtract({
...defaultSettings,
});
assert.equal(result.success, true);
assert.equal(defaultSettings.extractPipelineVersion, "split-v1");
assert.deepEqual(
capturedTaskTypes,
["extract_objective", "extract_subjective"],
"defaultSettings should call split objective+subjective extraction",
);
const subjectivePayloadText = JSON.stringify(
capturedPayloads.find((payload) => payload.taskType === "extract_subjective") || {},
);
const subjectivePayload = capturedPayloads.find(
(payload) => payload.taskType === "extract_subjective",
);
const objectiveRefMapBlock = subjectivePayload?.promptMessages?.find(
(message) => message.sourceKey === "objectiveRefMap",
);
assert.match(
subjectivePayloadText,
/evt-clock/,
"subjective extraction prompt should receive objective draft/ref context",
);
assert.match(
String(objectiveRefMapBlock?.content || ""),
/evt-clock/,
"subjective extraction prompt should render objectiveRefMap with objective refs",
);
}
// Removed legacy knobs are ignored and must not revive the old single extract task.
for (const legacyPatch of [
{ extractPrompt: "CUSTOM LEGACY EXTRACT PROMPT" },
{ extractPipelineVersion: "legacy-single" },
{
taskProfiles: {
...defaultSettings.taskProfiles,
extract: {
activeProfileId: "legacy-custom",
profiles: [
{
id: "legacy-custom",
taskType: "extract",
builtin: false,
blocks: [],
},
],
},
},
},
]) {
const { result, capturedTaskTypes } = await captureTaskTypesForExtract({
...defaultSettings,
...legacyPatch,
});
assert.equal(result.success, true);
assert.deepEqual(capturedTaskTypes, ["extract_objective", "extract_subjective"]);
assert.equal(capturedTaskTypes.includes("extract"), false);
}
// split-v1 calls objective then subjective, merges both stage outputs, and commits once.
@@ -310,7 +319,7 @@ function createDefaultExtractProfileSettings(mutator) {
const result = await extractMemories({
graph,
...baseExtractParams,
settings: { extractPipelineVersion: "split-v1" },
settings: defaultSettings,
});
assert.deepEqual(
@@ -366,7 +375,7 @@ function createDefaultExtractProfileSettings(mutator) {
const result = await extractMemories({
graph,
...baseExtractParams,
settings: { extractPipelineVersion: "split-v1" },
settings: defaultSettings,
});
assert.deepEqual(
@@ -383,92 +392,4 @@ function createDefaultExtractProfileSettings(mutator) {
}
}
// Legacy guard: a non-empty legacy extractPrompt should force the single extract taskType path.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract({
...defaultSettings,
extractPipelineVersion: "split-v1",
extractPrompt: "CUSTOM LEGACY EXTRACT PROMPT",
});
assert.equal(result.success, true);
assert.deepEqual(
capturedTaskTypes,
["extract"],
"non-empty extractPrompt should guard back to legacy taskType extract",
);
}
// Legacy guard: an active customized legacy extract task profile should force the single extract path.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract(
createCustomizedLegacyExtractProfileSettings(),
);
assert.equal(result.success, true);
assert.deepEqual(
capturedTaskTypes,
["extract"],
"customized active taskProfiles.extract profile should guard back to legacy taskType extract",
);
}
// Legacy guard: an explicit legacy override should always keep the single extract path.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract({
...defaultSettings,
extractPipelineVersion: "legacy-single",
});
assert.equal(result.success, true);
assert.deepEqual(capturedTaskTypes, ["extract"]);
}
// Legacy guard: migrated legacy default-looking profiles are conservative legacy.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract(
createDefaultExtractProfileSettings((profile) => {
profile.metadata = {
...(profile.metadata || {}),
migratedFromLegacy: true,
};
}),
);
assert.equal(result.success, true);
assert.deepEqual(capturedTaskTypes, ["extract"]);
}
// Legacy guard: stale default profile metadata is conservative legacy.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract(
createDefaultExtractProfileSettings((profile) => {
profile.metadata = {
...(profile.metadata || {}),
defaultTemplateFingerprint: "stale-fingerprint",
};
}),
);
assert.equal(result.success, true);
assert.deepEqual(capturedTaskTypes, ["extract"]);
}
// Legacy guard: modified default profile content is conservative legacy even if id/builtin remain default.
{
const { result, capturedTaskTypes } = await captureTaskTypesForExtract(
createDefaultExtractProfileSettings((profile) => {
profile.blocks = (profile.blocks || []).map((block, index) =>
index === 0
? { ...block, content: `${String(block.content || "")}
CUSTOM_DEFAULT_PROFILE_SENTINEL` }
: { ...block },
);
}),
);
assert.equal(result.success, true);
assert.deepEqual(capturedTaskTypes, ["extract"]);
}
console.log("extractor-split-pipeline tests passed");

View File

@@ -4,8 +4,8 @@ import { createDefaultTaskProfiles } from "../prompting/prompt-profiles.js";
function buildSettingsWithExtractGeneration(generation) {
const taskProfiles = createDefaultTaskProfiles();
taskProfiles.extract.profiles[0].generation = {
...taskProfiles.extract.profiles[0].generation,
taskProfiles.extract_objective.profiles[0].generation = {
...taskProfiles.extract_objective.profiles[0].generation,
...generation,
};
return {
@@ -28,7 +28,7 @@ const openAiLikeSettings = buildSettingsWithExtractGeneration({
const openAiLike = resolveTaskGenerationOptions(
openAiLikeSettings,
"extract",
"extract_objective",
{ max_completion_tokens: 256 },
{ mode: "dedicated-openai-compatible" },
);
@@ -50,7 +50,7 @@ assert.ok(
const conservative = resolveTaskGenerationOptions(
openAiLikeSettings,
"extract",
"extract_objective",
{ max_completion_tokens: 256 },
{ mode: "sillytavern-current-model" },
);
@@ -74,7 +74,7 @@ const fallbackSettings = buildSettingsWithExtractGeneration({
});
const fallback = resolveTaskGenerationOptions(
fallbackSettings,
"extract",
"extract_objective",
{ max_completion_tokens: 300 },
{ mode: "conservative" },
);

View File

@@ -85,8 +85,8 @@ if (originalSendOpenAIRequest === undefined) {
function buildStreamingSettings(generation = {}, overrides = {}) {
const taskProfiles = createDefaultTaskProfiles();
taskProfiles.extract.profiles[0].generation = {
...taskProfiles.extract.profiles[0].generation,
taskProfiles.extract_objective.profiles[0].generation = {
...taskProfiles.extract_objective.profiles[0].generation,
...generation,
};
return {
@@ -122,7 +122,7 @@ function createSseResponse(events = [], status = 200) {
);
}
function getSnapshot(taskKey = "extract") {
function getSnapshot(taskKey = "extract_objective") {
return globalThis.__stBmeRuntimeDebugState?.taskLlmRequests?.[taskKey] || null;
}
@@ -163,14 +163,14 @@ async function testDedicatedStreamingSuccess() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:stream-success",
});
assert.deepEqual(result, { ok: true });
assert.equal(fetchCount, 1);
const snapshot = getSnapshot("extract");
const snapshot = getSnapshot("extract_objective");
assert.ok(snapshot);
assert.equal(snapshot.streamRequested ?? true, true);
assert.equal(snapshot.streamActive ?? false, false);
@@ -236,14 +236,14 @@ async function testDedicatedStreamingFallsBackToNonStream() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:stream-fallback",
});
assert.deepEqual(result, { ok: true });
assert.equal(fetchCount, 2);
const snapshot = getSnapshot("extract");
const snapshot = getSnapshot("extract_objective");
assert.ok(snapshot);
assert.equal(snapshot.streamRequested ?? true, true);
assert.equal(snapshot.streamCompleted ?? false, false);
@@ -312,7 +312,7 @@ async function testDedicatedStreamingAbortDoesNotLeaveActiveState() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:stream-abort",
signal: controller.signal,
});
@@ -325,7 +325,7 @@ async function testDedicatedStreamingAbortDoesNotLeaveActiveState() {
(error) => error?.name === "AbortError",
);
const snapshot = getSnapshot("extract");
const snapshot = getSnapshot("extract_objective");
assert.ok(snapshot);
assert.equal(snapshot.streamRequested ?? true, true);
assert.equal(snapshot.streamActive ?? false, false);
@@ -397,14 +397,14 @@ async function testJsonRetryKeepsProfileCompletionTokens() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 1,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:json-retry-keeps-profile-tokens",
});
assert.deepEqual(result, { ok: true });
assert.equal(fetchCount, 2);
const snapshot = getSnapshot("extract");
const snapshot = getSnapshot("extract_objective");
assert.ok(snapshot);
assert.equal(snapshot.requestBody?.maxTokens ?? 7777, 7777);
assert.equal(
@@ -456,7 +456,7 @@ async function testAnthropicRouteUsesReverseProxyAndDisablesStreaming() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:anthropic-route",
});
@@ -467,7 +467,7 @@ async function testAnthropicRouteUsesReverseProxyAndDisablesStreaming() {
assert.equal(requestBody?.stream, false);
assert.ok(requestBody?.json_schema);
const snapshot = getSnapshot("extract");
const snapshot = getSnapshot("extract_objective");
assert.ok(snapshot);
assert.equal(
snapshot.route || snapshot.effectiveRoute || "dedicated-anthropic-claude",

View File

@@ -160,7 +160,7 @@ try {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:luker-route",
});
@@ -190,7 +190,7 @@ try {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:luker-global-stale",
});
@@ -210,7 +210,7 @@ try {
capturedFetchBody = null;
sendOpenAIRequestCalls = 0;
const taskProfiles = createDefaultTaskProfiles();
taskProfiles.extract.profiles[0].generation.llm_preset = "luker-profile-alpha";
taskProfiles.extract_objective.profiles[0].generation.llm_preset = "luker-profile-alpha";
extensionsApi.extension_settings.st_bme = {
llmApiUrl: "https://stale-generic-config.invalid/v1",
llmApiKey: "sk-stale-generic",
@@ -246,7 +246,7 @@ try {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:luker-profile-route",
});

View File

@@ -7777,12 +7777,12 @@ async function testLlmOutputRegexCleansResponseBeforeJsonParse() {
delete globalThis.__stBmeRuntimeDebugState;
const taskProfiles = createDefaultTaskProfiles();
taskProfiles.extract.profiles[0].regex = {
...taskProfiles.extract.profiles[0].regex,
taskProfiles.extract_objective.profiles[0].regex = {
...taskProfiles.extract_objective.profiles[0].regex,
enabled: true,
inheritStRegex: false,
stages: {
...taskProfiles.extract.profiles[0].regex.stages,
...taskProfiles.extract_objective.profiles[0].regex.stages,
"output.rawResponse": true,
"output.beforeParse": true,
},
@@ -7854,13 +7854,13 @@ async function testLlmOutputRegexCleansResponseBeforeJsonParse() {
systemPrompt: "system",
userPrompt: "user",
maxRetries: 0,
taskType: "extract",
taskType: "extract_objective",
requestSource: "test:output-regex",
});
assert.deepEqual(result, { ok: true });
const snapshot =
globalThis.__stBmeRuntimeDebugState?.taskLlmRequests?.extract;
globalThis.__stBmeRuntimeDebugState?.taskLlmRequests?.extract_objective;
assert.ok(snapshot);
assert.equal(snapshot.responseCleaning?.applied, true);
assert.equal(snapshot.responseCleaning?.changed, true);

View File

@@ -58,8 +58,13 @@ const settings = {
taskProfiles: createDefaultTaskProfiles(),
};
const extractPromptBuild = await buildTaskPrompt(settings, "extract", {
taskName: "extract",
await assert.rejects(
() => buildTaskPrompt(settings, "extract", { taskName: "extract" }),
/Unsupported task type: extract/,
);
const extractPromptBuild = await buildTaskPrompt(settings, "extract_objective", {
taskName: "extract_objective",
charDescription: "角色描述",
userPersona: "用户设定",
recentMessages: "A: 你好\nB: 世界",
@@ -88,11 +93,11 @@ const extractFormatBlock = extractPayload.promptMessages.find(
const extractRulesBlock = extractPayload.promptMessages.find(
(message) => message.blockName === "行为规则",
);
assert.match(String(extractFormatBlock?.content || ""), /cognitionUpdates/);
assert.doesNotMatch(String(extractFormatBlock?.content || ""), /cognitionUpdates/);
assert.match(String(extractFormatBlock?.content || ""), /regionUpdates/);
assert.match(String(extractFormatBlock?.content || ""), /batchStoryTime/);
assert.match(String(extractFormatBlock?.content || ""), /storyTime/);
assert.match(String(extractRulesBlock?.content || ""), /涉及到的角色都尽量尝试补 cognitionUpdates/);
assert.match(String(extractRulesBlock?.content || ""), /禁止输出/);
assert.match(String(extractRulesBlock?.content || ""), /batchStoryTime/);
assert.deepEqual(
extractPayload.promptMessages
@@ -203,8 +208,8 @@ initializeHostAdapter({
},
});
const regexAwarePromptBuild = await buildTaskPrompt(settings, "extract", {
taskName: "extract",
const regexAwarePromptBuild = await buildTaskPrompt(settings, "extract_objective", {
taskName: "extract_objective",
charDescription: "",
userPersona: "",
recentMessages: "这里会被 chatMessages 回填",
@@ -260,29 +265,13 @@ initializeHostAdapter({});
const splitContextTaskProfiles = createDefaultTaskProfiles();
const subjectiveProfile = splitContextTaskProfiles.extract_subjective.profiles[0];
subjectiveProfile.blocks = [
createBuiltinPromptBlock("extract_subjective", "objectiveExtractionDraft", {
name: "客观提取草稿",
order: 0,
}),
createBuiltinPromptBlock("extract_subjective", "objectiveRefMap", {
name: "客观引用映射",
order: 1,
}),
createBuiltinPromptBlock("extract_subjective", "ownerContext", {
name: "视角主体上下文",
order: 2,
}),
createBuiltinPromptBlock("extract_subjective", "batchStoryTime", {
name: "批次故事时间",
order: 3,
}),
createBuiltinPromptBlock("extract_subjective", "relevantPovMemories", {
name: "相关主观记忆",
order: 4,
order: 0,
}),
createBuiltinPromptBlock("extract_subjective", "cognitionStateDigest", {
name: "认知状态摘要",
order: 5,
order: 1,
}),
];
@@ -305,19 +294,22 @@ const splitContextPayload = buildTaskLlmPayload(
splitContextPromptBuild,
"fallback-user",
);
assert.deepEqual(
splitContextPayload.promptMessages
.map((message) => message.sourceKey)
.filter(Boolean),
[
"objectiveExtractionDraft",
"objectiveRefMap",
"ownerContext",
"batchStoryTime",
"relevantPovMemories",
"cognitionStateDigest",
],
);
const splitContextSourceKeys = splitContextPayload.promptMessages
.map((message) => message.sourceKey)
.filter(Boolean);
for (const sourceKey of [
"objectiveExtractionDraft",
"objectiveRefMap",
"ownerContext",
"batchStoryTime",
"relevantPovMemories",
"cognitionStateDigest",
]) {
assert.ok(
splitContextSourceKeys.includes(sourceKey),
`subjective prompt should include ${sourceKey}`,
);
}
assert.match(
String(
splitContextPayload.promptMessages.find(

View File

@@ -55,7 +55,7 @@ const settings = {
taskProfilesVersion: 3,
taskProfiles: createDefaultTaskProfiles(),
};
const extractProfile = settings.taskProfiles.extract.profiles[0];
const extractProfile = settings.taskProfiles.extract_objective.profiles[0];
extractProfile.regex = {
...(extractProfile.regex || {}),
enabled: true,
@@ -105,8 +105,8 @@ extractProfile.regex = {
],
};
const promptBuild = await buildTaskPrompt(settings, "extract", {
taskName: "extract",
const promptBuild = await buildTaskPrompt(settings, "extract_objective", {
taskName: "extract_objective",
charDescription: "",
userPersona: "",
recentMessages: "这里会被 chatMessages 回填",

View File

@@ -14,7 +14,8 @@ import {
} from "../prompting/prompt-profiles.js";
const legacySettings = {
extractPrompt: "旧提取提示",
extractObjectivePrompt: "旧客观提取提示",
extractSubjectivePrompt: "旧主观提取提示",
recallPrompt: "旧召回提示",
compressPrompt: "",
synopsisPrompt: "",
@@ -25,7 +26,7 @@ const legacySettings = {
const migrated = migrateLegacyTaskProfiles(legacySettings);
assert.equal(migrated.taskProfilesVersion, 3);
assert.ok(migrated.taskProfiles);
assert.ok(migrated.taskProfiles.extract);
assert.ok(migrated.taskProfiles.extract_objective);
assert.ok(migrated.taskProfiles.recall);
assert.ok(migrated.taskProfiles.planner);
@@ -34,9 +35,9 @@ const extractProfile = getActiveTaskProfile(
...legacySettings,
taskProfiles: migrated.taskProfiles,
},
"extract",
"extract_objective",
);
assert.equal(extractProfile.taskType, "extract");
assert.equal(extractProfile.taskType, "extract_objective");
assert.equal(extractProfile.id, "default");
assert.ok(Array.isArray(extractProfile.blocks));
assert.equal(extractProfile.blocks.length, 16);
@@ -105,15 +106,17 @@ assert.deepEqual(
);
assert.equal(
extractProfile.metadata.legacyPromptField,
"extractPrompt",
"extractObjectivePrompt",
);
assert.equal(
extractProfile.metadata.legacyPromptSnapshot,
"旧提取提示",
"旧客观提取提示",
);
const defaults = createDefaultTaskProfiles();
assert.ok(defaults.extract.profiles.length > 0);
assert.equal(defaults.extract, undefined);
assert.ok(defaults.extract_objective.profiles.length > 0);
assert.ok(defaults.extract_subjective.profiles.length > 0);
assert.ok(defaults.recall.profiles.length > 0);
assert.ok(defaults.compress.profiles.length > 0);
assert.ok(defaults.synopsis.profiles.length > 0);
@@ -406,7 +409,7 @@ const upgradedLegacyDefault = getActiveTaskProfile(
profiles: [
{
id: "default",
taskType: "extract",
taskType: "extract_objective",
builtin: true,
blocks: [
{
@@ -471,14 +474,18 @@ const upgradedLegacyDefault = getActiveTaskProfile(
},
},
},
"extract",
"extract_objective",
);
assert.equal(upgradedLegacyDefault.blocks.length, 16);
assert.equal(upgradedLegacyDefault.blocks[0].name, "抬头");
assert.match(upgradedLegacyDefault.blocks[0].content, /虚拟的世界/);
assert.equal(upgradedLegacyDefault.blocks[0].role, "system");
assert.equal(upgradedLegacyDefault.blocks[0].injectionMode, "relative");
assert.equal(upgradedLegacyDefault.blocks[1].content, "保留我自己的角色定义");
assert.match(
upgradedLegacyDefault.blocks[1].content,
/客观事实提取师/,
"removed legacy extract profile should be replaced by the current objective extraction default",
);
const upgradedIdentityAck = upgradedLegacyDefault.blocks.find(
(block) => block.id === "default-identity-ack",
);
@@ -497,18 +504,18 @@ assert.ok(
assert.equal(upgradedInfoAck.role, "assistant");
assert.equal(upgradedLegacyDefault.blocks[14].id, "default-format");
assert.equal(upgradedLegacyDefault.blocks[15].id, "default-rules");
assert.equal(upgradedLegacyDefault.blocks[14].content, "保留我自己的输出格式");
assert.equal(upgradedLegacyDefault.blocks[15].content, "保留我自己的行为规则");
assert.match(upgradedLegacyDefault.blocks[14].content, /batchStoryTime/);
assert.match(upgradedLegacyDefault.blocks[15].content, /禁止输出/);
assert.equal(upgradedLegacyDefault.blocks[14].role, "user");
assert.equal(upgradedLegacyDefault.blocks[15].role, "user");
const currentDefaults = createDefaultTaskProfiles();
const currentDefaultExtract = currentDefaults.extract.profiles[0];
const currentDefaultExtract = currentDefaults.extract_objective.profiles[0];
const staleBuiltinDefaults = ensureTaskProfiles({
taskProfilesVersion: 3,
taskProfiles: {
extract: {
extract_objective: {
activeProfileId: "default",
profiles: [
{
@@ -528,7 +535,7 @@ const staleBuiltinDefaults = ensureTaskProfiles({
},
{
id: "extract-custom-1",
taskType: "extract",
taskType: "extract_objective",
builtin: false,
name: "我的自定义预设",
promptMode: "block-based",
@@ -561,10 +568,10 @@ const staleBuiltinDefaults = ensureTaskProfiles({
},
},
});
const refreshedDefaultExtract = staleBuiltinDefaults.extract.profiles.find(
const refreshedDefaultExtract = staleBuiltinDefaults.extract_objective.profiles.find(
(profile) => profile.id === "default",
);
const preservedCustomExtract = staleBuiltinDefaults.extract.profiles.find(
const preservedCustomExtract = staleBuiltinDefaults.extract_objective.profiles.find(
(profile) => profile.id === "extract-custom-1",
);
@@ -586,7 +593,7 @@ assert.equal(
assert.match(
refreshedDefaultExtract.blocks.find((block) => block.id === "default-format")
?.content || "",
/cognitionUpdates/,
/regionUpdates/,
);
assert.ok(preservedCustomExtract);
assert.equal(
@@ -597,7 +604,7 @@ assert.equal(
const sameStampBuiltinDefault = ensureTaskProfiles({
taskProfilesVersion: 3,
taskProfiles: {
extract: {
extract_objective: {
activeProfileId: "default",
profiles: [
{
@@ -615,7 +622,7 @@ const sameStampBuiltinDefault = ensureTaskProfiles({
},
},
});
const sameStampDefaultExtract = sameStampBuiltinDefault.extract.profiles.find(
const sameStampDefaultExtract = sameStampBuiltinDefault.extract_objective.profiles.find(
(profile) => profile.id === "default",
);
assert.equal(
@@ -627,7 +634,7 @@ assert.equal(
const sameTimestampButChangedTemplateDefaults = ensureTaskProfiles({
taskProfilesVersion: 3,
taskProfiles: {
extract: {
extract_objective: {
activeProfileId: "default",
profiles: [
{
@@ -647,7 +654,7 @@ const sameTimestampButChangedTemplateDefaults = ensureTaskProfiles({
},
});
const fingerprintRefreshedDefault =
sameTimestampButChangedTemplateDefaults.extract.profiles.find(
sameTimestampButChangedTemplateDefaults.extract_objective.profiles.find(
(profile) => profile.id === "default",
);
assert.equal(
@@ -692,11 +699,11 @@ const legacyRegexSettings = {
taskProfilesVersion: 3,
taskProfiles: createDefaultTaskProfiles(),
};
legacyRegexSettings.taskProfiles.extract.activeProfileId = "default";
legacyRegexSettings.taskProfiles.extract.profiles.push(
normalizeTaskProfile("extract", {
legacyRegexSettings.taskProfiles.extract_objective.activeProfileId = "default";
legacyRegexSettings.taskProfiles.extract_objective.profiles.push(
normalizeTaskProfile("extract_objective", {
id: "extract-legacy-regex",
taskType: "extract",
taskType: "extract_objective",
name: "旧正则副本",
builtin: false,
regex: {
@@ -729,7 +736,7 @@ assert.deepEqual(
],
);
assert.deepEqual(
migratedLegacyRegex.settings.taskProfiles.extract.profiles.find(
migratedLegacyRegex.settings.taskProfiles.extract_objective.profiles.find(
(profile) => profile.id === "extract-legacy-regex",
)?.regex?.localRules || [],
[],
@@ -761,10 +768,10 @@ const existingGlobalRegexSettings = {
},
taskProfiles: createDefaultTaskProfiles(),
};
existingGlobalRegexSettings.taskProfiles.extract.profiles.push(
normalizeTaskProfile("extract", {
existingGlobalRegexSettings.taskProfiles.extract_objective.profiles.push(
normalizeTaskProfile("extract_objective", {
id: "extract-legacy-extra",
taskType: "extract",
taskType: "extract_objective",
name: "旧规则补充",
builtin: false,
regex: {
@@ -807,7 +814,7 @@ const importedLegacyProfileMigration = migrateLegacyProfileRegexToGlobal(
localRules: [],
},
{
taskType: "extract",
taskType: "extract_objective",
regex: {
enabled: false,
inheritStRegex: false,

View File

@@ -3,6 +3,7 @@ import {
cloneTaskProfile,
createBuiltinPromptBlock,
createCustomPromptBlock,
createDefaultTaskProfile,
createDefaultTaskProfiles,
createLocalRegexRule,
exportTaskProfile,
@@ -18,22 +19,22 @@ import {
} from "../prompting/prompt-profiles.js";
const taskProfiles = createDefaultTaskProfiles();
const baseProfile = taskProfiles.extract.profiles[0];
const baseProfile = taskProfiles.extract_objective.profiles[0];
assert.equal(baseProfile.generation.llm_preset, "");
const clonedProfile = cloneTaskProfile(baseProfile, {
taskType: "extract",
taskType: "extract_objective",
name: "激进提取",
});
clonedProfile.generation.llm_preset = "Recall-API";
clonedProfile.blocks = [
...clonedProfile.blocks,
createBuiltinPromptBlock("extract", "userMessage", {
createBuiltinPromptBlock("extract_objective", "userMessage", {
name: "用户消息块",
injectionMode: "prepend",
order: 1,
}),
createCustomPromptBlock("extract", {
createCustomPromptBlock("extract_objective", {
name: "补充说明",
content: "请关注 {{userMessage}}",
role: "user",
@@ -41,20 +42,20 @@ clonedProfile.blocks = [
}),
];
clonedProfile.regex.localRules = [
createLocalRegexRule("extract", {
createLocalRegexRule("extract_objective", {
script_name: "裁边",
find_regex: "/^foo/g",
replace_string: "bar",
}),
];
const updatedProfiles = upsertTaskProfile(taskProfiles, "extract", clonedProfile, {
const updatedProfiles = upsertTaskProfile(taskProfiles, "extract_objective", clonedProfile, {
setActive: true,
});
const activeProfile = getActiveTaskProfile(
{ taskProfiles: updatedProfiles },
"extract",
"extract_objective",
);
assert.equal(activeProfile.name, "激进提取");
assert.equal(activeProfile.blocks.length, 18);
@@ -75,16 +76,16 @@ assert.equal(activeProfile.generation.llm_preset, "Recall-API");
const exported = exportTaskProfile(
updatedProfiles,
"extract",
"extract_objective",
clonedProfile.id,
);
assert.equal(exported.format, "st-bme-task-profile");
assert.equal(exported.taskType, "extract");
assert.equal(exported.taskType, "extract_objective");
assert.equal(exported.profile.name, "激进提取");
assert.equal(exported.profile.generation.llm_preset, "Recall-API");
const imported = importTaskProfile(updatedProfiles, JSON.stringify(exported));
assert.equal(imported.taskType, "extract");
assert.equal(imported.taskType, "extract_objective");
assert.notEqual(imported.profile.id, clonedProfile.id);
assert.equal(imported.profile.generation.llm_preset, "Recall-API");
assert.ok(
@@ -93,14 +94,29 @@ assert.ok(
),
);
const restoredProfiles = restoreDefaultTaskProfile(imported.taskProfiles, "extract");
const restoredProfiles = restoreDefaultTaskProfile(imported.taskProfiles, "extract_objective");
const restoredActive = getActiveTaskProfile(
{ taskProfiles: restoredProfiles },
"extract",
"extract_objective",
);
assert.equal(restoredActive.id, "default");
assert.equal(getLegacyPromptFieldForTask("extract"), "extractPrompt");
assert.equal(getTaskTypeMeta("extract").label, "旧提取");
assert.equal(getLegacyPromptFieldForTask("extract"), "");
assert.equal(getTaskTypeMeta("extract").label, "extract");
assert.equal(createDefaultTaskProfile("extract"), null);
assert.equal(getActiveTaskProfile({ taskProfiles }, "extract"), null);
assert.throws(
() => importTaskProfile(taskProfiles, JSON.stringify({
format: "st-bme-task-profile",
taskType: "extract",
profile: { id: "legacy-extract", taskType: "extract", blocks: [] },
})),
/Unsupported task type: extract/,
);
assert.equal(
getTaskTypeOptions().some((option) => option.id === "extract"),
false,
);
assert.equal(getTaskTypes().includes("extract"), false);
assert.ok(getTaskTypes().includes("extract_objective"));
assert.ok(getTaskTypes().includes("extract_subjective"));

View File

@@ -93,13 +93,13 @@ function createTavernRule(id, findRegex, replaceString, overrides = {}) {
function buildSettings(regex = {}) {
return {
taskProfiles: {
extract: {
extract_objective: {
activeProfileId: "default",
profiles: [
{
id: "default",
name: "Regex Test",
taskType: "extract",
taskType: "extract_objective",
builtin: false,
blocks: [],
regex: {
@@ -220,7 +220,7 @@ try {
const defaultProfiles = createDefaultTaskProfiles();
const defaultExtractStages =
defaultProfiles.extract?.profiles?.[0]?.regex?.stages || {};
defaultProfiles.extract_objective?.profiles?.[0]?.regex?.stages || {};
assert.equal(
isTaskRegexStageEnabled(defaultExtractStages, "input.finalPrompt"),
false,
@@ -251,7 +251,7 @@ try {
);
const normalizedLegacyOnlyProfile = normalizeTaskProfile(
"extract",
"extract_objective",
{
id: "legacy-only-profile",
name: "legacy only",
@@ -287,7 +287,7 @@ try {
const coreBridgeDebug = { entries: [] };
const coreBridgeOutput = applyHostRegexReuse(
buildSettings(),
"extract",
"extract_objective",
"Alpha Beta",
{
sourceType: "user_input",
@@ -308,7 +308,7 @@ try {
]);
assert.equal(coreBridgeDebug.entries[0].executionMode, "host-real");
assert.equal(
inspectTaskRegexReuse(buildSettings(), "extract").host.bridgeTier,
inspectTaskRegexReuse(buildSettings(), "extract_objective").host.bridgeTier,
"core-real",
);
setCoreRegexedStringHandler(null);
@@ -375,7 +375,7 @@ try {
const fullBridgeDebug = { entries: [] };
const fullBridgeOutput = applyHostRegexReuse(
fullBridgeSettings,
"extract",
"extract_objective",
"Alpha Beta",
{
sourceType: "user_input",
@@ -407,13 +407,13 @@ try {
["__host_formatter__"],
);
assert.equal(
inspectTaskRegexReuse(fullBridgeSettings, "extract").host.bridgeTier,
inspectTaskRegexReuse(fullBridgeSettings, "extract_objective").host.bridgeTier,
"helper-bridge",
);
assert.equal(
applyTaskRegex(
fullBridgeSettings,
"extract",
"extract_objective",
"input.finalPrompt",
"Beta",
{ entries: [] },
@@ -460,7 +460,7 @@ try {
const fallbackDebug = { entries: [] };
const fallbackOutput = applyHostRegexReuse(
buildSettings(),
"extract",
"extract_objective",
"Gamma",
{
sourceType: "world_info",
@@ -493,7 +493,7 @@ try {
character: false,
},
}),
"extract",
"extract_objective",
"Gamma",
{
sourceType: "world_info",
@@ -512,7 +512,7 @@ try {
character: false,
},
}),
"extract",
"extract_objective",
"Gamma",
{
sourceType: "world_info",
@@ -549,7 +549,7 @@ try {
],
});
initializeFallbackHostAdapter();
const fallbackInspect = inspectTaskRegexReuse(buildSettings(), "extract");
const fallbackInspect = inspectTaskRegexReuse(buildSettings(), "extract_objective");
assert.equal(fallbackInspect.activeRuleCount, 3);
assert.deepEqual(
fallbackInspect.activeRules.map((rule) => rule.id),
@@ -601,7 +601,7 @@ try {
const disallowedOutput = applyHostRegexReuse(
buildSettings(),
"extract",
"extract_objective",
"Gamma",
{
sourceType: "world_info",
@@ -611,7 +611,7 @@ try {
);
assert.equal(disallowedOutput.text, "G2");
const disallowedInspect = inspectTaskRegexReuse(buildSettings(), "extract");
const disallowedInspect = inspectTaskRegexReuse(buildSettings(), "extract_objective");
assert.equal(disallowedInspect.activeRuleCount, 1);
assert.equal(
disallowedInspect.sources.find((source) => source.type === "preset")
@@ -663,7 +663,7 @@ try {
const userReuseResult = applyHostRegexReuse(
tavernSemanticsSettings,
"extract",
"extract_objective",
"Alpha",
{
sourceType: "user_input",
@@ -676,7 +676,7 @@ try {
assert.equal(userReuseResult.skippedDisplayOnlyRuleCount >= 1, true);
const aiReuseResult = applyHostRegexReuse(
tavernSemanticsSettings,
"extract",
"extract_objective",
"Answer Lore",
{
sourceType: "ai_output",
@@ -686,7 +686,7 @@ try {
);
assert.equal(aiReuseResult.text, "AI Lore");
assert.equal(aiReuseResult.executionMode, "host-fallback");
const markdownInspect = inspectTaskRegexReuse(tavernSemanticsSettings, "extract");
const markdownInspect = inspectTaskRegexReuse(tavernSemanticsSettings, "extract_objective");
const markdownRule = markdownInspect.activeRules.find(
(rule) => rule.id === "markdown-only",
);
@@ -717,7 +717,7 @@ try {
const markdownFinalDebug = { entries: [] };
const markdownFallbackResult = applyHostRegexReuse(
markdownOnlyFinalPromptSettings,
"extract",
"extract_objective",
"Decor",
{
sourceType: "user_input",
@@ -750,7 +750,7 @@ try {
initializeFallbackHostAdapter();
const beautifyFinalInspect = inspectTaskRegexReuse(
beautifyFinalPromptSettings,
"extract",
"extract_objective",
);
const beautifyFinalRule = beautifyFinalInspect.activeRules.find(
(rule) => rule.id === "beautify-final-strip",
@@ -760,7 +760,7 @@ try {
const beautifyFinalDebug = { entries: [] };
const beautifyFallbackResult = applyHostRegexReuse(
beautifyFinalPromptSettings,
"extract",
"extract_objective",
"Decor",
{
sourceType: "user_input",
@@ -785,7 +785,7 @@ try {
});
const beautifyStageOffInspect = inspectTaskRegexReuse(
beautifyFinalPromptStageOffSettings,
"extract",
"extract_objective",
);
const beautifyStageOffRule = beautifyStageOffInspect.activeRules.find(
(rule) => rule.id === "beautify-final-strip",
@@ -835,7 +835,7 @@ try {
const destinationDebug = { entries: [] };
const destinationReuseResult = applyHostRegexReuse(
destinationBeautifySettings,
"extract",
"extract_objective",
"DecorPlain",
{
sourceType: "user_input",
@@ -848,7 +848,7 @@ try {
assert.deepEqual(destinationDebug.entries[0].appliedRules, []);
const destinationInspect = inspectTaskRegexReuse(
destinationBeautifySettings,
"extract",
"extract_objective",
);
const destinationBeautifyRule = destinationInspect.activeRules.find(
(rule) => rule.id === "destination-display-only-beautify",
@@ -892,7 +892,7 @@ try {
initializeFallbackHostAdapter();
const mixedReuseResult = applyHostRegexReuse(
tavernSemanticsSettings,
"extract",
"extract_objective",
"User Reply Lore",
{
sourceType: "ai_output",
@@ -922,7 +922,7 @@ try {
const outputGuardDebug = { entries: [] };
const outputGuardResult = applyTaskRegex(
outputGuardSettings,
"extract",
"extract_objective",
"output.rawResponse",
"JSON 美化",
outputGuardDebug,
@@ -952,7 +952,7 @@ try {
taskProfiles: createDefaultTaskProfiles(),
globalTaskRegex: createDefaultGlobalTaskRegex(),
},
"extract",
"extract_objective",
"input.recentMessages",
[
"前缀",
@@ -1008,7 +1008,7 @@ try {
localRules: [],
},
},
"extract",
"extract_objective",
"input.recentMessages",
"<choice>保留</choice><thinking>保留</thinking>",
explicitEmptyGlobalDebug,