agent-ablation 0.2.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,10 +20,22 @@ 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,
25
+ batchAblationAsync: () => batchAblationAsync,
26
+ formatAsciiTable: () => formatAsciiTable,
27
+ formatMarkdownReport: () => formatMarkdownReport,
28
+ fromAISDKSteps: () => fromAISDKSteps,
29
+ fromAutoGenMessages: () => fromAutoGenMessages,
30
+ fromCrewAITasks: () => fromCrewAITasks,
24
31
  fromLangGraphMessages: () => fromLangGraphMessages,
25
32
  fromRecords: () => fromRecords,
26
- runAblation: () => runAblation
33
+ majorityVote: () => majorityVote,
34
+ runAblation: () => runAblation,
35
+ runAblationAsync: () => runAblationAsync,
36
+ runBackwardElimination: () => runBackwardElimination,
37
+ runBackwardEliminationAsync: () => runBackwardEliminationAsync,
38
+ runPairwiseAblation: () => runPairwiseAblation
27
39
  });
28
40
  module.exports = __toCommonJS(index_exports);
29
41
 
@@ -80,10 +92,220 @@ function fromRecords(records, options) {
80
92
  });
81
93
  }
82
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
+
83
286
  // src/index.ts
84
287
  function defaultEquals(a, b) {
85
288
  return a === b;
86
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
+ }
87
309
  function runAblation(findings, decide, equals = defaultEquals) {
88
310
  const baseline = decide(findings.slice());
89
311
  const perAgent = findings.map((finding, index) => {
@@ -106,35 +328,340 @@ function runAblation(findings, decide, equals = defaultEquals) {
106
328
  loadBearingRatio
107
329
  };
108
330
  }
109
- function batchAblation(cases, decide, equals = defaultEquals) {
110
- 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;
111
369
  const appearances = /* @__PURE__ */ new Map();
112
370
  const changedCounts = /* @__PURE__ */ new Map();
113
- 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
+ }
114
396
  for (const perAgent of result.perAgent) {
115
397
  const id = perAgent.removedAgentId;
116
398
  appearances.set(id, (appearances.get(id) ?? 0) + 1);
117
399
  if (perAgent.changed) {
118
400
  changedCounts.set(id, (changedCounts.get(id) ?? 0) + 1);
119
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
+ }
120
410
  }
121
411
  }
122
412
  const perAgentInfluence = {};
413
+ const perAgentStats = {};
123
414
  for (const [id, count] of appearances) {
124
- 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;
125
432
  }
126
433
  const averageLoadBearingRatio = results.length === 0 ? 0 : results.reduce((sum, r) => sum + r.loadBearingRatio, 0) / results.length;
127
434
  const summary = {
128
435
  cases: cases.length,
129
436
  averageLoadBearingRatio,
130
- perAgentInfluence
437
+ perAgentInfluence,
438
+ perAgentStats
131
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);
132
514
  return { results, summary };
133
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
+ }
134
649
  // Annotate the CommonJS export names for ESM import in node:
135
650
  0 && (module.exports = {
651
+ aggregateBatchResults,
136
652
  batchAblation,
653
+ batchAblationAsync,
654
+ formatAsciiTable,
655
+ formatMarkdownReport,
656
+ fromAISDKSteps,
657
+ fromAutoGenMessages,
658
+ fromCrewAITasks,
137
659
  fromLangGraphMessages,
138
660
  fromRecords,
139
- runAblation
661
+ majorityVote,
662
+ runAblation,
663
+ runAblationAsync,
664
+ runBackwardElimination,
665
+ runBackwardEliminationAsync,
666
+ runPairwiseAblation
140
667
  });