Files
ST-Bionic-Memory-Ecology/extraction-controller.js

225 lines
7.0 KiB
JavaScript

// ST-BME: 提取编排控制器(纯函数)
// 通过 runtime 依赖注入,避免直接访问 index.js 模块级状态。
export async function executeExtractionBatchController(
runtime,
{
chat,
startIdx,
endIdx,
settings,
smartTriggerDecision = null,
signal = undefined,
} = {},
) {
runtime.ensureCurrentGraphRuntimeState();
runtime.throwIfAborted(signal, "提取已终止");
const currentGraph = runtime.getCurrentGraph();
const lastProcessed = runtime.getLastProcessedAssistantFloor();
const extractionCountBefore = runtime.getExtractionCount();
const beforeSnapshot = runtime.cloneGraphSnapshot(currentGraph);
const messages = runtime.buildExtractionMessages(chat, startIdx, endIdx, settings);
const batchStatus = runtime.createBatchStatusSkeleton({
processedRange: [startIdx, endIdx],
extractionCountBefore,
});
runtime.console.log(
`[ST-BME] 开始提取: 楼层 ${startIdx}-${endIdx}` +
(smartTriggerDecision?.triggered
? ` [智能触发 score=${smartTriggerDecision.score}; ${smartTriggerDecision.reasons.join(" / ")}]`
: ""),
);
const result = await runtime.extractMemories({
graph: currentGraph,
messages,
startSeq: startIdx,
endSeq: endIdx,
lastProcessedSeq: lastProcessed,
schema: runtime.getSchema(),
embeddingConfig: runtime.getEmbeddingConfig(),
extractPrompt: undefined,
settings,
signal,
onStreamProgress: ({ previewText, receivedChars }) => {
const preview =
previewText?.length > 60 ? "…" + previewText.slice(-60) : previewText || "";
runtime.setLastExtractionStatus(
"AI 生成中",
`${preview} [${receivedChars}字]`,
"running",
{ noticeMarquee: true },
);
},
});
if (!result.success) {
runtime.setBatchStageOutcome(
batchStatus,
"core",
"failed",
result?.error || "提取阶段未返回有效操作",
);
runtime.finalizeBatchStatus(batchStatus, runtime.getExtractionCount());
runtime.getCurrentGraph().historyState.lastBatchStatus = batchStatus;
return {
success: false,
result,
effects: null,
batchStatus,
error: result?.error || "提取阶段未返回有效操作",
};
}
runtime.setBatchStageOutcome(batchStatus, "core", "success");
const effects = await runtime.handleExtractionSuccess(
result,
endIdx,
settings,
signal,
batchStatus,
);
const finalizedBatchStatus =
effects?.batchStatus ||
runtime.finalizeBatchStatus(batchStatus, runtime.getExtractionCount());
runtime.getCurrentGraph().historyState.lastBatchStatus = {
...finalizedBatchStatus,
historyAdvanced: runtime.shouldAdvanceProcessedHistory(finalizedBatchStatus),
};
if (runtime.getCurrentGraph().historyState.lastBatchStatus.historyAdvanced) {
runtime.updateProcessedHistorySnapshot(chat, endIdx);
}
const afterSnapshot = runtime.cloneGraphSnapshot(runtime.getCurrentGraph());
const postProcessArtifacts = runtime.computePostProcessArtifacts(
beforeSnapshot,
afterSnapshot,
effects?.postProcessArtifacts || [],
);
runtime.appendBatchJournal(
runtime.getCurrentGraph(),
runtime.createBatchJournalEntry(beforeSnapshot, afterSnapshot, {
processedRange: [startIdx, endIdx],
postProcessArtifacts,
vectorHashesInserted: effects?.vectorHashesInserted || [],
extractionCountBefore,
}),
);
runtime.saveGraphToChat({ reason: "extraction-batch-complete" });
return {
success: finalizedBatchStatus.completed,
result,
effects,
batchStatus: finalizedBatchStatus,
error: finalizedBatchStatus.completed
? ""
: effects?.vectorError ||
finalizedBatchStatus.errors?.[0] ||
"批次未完成 finalize 闭环",
};
}
export async function runExtractionController(runtime) {
if (runtime.getIsExtracting() || !runtime.getCurrentGraph()) return;
const settings = runtime.getSettings();
if (!settings.enabled) return;
if (!runtime.ensureGraphMutationReady("自动提取", { notify: false })) {
runtime.setLastExtractionStatus(
"等待图谱加载",
runtime.getGraphMutationBlockReason("自动提取"),
"warning",
{ syncRuntime: true },
);
return;
}
if (!(await runtime.recoverHistoryIfNeeded("auto-extract"))) return;
const context = runtime.getContext();
const chat = context.chat;
if (!chat || chat.length === 0) return;
const assistantTurns = runtime.getAssistantTurns(chat);
const lastProcessed = runtime.getLastProcessedAssistantFloor();
const unprocessedAssistantTurns = assistantTurns.filter((i) => i > lastProcessed);
if (unprocessedAssistantTurns.length === 0) return;
const extractEvery = runtime.clampInt(settings.extractEvery, 1, 1, 50);
const smartTriggerDecision = settings.enableSmartTrigger
? runtime.getSmartTriggerDecision(chat, lastProcessed, settings)
: { triggered: false, score: 0, reasons: [] };
if (
unprocessedAssistantTurns.length < extractEvery &&
!smartTriggerDecision.triggered
) {
return;
}
const batchAssistantTurns = smartTriggerDecision.triggered
? unprocessedAssistantTurns
: unprocessedAssistantTurns.slice(0, extractEvery);
const startIdx = batchAssistantTurns[0];
const endIdx = batchAssistantTurns[batchAssistantTurns.length - 1];
runtime.setIsExtracting(true);
const extractionController = runtime.beginStageAbortController("extraction");
const extractionSignal = extractionController.signal;
runtime.setLastExtractionStatus(
"提取中",
`楼层 ${startIdx}-${endIdx}${smartTriggerDecision.triggered ? " · 智能触发" : ""}`,
"running",
{ syncRuntime: true },
);
try {
const batchResult = await runtime.executeExtractionBatch({
chat,
startIdx,
endIdx,
settings,
smartTriggerDecision,
signal: extractionSignal,
});
if (!batchResult.success) {
const message =
batchResult.error ||
batchResult?.result?.error ||
"提取批次未返回有效结果";
runtime.console.warn("[ST-BME] 提取批次未返回有效结果:", message);
runtime.notifyExtractionIssue(message);
return;
}
runtime.setLastExtractionStatus(
"提取完成",
`楼层 ${startIdx}-${endIdx} · 新建 ${batchResult.result?.newNodes || 0} · 更新 ${batchResult.result?.updatedNodes || 0} · 新边 ${batchResult.result?.newEdges || 0}`,
"success",
{ syncRuntime: true },
);
} catch (e) {
if (runtime.isAbortError(e)) {
runtime.setLastExtractionStatus(
"提取已终止",
e?.message || "已手动终止当前提取",
"warning",
{
syncRuntime: true,
},
);
return;
}
runtime.console.error("[ST-BME] 提取失败:", e);
runtime.notifyExtractionIssue(e?.message || String(e) || "自动提取失败");
} finally {
runtime.finishStageAbortController("extraction", extractionController);
runtime.setIsExtracting(false);
}
}