agent-ablation 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,13 +20,292 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ aggregateBatchResults: () => aggregateBatchResults,
23
24
  batchAblation: () => batchAblation,
24
- runAblation: () => runAblation
25
+ batchAblationAsync: () => batchAblationAsync,
26
+ formatAsciiTable: () => formatAsciiTable,
27
+ formatMarkdownReport: () => formatMarkdownReport,
28
+ fromAISDKSteps: () => fromAISDKSteps,
29
+ fromAutoGenMessages: () => fromAutoGenMessages,
30
+ fromCrewAITasks: () => fromCrewAITasks,
31
+ fromLangGraphMessages: () => fromLangGraphMessages,
32
+ fromRecords: () => fromRecords,
33
+ majorityVote: () => majorityVote,
34
+ runAblation: () => runAblation,
35
+ runAblationAsync: () => runAblationAsync,
36
+ runBackwardElimination: () => runBackwardElimination,
37
+ runBackwardEliminationAsync: () => runBackwardEliminationAsync,
38
+ runPairwiseAblation: () => runPairwiseAblation
25
39
  });
26
40
  module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/adapters/langgraph.ts
43
+ function fromLangGraphMessages(messages, options) {
44
+ const findings = [];
45
+ for (const message of messages) {
46
+ if (!message || typeof message !== "object") {
47
+ continue;
48
+ }
49
+ if (typeof message.name !== "string" || message.name.trim().length === 0) {
50
+ continue;
51
+ }
52
+ const agentId = message.name;
53
+ const score = options.scoreOf(message);
54
+ const confidence = options.confidenceOf ? options.confidenceOf(message) : void 0;
55
+ let metadata;
56
+ if (typeof message.content === "string") {
57
+ metadata = { raw: message.content };
58
+ } else if (typeof message.content === "object" && message.content !== null && !Array.isArray(message.content)) {
59
+ metadata = { ...message.content };
60
+ } else if (message.content !== void 0 && message.content !== null) {
61
+ metadata = { raw: message.content };
62
+ }
63
+ const finding = {
64
+ agentId,
65
+ score
66
+ };
67
+ if (confidence !== void 0) {
68
+ finding.confidence = confidence;
69
+ }
70
+ if (metadata !== void 0) {
71
+ finding.metadata = metadata;
72
+ }
73
+ findings.push(finding);
74
+ }
75
+ return findings;
76
+ }
77
+ function fromRecords(records, options) {
78
+ return records.map((record, index) => {
79
+ const agentId = options.agentId(record, index);
80
+ const score = options.scoreOf(record, index);
81
+ const confidence = options.confidenceOf ? options.confidenceOf(record, index) : void 0;
82
+ const metadata = typeof record === "object" && record !== null && !Array.isArray(record) ? { ...record } : { raw: record };
83
+ const finding = {
84
+ agentId,
85
+ score,
86
+ metadata
87
+ };
88
+ if (confidence !== void 0) {
89
+ finding.confidence = confidence;
90
+ }
91
+ return finding;
92
+ });
93
+ }
94
+
95
+ // src/adapters/crewai.ts
96
+ function fromCrewAITasks(outputs, options = {}) {
97
+ const findings = [];
98
+ for (const output of outputs) {
99
+ if (!output || typeof output !== "object") continue;
100
+ let agentId;
101
+ if (options.agentIdOf) {
102
+ agentId = options.agentIdOf(output);
103
+ } else if (typeof output.agent === "string") {
104
+ agentId = output.agent;
105
+ } else if (typeof output.agent === "object" && output.agent !== null) {
106
+ agentId = output.agent.role || output.agent.name;
107
+ }
108
+ if (!agentId || agentId.trim().length === 0) continue;
109
+ let score = 0;
110
+ if (options.scoreOf) {
111
+ score = options.scoreOf(output);
112
+ } else if (typeof output.score === "number") {
113
+ score = output.score;
114
+ } else if (output.json_dict && typeof output.json_dict.score === "number") {
115
+ score = output.json_dict.score;
116
+ }
117
+ const confidence = options.confidenceOf ? options.confidenceOf(output) : output.json_dict && typeof output.json_dict.confidence === "number" ? output.json_dict.confidence : void 0;
118
+ const cost = options.costOf ? options.costOf(output) : typeof output.cost === "number" ? output.cost : void 0;
119
+ const tokens = options.tokensOf ? options.tokensOf(output) : typeof output.tokens === "number" ? output.tokens : void 0;
120
+ const latencyMs = typeof output.latencyMs === "number" ? output.latencyMs : void 0;
121
+ const metadata = {
122
+ ...output.json_dict || {},
123
+ ...output.raw ? { raw: output.raw } : {}
124
+ };
125
+ const finding = {
126
+ agentId: agentId.trim(),
127
+ score,
128
+ metadata
129
+ };
130
+ if (confidence !== void 0) finding.confidence = confidence;
131
+ if (cost !== void 0) finding.cost = cost;
132
+ if (tokens !== void 0) finding.tokens = tokens;
133
+ if (latencyMs !== void 0) finding.latencyMs = latencyMs;
134
+ findings.push(finding);
135
+ }
136
+ return findings;
137
+ }
138
+
139
+ // src/adapters/autogen.ts
140
+ function fromAutoGenMessages(messages, options) {
141
+ const findings = [];
142
+ for (const message of messages) {
143
+ if (!message || typeof message !== "object") continue;
144
+ const agentId = message.name || (message.role && message.role !== "user" && message.role !== "system" ? message.role : void 0);
145
+ if (!agentId || agentId.trim().length === 0) continue;
146
+ const score = options.scoreOf(message);
147
+ const confidence = options.confidenceOf ? options.confidenceOf(message) : void 0;
148
+ const tokens = options.tokensOf ? options.tokensOf(message) : void 0;
149
+ const cost = options.costOf ? options.costOf(message) : void 0;
150
+ let metadata = {};
151
+ if (typeof message.content === "object" && message.content !== null && !Array.isArray(message.content)) {
152
+ metadata = { ...message.content };
153
+ } else if (message.content !== void 0) {
154
+ metadata = { raw: message.content };
155
+ }
156
+ if (message.context) {
157
+ metadata.context = message.context;
158
+ }
159
+ const finding = {
160
+ agentId: agentId.trim(),
161
+ score,
162
+ metadata
163
+ };
164
+ if (confidence !== void 0) finding.confidence = confidence;
165
+ if (tokens !== void 0) finding.tokens = tokens;
166
+ if (cost !== void 0) finding.cost = cost;
167
+ findings.push(finding);
168
+ }
169
+ return findings;
170
+ }
171
+
172
+ // src/adapters/vercel.ts
173
+ function fromAISDKSteps(steps, options) {
174
+ const findings = [];
175
+ for (const step of steps) {
176
+ if (!step || typeof step !== "object") continue;
177
+ const agentId = options.agentIdOf ? options.agentIdOf(step) : step.toolName || step.stepType;
178
+ if (!agentId || agentId.trim().length === 0) continue;
179
+ const score = options.scoreOf(step);
180
+ const confidence = options.confidenceOf ? options.confidenceOf(step) : void 0;
181
+ const tokens = step.usage?.totalTokens ?? ((step.usage?.promptTokens || 0) + (step.usage?.completionTokens || 0) || void 0);
182
+ const cost = options.costOf ? options.costOf(step) : void 0;
183
+ const latencyMs = typeof step.latencyMs === "number" ? step.latencyMs : void 0;
184
+ let metadata = {};
185
+ if (typeof step.result === "object" && step.result !== null && !Array.isArray(step.result)) {
186
+ metadata = { ...step.result };
187
+ } else if (step.result !== void 0) {
188
+ metadata = { result: step.result };
189
+ }
190
+ if (step.args) {
191
+ metadata.args = step.args;
192
+ }
193
+ const finding = {
194
+ agentId: agentId.trim(),
195
+ score,
196
+ metadata
197
+ };
198
+ if (confidence !== void 0) finding.confidence = confidence;
199
+ if (tokens !== void 0) finding.tokens = tokens;
200
+ if (cost !== void 0) finding.cost = cost;
201
+ if (latencyMs !== void 0) finding.latencyMs = latencyMs;
202
+ findings.push(finding);
203
+ }
204
+ return findings;
205
+ }
206
+
207
+ // src/reporters/index.ts
208
+ function formatMarkdownReport(summary, options = {}) {
209
+ const title = options.title || "Agent Ablation & ROI Evaluation Report";
210
+ const lines = [];
211
+ lines.push(`# ${title}`);
212
+ lines.push("");
213
+ lines.push(`- **Evaluated Cases:** ${summary.cases}`);
214
+ lines.push(`- **Average Load-Bearing Ratio:** ${(summary.averageLoadBearingRatio * 100).toFixed(1)}%`);
215
+ if (summary.accuracy !== void 0) {
216
+ lines.push(`- **Baseline Accuracy:** ${(summary.accuracy.baselineAccuracy * 100).toFixed(1)}%`);
217
+ }
218
+ lines.push("");
219
+ lines.push("### Per-Agent Influence Breakdown");
220
+ lines.push("");
221
+ lines.push("| Agent ID | Appearances | Verdict Flips | Influence Share | Net Accuracy Impact |");
222
+ lines.push("| :--- | :--- | :--- | :--- | :--- |");
223
+ const agentIds = Object.keys(summary.perAgentInfluence);
224
+ for (const id of agentIds) {
225
+ const influence = summary.perAgentInfluence[id] ?? 0;
226
+ const stats = summary.perAgentStats?.[id];
227
+ const appearances = stats?.appearances ?? "-";
228
+ const flips = stats?.flips ?? "-";
229
+ let netAccStr = "N/A";
230
+ if (stats?.netAccuracyImpact !== void 0) {
231
+ const sign = stats.netAccuracyImpact > 0 ? "+" : "";
232
+ netAccStr = `${sign}${(stats.netAccuracyImpact * 100).toFixed(1)}% (${stats.role || "Neutral"})`;
233
+ }
234
+ lines.push(
235
+ `| \`${id}\` | ${appearances} | ${flips} | ${(influence * 100).toFixed(1)}% | ${netAccStr} |`
236
+ );
237
+ }
238
+ lines.push("");
239
+ if (summary.roi && options.includeRoi !== false) {
240
+ lines.push("### Cost & Telemetry ROI Analysis");
241
+ lines.push("");
242
+ lines.push("| Agent ID | Total Cost | Total Tokens | Cost / Verdict Flip | Cost Share | ROI Efficiency |");
243
+ lines.push("| :--- | :--- | :--- | :--- | :--- | :--- |");
244
+ for (const [id, agentRoi] of Object.entries(summary.roi.agents)) {
245
+ const costStr = agentRoi.totalCost !== void 0 ? `$${agentRoi.totalCost.toFixed(4)}` : "N/A";
246
+ const tokenStr = agentRoi.totalTokens !== void 0 ? agentRoi.totalTokens.toLocaleString() : "N/A";
247
+ const costPerFlipStr = agentRoi.costPerVerdictFlip !== void 0 ? `$${agentRoi.costPerVerdictFlip.toFixed(4)}` : "N/A";
248
+ const costShareStr = agentRoi.costShare !== void 0 ? `${(agentRoi.costShare * 100).toFixed(1)}%` : "N/A";
249
+ const roiEffStr = agentRoi.efficiencyRatio !== void 0 ? `${agentRoi.efficiencyRatio.toFixed(2)}x` : "N/A";
250
+ lines.push(
251
+ `| \`${id}\` | ${costStr} | ${tokenStr} | ${costPerFlipStr} | ${costShareStr} | ${roiEffStr} |`
252
+ );
253
+ }
254
+ lines.push("");
255
+ }
256
+ if (summary.roi?.recommendations && summary.roi.recommendations.length > 0 && options.includeRecommendations !== false) {
257
+ lines.push("### Optimization & Pruning Recommendations");
258
+ lines.push("");
259
+ for (const rec of summary.roi.recommendations) {
260
+ lines.push(`- \u26A0\uFE0F **\`${rec.agentId}\`**: ${rec.reason}`);
261
+ }
262
+ lines.push("");
263
+ }
264
+ return lines.join("\n");
265
+ }
266
+ function formatAsciiTable(summary) {
267
+ const lines = [];
268
+ lines.push("================ AGENT ABLATION SUMMARY ================");
269
+ lines.push(`Cases: ${summary.cases} | Mean Load-Bearing Ratio: ${(summary.averageLoadBearingRatio * 100).toFixed(1)}%`);
270
+ lines.push("--------------------------------------------------------");
271
+ lines.push("Agent ID | Flips | Influence | Cost/Flip");
272
+ lines.push("--------------------------------------------------------");
273
+ for (const [id, influence] of Object.entries(summary.perAgentInfluence)) {
274
+ const paddedId = id.padEnd(23, " ").slice(0, 23);
275
+ const stats = summary.perAgentStats?.[id];
276
+ const flips = stats ? `${stats.flips}/${stats.appearances}`.padEnd(5, " ") : "N/A ";
277
+ const infStr = `${(influence * 100).toFixed(1)}%`.padEnd(9, " ");
278
+ const agentRoi = summary.roi?.agents[id];
279
+ const costPerFlip = agentRoi?.costPerVerdictFlip !== void 0 ? `$${agentRoi.costPerVerdictFlip.toFixed(3)}` : "N/A";
280
+ lines.push(`${paddedId} | ${flips} | ${infStr} | ${costPerFlip}`);
281
+ }
282
+ lines.push("========================================================");
283
+ return lines.join("\n");
284
+ }
285
+
286
+ // src/index.ts
27
287
  function defaultEquals(a, b) {
28
288
  return a === b;
29
289
  }
290
+ function majorityVote(samples, equals = defaultEquals) {
291
+ if (samples.length === 0) {
292
+ throw new Error("Cannot compute majority vote of empty sample array");
293
+ }
294
+ if (samples.length === 1) {
295
+ return samples[0];
296
+ }
297
+ const clusters = [];
298
+ for (const sample of samples) {
299
+ const existing = clusters.find((c) => equals(c.value, sample));
300
+ if (existing) {
301
+ existing.count += 1;
302
+ } else {
303
+ clusters.push({ value: sample, count: 1 });
304
+ }
305
+ }
306
+ clusters.sort((a, b) => b.count - a.count);
307
+ return clusters[0].value;
308
+ }
30
309
  function runAblation(findings, decide, equals = defaultEquals) {
31
310
  const baseline = decide(findings.slice());
32
311
  const perAgent = findings.map((finding, index) => {
@@ -49,33 +328,340 @@ function runAblation(findings, decide, equals = defaultEquals) {
49
328
  loadBearingRatio
50
329
  };
51
330
  }
52
- function batchAblation(cases, decide, equals = defaultEquals) {
53
- const results = cases.map((findings) => runAblation(findings, decide, equals));
331
+ async function runAblationAsync(findings, decide, options = {}) {
332
+ const equals = options.equals || defaultEquals;
333
+ const samples = options.samples ?? 1;
334
+ async function evaluate(fs) {
335
+ if (samples <= 1) {
336
+ return await decide(fs.slice());
337
+ }
338
+ const sampleResults = [];
339
+ for (let i = 0; i < samples; i++) {
340
+ sampleResults.push(await decide(fs.slice()));
341
+ }
342
+ return options.aggregateSamples ? options.aggregateSamples(sampleResults) : majorityVote(sampleResults, equals);
343
+ }
344
+ const baseline = await evaluate(findings);
345
+ const perAgent = [];
346
+ for (let index = 0; index < findings.length; index++) {
347
+ const finding = findings[index];
348
+ const without = findings.slice(0, index).concat(findings.slice(index + 1));
349
+ const verdictWithout = await evaluate(without);
350
+ perAgent.push({
351
+ removedAgentId: finding.agentId,
352
+ verdictWithout,
353
+ changed: !equals(baseline, verdictWithout)
354
+ });
355
+ }
356
+ const totalAgents = findings.length;
357
+ const loadBearingCount = perAgent.filter((p) => p.changed).length;
358
+ const loadBearingRatio = totalAgents === 0 ? 0 : loadBearingCount / totalAgents;
359
+ return {
360
+ baseline,
361
+ perAgent,
362
+ loadBearingCount,
363
+ totalAgents,
364
+ loadBearingRatio
365
+ };
366
+ }
367
+ function aggregateBatchResults(cases, results, options = {}) {
368
+ const equals = options.equals || defaultEquals;
54
369
  const appearances = /* @__PURE__ */ new Map();
55
370
  const changedCounts = /* @__PURE__ */ new Map();
56
- for (const result of results) {
371
+ const protectiveCounts = /* @__PURE__ */ new Map();
372
+ const correctiveCounts = /* @__PURE__ */ new Map();
373
+ const totalCostByAgent = /* @__PURE__ */ new Map();
374
+ const totalTokensByAgent = /* @__PURE__ */ new Map();
375
+ const totalLatencyByAgent = /* @__PURE__ */ new Map();
376
+ let hasTelemetry = false;
377
+ for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
378
+ const caseFindings = cases[caseIdx];
379
+ const result = results[caseIdx];
380
+ const gt = options.groundTruth ? options.groundTruth[caseIdx] : void 0;
381
+ const baselineCorrect = gt !== void 0 ? equals(result.baseline, gt) : void 0;
382
+ for (const f of caseFindings) {
383
+ if (f.cost !== void 0) {
384
+ totalCostByAgent.set(f.agentId, (totalCostByAgent.get(f.agentId) ?? 0) + f.cost);
385
+ hasTelemetry = true;
386
+ }
387
+ if (f.tokens !== void 0) {
388
+ totalTokensByAgent.set(f.agentId, (totalTokensByAgent.get(f.agentId) ?? 0) + f.tokens);
389
+ hasTelemetry = true;
390
+ }
391
+ if (f.latencyMs !== void 0) {
392
+ totalLatencyByAgent.set(f.agentId, (totalLatencyByAgent.get(f.agentId) ?? 0) + f.latencyMs);
393
+ hasTelemetry = true;
394
+ }
395
+ }
57
396
  for (const perAgent of result.perAgent) {
58
397
  const id = perAgent.removedAgentId;
59
398
  appearances.set(id, (appearances.get(id) ?? 0) + 1);
60
399
  if (perAgent.changed) {
61
400
  changedCounts.set(id, (changedCounts.get(id) ?? 0) + 1);
62
401
  }
402
+ if (gt !== void 0 && baselineCorrect !== void 0) {
403
+ const withoutCorrect = equals(perAgent.verdictWithout, gt);
404
+ if (baselineCorrect && !withoutCorrect) {
405
+ protectiveCounts.set(id, (protectiveCounts.get(id) ?? 0) + 1);
406
+ } else if (!baselineCorrect && withoutCorrect) {
407
+ correctiveCounts.set(id, (correctiveCounts.get(id) ?? 0) + 1);
408
+ }
409
+ }
63
410
  }
64
411
  }
65
412
  const perAgentInfluence = {};
413
+ const perAgentStats = {};
66
414
  for (const [id, count] of appearances) {
67
- perAgentInfluence[id] = (changedCounts.get(id) ?? 0) / count;
415
+ const flips = changedCounts.get(id) ?? 0;
416
+ const influence = flips / count;
417
+ perAgentInfluence[id] = influence;
418
+ const stats = {
419
+ appearances: count,
420
+ flips
421
+ };
422
+ if (options.groundTruth) {
423
+ const protective = protectiveCounts.get(id) ?? 0;
424
+ const corrective = correctiveCounts.get(id) ?? 0;
425
+ const net = (protective - corrective) / count;
426
+ stats.protectiveFlips = protective;
427
+ stats.correctiveFlips = corrective;
428
+ stats.netAccuracyImpact = net;
429
+ stats.role = net > 0.01 ? "Protective" : net < -0.01 ? "Harmful" : "Neutral";
430
+ }
431
+ perAgentStats[id] = stats;
68
432
  }
69
433
  const averageLoadBearingRatio = results.length === 0 ? 0 : results.reduce((sum, r) => sum + r.loadBearingRatio, 0) / results.length;
70
434
  const summary = {
71
435
  cases: cases.length,
72
436
  averageLoadBearingRatio,
73
- perAgentInfluence
437
+ perAgentInfluence,
438
+ perAgentStats
74
439
  };
440
+ if (options.groundTruth && options.groundTruth.length === results.length) {
441
+ const correctCount = results.filter((r, idx) => equals(r.baseline, options.groundTruth[idx])).length;
442
+ summary.accuracy = {
443
+ baselineAccuracy: results.length === 0 ? 0 : correctCount / results.length,
444
+ correctBaselineCount: correctCount,
445
+ totalEvaluated: results.length
446
+ };
447
+ }
448
+ if (hasTelemetry) {
449
+ let pipelineTotalCost = 0;
450
+ let pipelineTotalTokens = 0;
451
+ for (const c of totalCostByAgent.values()) pipelineTotalCost += c;
452
+ for (const t of totalTokensByAgent.values()) pipelineTotalTokens += t;
453
+ const agentsRoi = {};
454
+ const recommendations = [];
455
+ for (const [id, count] of appearances) {
456
+ const agentCost = totalCostByAgent.get(id);
457
+ const agentTokens = totalTokensByAgent.get(id);
458
+ const agentLatency = totalLatencyByAgent.get(id);
459
+ const flips = changedCounts.get(id) ?? 0;
460
+ const influence = perAgentInfluence[id] ?? 0;
461
+ const roi = {};
462
+ if (agentCost !== void 0) {
463
+ roi.totalCost = agentCost;
464
+ if (flips > 0) roi.costPerVerdictFlip = agentCost / flips;
465
+ if (pipelineTotalCost > 0) roi.costShare = agentCost / pipelineTotalCost;
466
+ }
467
+ if (agentTokens !== void 0) {
468
+ roi.totalTokens = agentTokens;
469
+ if (flips > 0) roi.tokensPerVerdictFlip = agentTokens / flips;
470
+ }
471
+ if (agentLatency !== void 0) {
472
+ roi.averageLatencyMs = agentLatency / count;
473
+ }
474
+ if (roi.costShare !== void 0 && roi.costShare > 0) {
475
+ roi.efficiencyRatio = influence / roi.costShare;
476
+ }
477
+ agentsRoi[id] = roi;
478
+ if (roi.costShare !== void 0 && roi.costShare >= 0.2 && influence <= 0.05) {
479
+ recommendations.push({
480
+ agentId: id,
481
+ recommendation: "prune",
482
+ reason: `High compute cost (${(roi.costShare * 100).toFixed(1)}% of total cost) with negligible verdict impact (${(influence * 100).toFixed(1)}% influence).`
483
+ });
484
+ } else if (roi.efficiencyRatio !== void 0 && roi.efficiencyRatio < 0.25 && (roi.totalCost ?? 0) > 0.1) {
485
+ recommendations.push({
486
+ agentId: id,
487
+ recommendation: "downgrade_model",
488
+ reason: `Low efficiency ratio (${roi.efficiencyRatio.toFixed(2)}x). Consider replacing with a smaller/cheaper model.`
489
+ });
490
+ }
491
+ }
492
+ summary.roi = {
493
+ totalCost: pipelineTotalCost,
494
+ totalTokens: pipelineTotalTokens,
495
+ agents: agentsRoi,
496
+ recommendations
497
+ };
498
+ }
499
+ return summary;
500
+ }
501
+ function batchAblation(cases, decide, options = defaultEquals) {
502
+ const opts = typeof options === "function" ? { equals: options } : options;
503
+ const equals = opts.equals || defaultEquals;
504
+ const results = cases.map((findings) => runAblation(findings, decide, equals));
505
+ const summary = aggregateBatchResults(cases, results, opts);
506
+ return { results, summary };
507
+ }
508
+ async function batchAblationAsync(cases, decide, options = {}) {
509
+ const results = [];
510
+ for (const c of cases) {
511
+ results.push(await runAblationAsync(c, decide, options));
512
+ }
513
+ const summary = aggregateBatchResults(cases, results, options);
75
514
  return { results, summary };
76
515
  }
516
+ function runBackwardElimination(findings, decide, equals = defaultEquals) {
517
+ const baseline = decide(findings.slice());
518
+ let currentFindings = findings.slice();
519
+ const eliminatedAgentIds = [];
520
+ const steps = [];
521
+ while (currentFindings.length > 1) {
522
+ let candidateIndexToRemove = -1;
523
+ let candidateVerdict;
524
+ for (let i = 0; i < currentFindings.length; i++) {
525
+ const withoutCandidate = currentFindings.slice(0, i).concat(currentFindings.slice(i + 1));
526
+ const v = decide(withoutCandidate);
527
+ if (equals(baseline, v)) {
528
+ candidateIndexToRemove = i;
529
+ candidateVerdict = v;
530
+ break;
531
+ }
532
+ }
533
+ if (candidateIndexToRemove === -1) {
534
+ return {
535
+ baseline,
536
+ minimalFindings: currentFindings,
537
+ minimalAgentIds: currentFindings.map((f) => f.agentId),
538
+ eliminatedAgentIds,
539
+ steps,
540
+ stoppedDueToVerdictFlip: true
541
+ };
542
+ }
543
+ const removed = currentFindings[candidateIndexToRemove];
544
+ currentFindings = currentFindings.slice(0, candidateIndexToRemove).concat(currentFindings.slice(candidateIndexToRemove + 1));
545
+ eliminatedAgentIds.push(removed.agentId);
546
+ steps.push({
547
+ step: steps.length + 1,
548
+ eliminatedAgentId: removed.agentId,
549
+ remainingAgentIds: currentFindings.map((f) => f.agentId),
550
+ verdict: candidateVerdict
551
+ });
552
+ }
553
+ return {
554
+ baseline,
555
+ minimalFindings: currentFindings,
556
+ minimalAgentIds: currentFindings.map((f) => f.agentId),
557
+ eliminatedAgentIds,
558
+ steps,
559
+ stoppedDueToVerdictFlip: false
560
+ };
561
+ }
562
+ async function runBackwardEliminationAsync(findings, decide, options = {}) {
563
+ const equals = options.equals || defaultEquals;
564
+ const samples = options.samples ?? 1;
565
+ async function evaluate(fs) {
566
+ if (samples <= 1) return await decide(fs.slice());
567
+ const sampleResults = [];
568
+ for (let i = 0; i < samples; i++) sampleResults.push(await decide(fs.slice()));
569
+ return options.aggregateSamples ? options.aggregateSamples(sampleResults) : majorityVote(sampleResults, equals);
570
+ }
571
+ const baseline = await evaluate(findings);
572
+ let currentFindings = findings.slice();
573
+ const eliminatedAgentIds = [];
574
+ const steps = [];
575
+ while (currentFindings.length > 1) {
576
+ let candidateIndexToRemove = -1;
577
+ let candidateVerdict;
578
+ for (let i = 0; i < currentFindings.length; i++) {
579
+ const withoutCandidate = currentFindings.slice(0, i).concat(currentFindings.slice(i + 1));
580
+ const v = await evaluate(withoutCandidate);
581
+ if (equals(baseline, v)) {
582
+ candidateIndexToRemove = i;
583
+ candidateVerdict = v;
584
+ break;
585
+ }
586
+ }
587
+ if (candidateIndexToRemove === -1) {
588
+ return {
589
+ baseline,
590
+ minimalFindings: currentFindings,
591
+ minimalAgentIds: currentFindings.map((f) => f.agentId),
592
+ eliminatedAgentIds,
593
+ steps,
594
+ stoppedDueToVerdictFlip: true
595
+ };
596
+ }
597
+ const removed = currentFindings[candidateIndexToRemove];
598
+ currentFindings = currentFindings.slice(0, candidateIndexToRemove).concat(currentFindings.slice(candidateIndexToRemove + 1));
599
+ eliminatedAgentIds.push(removed.agentId);
600
+ steps.push({
601
+ step: steps.length + 1,
602
+ eliminatedAgentId: removed.agentId,
603
+ remainingAgentIds: currentFindings.map((f) => f.agentId),
604
+ verdict: candidateVerdict
605
+ });
606
+ }
607
+ return {
608
+ baseline,
609
+ minimalFindings: currentFindings,
610
+ minimalAgentIds: currentFindings.map((f) => f.agentId),
611
+ eliminatedAgentIds,
612
+ steps,
613
+ stoppedDueToVerdictFlip: false
614
+ };
615
+ }
616
+ function runPairwiseAblation(findings, decide, equals = defaultEquals) {
617
+ const singleResult = runAblation(findings, decide, equals);
618
+ const baseline = singleResult.baseline;
619
+ const singleChangedMap = /* @__PURE__ */ new Map();
620
+ for (const p of singleResult.perAgent) {
621
+ singleChangedMap.set(p.removedAgentId, p.changed);
622
+ }
623
+ const pairs = [];
624
+ for (let i = 0; i < findings.length; i++) {
625
+ for (let j = i + 1; j < findings.length; j++) {
626
+ const agentI = findings[i];
627
+ const agentJ = findings[j];
628
+ const withoutPair = findings.filter((_, idx) => idx !== i && idx !== j);
629
+ const verdictWithout = decide(withoutPair);
630
+ const changed = !equals(baseline, verdictWithout);
631
+ const singleIChanged = singleChangedMap.get(agentI.agentId) ?? false;
632
+ const singleJChanged = singleChangedMap.get(agentJ.agentId) ?? false;
633
+ const isInteraction = changed && !singleIChanged && !singleJChanged;
634
+ pairs.push({
635
+ pair: [agentI.agentId, agentJ.agentId],
636
+ verdictWithout,
637
+ changed,
638
+ isInteraction
639
+ });
640
+ }
641
+ }
642
+ const interactionCount = pairs.filter((p) => p.isInteraction).length;
643
+ return {
644
+ baseline,
645
+ pairs,
646
+ interactionCount
647
+ };
648
+ }
77
649
  // Annotate the CommonJS export names for ESM import in node:
78
650
  0 && (module.exports = {
651
+ aggregateBatchResults,
79
652
  batchAblation,
80
- runAblation
653
+ batchAblationAsync,
654
+ formatAsciiTable,
655
+ formatMarkdownReport,
656
+ fromAISDKSteps,
657
+ fromAutoGenMessages,
658
+ fromCrewAITasks,
659
+ fromLangGraphMessages,
660
+ fromRecords,
661
+ majorityVote,
662
+ runAblation,
663
+ runAblationAsync,
664
+ runBackwardElimination,
665
+ runBackwardEliminationAsync,
666
+ runPairwiseAblation
81
667
  });