story-summary: facts migration + recall enhancements
This commit is contained in:
@@ -11,7 +11,7 @@ import { getAllEventVectors, getAllChunkVectors, getChunksByFloors, getMeta } fr
|
||||
import { embed, getEngineFingerprint } from './embedder.js';
|
||||
import { xbLog } from '../../../core/debug-core.js';
|
||||
import { getContext } from '../../../../../../extensions.js';
|
||||
import { getSummaryStore } from '../data/store.js';
|
||||
import { getSummaryStore, getFacts, getNewCharacters, isRelationFact } from '../data/store.js';
|
||||
import { filterText } from './text-filter.js';
|
||||
import {
|
||||
searchStateAtoms,
|
||||
@@ -258,6 +258,23 @@ function buildEntityLexicon(store, allEvents) {
|
||||
const userName = normalize(name1);
|
||||
const set = new Set();
|
||||
|
||||
const facts = getFacts(store);
|
||||
for (const f of facts) {
|
||||
if (f?.retracted) continue;
|
||||
const s = normalize(f?.s);
|
||||
if (s) set.add(s);
|
||||
if (isRelationFact(f)) {
|
||||
const o = normalize(f?.o);
|
||||
if (o) set.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
const chars = getNewCharacters(store);
|
||||
for (const m of chars || []) {
|
||||
const s = normalize(typeof m === 'string' ? m : m?.name);
|
||||
if (s) set.add(s);
|
||||
}
|
||||
|
||||
for (const e of allEvents || []) {
|
||||
for (const p of e.participants || []) {
|
||||
const s = normalize(p);
|
||||
@@ -265,30 +282,11 @@ function buildEntityLexicon(store, allEvents) {
|
||||
}
|
||||
}
|
||||
|
||||
const json = store?.json || {};
|
||||
|
||||
for (const m of json.characters?.main || []) {
|
||||
const s = normalize(typeof m === 'string' ? m : m?.name);
|
||||
if (s) set.add(s);
|
||||
}
|
||||
|
||||
for (const a of json.arcs || []) {
|
||||
for (const a of store?.json?.arcs || []) {
|
||||
const s = normalize(a?.name);
|
||||
if (s) set.add(s);
|
||||
}
|
||||
|
||||
for (const w of json.world || []) {
|
||||
const t = normalize(w?.topic);
|
||||
if (t && !t.includes('::')) set.add(t);
|
||||
}
|
||||
|
||||
for (const r of json.characters?.relationships || []) {
|
||||
const from = normalize(r?.from);
|
||||
const to = normalize(r?.to);
|
||||
if (from) set.add(from);
|
||||
if (to) set.add(to);
|
||||
}
|
||||
|
||||
const stop = new Set([userName, '我', '你', '他', '她', '它', '用户', '角色', 'assistant'].map(normalize).filter(Boolean));
|
||||
|
||||
return Array.from(set)
|
||||
@@ -296,6 +294,79 @@ function buildEntityLexicon(store, allEvents) {
|
||||
.slice(0, 5000);
|
||||
}
|
||||
|
||||
function buildFactGraph(facts) {
|
||||
const graph = new Map();
|
||||
|
||||
for (const f of facts || []) {
|
||||
if (f?.retracted) continue;
|
||||
if (!isRelationFact(f)) continue;
|
||||
|
||||
const s = normalize(f?.s);
|
||||
const o = normalize(f?.o);
|
||||
if (!s || !o) continue;
|
||||
|
||||
if (!graph.has(s)) graph.set(s, new Set());
|
||||
if (!graph.has(o)) graph.set(o, new Set());
|
||||
graph.get(s).add(o);
|
||||
graph.get(o).add(s);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
function expandByFacts(presentEntities, facts, maxDepth = 2) {
|
||||
const graph = buildFactGraph(facts);
|
||||
const expanded = new Map();
|
||||
const seeds = Array.from(presentEntities || []).map(normalize).filter(Boolean);
|
||||
|
||||
seeds.forEach(e => expanded.set(e, 1.0));
|
||||
|
||||
let frontier = [...seeds];
|
||||
for (let d = 1; d <= maxDepth && frontier.length; d++) {
|
||||
const next = [];
|
||||
const decay = Math.pow(0.5, d);
|
||||
|
||||
for (const e of frontier) {
|
||||
const neighbors = graph.get(e);
|
||||
if (!neighbors) continue;
|
||||
|
||||
for (const neighbor of neighbors) {
|
||||
if (!expanded.has(neighbor)) {
|
||||
expanded.set(neighbor, decay);
|
||||
next.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frontier = next.slice(0, 20);
|
||||
}
|
||||
|
||||
return Array.from(expanded.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 15)
|
||||
.map(([term]) => term);
|
||||
}
|
||||
|
||||
function stripFloorTag(s) {
|
||||
return String(s || '').replace(/\s*\(#\d+(?:-\d+)?\)\s*$/, '').trim();
|
||||
}
|
||||
|
||||
export function buildEventEmbeddingText(ev) {
|
||||
const parts = [];
|
||||
|
||||
if (ev?.title) parts.push(ev.title);
|
||||
|
||||
const people = (ev?.participants || []).join(' ');
|
||||
if (people) parts.push(people);
|
||||
|
||||
if (ev?.type) parts.push(ev.type);
|
||||
|
||||
const summary = stripFloorTag(ev?.summary);
|
||||
if (summary) parts.push(summary);
|
||||
|
||||
return parts.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从分段消息中提取实体,继承消息权重
|
||||
* @param {string[]} segments
|
||||
@@ -621,6 +692,7 @@ function formatRecallLog({
|
||||
chunkPreFilterStats = null,
|
||||
l0Results = [],
|
||||
textGapInfo = null,
|
||||
expandedTerms = [],
|
||||
}) {
|
||||
const lines = [
|
||||
'\u2554' + '\u2550'.repeat(62) + '\u2557',
|
||||
@@ -663,6 +735,9 @@ function formatRecallLog({
|
||||
} else {
|
||||
lines.push(' (无)');
|
||||
}
|
||||
if (expandedTerms?.length) {
|
||||
lines.push(` 扩散: ${expandedTerms.join('、')}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('\u250c' + '\u2500'.repeat(61) + '\u2510');
|
||||
@@ -757,12 +832,15 @@ export async function recallMemory(queryText, allEvents, vectorConfig, options =
|
||||
const lexicon = buildEntityLexicon(store, allEvents);
|
||||
const queryEntityWeights = extractEntitiesWithWeights(segments, weights, lexicon);
|
||||
const queryEntities = Array.from(queryEntityWeights.keys());
|
||||
const facts = getFacts(store);
|
||||
const expandedTerms = expandByFacts(queryEntities, facts, 2);
|
||||
|
||||
// 构建文本查询串:最后一条消息 + 实体 + 关键词
|
||||
const lastSeg = segments[segments.length - 1] || '';
|
||||
const queryTextForSearch = [
|
||||
lastSeg,
|
||||
...queryEntities,
|
||||
...expandedTerms,
|
||||
...(store?.json?.keywords || []).slice(0, 5).map(k => k.text),
|
||||
].join(' ');
|
||||
|
||||
@@ -824,6 +902,7 @@ export async function recallMemory(queryText, allEvents, vectorConfig, options =
|
||||
chunkPreFilterStats,
|
||||
l0Results,
|
||||
textGapInfo,
|
||||
expandedTerms,
|
||||
});
|
||||
|
||||
console.group('%c[Recall]', 'color: #7c3aed; font-weight: bold');
|
||||
|
||||
Reference in New Issue
Block a user