@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
481 lines • 15.3 kB
JavaScript
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