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

@@ -6,7 +6,7 @@
import { getContext } from "../../../../../../extensions.js";
import { xbLog } from "../../../core/debug-core.js";
import { getSummaryStore } from "../data/store.js";
import { getSummaryStore, getFacts, isRelationFact } from "../data/store.js";
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
import { recallMemory, buildQueryText } from "../vector/recall.js";
import { getChunksByFloors, getAllChunkVectors, getAllEventVectors, getMeta } from "../vector/chunk-store.js";
@@ -111,10 +111,18 @@ function buildPostscript() {
// 格式化函数
// ─────────────────────────────────────────────────────────────────────────────
function formatWorldLines(world) {
return [...(world || [])]
.sort((a, b) => (b.floor || 0) - (a.floor || 0))
.map(w => `- ${w.topic}${w.content}`);
function formatFactsForInjection(facts) {
const activeFacts = (facts || []).filter(f => !f.retracted);
if (!activeFacts.length) return [];
return activeFacts
.sort((a, b) => (b.since || 0) - (a.since || 0))
.map(f => {
const since = f.since ? ` (#${f.since + 1})` : '';
if (isRelationFact(f) && f.trend) {
return `- ${f.s} ${f.p}: ${f.o} [${f.trend}]${since}`;
}
return `- ${f.s}${f.p}: ${f.o}${since}`;
});
}
function formatArcLine(a) {
@@ -189,7 +197,7 @@ function formatInjectionLog(stats, details, recentOrphanStats = null) {
// [1] 世界约束
lines.push(` [1] 世界约束 (上限 2000)`);
lines.push(` 选入: ${stats.world.count} 条 | 消耗: ${stats.world.tokens} tokens`);
lines.push(` 选入: ${stats.facts.count} 条 | 消耗: ${stats.facts.tokens} tokens`);
lines.push('');
// [2] 核心经历 + 过往背景
@@ -229,7 +237,7 @@ function formatInjectionLog(stats, details, recentOrphanStats = null) {
const pctStr = pct(tokens, total) + '%';
return ` ${label.padEnd(6)} ${'█'.repeat(width).padEnd(30)} ${String(tokens).padStart(5)} (${pctStr})`;
};
lines.push(bar(stats.world.tokens, '约束'));
lines.push(bar(stats.facts.tokens, '约束'));
lines.push(bar(stats.events.tokens + stats.evidence.tokens, '经历'));
lines.push(bar(stats.orphans.tokens, '远期'));
lines.push(bar(recentOrphanStats?.tokens || 0, '待整理'));
@@ -263,9 +271,9 @@ function buildNonVectorPrompt(store) {
const data = store.json || {};
const sections = [];
if (data.world?.length) {
const lines = formatWorldLines(data.world);
sections.push(`[世界约束] 已确立的事实\n${lines.join("\n")}`);
const factLines = formatFactsForInjection(getFacts(store));
if (factLines.length) {
sections.push(`[定了的事] 已确立的事实\n${factLines.join("\n")}`);
}
if (data.events?.length) {
@@ -330,7 +338,7 @@ async function buildVectorPrompt(store, recallResult, causalById, queryEntities
// ═══════════════════════════════════════════════════════════════════
const assembled = {
world: { lines: [], tokens: 0 },
facts: { lines: [], tokens: 0 },
arcs: { lines: [], tokens: 0 },
events: { direct: [], similar: [] },
orphans: { lines: [], tokens: 0 },
@@ -339,7 +347,7 @@ async function buildVectorPrompt(store, recallResult, causalById, queryEntities
const injectionStats = {
budget: { max: TOTAL_BUDGET_MAX, used: 0 },
world: { count: 0, tokens: 0 },
facts: { count: 0, tokens: 0 },
arcs: { count: 0, tokens: 0 },
events: { selected: 0, tokens: 0 },
evidence: { attached: 0, tokens: 0 },
@@ -360,16 +368,16 @@ async function buildVectorPrompt(store, recallResult, causalById, queryEntities
// ═══════════════════════════════════════════════════════════════════
// [优先级 1] 世界约束 - 最高优先级
// ═══════════════════════════════════════════════════════════════════
const worldLines = formatWorldLines(data.world);
if (worldLines.length) {
const factLines = formatFactsForInjection(getFacts(store));
if (factLines.length) {
const l3Budget = { used: 0, max: Math.min(L3_MAX, total.max - total.used) };
for (const line of worldLines) {
if (!pushWithBudget(assembled.world.lines, line, l3Budget)) break;
for (const line of factLines) {
if (!pushWithBudget(assembled.facts.lines, line, l3Budget)) break;
}
assembled.world.tokens = l3Budget.used;
assembled.facts.tokens = l3Budget.used;
total.used += l3Budget.used;
injectionStats.world.count = assembled.world.lines.length;
injectionStats.world.tokens = l3Budget.used;
injectionStats.facts.count = assembled.facts.lines.length;
injectionStats.facts.tokens = l3Budget.used;
}
// ═══════════════════════════════════════════════════════════════════
@@ -599,8 +607,8 @@ async function buildVectorPrompt(store, recallResult, causalById, queryEntities
// ═══════════════════════════════════════════════════════════════════════
const sections = [];
// 1. 世界约束 → 定了的事
if (assembled.world.lines.length) {
sections.push(`[定了的事] 已确立的事实\n${assembled.world.lines.join("\n")}`);
if (assembled.facts.lines.length) {
sections.push(`[定了的事] 已确立的事实\n${assembled.facts.lines.join("\n")}`);
}
// 2. 核心经历 → 印象深的事
if (assembled.events.direct.length) {
@@ -632,6 +640,8 @@ if (!sections.length) {
`<剧情记忆>\n\n${sections.join("\n\n")}\n\n</剧情记忆>\n` +
`${buildPostscript()}`;
// ★ 修复:先写回预算统计,再生成日志
injectionStats.budget.used = total.used + (assembled.recentOrphans.tokens || 0);
const injectionLogText = formatInjectionLog(injectionStats, details, recentOrphanStats);
return { promptText, injectionLogText, injectionStats };
@@ -835,4 +845,4 @@ export async function buildVectorPromptText(excludeLastAi = false, hooks = {}) {
}
return { text: finalText, logText: (recallResult.logText || "") + (injectionLogText || "") };
}
}