UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

79 lines • 3.34 kB
/** * pairwiseSimilarity — pairwise cosine over a set of texts * (RFC-002 C1's core: tool descriptions → matrix + ranked pairs). * * Pattern: pure async function, embedder-injected. No thresholds, no * verdicts, no lint rules — those are C1's `analyzeToolCatalog` * policy layer ON TOP of this geometry. The core stays * reusable for any "how confusable are these texts" question. * Role: `src/influence-core/` leaf. No agent/runtime imports. * * Honest claim: similarity is embedding geometry over the DESCRIPTIONS * — a confusability HEURISTIC, not a measurement of the model's actual * selection function (RFC-002 §2; tier 3 validates the proxy via * choice-entropy sampling). */ import { cosineSimilarity } from '../memory/embedding/cosine.js'; /** * Embed every item once (deduplicated batch) and compute the full * cosine matrix plus ranked upper-triangle pairs (descending; ties * keep input pair order). * * Invariants (pinned by property tests): * - `matrix[i][j] === matrix[j][i]` — computed once, mirrored. * - `matrix[i][i] === 1` EXACTLY — set by definition, so * self-similarity is an invariant rather than a float artifact * (and duplicate texts at different ids still compare via cosine). * - N items → N·(N−1)/2 pairs. */ export async function pairwiseSimilarity(args) { const { items, embedder } = args; assertUniqueIds(items); const ids = items.map((item) => item.id); if (items.length === 0) return { ids, matrix: [], pairs: [] }; // Deduplicated embedding pass (identical descriptions embed once). const distinct = [...new Set(items.map((item) => item.text))]; const vectors = embedder.embedBatch ? await embedder.embedBatch({ texts: distinct, ...(args.signal ? { signal: args.signal } : {}), }) : await sequentialEmbed(embedder, distinct, args.signal); const vectorByText = new Map(); for (let i = 0; i < distinct.length; i++) vectorByText.set(distinct[i], vectors[i]); const itemVecs = items.map((item) => vectorByText.get(item.text)); // Upper triangle once, mirrored; diagonal exactly 1 by definition. const matrix = items.map(() => new Array(items.length).fill(0)); const pairs = []; for (let i = 0; i < items.length; i++) { matrix[i][i] = 1; for (let j = i + 1; j < items.length; j++) { const similarity = cosineSimilarity(itemVecs[i], itemVecs[j]); matrix[i][j] = similarity; matrix[j][i] = similarity; pairs.push({ a: ids[i], b: ids[j], similarity }); } } // Stable sort — ties keep (i, j) input order. pairs.sort((p, q) => q.similarity - p.similarity); return { ids, matrix, pairs }; } async function sequentialEmbed(embedder, texts, signal) { const out = []; for (const text of texts) { out.push(await embedder.embed({ text, ...(signal ? { signal } : {}) })); } return out; } function assertUniqueIds(items) { const seen = new Set(); for (const item of items) { if (seen.has(item.id)) { throw new Error(`pairwiseSimilarity: duplicate item id '${item.id}' — ids must be unique`); } seen.add(item.id); } } //# sourceMappingURL=similarity.js.map