story-summary: facts migration + recall enhancements

This commit is contained in:
2026-02-02 21:45:01 +08:00
parent d3f772073f
commit fb8ed8037c
8 changed files with 570 additions and 289 deletions

View File

@@ -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');