refactor(prompt): structure constraints people/world and harden formatting

chore(diffusion): raise DIFFUSION_CAP to 100
This commit is contained in:
2026-02-14 21:30:57 +08:00
parent e8383570e0
commit 300ed2798f
2 changed files with 117 additions and 32 deletions

View File

@@ -303,26 +303,103 @@ function filterConstraintsByRelevance(facts, focusEntities, knownCharacters) {
} }
/** /**
* 格式化 constraints 用于注入 * Build people dictionary for constraints display.
* @param {object[]} facts - 所有 facts * Primary source: selected event participants; fallback: focus entities.
* @param {string[]} focusEntities - 焦点实体 *
* @param {Set<string>} knownCharacters - 已知角色 * @param {object|null} recallResult
* @returns {string[]} 格式化后的行 * @param {string[]} focusEntities
* @returns {Map<string, string>} normalize(name) -> display name
*/ */
function formatConstraintsForInjection(facts, focusEntities, knownCharacters) { function buildConstraintPeopleDict(recallResult, focusEntities = []) {
const filtered = filterConstraintsByRelevance(facts, focusEntities, knownCharacters); const dict = new Map();
const add = (raw) => {
const display = String(raw || '').trim();
const key = normalize(display);
if (!display || !key) return;
if (!dict.has(key)) dict.set(key, display);
};
if (!filtered.length) return []; const selectedEvents = recallResult?.events || [];
for (const item of selectedEvents) {
const participants = item?.event?.participants || [];
for (const p of participants) add(p);
}
return filtered if (dict.size === 0) {
.sort((a, b) => (b.since || 0) - (a.since || 0)) for (const f of (focusEntities || [])) add(f);
.map(f => { }
const since = f.since ? ` (#${f.since + 1})` : '';
if (isRelationFact(f) && f.trend) { return dict;
return `- ${f.s} ${f.p}: ${f.o} [${f.trend}]${since}`; }
/**
* Group filtered constraints into people/world buckets.
* @param {object[]} facts
* @param {Map<string, string>} peopleDict
* @returns {{ people: Map<string, object[]>, world: object[] }}
*/
function groupConstraintsForDisplay(facts, peopleDict) {
const people = new Map();
const world = [];
for (const f of (facts || [])) {
const subjectNorm = normalize(f?.s);
const displayName = peopleDict.get(subjectNorm);
if (displayName) {
if (!people.has(displayName)) people.set(displayName, []);
people.get(displayName).push(f);
} else {
world.push(f);
}
}
return { people, world };
}
function formatConstraintLine(f, includeSubject = false) {
const subject = String(f?.s || '').trim();
const predicate = String(f?.p || '').trim();
const object = String(f?.o || '').trim();
const trendRaw = String(f?.trend || '').trim();
const hasSince = f?.since !== undefined && f?.since !== null;
const since = hasSince ? ` (#${f.since + 1})` : '';
const trend = isRelationFact(f) && trendRaw ? ` [${trendRaw}]` : '';
if (includeSubject) {
return `- ${subject} ${predicate}: ${object}${trend}${since}`;
}
return `- ${predicate}: ${object}${trend}${since}`;
}
/**
* Render grouped constraints into structured human-readable lines.
* @param {{ people: Map<string, object[]>, world: object[] }} grouped
* @returns {string[]}
*/
function formatConstraintsStructured(grouped) {
const lines = [];
const people = grouped?.people || new Map();
const world = grouped?.world || [];
if (people.size > 0) {
lines.push('people:');
for (const [name, facts] of people.entries()) {
lines.push(` ${name}:`);
const sorted = [...facts].sort((a, b) => (b.since || 0) - (a.since || 0));
for (const f of sorted) {
lines.push(` ${formatConstraintLine(f, false)}`);
} }
return `- ${f.s}${f.p}: ${f.o}${since}`; }
}); }
if (world.length > 0) {
lines.push('world:');
const sortedWorld = [...world].sort((a, b) => (b.since || 0) - (a.since || 0));
for (const f of sortedWorld) {
lines.push(` ${formatConstraintLine(f, true)}`);
}
}
return lines;
} }
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
@@ -610,18 +687,23 @@ function buildNonVectorPrompt(store) {
const data = store.json || {}; const data = store.json || {};
const sections = []; const sections = [];
// [Constraints] L3 Facts // [Constraints] L3 Facts (structured: people/world)
const allFacts = getFacts(); const allFacts = getFacts().filter(f => !f.retracted);
const constraintLines = allFacts const nonVectorPeopleDict = buildConstraintPeopleDict(
.filter(f => !f.retracted) { events: data.events || [] },
.sort((a, b) => (b.since || 0) - (a.since || 0)) []
.map(f => { );
const since = f.since ? ` (#${f.since + 1})` : ''; const nonVectorFocus = nonVectorPeopleDict.size > 0
if (isRelationFact(f) && f.trend) { ? [...nonVectorPeopleDict.values()]
return `- ${f.s} ${f.p}: ${f.o} [${f.trend}]${since}`; : [...getKnownCharacters(store)];
} const nonVectorKnownCharacters = getKnownCharacters(store);
return `- ${f.s}${f.p}: ${f.o}${since}`; const filteredConstraints = filterConstraintsByRelevance(
}); allFacts,
nonVectorFocus,
nonVectorKnownCharacters
);
const groupedConstraints = groupConstraintsForDisplay(filteredConstraints, nonVectorPeopleDict);
const constraintLines = formatConstraintsStructured(groupedConstraints);
if (constraintLines.length) { if (constraintLines.length) {
sections.push(`[定了的事] 已确立的事实\n${constraintLines.join("\n")}`); sections.push(`[定了的事] 已确立的事实\n${constraintLines.join("\n")}`);
@@ -743,11 +825,14 @@ async function buildVectorPrompt(store, recallResult, causalById, focusEntities,
const allFacts = getFacts(); const allFacts = getFacts();
const knownCharacters = getKnownCharacters(store); const knownCharacters = getKnownCharacters(store);
const constraintLines = formatConstraintsForInjection(allFacts, focusEntities, knownCharacters); const filteredConstraints = filterConstraintsByRelevance(allFacts, focusEntities, knownCharacters);
const constraintPeopleDict = buildConstraintPeopleDict(recallResult, focusEntities);
const groupedConstraints = groupConstraintsForDisplay(filteredConstraints, constraintPeopleDict);
const constraintLines = formatConstraintsStructured(groupedConstraints);
if (metrics) { if (metrics) {
metrics.constraint.total = allFacts.length; metrics.constraint.total = allFacts.length;
metrics.constraint.filtered = allFacts.length - constraintLines.length; metrics.constraint.filtered = allFacts.length - filteredConstraints.length;
} }
if (constraintLines.length) { if (constraintLines.length) {
@@ -759,7 +844,7 @@ async function buildVectorPrompt(store, recallResult, causalById, focusEntities,
total.used += constraintBudget.used; total.used += constraintBudget.used;
injectionStats.constraint.count = assembled.constraints.lines.length; injectionStats.constraint.count = assembled.constraints.lines.length;
injectionStats.constraint.tokens = constraintBudget.used; injectionStats.constraint.tokens = constraintBudget.used;
injectionStats.constraint.filtered = allFacts.length - constraintLines.length; injectionStats.constraint.filtered = allFacts.length - filteredConstraints.length;
if (metrics) { if (metrics) {
metrics.constraint.injected = assembled.constraints.lines.length; metrics.constraint.injected = assembled.constraints.lines.length;

View File

@@ -63,7 +63,7 @@ const CONFIG = {
// Post-verification (Cosine Gate) // Post-verification (Cosine Gate)
COSINE_GATE: 0.48, // min cosine(queryVector, stateVector) COSINE_GATE: 0.48, // min cosine(queryVector, stateVector)
SCORE_FLOOR: 0.12, // min finalScore = PPR_normalized × cosine SCORE_FLOOR: 0.12, // min finalScore = PPR_normalized × cosine
DIFFUSION_CAP: 80, // max diffused nodes (excluding seeds) DIFFUSION_CAP: 100, // max diffused nodes (excluding seeds)
}; };
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════