refactor(prompt): structure constraints people/world and harden formatting
chore(diffusion): raise DIFFUSION_CAP to 100
This commit is contained in:
@@ -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) {
|
||||||
return filtered
|
const participants = item?.event?.participants || [];
|
||||||
.sort((a, b) => (b.since || 0) - (a.since || 0))
|
for (const p of participants) add(p);
|
||||||
.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}`;
|
|
||||||
});
|
if (dict.size === 0) {
|
||||||
|
for (const f of (focusEntities || [])) add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user