Refine automatic consolidation and compression triggers

This commit is contained in:
Youzini-afk
2026-04-04 15:54:39 +08:00
parent 4bbbd4d09d
commit f367b8989c
7 changed files with 977 additions and 98 deletions

View File

@@ -111,6 +111,153 @@ const CONSOLIDATION_SYSTEM_PROMPT = `你是一个记忆整合分析器。当新
- 不要对无关记忆强行建立联系
- neighbor_updates 中每条必须有实际意义的修改`;
function normalizeLatestOnlyIdentityValue(value) {
return String(value ?? "")
.trim()
.replace(/\s+/g, " ")
.toLowerCase();
}
export async function analyzeAutoConsolidationGate({
graph,
newNodeIds,
embeddingConfig,
schema = [],
conflictThreshold = 0.85,
signal,
} = {}) {
const normalizedThreshold = Number.isFinite(Number(conflictThreshold))
? Math.max(0, Math.min(1, Number(conflictThreshold)))
: 0.85;
const safeNewNodeIds = Array.isArray(newNodeIds) ? newNodeIds : [];
if (!graph || safeNewNodeIds.length === 0) {
return {
triggered: false,
reason: "本批新增少且无明显重复风险,跳过自动整合",
matchedScore: null,
matchedNodeId: "",
detection: "none",
};
}
const schemaByType = new Map(
(Array.isArray(schema) ? schema : [])
.filter((typeDef) => typeDef?.id)
.map((typeDef) => [String(typeDef.id), typeDef]),
);
const activeNodes = getActiveNodes(graph).filter((node) => !node?.archived);
const vectorConfigValid = validateVectorConfig(embeddingConfig).valid;
let bestVectorMatch = null;
for (const newNodeId of safeNewNodeIds) {
throwIfAborted(signal);
const node = getNode(graph, newNodeId);
if (!node || node.archived) continue;
const typeDef = schemaByType.get(String(node.type || ""));
const scopedCandidates = activeNodes.filter(
(candidate) =>
candidate?.id !== node.id && canMergeScopedMemories(node, candidate),
);
if (typeDef?.latestOnly) {
for (const field of ["name", "title"]) {
const normalizedIdentity = normalizeLatestOnlyIdentityValue(
node?.fields?.[field],
);
if (!normalizedIdentity) continue;
const matchedNode = scopedCandidates.find(
(candidate) =>
candidate?.type === node.type &&
normalizeLatestOnlyIdentityValue(candidate?.fields?.[field]) ===
normalizedIdentity,
);
if (matchedNode) {
return {
triggered: true,
reason: `本批仅新增 ${safeNewNodeIds.length} 个节点,但 latestOnly 的 ${field} 与旧记忆完全一致,已触发自动整合`,
matchedScore: 1,
matchedNodeId: matchedNode.id,
detection: `latestOnly:${field}`,
};
}
}
}
if (!vectorConfigValid) continue;
const text = buildNodeVectorText(node);
if (!text) continue;
try {
const neighbors = await findSimilarNodesByText(
graph,
text,
embeddingConfig,
1,
scopedCandidates,
signal,
);
const topNeighbor = Array.isArray(neighbors) ? neighbors[0] : null;
if (!topNeighbor?.nodeId) continue;
if (
!bestVectorMatch ||
Number(topNeighbor.score || 0) > Number(bestVectorMatch.score || 0)
) {
bestVectorMatch = {
score: Number(topNeighbor.score || 0),
nodeId: topNeighbor.nodeId,
};
}
if (Number(topNeighbor.score || 0) >= normalizedThreshold) {
return {
triggered: true,
reason: `本批仅新增 ${safeNewNodeIds.length} 个节点,但与旧记忆高度相似(${Number(topNeighbor.score || 0).toFixed(3)} >= ${normalizedThreshold.toFixed(2)}),已触发自动整合`,
matchedScore: Number(topNeighbor.score || 0),
matchedNodeId: topNeighbor.nodeId,
detection: "vector",
};
}
} catch (error) {
if (isAbortError(error)) throw error;
console.warn(
`[ST-BME] 自动整合门禁近邻查询失败 (${newNodeId}):`,
error?.message || error,
);
}
}
if (bestVectorMatch) {
return {
triggered: false,
reason: `本批新增少且最高相似度 ${bestVectorMatch.score.toFixed(3)} 未达到阈值 ${normalizedThreshold.toFixed(2)},跳过自动整合`,
matchedScore: bestVectorMatch.score,
matchedNodeId: bestVectorMatch.nodeId,
detection: "vector",
};
}
if (!vectorConfigValid) {
return {
triggered: false,
reason: "本批新增少且当前向量不可用,未检测到明确重复风险,跳过自动整合",
matchedScore: null,
matchedNodeId: "",
detection: "vector-unavailable",
};
}
return {
triggered: false,
reason: "本批新增少且无明显重复风险,跳过自动整合",
matchedScore: null,
matchedNodeId: "",
detection: "none",
};
}
/**
* 统一记忆整合主函数(批量化版)
*