agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
93 lines • 4.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.scoreContrastiveInfluence = void 0;
/**
* contrastive — influence scoring against a REFERENCE output (RFC-003).
*
* Pattern: a SEPARATE, opt-in second stage over the four-signal scorer — not a
* modification of `scoreInfluence`. Same `InfluenceScore[]` return, so
* `rankingConfidence` and the rest compose on it unchanged.
* Role: `src/influence-core/` leaf, sibling to `scoreInfluence`.
*
* ## Why this exists (the topical-innocent confound)
*
* Plain output-similarity (`scoreInfluence`'s FA) ranks a source by how much it
* resembles the actual answer. That is confounded by **topically-central
* innocents**: the policy a refund decision is *about* resembles ANY refund
* output — right or wrong — so it can out-rank the source that actually caused
* the wrong one. The fix: score by CONTRAST against a reference output (a
* known-good / expected / prior-good run). A topical innocent is similar to
* BOTH outputs, so it cancels (~0 contrast); the real culprit is similar to the
* WRONG output specifically, so it stands out.
*
* contrastive FA(e) = sim(e, answer) − sim(e, reference)
*
* Everything else (the AVG / PERSIST / DEPTH reasoning-trace signals, the
* composite, adaptive weights) is shared with `scoreInfluence` verbatim — only
* the FA term is contrastive.
*
* Honest claim (RFC-002 §2): still an embedding-geometry PROXY, never causal —
* the contrast removes a confound, it does not prove causation. Ablation is the
* causal tier. And it is OPT-IN: it needs a reference output, so it is for
* regression / eval debugging (you have a prior-good or expected output), not
* cold localization — without a reference, use `scoreInfluence`.
*/
const cosine_js_1 = require("../memory/embedding/cosine.js");
const signals_js_1 = require("./signals.js");
const types_js_1 = require("./types.js");
/**
* Score evidence by CONTRASTIVE influence: `sim(e, answer) − sim(e, reference)`
* for the FA term, the four-signal composite otherwise. Returns `InfluenceScore[]`
* sorted descending — drop-in compatible with `scoreInfluence` consumers
* (`rankingConfidence`, etc.).
*
* @throws when an evidence id is duplicated (same contract as `scoreInfluence`).
*/
async function scoreContrastiveInfluence(args) {
const weights = args.weights ?? types_js_1.DEFAULT_INFLUENCE_WEIGHTS;
const threshold = args.persistenceThreshold ?? types_js_1.DEFAULT_PERSISTENCE_THRESHOLD;
assertUniqueIds(args.evidence);
(0, signals_js_1.assertValidWeights)(weights, 'scoreContrastiveInfluence');
// ONE deduplicated embedding pass over every distinct text (answer + reference
// + evidence + ancestors).
const texts = new Set([args.answerText, args.referenceText]);
for (const item of args.evidence) {
texts.add(item.text);
for (const ancestor of item.ancestorTexts)
texts.add(ancestor);
}
const vectorByText = await (0, signals_js_1.embedAll)(args.embedder, [...texts], args.signal);
const answerVec = vectorByText.get(args.answerText);
const referenceVec = vectorByText.get(args.referenceText);
const scored = args.evidence.map((item) => {
const evidenceVec = vectorByText.get(item.text);
const ancestorVecs = item.ancestorTexts.map((t) => vectorByText.get(t));
// The only contrastive term: answer-similarity MINUS reference-similarity.
const faContrast = (0, cosine_js_1.cosineSimilarity)(evidenceVec, answerVec) - (0, cosine_js_1.cosineSimilarity)(evidenceVec, referenceVec);
const signals = {
fa: faContrast,
avg: (0, signals_js_1.averageRelevancy)(evidenceVec, ancestorVecs),
persist: (0, signals_js_1.persistence)(evidenceVec, ancestorVecs, threshold),
depth: (0, signals_js_1.structuralProximity)(ancestorVecs.length),
};
const effective = (0, signals_js_1.adaptWeights)(weights, ancestorVecs.length);
return {
id: item.id,
signals,
weights: effective.weights,
adapted: effective.adapted,
score: (0, signals_js_1.compositeScore)(signals, effective.weights),
};
});
return scored.sort((a, b) => b.score - a.score);
}
exports.scoreContrastiveInfluence = scoreContrastiveInfluence;
function assertUniqueIds(evidence) {
const seen = new Set();
for (const e of evidence) {
if (seen.has(e.id))
throw new Error(`scoreContrastiveInfluence: duplicate evidence id "${e.id}"`);
seen.add(e.id);
}
}
//# sourceMappingURL=contrastive.js.map