refactor: 合并精确对照+记忆进化为统一记忆整合模块 (consolidator.js)

This commit is contained in:
Youzini-afk
2026-03-25 14:21:52 +08:00
parent 84c64f01d5
commit 1fc7570614
6 changed files with 416 additions and 443 deletions

341
consolidator.js Normal file
View File

@@ -0,0 +1,341 @@
// ST-BME: 统一记忆整合引擎
// 合并 Mem0 精确对照 + A-MEM 记忆进化为单一阶段
// 每个新节点只需 1 次 embed + 1 次 LLM 调用
import { addEdge, createEdge, getActiveNodes, getNode } from './graph.js';
import { callLLMForJSON } from './llm.js';
import {
buildNodeVectorText,
findSimilarNodesByText,
validateVectorConfig,
} from './vector-index.js';
function createAbortError(message = '操作已终止') {
const error = new Error(message);
error.name = 'AbortError';
return error;
}
function isAbortError(error) {
return error?.name === 'AbortError';
}
function throwIfAborted(signal) {
if (signal?.aborted) {
throw signal.reason instanceof Error ? signal.reason : createAbortError();
}
}
/**
* 统一记忆整合系统提示词
* 同时完成 Mem0 冲突判定 + A-MEM 进化分析
*/
const CONSOLIDATION_SYSTEM_PROMPT = `你是一个记忆整合分析器。当新记忆加入知识图谱时,你需要同时完成两项任务:
**任务一:冲突检测**
判断新记忆与最近邻的已有记忆是否冲突或重复:
- skip: 新记忆与已有记忆完全重复,应丢弃
- merge: 新记忆是对旧记忆的修正或补充,应合并
- keep: 新记忆是全新信息,应保留
**任务二:进化分析**(仅当 action=keep 时需要)
分析新记忆是否揭示了关于旧记忆的新信息:
- 建立有意义的关联连接
- 反向更新旧记忆的描述或分类
输出严格 JSON
{
"action": "keep" | "merge" | "skip",
"merge_target_id": "仅 action=merge 时必填:要合并到的旧节点 ID",
"merged_fields": { "仅 action=merge 时可选:合并后的字段更新" },
"reason": "判定理由(简述)",
"evolution": {
"should_evolve": true/false,
"connections": ["需要建立链接的旧记忆 ID 列表"],
"neighbor_updates": [
{
"nodeId": "需更新的旧节点 ID",
"newContext": "基于新信息修正后的描述(不需修改则为 null",
"newTags": ["更新后的分类标签,不需修改则为 null"]
}
]
}
}
整合规则:
- 当 action=skip 时evolution 可省略或设 should_evolve=false
- 当 action=merge 时evolution 可省略或设 should_evolve=false
- 仅当 action=keep 且新信息确实改变了对旧记忆的理解时,才设 should_evolve=true
- 例如:揭露卧底身份 → 修正该角色之前事件中的动机描述
- 例如:发现地点的隐藏特性 → 更新地点节点的描述
- 不要对无关记忆强行建立联系
- neighbor_updates 中每条必须有实际意义的修改`;
/**
* 统一记忆整合主函数
*
* 合并了原先的 mem0ConflictCheck精确对照和 evolveMemories进化
* 实现"1 次 embed + 1 次 LLM"完成冲突检测 + 进化分析。
*
* @param {object} params
* @param {object} params.graph - 当前图状态
* @param {string[]} params.newNodeIds - 本次新创建的节点 ID 列表
* @param {object} params.embeddingConfig - Embedding API 配置
* @param {object} [params.options]
* @param {number} [params.options.neighborCount=5] - 近邻搜索数量
* @param {number} [params.options.conflictThreshold=0.85] - 冲突判定阈值(低于此值跳过冲突检测)
* @param {string} [params.customPrompt] - 自定义提示词
* @param {AbortSignal} [params.signal]
* @returns {Promise<{merged: number, skipped: number, kept: number, evolved: number, connections: number, updates: number}>}
*/
export async function consolidateMemories({
graph,
newNodeIds,
embeddingConfig,
options = {},
customPrompt,
signal,
}) {
const neighborCount = options.neighborCount ?? 5;
const conflictThreshold = options.conflictThreshold ?? 0.85;
const stats = {
merged: 0,
skipped: 0,
kept: 0,
evolved: 0,
connections: 0,
updates: 0,
};
if (!newNodeIds || newNodeIds.length === 0) return stats;
if (!validateVectorConfig(embeddingConfig).valid) {
console.log('[ST-BME] 记忆整合跳过:向量配置不可用');
return stats;
}
const activeNodes = getActiveNodes(graph).filter(n => {
const text = buildNodeVectorText(n);
return typeof text === 'string' && text.length > 0;
});
if (activeNodes.length < 2) return stats;
for (const newId of newNodeIds) {
throwIfAborted(signal);
const newNode = getNode(graph, newId);
if (!newNode || newNode.archived) continue;
const queryText = buildNodeVectorText(newNode);
if (!queryText) continue;
// 排除自身的候选池
const candidates = activeNodes.filter(n => n.id !== newId);
if (candidates.length === 0) {
stats.kept++;
continue;
}
try {
// ── 1次 Embed查近邻 ──
const neighbors = await findSimilarNodesByText(
graph,
queryText,
embeddingConfig,
neighborCount,
candidates,
signal,
);
if (neighbors.length === 0) {
stats.kept++;
continue;
}
// 构建近邻描述文本
const neighborsContext = neighbors.map(n => {
const node = getNode(graph, n.nodeId);
if (!node) return null;
const fieldsStr = Object.entries(node.fields)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
return `[${node.id}] 类型=${node.type}, ${fieldsStr}, 相似度=${n.score.toFixed(3)}${
(node.clusters || []).length > 0 ? `, 分类=${node.clusters.join('/')}` : ''
}`;
}).filter(Boolean).join('\n');
const newNodeFieldsStr = Object.entries(newNode.fields)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
// 检查是否有高相似度命中(决定是否启用冲突检测部分的提示)
const hasHighSimilarity = neighbors[0].score > conflictThreshold;
const userPrompt = [
'## 新加入的记忆',
`[${newNode.id}] 类型=${newNode.type}, ${newNodeFieldsStr}`,
'',
'## 最近邻的已有记忆',
neighborsContext,
'',
`${neighbors.length} 条近邻记忆。`,
hasHighSimilarity
? `最高相似度 ${neighbors[0].score.toFixed(3)} 超过阈值 ${conflictThreshold},请先判断是否冲突/重复,再分析进化关系。`
: '相似度均较低,请重点分析新记忆是否揭示了关于旧记忆的新信息。',
].join('\n');
// ── 1次 LLM统一判定 ──
const decision = await callLLMForJSON({
systemPrompt: customPrompt || CONSOLIDATION_SYSTEM_PROMPT,
userPrompt,
maxRetries: 1,
signal,
});
if (!decision) {
stats.kept++;
continue;
}
// ── 处理 action ──
switch (decision.action) {
case 'skip': {
console.log(`[ST-BME] 记忆整合: skip (重复) — ${newId}`);
newNode.archived = true;
stats.skipped++;
break;
}
case 'merge': {
const targetId = decision.merge_target_id;
const targetNode = targetId ? getNode(graph, targetId) : null;
if (targetNode && !targetNode.archived) {
console.log(`[ST-BME] 记忆整合: merge ${newId}${targetId}`);
// 合并字段到旧节点
if (decision.merged_fields && typeof decision.merged_fields === 'object') {
for (const [key, value] of Object.entries(decision.merged_fields)) {
if (value != null && value !== '') {
targetNode.fields[key] = value;
}
}
} else {
// 如果没提供 merged_fields将新节点的非空字段补充到旧节点
for (const [key, value] of Object.entries(newNode.fields)) {
if (value != null && value !== '' && !targetNode.fields[key]) {
targetNode.fields[key] = value;
}
}
}
// 更新旧节点的 seq 为更新的值
if (Number.isFinite(newNode.seq) && newNode.seq > (targetNode.seq || 0)) {
targetNode.seq = newNode.seq;
}
// 标记旧节点需要 re-embed
targetNode.embedding = null;
// 归档新节点
newNode.archived = true;
stats.merged++;
} else {
// merge target 无效,回退为 keep
console.warn(`[ST-BME] 记忆整合: merge target ${targetId} 不存在,回退为 keep`);
stats.kept++;
}
break;
}
case 'keep':
default: {
stats.kept++;
break;
}
}
// ── 处理 evolution仅 keep 时有意义,但也容错处理其它 action ──
const evolution = decision.evolution;
if (evolution?.should_evolve && !newNode.archived) {
stats.evolved++;
console.log(`[ST-BME] 记忆整合/进化触发: ${decision.reason || '(无理由)'}`);
// 建立关联边
if (Array.isArray(evolution.connections)) {
for (const targetId of evolution.connections) {
if (!getNode(graph, targetId)) continue;
const edge = createEdge({
fromId: newId,
toId: targetId,
relation: 'related',
strength: 0.7,
});
if (addEdge(graph, edge)) {
stats.connections++;
}
}
}
// 反向更新旧节点
if (Array.isArray(evolution.neighbor_updates)) {
for (const update of evolution.neighbor_updates) {
if (!update.nodeId) continue;
const oldNode = getNode(graph, update.nodeId);
if (!oldNode || oldNode.archived) continue;
let changed = false;
// 更新 context/state 字段
if (update.newContext && typeof update.newContext === 'string') {
if (oldNode.fields.state !== undefined) {
oldNode.fields.state = update.newContext;
changed = true;
} else if (oldNode.fields.summary !== undefined) {
oldNode.fields.summary = update.newContext;
changed = true;
} else if (oldNode.fields.core_note !== undefined) {
oldNode.fields.core_note = update.newContext;
changed = true;
}
}
// 更新分类标签
if (update.newTags && Array.isArray(update.newTags)) {
oldNode.clusters = update.newTags;
changed = true;
}
if (changed) {
oldNode.embedding = null;
if (!oldNode._evolutionHistory) oldNode._evolutionHistory = [];
oldNode._evolutionHistory.push({
triggeredBy: newId,
timestamp: Date.now(),
reason: decision.reason || '',
});
stats.updates++;
}
}
}
}
} catch (e) {
if (isAbortError(e)) throw e;
console.error(`[ST-BME] 记忆整合失败 (${newId}):`, e);
stats.kept++;
}
}
const actionSummary = [];
if (stats.merged > 0) actionSummary.push(`合并 ${stats.merged}`);
if (stats.skipped > 0) actionSummary.push(`跳过 ${stats.skipped}`);
if (stats.kept > 0) actionSummary.push(`保留 ${stats.kept}`);
if (stats.evolved > 0) actionSummary.push(`进化 ${stats.evolved}`);
if (stats.connections > 0) actionSummary.push(`新链接 ${stats.connections}`);
if (stats.updates > 0) actionSummary.push(`回溯更新 ${stats.updates}`);
if (actionSummary.length > 0) {
console.log(`[ST-BME] 记忆整合完成: ${actionSummary.join(', ')}`);
}
return stats;
}

View File

@@ -1,226 +0,0 @@
// ST-BME: 记忆进化引擎A-MEM 启发)
// 新节点写入后触发,回溯更新相关旧节点的 context/tags/links
import { getActiveNodes, getNode, createEdge, addEdge } from './graph.js';
import { callLLMForJSON } from './llm.js';
import {
buildNodeVectorText,
findSimilarNodesByText,
validateVectorConfig,
} from './vector-index.js';
function createAbortError(message = '操作已终止') {
const error = new Error(message);
error.name = 'AbortError';
return error;
}
function isAbortError(error) {
return error?.name === 'AbortError';
}
function throwIfAborted(signal) {
if (signal?.aborted) {
throw signal.reason instanceof Error ? signal.reason : createAbortError();
}
}
/**
* 进化系统提示词
* 参考 A-MEM process_memory() 的进化决策 Prompt
*/
const EVOLUTION_SYSTEM_PROMPT = `你是一个记忆进化分析器。当新的记忆加入知识图谱时,你需要分析它与现有记忆的关系。
你的任务:
1. 判断新记忆是否揭示了与旧记忆相关的新信息
2. 如果是,决定如何更新旧记忆的描述和分类
3. 建立新旧记忆之间的有意义连接
输出严格 JSON
{
"should_evolve": true/false,
"reason": "进化理由(简述)",
"suggested_connections": ["需要建立链接的旧记忆ID列表"],
"neighbor_updates": [
{
"nodeId": "需更新的旧节点ID",
"newContext": "基于新信息修正后的描述如不需修改则为null",
"newTags": ["更新后的分类标签如不需修改则为null"]
}
]
}
进化规则:
- 仅当新信息确实改变了对旧记忆的理解时才触发进化
- 例如:揭露卧底身份 → 修正该角色之前事件中的动机描述
- 例如:发现地点的隐藏特性 → 更新地点节点的描述
- 不要对无关记忆强行建立联系
- neighbor_updates 中每条必须有实际意义的修改`;
/**
* 记忆进化主函数
*
* @param {object} params
* @param {object} params.graph - 当前图状态
* @param {string[]} params.newNodeIds - 本次新创建的节点 ID 列表
* @param {object} params.embeddingConfig - Embedding API 配置
* @param {object} [params.options]
* @returns {Promise<{evolved: number, connections: number, updates: number}>}
*/
export async function evolveMemories({
graph,
newNodeIds,
embeddingConfig,
options = {},
customPrompt,
signal,
}) {
const neighborCount = options.neighborCount ?? 5;
const stats = { evolved: 0, connections: 0, updates: 0 };
if (!newNodeIds || newNodeIds.length === 0) return stats;
if (!validateVectorConfig(embeddingConfig).valid) {
console.log('[ST-BME] 记忆进化跳过:向量配置不可用');
return stats;
}
const activeNodes = getActiveNodes(graph);
if (activeNodes.length < 2) return stats; // 至少需要 2 个节点才有进化意义
for (const newId of newNodeIds) {
throwIfAborted(signal);
const newNode = getNode(graph, newId);
if (!newNode) continue;
const queryText = buildNodeVectorText(newNode);
if (!queryText) continue;
const candidates = activeNodes.filter(n => n.id !== newId);
if (candidates.length === 0) continue;
const neighbors = await findSimilarNodesByText(
graph,
queryText,
embeddingConfig,
neighborCount,
candidates,
signal,
);
if (neighbors.length === 0) continue;
// 构建 LLM 上下文
const neighborsContext = neighbors.map(n => {
const node = getNode(graph, n.nodeId);
if (!node) return null;
const fieldsStr = Object.entries(node.fields)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
return `[${node.id}] 类型=${node.type}, ${fieldsStr}, 分类=${(node.clusters || []).join('/')}`;
}).filter(Boolean).join('\n');
const newNodeFieldsStr = Object.entries(newNode.fields)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
const userPrompt = [
'## 新加入的记忆',
`[${newNode.id}] 类型=${newNode.type}, ${newNodeFieldsStr}`,
'',
'## 最近邻的已有记忆',
neighborsContext,
'',
`${neighbors.length} 条近邻记忆。请分析新记忆是否揭示了关于旧记忆的新信息。`,
].join('\n');
try {
const decision = await callLLMForJSON({
systemPrompt: customPrompt || EVOLUTION_SYSTEM_PROMPT,
userPrompt,
maxRetries: 1,
signal,
});
if (!decision || !decision.should_evolve) continue;
stats.evolved++;
console.log(`[ST-BME] 记忆进化触发: ${decision.reason || '(无理由)'}`);
// 1. 建立链接strengthen
if (decision.suggested_connections && Array.isArray(decision.suggested_connections)) {
for (const targetId of decision.suggested_connections) {
// 验证目标节点存在
if (!getNode(graph, targetId)) continue;
const edge = createEdge({
fromId: newId,
toId: targetId,
relation: 'related',
strength: 0.7,
});
if (addEdge(graph, edge)) {
stats.connections++;
}
}
}
// 2. 反向更新旧节点update_neighbor
if (decision.neighbor_updates && Array.isArray(decision.neighbor_updates)) {
for (const update of decision.neighbor_updates) {
if (!update.nodeId) continue;
const oldNode = getNode(graph, update.nodeId);
if (!oldNode) continue;
let changed = false;
// 更新 context/state 字段
if (update.newContext && typeof update.newContext === 'string') {
// 根据节点类型选择更新哪个字段
if (oldNode.fields.state !== undefined) {
oldNode.fields.state = update.newContext;
changed = true;
} else if (oldNode.fields.summary !== undefined) {
oldNode.fields.summary = update.newContext;
changed = true;
} else if (oldNode.fields.core_note !== undefined) {
oldNode.fields.core_note = update.newContext;
changed = true;
}
}
// 更新分类标签
if (update.newTags && Array.isArray(update.newTags)) {
oldNode.clusters = update.newTags;
changed = true;
}
if (changed) {
// 标记需要重新生成 embedding
oldNode.embedding = null;
// 记录进化历史
if (!oldNode._evolutionHistory) oldNode._evolutionHistory = [];
oldNode._evolutionHistory.push({
triggeredBy: newId,
timestamp: Date.now(),
reason: decision.reason || '',
});
stats.updates++;
}
}
}
} catch (e) {
if (isAbortError(e)) {
throw e;
}
console.error(`[ST-BME] 记忆进化失败 (${newId}):`, e);
}
}
if (stats.evolved > 0) {
console.log(
`[ST-BME] 记忆进化完成: ${stats.evolved} 次进化, ` +
`${stats.connections} 条新链接, ${stats.updates} 个节点回溯更新`,
);
}
return stats;
}

View File

@@ -22,9 +22,7 @@ import {
import { RELATION_TYPES } from "./schema.js"; import { RELATION_TYPES } from "./schema.js";
import { import {
buildNodeVectorText, buildNodeVectorText,
findSimilarNodesByText,
isDirectVectorConfig, isDirectVectorConfig,
validateVectorConfig,
} from "./vector-index.js"; } from "./vector-index.js";
function createAbortError(message = "操作已终止") { function createAbortError(message = "操作已终止") {
@@ -69,7 +67,6 @@ export async function extractMemories({
schema, schema,
embeddingConfig, embeddingConfig,
extractPrompt, extractPrompt,
v2Options = {},
signal = undefined, signal = undefined,
}) { }) {
throwIfAborted(signal); throwIfAborted(signal);
@@ -84,8 +81,7 @@ export async function extractMemories({
}; };
} }
const enablePreciseConflict = v2Options.enablePreciseConflict ?? true;
const conflictThreshold = v2Options.conflictThreshold ?? 0.85;
const effectiveStartSeq = Number.isFinite(startSeq) const effectiveStartSeq = Number.isFinite(startSeq)
? startSeq ? startSeq
@@ -154,17 +150,7 @@ export async function extractMemories({
}; };
} }
// ========== v2: Mem0 精确对照阶段 ==========
if (enablePreciseConflict && validateVectorConfig(embeddingConfig).valid) {
await mem0ConflictCheck(
graph,
result.operations,
embeddingConfig,
conflictThreshold,
effectiveEndSeq,
signal,
);
}
// 执行操作 // 执行操作
const stats = { newNodes: 0, updatedNodes: 0, newEdges: 0 }; const stats = { newNodes: 0, updatedNodes: 0, newEdges: 0 };
@@ -570,92 +556,6 @@ function buildDefaultExtractPrompt(schema) {
// ==================== v2 增强功能 ==================== // ==================== v2 增强功能 ====================
/**
* Mem0 启发的精确对照
* 对每条 create 操作搜索近邻,高相似度时让 LLM 判断 add/update/skip
*/
async function mem0ConflictCheck(
graph,
operations,
embeddingConfig,
threshold,
fallbackSeq,
signal,
) {
const activeNodes = getActiveNodes(graph).filter((node) => {
const text = buildNodeVectorText(node);
return typeof text === "string" && text.length > 0;
});
if (activeNodes.length === 0) return;
for (const op of operations) {
if (op.action !== "create") continue;
const factText =
op.fields?.summary || op.fields?.name || op.fields?.title || "";
if (!factText) continue;
try {
throwIfAborted(signal);
const similar = await findSimilarNodesByText(
graph,
factText,
embeddingConfig,
3,
activeNodes,
signal,
);
if (similar.length > 0 && similar[0].score > threshold) {
const topMatch = graph.nodes.find((n) => n.id === similar[0].nodeId);
if (!topMatch) continue;
const topFields = Object.entries(topMatch.fields)
.map(([k, v]) => `${k}: ${v}`)
.join(", ");
const decision = await callLLMForJSON({
systemPrompt: [
"判断新信息与已有记忆的关系。输出严格 JSON",
'{"action": "add"|"update"|"skip", "targetId": "旧节点ID", "mergedFields": {}}',
"- add: 新信息完全不同,应新建",
"- update: 新信息是对旧记忆的修正/补充",
"- skip: 与旧记忆完全重复",
].join("\n"),
userPrompt: [
`新信息: [${op.type}] ${factText}`,
`最相似旧记忆: [${topMatch.id}] 类型=${topMatch.type}, ${topFields}`,
`相似度: ${similar[0].score.toFixed(3)}`,
].join("\n"),
maxRetries: 1,
signal,
});
if (decision?.action === "update" && decision.targetId) {
console.log(
`[ST-BME] Mem0对照: create->update (${decision.targetId})`,
);
op.action = "update";
op.nodeId = decision.targetId;
op.sourceNodeId = op.sourceNodeId || topMatch.id;
op.seq = Number.isFinite(op.seq) ? op.seq : fallbackSeq;
if (decision.mergedFields) {
op.fields = { ...op.fields, ...decision.mergedFields };
}
} else if (decision?.action === "skip") {
console.log("[ST-BME] Mem0对照: create->skip (重复)");
op.action = "_skip";
}
}
} catch (e) {
if (isAbortError(e)) {
throw e;
}
console.warn("[ST-BME] Mem0对照失败保持原操作:", e.message);
}
}
}
/** /**
* 全局故事概要生成MemoRAG 启发) * 全局故事概要生成MemoRAG 启发)
* 基于图中事件/角色/主线自动生成/更新 synopsis 节点 * 基于图中事件/角色/主线自动生成/更新 synopsis 节点

View File

@@ -15,7 +15,7 @@ import {
} from "../../../extensions.js"; } from "../../../extensions.js";
import { compressAll, sleepCycle } from "./compressor.js"; import { compressAll, sleepCycle } from "./compressor.js";
import { evolveMemories } from "./evolution.js"; import { consolidateMemories } from "./consolidator.js";
import { import {
extractMemories, extractMemories,
generateReflection, generateReflection,
@@ -122,14 +122,10 @@ const defaultSettings = {
// ====== v2 增强设置 ====== // ====== v2 增强设置 ======
// ③ A-MEM 记忆进化 // ③ 记忆整合(合并精确对照 + 记忆进化
enableEvolution: true, // 启用记忆进化 enableConsolidation: true, // 启用记忆整合
evoNeighborCount: 5, // 近邻搜索数量 consolidationNeighborCount: 5, // 近邻搜索数量
evoConsolidateEvery: 50, // 每 N 次进化后整理 consolidationThreshold: 0.85, // 冲突判定相似度阈值
// ② Mem0 精确对照
enablePreciseConflict: true, // 启用精确对照
conflictThreshold: 0.85, // 相似度阈值
// ⑨ 全局故事概要 // ⑨ 全局故事概要
enableSynopsis: true, // 启用全局概要 enableSynopsis: true, // 启用全局概要
@@ -1642,20 +1638,23 @@ async function handleExtractionSuccess(
currentGraph.historyState.extractionCount = extractionCount; currentGraph.historyState.extractionCount = extractionCount;
updateLastExtractedItems(result.newNodeIds || []); updateLastExtractedItems(result.newNodeIds || []);
if (settings.enableEvolution && result.newNodeIds?.length > 0) { if (settings.enableConsolidation && result.newNodeIds?.length > 0) {
try { try {
await evolveMemories({ await consolidateMemories({
graph: currentGraph, graph: currentGraph,
newNodeIds: result.newNodeIds, newNodeIds: result.newNodeIds,
embeddingConfig: getEmbeddingConfig(), embeddingConfig: getEmbeddingConfig(),
options: { neighborCount: settings.evoNeighborCount }, options: {
customPrompt: settings.evolutionPrompt || undefined, neighborCount: settings.consolidationNeighborCount,
conflictThreshold: settings.consolidationThreshold,
},
customPrompt: settings.consolidationPrompt || undefined,
signal, signal,
}); });
postProcessArtifacts.push("evolution"); postProcessArtifacts.push("consolidation");
} catch (e) { } catch (e) {
if (isAbortError(e)) throw e; if (isAbortError(e)) throw e;
console.error("[ST-BME] 记忆进化失败:", e); console.error("[ST-BME] 记忆整合失败:", e);
} }
} }
@@ -2173,10 +2172,6 @@ async function executeExtractionBatch({
schema: getSchema(), schema: getSchema(),
embeddingConfig: getEmbeddingConfig(), embeddingConfig: getEmbeddingConfig(),
extractPrompt: settings.extractPrompt || undefined, extractPrompt: settings.extractPrompt || undefined,
v2Options: {
enablePreciseConflict: settings.enablePreciseConflict,
conflictThreshold: settings.conflictThreshold,
},
signal, signal,
}); });
@@ -3356,18 +3351,21 @@ async function onManualEvolve() {
} }
const beforeSnapshot = cloneGraphSnapshot(currentGraph); const beforeSnapshot = cloneGraphSnapshot(currentGraph);
const result = await evolveMemories({ const result = await consolidateMemories({
graph: currentGraph, graph: currentGraph,
newNodeIds: candidateIds, newNodeIds: candidateIds,
embeddingConfig: getEmbeddingConfig(), embeddingConfig: getEmbeddingConfig(),
options: { neighborCount: getSettings().evoNeighborCount }, options: {
neighborCount: getSettings().consolidationNeighborCount,
conflictThreshold: getSettings().consolidationThreshold,
},
}); });
await recordGraphMutation({ await recordGraphMutation({
beforeSnapshot, beforeSnapshot,
artifactTags: ["evolution"], artifactTags: ["consolidation"],
}); });
toastr.success( toastr.success(
`进化完成:${result.evolved} 次进化${result.connections} 条链接${result.updates} 个回溯更新`, `整合完成:合并 ${result.merged},跳过 ${result.skipped},保留 ${result.kept},进化 ${result.evolved}新链接 ${result.connections}回溯更新 ${result.updates}`,
); );
} }

View File

@@ -551,19 +551,12 @@
</span> </span>
<input id="bme-setting-recall-enabled" type="checkbox" /> <input id="bme-setting-recall-enabled" type="checkbox" />
</label> </label>
<label class="bme-toggle-item" for="bme-setting-evolution-enabled"> <label class="bme-toggle-item" for="bme-setting-consolidation-enabled">
<span class="bme-toggle-copy"> <span class="bme-toggle-copy">
<span class="bme-toggle-title">启用记忆进化</span> <span class="bme-toggle-title">启用记忆整合</span>
<span class="bme-toggle-desc">根据新提取结果反向修正既有记忆</span> <span class="bme-toggle-desc">去重/冲突检测 + 反向修正既有记忆 + 建立关联边</span>
</span> </span>
<input id="bme-setting-evolution-enabled" type="checkbox" /> <input id="bme-setting-consolidation-enabled" type="checkbox" />
</label>
<label class="bme-toggle-item" for="bme-setting-precise-conflict-enabled">
<span class="bme-toggle-copy">
<span class="bme-toggle-title">启用精确对照</span>
<span class="bme-toggle-desc">识别与既有记忆冲突或需要修订的片段。</span>
</span>
<input id="bme-setting-precise-conflict-enabled" type="checkbox" />
</label> </label>
<label class="bme-toggle-item" for="bme-setting-synopsis-enabled"> <label class="bme-toggle-item" for="bme-setting-synopsis-enabled">
<span class="bme-toggle-copy"> <span class="bme-toggle-copy">
@@ -782,42 +775,22 @@
</div> </div>
</div> </div>
<div class="bme-config-card bme-guarded-card" data-guard-settings="enableEvolution"> <div class="bme-config-card bme-guarded-card" data-guard-settings="enableConsolidation">
<div class="bme-config-card-head"> <div class="bme-config-card-head">
<div> <div>
<div class="bme-config-card-title">进化</div> <div class="bme-config-card-title">记忆整合</div>
<div class="bme-config-card-subtitle">控制邻搜索规模和整理频率</div> <div class="bme-config-card-subtitle">控制邻搜索规模和冲突判定阈值</div>
</div> </div>
<div class="bme-config-guard-note">在“功能开关”中启用后生效。</div> <div class="bme-config-guard-note">在“功能开关”中启用后生效。</div>
</div> </div>
<div class="bme-config-row"> <div class="bme-config-row">
<label for="bme-setting-evo-neighbor-count">近邻数量</label> <label for="bme-setting-consolidation-neighbor-count">近邻数量</label>
<input id="bme-setting-evo-neighbor-count" class="bme-config-input" type="number" min="1" max="20" /> <input id="bme-setting-consolidation-neighbor-count" class="bme-config-input" type="number" min="1" max="20" />
</div> </div>
<div class="bme-config-row"> <div class="bme-config-row">
<label for="bme-setting-evo-consolidate-every">整理频率</label> <label for="bme-setting-consolidation-threshold">冲突判定阈值</label>
<input <input
id="bme-setting-evo-consolidate-every" id="bme-setting-consolidation-threshold"
class="bme-config-input"
type="number"
min="1"
max="500"
/>
</div>
</div>
<div class="bme-config-card bme-guarded-card" data-guard-settings="enablePreciseConflict">
<div class="bme-config-card-head">
<div>
<div class="bme-config-card-title">冲突对照</div>
<div class="bme-config-card-subtitle">调整对照阶段的判定阈值。</div>
</div>
<div class="bme-config-guard-note">在“功能开关”中启用后生效。</div>
</div>
<div class="bme-config-row">
<label for="bme-setting-conflict-threshold">对照阈值</label>
<input
id="bme-setting-conflict-threshold"
class="bme-config-input" class="bme-config-input"
type="number" type="number"
min="0.5" min="0.5"
@@ -986,26 +959,26 @@
<textarea id="bme-setting-recall-prompt" class="bme-config-textarea" placeholder="留空 = 默认召回 prompt"></textarea> <textarea id="bme-setting-recall-prompt" class="bme-config-textarea" placeholder="留空 = 默认召回 prompt"></textarea>
</div> </div>
<div class="bme-prompt-card" data-setting-key="evolutionPrompt" data-default-prompt="evolution"> <div class="bme-prompt-card" data-setting-key="consolidationPrompt" data-default-prompt="consolidation">
<div class="bme-prompt-card-head"> <div class="bme-prompt-card-head">
<div> <div>
<div class="bme-config-card-title">记忆进化</div> <div class="bme-config-card-title">记忆整合</div>
<div class="bme-config-card-subtitle">用于分析新记忆如何修正或连接既有节点</div> <div class="bme-config-card-subtitle">用于分析新记忆与旧记忆的冲突、去重和进化关系</div>
</div> </div>
<div class="bme-prompt-card-actions"> <div class="bme-prompt-card-actions">
<span class="bme-prompt-status"></span> <span class="bme-prompt-status"></span>
<button <button
class="menu_button bme-prompt-reset" class="menu_button bme-prompt-reset"
data-setting-key="evolutionPrompt" data-setting-key="consolidationPrompt"
data-default-prompt="evolution" data-default-prompt="consolidation"
data-target-id="bme-setting-evolution-prompt" data-target-id="bme-setting-consolidation-prompt"
type="button" type="button"
> >
恢复默认 恢复默认
</button> </button>
</div> </div>
</div> </div>
<textarea id="bme-setting-evolution-prompt" class="bme-config-textarea" placeholder="留空 = 默认进化 prompt"></textarea> <textarea id="bme-setting-consolidation-prompt" class="bme-config-textarea" placeholder="留空 = 默认整合 prompt"></textarea>
</div> </div>
<div class="bme-prompt-card" data-setting-key="compressPrompt" data-default-prompt="compress"> <div class="bme-prompt-card" data-setting-key="compressPrompt" data-default-prompt="compress">

View File

@@ -54,28 +54,30 @@ const DEFAULT_PROMPTS = {
'{"selected_ids": ["id1", "id2", ...], "reason": "简要说明选择理由"}', '{"selected_ids": ["id1", "id2", ...], "reason": "简要说明选择理由"}',
].join("\n"), ].join("\n"),
evolution: [ consolidation: [
"你是一个记忆进化分析器。当新记忆加入知识图谱时,你需要分析它与现有记忆的关系。", "你是一个记忆整合分析器。当新记忆加入知识图谱时,你需要同时完成两项任务:",
"", "",
"你的任务:", "任务一:冲突检测",
"1. 判断新记忆是否揭示了与旧记忆相关的新信息", "- skip: 新记忆与已有记忆完全重复",
"2. 如果是,决定如何更新旧记忆的描述和分类", "- merge: 新记忆是对旧记忆的修正/补充",
"3. 建立新旧记忆之间的有意义连接", "- keep: 新记忆是全新信息",
"",
"任务二:进化分析(仅 action=keep 时)",
"- 建立关联连接",
"- 反向更新旧记忆",
"", "",
"输出严格 JSON", "输出严格 JSON",
"{", "{",
' "should_evolve": true/false,', ' "action": "keep"|"merge"|"skip",',
' "reason": "进化理由",', ' "merge_target_id": "旧节点ID",',
' "suggested_connections": ["旧记忆ID"],', ' "merged_fields": {},',
' "neighbor_updates": [', ' "reason": "理由",',
' {"nodeId": "旧节点ID", "newContext": "修正描述", "newTags": ["标签"]}', ' "evolution": {',
" ]", ' "should_evolve": true/false,',
' "connections": ["旧记忆ID"],',
' "neighbor_updates": [{"nodeId": "旧节点ID", "newContext": "修正描述", "newTags": ["标签"]}]',
" }",
"}", "}",
"",
"进化规则:",
"- 仅当新信息确实改变了对旧记忆的理解时才触发进化",
"- 例如:揭露卧底身份 → 修正该角色之前事件中的动机描述",
"- 不要对无关记忆强行建立联系",
].join("\n"), ].join("\n"),
compress: [ compress: [
@@ -732,11 +734,7 @@ function _refreshConfigTab() {
"bme-setting-recall-graph-diffusion-enabled", "bme-setting-recall-graph-diffusion-enabled",
settings.recallEnableGraphDiffusion ?? true, settings.recallEnableGraphDiffusion ?? true,
); );
_setCheckboxValue("bme-setting-evolution-enabled", settings.enableEvolution ?? true); _setCheckboxValue("bme-setting-consolidation-enabled", settings.enableConsolidation ?? true);
_setCheckboxValue(
"bme-setting-precise-conflict-enabled",
settings.enablePreciseConflict ?? true,
);
_setCheckboxValue("bme-setting-synopsis-enabled", settings.enableSynopsis ?? true); _setCheckboxValue("bme-setting-synopsis-enabled", settings.enableSynopsis ?? true);
_setCheckboxValue( _setCheckboxValue(
"bme-setting-visibility-enabled", "bme-setting-visibility-enabled",
@@ -790,16 +788,12 @@ function _refreshConfigTab() {
settings.importanceWeight ?? 0.1, settings.importanceWeight ?? 0.1,
); );
_setInputValue( _setInputValue(
"bme-setting-evo-neighbor-count", "bme-setting-consolidation-neighbor-count",
settings.evoNeighborCount ?? 5, settings.consolidationNeighborCount ?? 5,
); );
_setInputValue( _setInputValue(
"bme-setting-evo-consolidate-every", "bme-setting-consolidation-threshold",
settings.evoConsolidateEvery ?? 50, settings.consolidationThreshold ?? 0.85,
);
_setInputValue(
"bme-setting-conflict-threshold",
settings.conflictThreshold ?? 0.85,
); );
_setInputValue("bme-setting-synopsis-every", settings.synopsisEveryN ?? 5); _setInputValue("bme-setting-synopsis-every", settings.synopsisEveryN ?? 5);
_setInputValue( _setInputValue(
@@ -855,7 +849,7 @@ function _refreshConfigTab() {
_setInputValue("bme-setting-extract-prompt", settings.extractPrompt || DEFAULT_PROMPTS.extract); _setInputValue("bme-setting-extract-prompt", settings.extractPrompt || DEFAULT_PROMPTS.extract);
_setInputValue("bme-setting-recall-prompt", settings.recallPrompt || DEFAULT_PROMPTS.recall); _setInputValue("bme-setting-recall-prompt", settings.recallPrompt || DEFAULT_PROMPTS.recall);
_setInputValue("bme-setting-evolution-prompt", settings.evolutionPrompt || DEFAULT_PROMPTS.evolution); _setInputValue("bme-setting-consolidation-prompt", settings.consolidationPrompt || DEFAULT_PROMPTS.consolidation);
_setInputValue("bme-setting-compress-prompt", settings.compressPrompt || DEFAULT_PROMPTS.compress); _setInputValue("bme-setting-compress-prompt", settings.compressPrompt || DEFAULT_PROMPTS.compress);
_setInputValue("bme-setting-synopsis-prompt", settings.synopsisPrompt || DEFAULT_PROMPTS.synopsis); _setInputValue("bme-setting-synopsis-prompt", settings.synopsisPrompt || DEFAULT_PROMPTS.synopsis);
_setInputValue("bme-setting-reflection-prompt", settings.reflectionPrompt || DEFAULT_PROMPTS.reflection); _setInputValue("bme-setting-reflection-prompt", settings.reflectionPrompt || DEFAULT_PROMPTS.reflection);
@@ -901,12 +895,8 @@ function _bindConfigControls() {
_patchSettings({ recallEnableGraphDiffusion: checked }); _patchSettings({ recallEnableGraphDiffusion: checked });
_refreshStageCardStates(); _refreshStageCardStates();
}); });
bindCheckbox("bme-setting-evolution-enabled", (checked) => { bindCheckbox("bme-setting-consolidation-enabled", (checked) => {
_patchSettings({ enableEvolution: checked }); _patchSettings({ enableConsolidation: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-precise-conflict-enabled", (checked) => {
_patchSettings({ enablePreciseConflict: checked });
_refreshGuardedConfigStates(); _refreshGuardedConfigStates();
}); });
bindCheckbox("bme-setting-synopsis-enabled", (checked) => { bindCheckbox("bme-setting-synopsis-enabled", (checked) => {
@@ -969,14 +959,11 @@ function _bindConfigControls() {
bindFloat("bme-setting-importance-weight", 0.1, 0, 1, (value) => bindFloat("bme-setting-importance-weight", 0.1, 0, 1, (value) =>
_patchSettings({ importanceWeight: value }), _patchSettings({ importanceWeight: value }),
); );
bindNumber("bme-setting-evo-neighbor-count", 5, 1, 20, (value) => bindNumber("bme-setting-consolidation-neighbor-count", 5, 1, 20, (value) =>
_patchSettings({ evoNeighborCount: value }), _patchSettings({ consolidationNeighborCount: value }),
); );
bindNumber("bme-setting-evo-consolidate-every", 50, 1, 500, (value) => bindFloat("bme-setting-consolidation-threshold", 0.85, 0.5, 0.99, (value) =>
_patchSettings({ evoConsolidateEvery: value }), _patchSettings({ consolidationThreshold: value }),
);
bindFloat("bme-setting-conflict-threshold", 0.85, 0.5, 0.99, (value) =>
_patchSettings({ conflictThreshold: value }),
); );
bindNumber("bme-setting-synopsis-every", 5, 1, 100, (value) => bindNumber("bme-setting-synopsis-every", 5, 1, 100, (value) =>
_patchSettings({ synopsisEveryN: value }), _patchSettings({ synopsisEveryN: value }),
@@ -1061,9 +1048,9 @@ function _bindConfigControls() {
"recall", "recall",
); );
bindPromptText( bindPromptText(
"bme-setting-evolution-prompt", "bme-setting-consolidation-prompt",
"evolutionPrompt", "consolidationPrompt",
"evolution", "consolidation",
); );
bindPromptText( bindPromptText(
"bme-setting-compress-prompt", "bme-setting-compress-prompt",