mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
feat: 改进召回精排反馈与上下文配置
This commit is contained in:
99
index.js
99
index.js
@@ -84,6 +84,7 @@ const defaultSettings = {
|
|||||||
recallEnableGraphDiffusion: true, // 是否启用图扩散
|
recallEnableGraphDiffusion: true, // 是否启用图扩散
|
||||||
recallDiffusionTopK: 100, // 图扩散阶段保留的候选上限
|
recallDiffusionTopK: 100, // 图扩散阶段保留的候选上限
|
||||||
recallLlmCandidatePool: 30, // 传给 LLM 精排的候选池大小
|
recallLlmCandidatePool: 30, // 传给 LLM 精排的候选池大小
|
||||||
|
recallLlmContextMessages: 4, // 传给 LLM 精排的最近非系统消息数
|
||||||
|
|
||||||
// 注入设置
|
// 注入设置
|
||||||
injectPosition: "atDepth", // 注入位置
|
injectPosition: "atDepth", // 注入位置
|
||||||
@@ -166,10 +167,16 @@ let isRecalling = false;
|
|||||||
let lastInjectionContent = "";
|
let lastInjectionContent = "";
|
||||||
let lastExtractedItems = []; // 最近提取的节点(面板展示用)
|
let lastExtractedItems = []; // 最近提取的节点(面板展示用)
|
||||||
let lastRecalledItems = []; // 最近召回的节点(面板展示用)
|
let lastRecalledItems = []; // 最近召回的节点(面板展示用)
|
||||||
|
let lastRecallStatus = {
|
||||||
|
text: "待命",
|
||||||
|
meta: "尚未执行召回",
|
||||||
|
level: "idle",
|
||||||
|
};
|
||||||
let extractionCount = 0; // v2: 提取次数计数器(定期触发概要/遗忘/反思)
|
let extractionCount = 0; // v2: 提取次数计数器(定期触发概要/遗忘/反思)
|
||||||
let serverSettingsSaveTimer = null;
|
let serverSettingsSaveTimer = null;
|
||||||
let isRecoveringHistory = false;
|
let isRecoveringHistory = false;
|
||||||
let lastHistoryWarningAt = 0;
|
let lastHistoryWarningAt = 0;
|
||||||
|
let lastRecallFallbackNoticeAt = 0;
|
||||||
|
|
||||||
function getNodeDisplayName(node) {
|
function getNodeDisplayName(node) {
|
||||||
return (
|
return (
|
||||||
@@ -279,6 +286,11 @@ function ensureCurrentGraphRuntimeState() {
|
|||||||
function clearInjectionState() {
|
function clearInjectionState() {
|
||||||
lastInjectionContent = "";
|
lastInjectionContent = "";
|
||||||
lastRecalledItems = [];
|
lastRecalledItems = [];
|
||||||
|
lastRecallStatus = {
|
||||||
|
text: "待命",
|
||||||
|
meta: "当前无有效注入内容",
|
||||||
|
level: "idle",
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
@@ -291,6 +303,21 @@ function clearInjectionState() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[ST-BME] 清理旧注入失败:", error);
|
console.warn("[ST-BME] 清理旧注入失败:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshPanelLiveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPanelLiveState() {
|
||||||
|
_panelModule?.refreshLiveState?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLastRecallStatus(text, meta, level = "info") {
|
||||||
|
lastRecallStatus = {
|
||||||
|
text: String(text || "待命"),
|
||||||
|
meta: String(meta || ""),
|
||||||
|
level,
|
||||||
|
};
|
||||||
|
refreshPanelLiveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function recordGraphMutation({
|
async function recordGraphMutation({
|
||||||
@@ -560,6 +587,12 @@ function updateModuleSettings(patch = {}) {
|
|||||||
);
|
);
|
||||||
lastInjectionContent = "";
|
lastInjectionContent = "";
|
||||||
lastRecalledItems = [];
|
lastRecalledItems = [];
|
||||||
|
lastRecallStatus = {
|
||||||
|
text: "已停用",
|
||||||
|
meta: "插件已关闭,注入内容已清空",
|
||||||
|
level: "idle",
|
||||||
|
};
|
||||||
|
refreshPanelLiveState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[ST-BME] 关闭插件时清理注入失败:", error);
|
console.warn("[ST-BME] 关闭插件时清理注入失败:", error);
|
||||||
}
|
}
|
||||||
@@ -583,6 +616,11 @@ function loadGraphFromChat() {
|
|||||||
lastExtractedItems = [];
|
lastExtractedItems = [];
|
||||||
lastRecalledItems = [];
|
lastRecalledItems = [];
|
||||||
lastInjectionContent = "";
|
lastInjectionContent = "";
|
||||||
|
lastRecallStatus = {
|
||||||
|
text: "待命",
|
||||||
|
meta: "当前聊天尚未建立记忆图谱",
|
||||||
|
level: "idle",
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,6 +636,11 @@ function loadGraphFromChat() {
|
|||||||
lastExtractedItems = [];
|
lastExtractedItems = [];
|
||||||
updateLastRecalledItems(currentGraph.lastRecallResult || []);
|
updateLastRecalledItems(currentGraph.lastRecallResult || []);
|
||||||
lastInjectionContent = "";
|
lastInjectionContent = "";
|
||||||
|
lastRecallStatus = {
|
||||||
|
text: "待命",
|
||||||
|
meta: "已加载聊天图谱,等待下一次召回",
|
||||||
|
level: "idle",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGraphToChat() {
|
function saveGraphToChat() {
|
||||||
@@ -1186,22 +1229,39 @@ async function runRecall() {
|
|||||||
// 获取最新用户消息
|
// 获取最新用户消息
|
||||||
let userMessage = "";
|
let userMessage = "";
|
||||||
const recentMessages = [];
|
const recentMessages = [];
|
||||||
|
const recentContextMessageLimit = clampInt(
|
||||||
|
settings.recallLlmContextMessages,
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = chat.length - 1; i >= 0 && recentMessages.length < 4; i--) {
|
for (
|
||||||
|
let i = chat.length - 1;
|
||||||
|
i >= 0 && (!userMessage || recentMessages.length < recentContextMessageLimit);
|
||||||
|
i--
|
||||||
|
) {
|
||||||
const msg = chat[i];
|
const msg = chat[i];
|
||||||
if (msg.is_system) continue;
|
if (msg.is_system) continue;
|
||||||
|
|
||||||
if (msg.is_user && !userMessage) {
|
if (msg.is_user && !userMessage) {
|
||||||
userMessage = msg.mes || "";
|
userMessage = msg.mes || "";
|
||||||
}
|
}
|
||||||
|
if (recentMessages.length < recentContextMessageLimit) {
|
||||||
recentMessages.unshift(
|
recentMessages.unshift(
|
||||||
`[${msg.is_user ? "user" : "assistant"}]: ${msg.mes || ""}`,
|
`[${msg.is_user ? "user" : "assistant"}]: ${msg.mes || ""}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!userMessage) return;
|
if (!userMessage) return;
|
||||||
|
|
||||||
console.log("[ST-BME] 开始召回");
|
console.log("[ST-BME] 开始召回");
|
||||||
|
setLastRecallStatus(
|
||||||
|
"召回中",
|
||||||
|
`上下文 ${recentMessages.length} 条 · 当前用户消息长度 ${userMessage.length}`,
|
||||||
|
"running",
|
||||||
|
);
|
||||||
|
|
||||||
const result = await retrieve({
|
const result = await retrieve({
|
||||||
graph: currentGraph,
|
graph: currentGraph,
|
||||||
@@ -1235,6 +1295,12 @@ async function runRecall() {
|
|||||||
// 格式化注入文本
|
// 格式化注入文本
|
||||||
const injectionText = formatInjection(result, getSchema()).trim();
|
const injectionText = formatInjection(result, getSchema()).trim();
|
||||||
lastInjectionContent = injectionText;
|
lastInjectionContent = injectionText;
|
||||||
|
const retrievalMeta = result?.meta?.retrieval || {};
|
||||||
|
const llmMeta = retrievalMeta.llm || {
|
||||||
|
status: settings.recallEnableLLM ? "unknown" : "disabled",
|
||||||
|
reason: settings.recallEnableLLM ? "未提供 LLM 状态" : "LLM 精排已关闭",
|
||||||
|
candidatePool: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if (injectionText) {
|
if (injectionText) {
|
||||||
const tokens = estimateTokens(injectionText);
|
const tokens = estimateTokens(injectionText);
|
||||||
@@ -1255,10 +1321,40 @@ async function runRecall() {
|
|||||||
currentGraph.lastRecallResult = result.selectedNodeIds;
|
currentGraph.lastRecallResult = result.selectedNodeIds;
|
||||||
updateLastRecalledItems(result.selectedNodeIds || []);
|
updateLastRecalledItems(result.selectedNodeIds || []);
|
||||||
saveGraphToChat();
|
saveGraphToChat();
|
||||||
|
|
||||||
|
const llmLabel =
|
||||||
|
llmMeta.status === "llm"
|
||||||
|
? "LLM 精排完成"
|
||||||
|
: llmMeta.status === "fallback"
|
||||||
|
? "LLM 回退评分"
|
||||||
|
: llmMeta.status === "disabled"
|
||||||
|
? "仅评分排序"
|
||||||
|
: "召回完成";
|
||||||
|
setLastRecallStatus(
|
||||||
|
llmLabel,
|
||||||
|
`ctx ${recentMessages.length} · vector ${retrievalMeta.vectorHits ?? 0} · diffusion ${retrievalMeta.diffusionHits ?? 0} · llm pool ${llmMeta.candidatePool ?? 0} · recall ${result.stats.recallCount}`,
|
||||||
|
llmMeta.status === "fallback" ? "warning" : "success",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (llmMeta.status === "fallback") {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastRecallFallbackNoticeAt > 15000) {
|
||||||
|
lastRecallFallbackNoticeAt = now;
|
||||||
|
toastr.warning(
|
||||||
|
llmMeta.reason || "LLM 精排未返回有效结果,已回退到评分排序",
|
||||||
|
"ST-BME 召回提示",
|
||||||
|
{ timeOut: 4500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[ST-BME] 召回失败:", e);
|
console.error("[ST-BME] 召回失败:", e);
|
||||||
|
const message = e?.message || String(e);
|
||||||
|
setLastRecallStatus("召回失败", message, "error");
|
||||||
|
toastr.error(`召回失败: ${message}`);
|
||||||
} finally {
|
} finally {
|
||||||
isRecalling = false;
|
isRecalling = false;
|
||||||
|
refreshPanelLiveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1669,6 +1765,7 @@ async function onReembedDirect() {
|
|||||||
getSettings: () => getSettings(),
|
getSettings: () => getSettings(),
|
||||||
getLastExtract: () => lastExtractedItems,
|
getLastExtract: () => lastExtractedItems,
|
||||||
getLastRecall: () => lastRecalledItems,
|
getLastRecall: () => lastRecalledItems,
|
||||||
|
getLastRecallStatus: () => lastRecallStatus,
|
||||||
getLastInjection: () => lastInjectionContent,
|
getLastInjection: () => lastInjectionContent,
|
||||||
updateSettings: (patch) => {
|
updateSettings: (patch) => {
|
||||||
const settings = updateModuleSettings(patch);
|
const settings = updateModuleSettings(patch);
|
||||||
|
|||||||
10
panel.html
10
panel.html
@@ -126,6 +126,10 @@
|
|||||||
<label>最近恢复</label>
|
<label>最近恢复</label>
|
||||||
<div class="bme-recent-meta" id="bme-status-recovery">—</div>
|
<div class="bme-recent-meta" id="bme-status-recovery">—</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bme-config-row">
|
||||||
|
<label>最近召回</label>
|
||||||
|
<div class="bme-recent-meta" id="bme-status-last-recall">—</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bme-mobile-graph-preview" id="bme-mobile-graph-area">
|
<div class="bme-mobile-graph-preview" id="bme-mobile-graph-area">
|
||||||
@@ -748,7 +752,7 @@
|
|||||||
<div class="bme-config-card-head">
|
<div class="bme-config-card-head">
|
||||||
<div>
|
<div>
|
||||||
<div class="bme-config-card-title">LLM 精确召回</div>
|
<div class="bme-config-card-title">LLM 精确召回</div>
|
||||||
<div class="bme-config-card-subtitle">控制是否启用 LLM 精排,以及传给 LLM 的候选池大小与最终保留上限。</div>
|
<div class="bme-config-card-subtitle">控制是否启用 LLM 精排,以及传给 LLM 的上下文消息数、候选池大小与最终保留上限。</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bme-config-guard-note">在“功能开关”中启用后生效。</div>
|
<div class="bme-config-guard-note">在“功能开关”中启用后生效。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -756,6 +760,10 @@
|
|||||||
<input id="bme-setting-recall-llm" type="checkbox" />
|
<input id="bme-setting-recall-llm" type="checkbox" />
|
||||||
<span>启用 LLM 精排</span>
|
<span>启用 LLM 精排</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="bme-config-row bme-stage-param">
|
||||||
|
<label for="bme-setting-recall-llm-context-messages">LLM 精排上下文消息数</label>
|
||||||
|
<input id="bme-setting-recall-llm-context-messages" class="bme-config-input" type="number" min="0" max="20" />
|
||||||
|
</div>
|
||||||
<div class="bme-config-row bme-stage-param">
|
<div class="bme-config-row bme-stage-param">
|
||||||
<label for="bme-setting-recall-llm-candidate-pool">LLM 精排候选池</label>
|
<label for="bme-setting-recall-llm-candidate-pool">LLM 精排候选池</label>
|
||||||
<input id="bme-setting-recall-llm-candidate-pool" class="bme-config-input" type="number" min="1" max="100" />
|
<input id="bme-setting-recall-llm-candidate-pool" class="bme-config-input" type="number" min="1" max="100" />
|
||||||
|
|||||||
51
panel.js
51
panel.js
@@ -120,6 +120,7 @@ let _getGraph = null;
|
|||||||
let _getSettings = null;
|
let _getSettings = null;
|
||||||
let _getLastExtract = null;
|
let _getLastExtract = null;
|
||||||
let _getLastRecall = null;
|
let _getLastRecall = null;
|
||||||
|
let _getLastRecallStatus = null;
|
||||||
let _getLastInjection = null;
|
let _getLastInjection = null;
|
||||||
let _updateSettings = null;
|
let _updateSettings = null;
|
||||||
let _actionHandlers = {};
|
let _actionHandlers = {};
|
||||||
@@ -141,6 +142,7 @@ export async function initPanel({
|
|||||||
getSettings,
|
getSettings,
|
||||||
getLastExtract,
|
getLastExtract,
|
||||||
getLastRecall,
|
getLastRecall,
|
||||||
|
getLastRecallStatus,
|
||||||
getLastInjection,
|
getLastInjection,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
actions,
|
actions,
|
||||||
@@ -149,6 +151,7 @@ export async function initPanel({
|
|||||||
_getSettings = getSettings;
|
_getSettings = getSettings;
|
||||||
_getLastExtract = getLastExtract;
|
_getLastExtract = getLastExtract;
|
||||||
_getLastRecall = getLastRecall;
|
_getLastRecall = getLastRecall;
|
||||||
|
_getLastRecallStatus = getLastRecallStatus;
|
||||||
_getLastInjection = getLastInjection;
|
_getLastInjection = getLastInjection;
|
||||||
_updateSettings = updateSettings;
|
_updateSettings = updateSettings;
|
||||||
_actionHandlers = actions || {};
|
_actionHandlers = actions || {};
|
||||||
@@ -176,6 +179,7 @@ export async function initPanel({
|
|||||||
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || "dashboard";
|
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || "dashboard";
|
||||||
_applyWorkspaceMode();
|
_applyWorkspaceMode();
|
||||||
_syncConfigSectionState();
|
_syncConfigSectionState();
|
||||||
|
_refreshRuntimeStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,6 +208,7 @@ export function openPanel() {
|
|||||||
const activeTabId =
|
const activeTabId =
|
||||||
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || currentTabId;
|
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || currentTabId;
|
||||||
_switchTab(activeTabId);
|
_switchTab(activeTabId);
|
||||||
|
_refreshRuntimeStatus();
|
||||||
_refreshGraph();
|
_refreshGraph();
|
||||||
_buildLegend();
|
_buildLegend();
|
||||||
}
|
}
|
||||||
@@ -226,6 +231,27 @@ export function updatePanelTheme(themeName) {
|
|||||||
_highlightThemeChoice(themeName);
|
_highlightThemeChoice(themeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function refreshLiveState() {
|
||||||
|
if (!overlayEl?.classList.contains("active")) return;
|
||||||
|
_refreshRuntimeStatus();
|
||||||
|
|
||||||
|
switch (currentTabId) {
|
||||||
|
case "dashboard":
|
||||||
|
_refreshDashboard();
|
||||||
|
break;
|
||||||
|
case "memory":
|
||||||
|
_refreshMemoryBrowser();
|
||||||
|
break;
|
||||||
|
case "injection":
|
||||||
|
void _refreshInjectionPreview();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_refreshGraph();
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Tab 切换 ====================
|
// ==================== Tab 切换 ====================
|
||||||
|
|
||||||
function _bindTabs() {
|
function _bindTabs() {
|
||||||
@@ -309,10 +335,6 @@ function _refreshDashboard() {
|
|||||||
_setText("bme-stat-edges", graph.edges.length);
|
_setText("bme-stat-edges", graph.edges.length);
|
||||||
_setText("bme-stat-archived", archivedCount);
|
_setText("bme-stat-archived", archivedCount);
|
||||||
_setText("bme-stat-frag", `${fragRate}%`);
|
_setText("bme-stat-frag", `${fragRate}%`);
|
||||||
_setText(
|
|
||||||
"bme-status-meta",
|
|
||||||
`NODES: ${activeNodes.length} | EDGES: ${graph.edges.length}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const chatId = graph?.historyState?.chatId || "—";
|
const chatId = graph?.historyState?.chatId || "—";
|
||||||
const lastProcessed = graph?.historyState?.lastProcessedAssistantFloor ?? -1;
|
const lastProcessed = graph?.historyState?.lastProcessedAssistantFloor ?? -1;
|
||||||
@@ -321,6 +343,7 @@ function _refreshDashboard() {
|
|||||||
const vectorMode = graph?.vectorIndexState?.mode || "—";
|
const vectorMode = graph?.vectorIndexState?.mode || "—";
|
||||||
const vectorSource = graph?.vectorIndexState?.source || "—";
|
const vectorSource = graph?.vectorIndexState?.source || "—";
|
||||||
const recovery = graph?.historyState?.lastRecoveryResult;
|
const recovery = graph?.historyState?.lastRecoveryResult;
|
||||||
|
const recallStatus = _getLastRecallStatus?.() || {};
|
||||||
|
|
||||||
_setText("bme-status-chat-id", chatId);
|
_setText("bme-status-chat-id", chatId);
|
||||||
_setText(
|
_setText(
|
||||||
@@ -339,6 +362,10 @@ function _refreshDashboard() {
|
|||||||
? `${recovery.status} · from ${recovery.fromFloor ?? "—"} · ${recovery.reason || "—"}`
|
? `${recovery.status} · from ${recovery.fromFloor ?? "—"} · ${recovery.reason || "—"}`
|
||||||
: "暂无恢复记录",
|
: "暂无恢复记录",
|
||||||
);
|
);
|
||||||
|
_setText(
|
||||||
|
"bme-status-last-recall",
|
||||||
|
recallStatus.meta || "尚未执行召回",
|
||||||
|
);
|
||||||
|
|
||||||
_renderRecentList("bme-recent-extract", _getLastExtract?.() || []);
|
_renderRecentList("bme-recent-extract", _getLastExtract?.() || []);
|
||||||
_renderRecentList("bme-recent-recall", _getLastRecall?.() || []);
|
_renderRecentList("bme-recent-recall", _getLastRecall?.() || []);
|
||||||
@@ -720,6 +747,10 @@ function _refreshConfigTab() {
|
|||||||
"bme-setting-recall-llm-candidate-pool",
|
"bme-setting-recall-llm-candidate-pool",
|
||||||
settings.recallLlmCandidatePool ?? 30,
|
settings.recallLlmCandidatePool ?? 30,
|
||||||
);
|
);
|
||||||
|
_setInputValue(
|
||||||
|
"bme-setting-recall-llm-context-messages",
|
||||||
|
settings.recallLlmContextMessages ?? 4,
|
||||||
|
);
|
||||||
_setInputValue("bme-setting-inject-depth", settings.injectDepth ?? 9999);
|
_setInputValue("bme-setting-inject-depth", settings.injectDepth ?? 9999);
|
||||||
_setInputValue("bme-setting-graph-weight", settings.graphWeight ?? 0.6);
|
_setInputValue("bme-setting-graph-weight", settings.graphWeight ?? 0.6);
|
||||||
_setInputValue("bme-setting-vector-weight", settings.vectorWeight ?? 0.3);
|
_setInputValue("bme-setting-vector-weight", settings.vectorWeight ?? 0.3);
|
||||||
@@ -892,6 +923,9 @@ function _bindConfigControls() {
|
|||||||
bindNumber("bme-setting-recall-llm-candidate-pool", 30, 1, 100, (value) =>
|
bindNumber("bme-setting-recall-llm-candidate-pool", 30, 1, 100, (value) =>
|
||||||
_patchSettings({ recallLlmCandidatePool: value }),
|
_patchSettings({ recallLlmCandidatePool: value }),
|
||||||
);
|
);
|
||||||
|
bindNumber("bme-setting-recall-llm-context-messages", 4, 0, 20, (value) =>
|
||||||
|
_patchSettings({ recallLlmContextMessages: value }),
|
||||||
|
);
|
||||||
bindNumber("bme-setting-inject-depth", 9999, 0, 9999, (value) =>
|
bindNumber("bme-setting-inject-depth", 9999, 0, 9999, (value) =>
|
||||||
_patchSettings({ injectDepth: value }),
|
_patchSettings({ injectDepth: value }),
|
||||||
);
|
);
|
||||||
@@ -1186,6 +1220,15 @@ function _setText(id, text) {
|
|||||||
if (el) el.textContent = String(text);
|
if (el) el.textContent = String(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _refreshRuntimeStatus() {
|
||||||
|
const recallStatus = _getLastRecallStatus?.() || {};
|
||||||
|
const text = recallStatus.text || "待命";
|
||||||
|
const meta = recallStatus.meta || "尚未执行召回";
|
||||||
|
_setText("bme-status-text", text);
|
||||||
|
_setText("bme-status-meta", meta);
|
||||||
|
_setText("bme-panel-status", text);
|
||||||
|
}
|
||||||
|
|
||||||
function _patchSettings(patch = {}, options = {}) {
|
function _patchSettings(patch = {}, options = {}) {
|
||||||
const settings = _updateSettings?.(patch) || _getSettings?.() || {};
|
const settings = _updateSettings?.(patch) || _getSettings?.() || {};
|
||||||
if (options.refreshGuards) _refreshGuardedConfigStates(settings);
|
if (options.refreshGuards) _refreshGuardedConfigStates(settings);
|
||||||
|
|||||||
73
retriever.js
73
retriever.js
@@ -76,9 +76,27 @@ export async function retrieve({
|
|||||||
let vectorResults = [];
|
let vectorResults = [];
|
||||||
let diffusionResults = [];
|
let diffusionResults = [];
|
||||||
let useLLM = false;
|
let useLLM = false;
|
||||||
|
let llmMeta = {
|
||||||
|
enabled: enableLLMRecall,
|
||||||
|
status: enableLLMRecall ? "pending" : "disabled",
|
||||||
|
reason: enableLLMRecall ? "" : "LLM 精排已关闭",
|
||||||
|
candidatePool: 0,
|
||||||
|
selectedSeedCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if (nodeCount === 0) {
|
if (nodeCount === 0) {
|
||||||
return buildResult(graph, [], schema);
|
return buildResult(graph, [], schema, {
|
||||||
|
retrieval: {
|
||||||
|
vectorHits: 0,
|
||||||
|
diffusionHits: 0,
|
||||||
|
scoredCandidates: 0,
|
||||||
|
llm: {
|
||||||
|
...llmMeta,
|
||||||
|
status: enableLLMRecall ? "skipped" : "disabled",
|
||||||
|
reason: "当前没有可参与召回的活跃节点",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 第 1 层:向量预筛 ==========
|
// ========== 第 1 层:向量预筛 ==========
|
||||||
@@ -208,7 +226,7 @@ export async function retrieve({
|
|||||||
0,
|
0,
|
||||||
Math.min(normalizedLlmCandidatePool, scoredNodes.length),
|
Math.min(normalizedLlmCandidatePool, scoredNodes.length),
|
||||||
);
|
);
|
||||||
selectedNodeIds = await llmRecall(
|
const llmResult = await llmRecall(
|
||||||
userMessage,
|
userMessage,
|
||||||
recentMessages,
|
recentMessages,
|
||||||
candidateNodes,
|
candidateNodes,
|
||||||
@@ -217,10 +235,25 @@ export async function retrieve({
|
|||||||
normalizedMaxRecallNodes,
|
normalizedMaxRecallNodes,
|
||||||
options.recallPrompt,
|
options.recallPrompt,
|
||||||
);
|
);
|
||||||
|
selectedNodeIds = llmResult.selectedNodeIds;
|
||||||
|
llmMeta = {
|
||||||
|
enabled: true,
|
||||||
|
status: llmResult.status,
|
||||||
|
reason: llmResult.reason,
|
||||||
|
candidatePool: candidateNodes.length,
|
||||||
|
selectedSeedCount: llmResult.selectedNodeIds.length,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
selectedNodeIds = scoredNodes
|
selectedNodeIds = scoredNodes
|
||||||
.slice(0, Math.min(normalizedTopK, scoredNodes.length))
|
.slice(0, Math.min(normalizedTopK, scoredNodes.length))
|
||||||
.map((s) => s.nodeId);
|
.map((s) => s.nodeId);
|
||||||
|
llmMeta = {
|
||||||
|
enabled: false,
|
||||||
|
status: "disabled",
|
||||||
|
reason: "LLM 精排已关闭,直接采用评分排序",
|
||||||
|
candidatePool: 0,
|
||||||
|
selectedSeedCount: selectedNodeIds.length,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedNodeIds = reconstructSceneNodeIds(
|
selectedNodeIds = reconstructSceneNodeIds(
|
||||||
@@ -265,7 +298,14 @@ export async function retrieve({
|
|||||||
|
|
||||||
selectedNodeIds = uniqueNodeIds(selectedNodeIds);
|
selectedNodeIds = uniqueNodeIds(selectedNodeIds);
|
||||||
|
|
||||||
return buildResult(graph, selectedNodeIds, schema);
|
return buildResult(graph, selectedNodeIds, schema, {
|
||||||
|
retrieval: {
|
||||||
|
vectorHits: vectorResults.length,
|
||||||
|
diffusionHits: diffusionResults.length,
|
||||||
|
scoredCandidates: scoredNodes.length,
|
||||||
|
llm: llmMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -374,14 +414,30 @@ async function llmRecall(
|
|||||||
|
|
||||||
if (result?.selected_ids && Array.isArray(result.selected_ids)) {
|
if (result?.selected_ids && Array.isArray(result.selected_ids)) {
|
||||||
// 校验 ID 有效性
|
// 校验 ID 有效性
|
||||||
const validIds = result.selected_ids.filter((id) =>
|
const validIds = uniqueNodeIds(
|
||||||
|
result.selected_ids.filter((id) =>
|
||||||
candidates.some((c) => c.nodeId === id),
|
candidates.some((c) => c.nodeId === id),
|
||||||
);
|
),
|
||||||
return validIds;
|
).slice(0, maxNodes);
|
||||||
|
|
||||||
|
if (validIds.length > 0 || result.selected_ids.length === 0) {
|
||||||
|
return {
|
||||||
|
selectedNodeIds: validIds,
|
||||||
|
status: "llm",
|
||||||
|
reason:
|
||||||
|
validIds.length < result.selected_ids.length
|
||||||
|
? "LLM 返回了部分无效或超限 ID,已自动裁剪"
|
||||||
|
: "LLM 精排完成",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLM 失败时回退到纯评分排序
|
// LLM 失败时回退到纯评分排序
|
||||||
return candidates.slice(0, maxNodes).map((c) => c.nodeId);
|
return {
|
||||||
|
selectedNodeIds: candidates.slice(0, maxNodes).map((c) => c.nodeId),
|
||||||
|
status: "fallback",
|
||||||
|
reason: "LLM 未返回有效 JSON 或有效候选,已回退到评分排序",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== v2 辅助函数 ====================
|
// ==================== v2 辅助函数 ====================
|
||||||
@@ -418,7 +474,7 @@ function filterByVisibility(nodes, characterName) {
|
|||||||
* 构建最终检索结果
|
* 构建最终检索结果
|
||||||
* 分离常驻注入(Core)和召回注入(Recall)
|
* 分离常驻注入(Core)和召回注入(Recall)
|
||||||
*/
|
*/
|
||||||
function buildResult(graph, selectedNodeIds, schema) {
|
function buildResult(graph, selectedNodeIds, schema, meta = {}) {
|
||||||
const coreNodes = [];
|
const coreNodes = [];
|
||||||
const recallNodes = [];
|
const recallNodes = [];
|
||||||
const selectedSet = new Set(uniqueNodeIds(selectedNodeIds));
|
const selectedSet = new Set(uniqueNodeIds(selectedNodeIds));
|
||||||
@@ -453,6 +509,7 @@ function buildResult(graph, selectedNodeIds, schema) {
|
|||||||
recallNodes,
|
recallNodes,
|
||||||
groupedRecallNodes,
|
groupedRecallNodes,
|
||||||
selectedNodeIds: [...selectedSet],
|
selectedNodeIds: [...selectedSet],
|
||||||
|
meta,
|
||||||
stats: {
|
stats: {
|
||||||
totalActive: activeNodes.length,
|
totalActive: activeNodes.length,
|
||||||
coreCount: coreNodes.length,
|
coreCount: coreNodes.length,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ assert.equal(defaultSettings.recallEnableVectorPrefilter, true);
|
|||||||
assert.equal(defaultSettings.recallEnableGraphDiffusion, true);
|
assert.equal(defaultSettings.recallEnableGraphDiffusion, true);
|
||||||
assert.equal(defaultSettings.recallDiffusionTopK, 100);
|
assert.equal(defaultSettings.recallDiffusionTopK, 100);
|
||||||
assert.equal(defaultSettings.recallLlmCandidatePool, 30);
|
assert.equal(defaultSettings.recallLlmCandidatePool, 30);
|
||||||
|
assert.equal(defaultSettings.recallLlmContextMessages, 4);
|
||||||
assert.equal(defaultSettings.injectDepth, 9999);
|
assert.equal(defaultSettings.injectDepth, 9999);
|
||||||
|
|
||||||
console.log("default-settings tests passed");
|
console.log("default-settings tests passed");
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ assert.deepEqual(state.vectorCalls, [4]);
|
|||||||
assert.equal(state.diffusionCalls.length, 0);
|
assert.equal(state.diffusionCalls.length, 0);
|
||||||
assert.equal(state.llmCandidateCount, 2);
|
assert.equal(state.llmCandidateCount, 2);
|
||||||
assert.deepEqual(Array.from(llmPoolResult.selectedNodeIds), ["rule-2", "rule-1"]);
|
assert.deepEqual(Array.from(llmPoolResult.selectedNodeIds), ["rule-2", "rule-1"]);
|
||||||
|
assert.equal(llmPoolResult.meta.retrieval.llm.status, "llm");
|
||||||
|
assert.equal(llmPoolResult.meta.retrieval.llm.candidatePool, 2);
|
||||||
|
|
||||||
state.vectorCalls.length = 0;
|
state.vectorCalls.length = 0;
|
||||||
state.diffusionCalls.length = 0;
|
state.diffusionCalls.length = 0;
|
||||||
@@ -187,5 +189,6 @@ await retrieve({
|
|||||||
assert.deepEqual(state.vectorCalls, [3]);
|
assert.deepEqual(state.vectorCalls, [3]);
|
||||||
assert.equal(state.diffusionCalls.length, 1);
|
assert.equal(state.diffusionCalls.length, 1);
|
||||||
assert.equal(state.diffusionCalls[0].options.topK, 7);
|
assert.equal(state.diffusionCalls[0].options.topK, 7);
|
||||||
|
assert.equal(noStageResult.meta.retrieval.llm.status, "disabled");
|
||||||
|
|
||||||
console.log("retrieval-config tests passed");
|
console.log("retrieval-config tests passed");
|
||||||
|
|||||||
Reference in New Issue
Block a user