UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

481 lines 15.3 kB
import { compareStrings, deepEqual } from "./comparison"; export function calculateSimilarityMatrix(outputs) { const n = outputs.length; const matrix = Array(n) .fill(0) .map(() => Array(n).fill(0)); for (let i = 0; i < n; i++) { matrix[i][i] = 1.0; for (let j = i + 1; j < n; j++) { const similarity = calculateOutputSimilarity(outputs[i], outputs[j]); matrix[i][j] = similarity; matrix[j][i] = similarity; } } return matrix; } export function calculateOutputSimilarity(a, b) { if (a.data && b.data) { return calculateStructuralSimilarity(a.data, b.data); } return compareStrings(a.text, b.text, { caseSensitive: false, normalizeWhitespace: true, algorithm: "levenshtein", }); } export function calculateStructuralSimilarity(a, b) { if (a === b) return 1.0; if (a === null || a === undefined) return b === null || b === undefined ? 1.0 : 0.0; if (b === null || b === undefined) return 0.0; const typeA = typeof a; const typeB = typeof b; if (typeA !== typeB) return 0.0; if (typeA !== "object") { if (typeA === "string") { if (a === b) return 1.0; return compareStrings(a, b, { caseSensitive: false, normalizeWhitespace: true, }); } if (typeA === "number") { if (a === b) return 1.0; const maxDiff = Math.max(Math.abs(a), Math.abs(b)); if (maxDiff === 0) return 1.0; return 1 - Math.abs(a - b) / maxDiff; } return a === b ? 1.0 : 0.0; } const isArrayA = Array.isArray(a); const isArrayB = Array.isArray(b); if (isArrayA !== isArrayB) return 0.0; if (isArrayA) { const lengthA = a.length; const lengthB = b.length; const maxLength = Math.max(lengthA, lengthB); if (maxLength === 0) return 1.0; if (lengthA === lengthB && deepEqual(a, b)) return 1.0; let matches = 0; const minLength = Math.min(lengthA, lengthB); for (let i = 0; i < minLength; i++) { matches += calculateStructuralSimilarity(a[i], b[i]); } return matches / maxLength; } const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length === keysB.length && deepEqual(a, b)) return 1.0; const allKeys = new Set([...keysA, ...keysB]); const total = allKeys.size; if (total === 0) return 1.0; let matches = 0; for (const key of allKeys) { if (key in a && key in b) { matches += calculateStructuralSimilarity(a[key], b[key]); } } return matches / total; } export function findAgreements(outputs, threshold = 0.8) { const agreements = []; if (!outputs[0]?.data) { const textAgreements = findTextAgreements(outputs, threshold); agreements.push(...textAgreements); } else { const structuredAgreements = findStructuredAgreements(outputs, threshold); agreements.push(...structuredAgreements); } return agreements; } function findTextAgreements(outputs, threshold) { const agreements = []; const groups = []; const used = new Set(); for (let i = 0; i < outputs.length; i++) { if (used.has(i)) continue; const group = [i]; used.add(i); for (let j = i + 1; j < outputs.length; j++) { if (used.has(j)) continue; const similarity = calculateOutputSimilarity(outputs[i], outputs[j]); if (similarity >= threshold) { group.push(j); used.add(j); } } if (group.length > 1) { groups.push(group); } } for (const group of groups) { const content = outputs[group[0]].text; const type = group.length === outputs.length ? "exact" : "similar"; agreements.push({ content, count: group.length, ratio: group.length / outputs.length, indices: group, type, }); } return agreements; } function findStructuredAgreements(outputs, threshold) { const agreements = []; const allPaths = new Set(); for (const output of outputs) { if (output.data) { getAllPaths(output.data).forEach((p) => allPaths.add(p)); } } for (const path of allPaths) { const values = outputs .map((o) => getValueAtPath(o.data, path)) .filter((v) => v !== undefined); if (values.length === 0) continue; const valueCounts = new Map(); values.forEach((v, i) => { const key = JSON.stringify(v); if (!valueCounts.has(key)) { valueCounts.set(key, []); } valueCounts.get(key).push(i); }); let maxCount = 0; let majorityValue; let majorityIndices = []; for (const [key, indices] of valueCounts) { if (indices.length > maxCount) { maxCount = indices.length; majorityValue = JSON.parse(key); majorityIndices = indices; } } const ratio = maxCount / outputs.length; if (ratio >= threshold) { agreements.push({ content: majorityValue, path, count: maxCount, ratio, indices: majorityIndices, type: ratio === 1.0 ? "exact" : "structural", }); } } return agreements; } export function findDisagreements(outputs, threshold = 0.8) { const disagreements = []; if (outputs[0]?.data) { const structuredDisagreements = findStructuredDisagreements(outputs, threshold); disagreements.push(...structuredDisagreements); } else { const textDisagreements = findTextDisagreements(outputs, threshold); disagreements.push(...textDisagreements); } return disagreements; } function findTextDisagreements(outputs, threshold) { const disagreements = []; const valueCounts = new Map(); outputs.forEach((output, i) => { const text = output.text.trim(); let grouped = false; for (const [key, indices] of valueCounts) { const similarity = compareStrings(text, key); if (similarity >= threshold) { indices.push(i); grouped = true; break; } } if (!grouped) { valueCounts.set(text, [i]); } }); if (valueCounts.size > 1) { const values = Array.from(valueCounts.entries()).map(([value, indices]) => ({ value, count: indices.length, indices, })); const severity = calculateDisagreementSeverity(values, outputs.length); disagreements.push({ values, severity, }); } return disagreements; } function findStructuredDisagreements(outputs, threshold) { const disagreements = []; const allPaths = new Set(); for (const output of outputs) { if (output.data) { getAllPaths(output.data).forEach((p) => allPaths.add(p)); } } for (const path of allPaths) { const values = outputs.map((o) => ({ value: getValueAtPath(o.data, path), index: outputs.indexOf(o), })); const valueCounts = new Map(); values.forEach(({ value, index }) => { if (value === undefined) return; const key = JSON.stringify(value); if (!valueCounts.has(key)) { valueCounts.set(key, []); } valueCounts.get(key).push(index); }); if (valueCounts.size > 1) { const distinctValues = Array.from(valueCounts.entries()).map(([value, indices]) => ({ value: JSON.parse(value), count: indices.length, indices, })); const maxCount = Math.max(...distinctValues.map((v) => v.count)); const majorityRatio = maxCount / outputs.length; if (majorityRatio >= threshold) { continue; } const severity = calculateDisagreementSeverity(distinctValues, outputs.length); disagreements.push({ path, values: distinctValues, severity, }); } } return disagreements; } function calculateDisagreementSeverity(values, total) { const maxCount = Math.max(...values.map((v) => v.count)); const ratio = maxCount / total; if (ratio >= 0.8) { return "minor"; } else if (ratio >= 0.6) { return "moderate"; } else if (ratio >= 0.4) { return "major"; } else { return "critical"; } } export function calculateFieldConsensus(outputs) { const fields = {}; const allPaths = new Set(); for (const output of outputs) { if (output.data) { getAllPaths(output.data).forEach((p) => allPaths.add(p)); } } for (const path of allPaths) { const values = outputs .map((o, i) => ({ value: getValueAtPath(o.data, path), index: i })) .filter((v) => v.value !== undefined); if (values.length === 0) continue; const votes = {}; const allValues = []; values.forEach(({ value }) => { const key = JSON.stringify(value); votes[key] = (votes[key] || 0) + 1; allValues.push(value); }); let maxVotes = 0; let consensusValue; for (const [key, count] of Object.entries(votes)) { if (count > maxVotes) { maxVotes = count; consensusValue = JSON.parse(key); } } const agreement = maxVotes / outputs.length; const unanimous = maxVotes === outputs.length; const confidence = agreement; fields[path] = { path, value: consensusValue, agreement, votes, values: allValues, unanimous, confidence, }; } const agreedFields = Object.keys(fields).filter((k) => fields[k].unanimous); const disagreedFields = Object.keys(fields).filter((k) => !fields[k].unanimous); const overallAgreement = Object.values(fields).reduce((sum, f) => sum + f.agreement, 0) / Object.keys(fields).length; return { fields, overallAgreement, agreedFields, disagreedFields, }; } function getAllPaths(obj, prefix = "") { const paths = []; if (obj && typeof obj === "object" && !Array.isArray(obj)) { for (const key of Object.keys(obj)) { const path = prefix ? `${prefix}.${key}` : key; paths.push(path); const value = obj[key]; if (value && typeof value === "object" && !Array.isArray(value)) { paths.push(...getAllPaths(value, path)); } } } return paths; } function getValueAtPath(obj, path) { if (!obj) return undefined; const parts = path.split("."); let current = obj; for (const part of parts) { if (current && typeof current === "object" && part in current) { current = current[part]; } else { return undefined; } } return current; } export function resolveMajority(outputs, weights) { if (outputs.length === 0) { throw new Error("No outputs to resolve"); } const outputWeights = weights || outputs.map((o) => o.weight ?? 1); if (outputs[0].data) { const fieldConsensus = calculateFieldConsensus(outputs); const consensusData = {}; for (const [path, field] of Object.entries(fieldConsensus.fields)) { setValueAtPath(consensusData, path, field.value); } return { ...outputs[0], index: outputs[0].index ?? 0, data: consensusData, text: JSON.stringify(consensusData), }; } let bestIndex = 0; let bestScore = -1; for (let i = 0; i < outputs.length; i++) { let score = 0; for (let j = 0; j < outputs.length; j++) { if (i !== j) { const similarity = calculateOutputSimilarity(outputs[i], outputs[j]); score += similarity * (outputWeights[j] ?? 1); } } if (score > bestScore) { bestScore = score; bestIndex = i; } } return outputs[bestIndex]; } export function resolveBest(outputs, weights) { if (outputs.length === 0) { throw new Error("No outputs to resolve"); } const outputWeights = weights || outputs.map((o) => o.weight ?? 1); let bestIndex = 0; let bestWeight = outputWeights[0] ?? 1; for (let i = 1; i < outputs.length; i++) { if ((outputWeights[i] ?? 1) > bestWeight) { bestWeight = outputWeights[i] ?? 1; bestIndex = i; } } return outputs[bestIndex]; } export function resolveMerge(outputs) { if (outputs.length === 0) { throw new Error("No outputs to resolve"); } if (outputs.length === 1) { return outputs[0]; } if (outputs[0].data) { const merged = {}; const allPaths = new Set(); outputs.forEach((o) => { if (o.data) { getAllPaths(o.data).forEach((p) => allPaths.add(p)); } }); for (const path of allPaths) { const values = outputs .map((o) => getValueAtPath(o.data, path)) .filter((v) => v !== undefined); if (values.length > 0) { setValueAtPath(merged, path, values[0]); } } return { ...outputs[0], index: outputs[0].index ?? 0, data: merged, text: JSON.stringify(merged), }; } const uniqueTexts = Array.from(new Set(outputs.map((o) => o.text.trim()))); const mergedText = uniqueTexts.join("\n\n"); return { ...outputs[0], index: outputs[0].index ?? 0, text: mergedText, }; } function setValueAtPath(obj, path, value) { const parts = path.split("."); let current = obj; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (!(part in current)) { current[part] = {}; } current = current[part]; } current[parts[parts.length - 1]] = value; } export function meetsMinimumAgreement(agreements, outputs, threshold) { if (threshold === 0) return true; if (outputs === 1) return true; if (agreements.length === 0) return false; const maxRatio = Math.max(...agreements.map((a) => a.count / outputs)); return maxRatio >= threshold; } //# sourceMappingURL=consensusUtils.js.map