@cassiomc1/forgeloop 1.7.0 → 1.8.1
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/DOCS_INDEX.md +1 -1
- package/benchmarks/execution-profiles/README.md +18 -0
- package/benchmarks/execution-profiles/api-feature.json +1 -1
- package/benchmarks/execution-profiles/authentication-change.json +1 -1
- package/benchmarks/execution-profiles/documentation-correction.json +1 -1
- package/benchmarks/execution-profiles/infrastructure-release.json +1 -1
- package/benchmarks/execution-profiles/novatask-saas-landing-page.json +1 -1
- package/benchmarks/execution-profiles/small-bug-fix.json +1 -1
- package/benchmarks/execution-profiles/static-landing-page.json +1 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +1 -1
- package/docs/EXECUTION_PROFILE_BENCHMARKS.md +218 -3
- package/docs/MCP.md +1 -1
- package/docs/RELEASE_CHECKLIST.md +12 -0
- package/package.json +7 -2
- package/schemas/execution-profile-benchmark-aggregate.schema.json +51 -1
- package/schemas/execution-profile-benchmark-run.schema.json +40 -2
- package/scripts/report-execution-profile-outliers.mjs +136 -0
- package/scripts/report-tail-interpretation.mjs +156 -0
- package/scripts/run-execution-profile-benchmarks.mjs +112 -36
- package/src/core/execution-profile-benchmarks.js +509 -35
|
@@ -2,12 +2,68 @@ import { canonicalFingerprint } from "./artifacts.js";
|
|
|
2
2
|
import { evaluateRoute } from "./router.js";
|
|
3
3
|
import { normalizeUsage } from "./usage.js";
|
|
4
4
|
|
|
5
|
-
export const BENCHMARK_VERSION = "
|
|
5
|
+
export const BENCHMARK_VERSION = "2";
|
|
6
|
+
export const SUPPORTED_BENCHMARK_VERSIONS = Object.freeze(["1", "2"]);
|
|
6
7
|
export const BENCHMARK_MODES = Object.freeze([
|
|
7
8
|
"direct",
|
|
8
9
|
"forgeloopBalanced",
|
|
9
10
|
"forgeloopAdaptive",
|
|
10
11
|
]);
|
|
12
|
+
// Observational runaway-execution diagnostics only; they never gate lifecycle
|
|
13
|
+
// truth or completion validity.
|
|
14
|
+
export const BENCHMARK_RUNAWAY_SIGNALS = Object.freeze([
|
|
15
|
+
"EXCESSIVE_MODEL_TURNS",
|
|
16
|
+
"EXCESSIVE_TOOL_CALLS",
|
|
17
|
+
"EXCESSIVE_VERIFICATION_CYCLES",
|
|
18
|
+
"REPEATED_CONTEXT_REFRESH",
|
|
19
|
+
"REPEATED_FILE_READ",
|
|
20
|
+
"REPEATED_GUIDE_LOAD",
|
|
21
|
+
"UNEXPECTED_RETRY",
|
|
22
|
+
"CORRECTION_LOOP",
|
|
23
|
+
"HOST_RETRY",
|
|
24
|
+
"MODEL_STALL",
|
|
25
|
+
"UNKNOWN_TOKEN_SPIKE",
|
|
26
|
+
]);
|
|
27
|
+
export const BENCHMARK_DIAGNOSTIC_COUNT_FIELDS = Object.freeze([
|
|
28
|
+
"verificationCycles",
|
|
29
|
+
"modelTurns",
|
|
30
|
+
"toolCalls",
|
|
31
|
+
"retries",
|
|
32
|
+
"correctionCycles",
|
|
33
|
+
"filesRead",
|
|
34
|
+
"filesWritten",
|
|
35
|
+
"contextRefreshes",
|
|
36
|
+
]);
|
|
37
|
+
export const BENCHMARK_TIERS = Object.freeze({
|
|
38
|
+
smoke: Object.freeze({ minimumRuns: 1, maximumRuns: 3, defaultRuns: 1 }),
|
|
39
|
+
evidence: Object.freeze({ minimumRuns: 5, maximumRuns: 10, defaultRuns: 5 }),
|
|
40
|
+
tail: Object.freeze({ minimumRuns: 20, maximumRuns: 30, defaultRuns: 20 }),
|
|
41
|
+
});
|
|
42
|
+
export const OUTLIER_POLICY = "TOKEN_IQR_1_5";
|
|
43
|
+
export const OUTLIER_MINIMUM_SAMPLES = 4;
|
|
44
|
+
export const TAIL_SAMPLE_MINIMUM = 20;
|
|
45
|
+
export const LOW_BASELINE_RATIO = 0.60;
|
|
46
|
+
export const BASELINE_TOKEN_REGIMES = Object.freeze([
|
|
47
|
+
"NORMAL",
|
|
48
|
+
"LOW_BASELINE_TOKEN_REGIME",
|
|
49
|
+
]);
|
|
50
|
+
export const TAIL_STATUSES = Object.freeze([
|
|
51
|
+
"TAIL_STABLE",
|
|
52
|
+
"TAIL_WARNING",
|
|
53
|
+
"TAIL_REGRESSION",
|
|
54
|
+
"NOT_ENOUGH_SAMPLES",
|
|
55
|
+
]);
|
|
56
|
+
export const DISTRIBUTION_TAIL_STATUSES = Object.freeze([
|
|
57
|
+
"TAIL_ACCEPTABLE",
|
|
58
|
+
"TAIL_REGRESSION",
|
|
59
|
+
"NOT_ENOUGH_SAMPLES",
|
|
60
|
+
]);
|
|
61
|
+
export const COMBINED_TAIL_STATUSES = Object.freeze([
|
|
62
|
+
"TAIL_CONSISTENT",
|
|
63
|
+
"TAIL_PAIRED_RATIO_SENSITIVE",
|
|
64
|
+
"TAIL_DISTRIBUTION_REGRESSION",
|
|
65
|
+
"TAIL_UNRESOLVED",
|
|
66
|
+
]);
|
|
11
67
|
export const BENCHMARK_VERIFICATION_RESULTS = Object.freeze([
|
|
12
68
|
"PASS",
|
|
13
69
|
"FAIL",
|
|
@@ -306,6 +362,81 @@ export function normalizeBenchmarkQuality(value) {
|
|
|
306
362
|
return { source, scores: normalizedScores };
|
|
307
363
|
}
|
|
308
364
|
|
|
365
|
+
function assertStringArray(value, label) {
|
|
366
|
+
if (!Array.isArray(value)) throw benchmarkError(`${label} must be an array`);
|
|
367
|
+
for (const item of value) {
|
|
368
|
+
if (typeof item !== "string" || item.length === 0) {
|
|
369
|
+
throw benchmarkError(`${label} must contain only non-empty strings`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Every diagnostics field is nullable. Unavailable host telemetry stays null;
|
|
376
|
+
// it is never estimated from prompt size, file size, or elapsed time.
|
|
377
|
+
export function normalizeBenchmarkDiagnostics(value) {
|
|
378
|
+
if (value === undefined || value === null) return null;
|
|
379
|
+
const diagnostics = assertObject(value, "diagnostics");
|
|
380
|
+
const allowedKeys = new Set([
|
|
381
|
+
"executionProfile",
|
|
382
|
+
...BENCHMARK_DIAGNOSTIC_COUNT_FIELDS,
|
|
383
|
+
"guideCount",
|
|
384
|
+
"guideIds",
|
|
385
|
+
"contextRefreshes",
|
|
386
|
+
"hostWarnings",
|
|
387
|
+
"terminationReason",
|
|
388
|
+
"flags",
|
|
389
|
+
]);
|
|
390
|
+
const unknownKey = Object.keys(diagnostics).find((key) => !allowedKeys.has(key));
|
|
391
|
+
if (unknownKey) throw benchmarkError(`diagnostics contains unsupported field: ${unknownKey}`);
|
|
392
|
+
const executionProfile = diagnostics.executionProfile ?? null;
|
|
393
|
+
assertNullableProfile(executionProfile, "diagnostics.executionProfile");
|
|
394
|
+
const normalized = { executionProfile };
|
|
395
|
+
for (const field of BENCHMARK_DIAGNOSTIC_COUNT_FIELDS) {
|
|
396
|
+
normalized[field] = assertNullableNonNegativeInteger(diagnostics[field] ?? null, `diagnostics.${field}`);
|
|
397
|
+
}
|
|
398
|
+
normalized.guideCount = assertNullableNonNegativeInteger(diagnostics.guideCount ?? null, "diagnostics.guideCount");
|
|
399
|
+
normalized.guideIds = assertStringArray(diagnostics.guideIds ?? [], "diagnostics.guideIds");
|
|
400
|
+
normalized.hostWarnings = assertStringArray(diagnostics.hostWarnings ?? [], "diagnostics.hostWarnings");
|
|
401
|
+
normalized.terminationReason = assertString(diagnostics.terminationReason ?? null, "diagnostics.terminationReason", { nullable: true });
|
|
402
|
+
const flags = assertStringArray(diagnostics.flags ?? [], "diagnostics.flags");
|
|
403
|
+
const unknownFlag = flags.find((flag) => !BENCHMARK_RUNAWAY_SIGNALS.includes(flag));
|
|
404
|
+
if (unknownFlag) throw benchmarkError(`diagnostics.flags contains unsupported signal: ${unknownFlag}`);
|
|
405
|
+
normalized.flags = BENCHMARK_RUNAWAY_SIGNALS.filter((signal) => flags.includes(signal));
|
|
406
|
+
return normalized;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Observational runaway-execution signals. Host-reported flags are preserved;
|
|
410
|
+
// deterministic signals are added only where the recorded diagnostics support
|
|
411
|
+
// them. A token outlier with no explanatory signal becomes UNKNOWN_TOKEN_SPIKE.
|
|
412
|
+
export function evaluateRunawaySignals(run, {
|
|
413
|
+
medianModelTurns = null,
|
|
414
|
+
medianToolCalls = null,
|
|
415
|
+
tokenOutlier = false,
|
|
416
|
+
} = {}) {
|
|
417
|
+
const diagnostics = normalizeBenchmarkDiagnostics(run?.diagnostics ?? null);
|
|
418
|
+
const signals = new Set(diagnostics?.flags ?? []);
|
|
419
|
+
const doubled = (value, median) => (
|
|
420
|
+
typeof value === "number"
|
|
421
|
+
&& typeof median === "number"
|
|
422
|
+
&& median > 0
|
|
423
|
+
&& value > median * 2
|
|
424
|
+
);
|
|
425
|
+
if (doubled(diagnostics?.modelTurns ?? null, medianModelTurns)) signals.add("EXCESSIVE_MODEL_TURNS");
|
|
426
|
+
if (doubled(diagnostics?.toolCalls ?? null, medianToolCalls)) signals.add("EXCESSIVE_TOOL_CALLS");
|
|
427
|
+
const verificationCycles = diagnostics?.verificationCycles ?? run?.verificationCycles ?? null;
|
|
428
|
+
if (verificationCycles !== null && verificationCycles > 1) signals.add("EXCESSIVE_VERIFICATION_CYCLES");
|
|
429
|
+
if ((diagnostics?.contextRefreshes ?? null) !== null && diagnostics.contextRefreshes > 1) {
|
|
430
|
+
signals.add("REPEATED_CONTEXT_REFRESH");
|
|
431
|
+
}
|
|
432
|
+
if ((diagnostics?.retries ?? null) !== null && diagnostics.retries > 0) signals.add("UNEXPECTED_RETRY");
|
|
433
|
+
if ((diagnostics?.correctionCycles ?? null) !== null && diagnostics.correctionCycles > 1) {
|
|
434
|
+
signals.add("CORRECTION_LOOP");
|
|
435
|
+
}
|
|
436
|
+
if (tokenOutlier && signals.size === 0) signals.add("UNKNOWN_TOKEN_SPIKE");
|
|
437
|
+
return BENCHMARK_RUNAWAY_SIGNALS.filter((signal) => signals.has(signal));
|
|
438
|
+
}
|
|
439
|
+
|
|
309
440
|
function contextUsageForRun(run) {
|
|
310
441
|
return normalizeBenchmarkContextUsage(run.contextUsage, run.metadata?.resolvedProfile ?? null);
|
|
311
442
|
}
|
|
@@ -352,8 +483,8 @@ function assertBenchmarkMetadata(metadata, run) {
|
|
|
352
483
|
export function assertBenchmarkRun(value) {
|
|
353
484
|
const run = assertObject(value, "benchmark run");
|
|
354
485
|
if (run.schemaVersion !== 1) throw benchmarkError("benchmark run schemaVersion must be 1");
|
|
355
|
-
if (run.benchmarkVersion
|
|
356
|
-
throw benchmarkError(`benchmark run benchmarkVersion must be ${
|
|
486
|
+
if (!SUPPORTED_BENCHMARK_VERSIONS.includes(run.benchmarkVersion)) {
|
|
487
|
+
throw benchmarkError(`benchmark run benchmarkVersion must be one of ${SUPPORTED_BENCHMARK_VERSIONS.join(", ")}`);
|
|
357
488
|
}
|
|
358
489
|
assertRunSetId(run.runSetId);
|
|
359
490
|
assertRunId(run.runId);
|
|
@@ -369,6 +500,7 @@ export function assertBenchmarkRun(value) {
|
|
|
369
500
|
assertNullableNonNegativeInteger(run.verificationCycles, "verificationCycles");
|
|
370
501
|
assertNullableNonNegativeInteger(run.comparableSteps, "comparableSteps");
|
|
371
502
|
assertBenchmarkMetadata(run.metadata, run);
|
|
503
|
+
if ("diagnostics" in run) normalizeBenchmarkDiagnostics(run.diagnostics);
|
|
372
504
|
normalizeBenchmarkContextUsage(run.contextUsage, run.metadata.resolvedProfile);
|
|
373
505
|
normalizeBenchmarkQuality(run.quality);
|
|
374
506
|
if (usage.model !== null && run.metadata.model !== usage.model) {
|
|
@@ -392,6 +524,7 @@ export function createBenchmarkRun({
|
|
|
392
524
|
verification = "NOT_AVAILABLE",
|
|
393
525
|
verificationCycles = null,
|
|
394
526
|
comparableSteps = null,
|
|
527
|
+
diagnostics = undefined,
|
|
395
528
|
contextUsage = undefined,
|
|
396
529
|
quality = undefined,
|
|
397
530
|
metadata = {},
|
|
@@ -402,6 +535,7 @@ export function createBenchmarkRun({
|
|
|
402
535
|
if (!profile) throw benchmarkError(`Unsupported benchmark mode: ${mode}`);
|
|
403
536
|
const resolvedProfile = metadata.resolvedProfile ?? profile.resolvedProfile;
|
|
404
537
|
normalizedModeProfile(mode, resolvedProfile);
|
|
538
|
+
const normalizedDiagnostics = normalizeBenchmarkDiagnostics(diagnostics);
|
|
405
539
|
const run = {
|
|
406
540
|
schemaVersion: 1,
|
|
407
541
|
benchmarkVersion: BENCHMARK_VERSION,
|
|
@@ -416,6 +550,7 @@ export function createBenchmarkRun({
|
|
|
416
550
|
verification,
|
|
417
551
|
verificationCycles,
|
|
418
552
|
comparableSteps,
|
|
553
|
+
...(normalizedDiagnostics === null ? {} : { diagnostics: normalizedDiagnostics }),
|
|
419
554
|
contextUsage: normalizeBenchmarkContextUsage(contextUsage, resolvedProfile),
|
|
420
555
|
quality: normalizeBenchmarkQuality(quality),
|
|
421
556
|
metadata: {
|
|
@@ -439,23 +574,70 @@ export function createBenchmarkRun({
|
|
|
439
574
|
return assertBenchmarkRun(run);
|
|
440
575
|
}
|
|
441
576
|
|
|
577
|
+
function sortedFinite(values) {
|
|
578
|
+
return values.filter((value) => typeof value === "number" && Number.isFinite(value)).sort((a, b) => a - b);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function percentileOf(sorted, fraction) {
|
|
582
|
+
const position = (sorted.length - 1) * fraction;
|
|
583
|
+
const lower = Math.floor(position);
|
|
584
|
+
const upper = Math.ceil(position);
|
|
585
|
+
if (lower === upper) return sorted[lower];
|
|
586
|
+
return sorted[lower] + ((sorted[upper] - sorted[lower]) * (position - lower));
|
|
587
|
+
}
|
|
588
|
+
|
|
442
589
|
function statistic(values) {
|
|
443
|
-
const usable = values
|
|
590
|
+
const usable = sortedFinite(values);
|
|
444
591
|
if (usable.length === 0) return { count: 0, average: null, p50: null, p95: null, minimum: null, maximum: null };
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
592
|
+
return {
|
|
593
|
+
count: usable.length,
|
|
594
|
+
average: Number((usable.reduce((sum, value) => sum + value, 0) / usable.length).toFixed(4)),
|
|
595
|
+
p50: Number(percentileOf(usable, 0.5).toFixed(4)),
|
|
596
|
+
p95: Number(percentileOf(usable, 0.95).toFixed(4)),
|
|
597
|
+
minimum: usable[0],
|
|
598
|
+
maximum: usable.at(-1),
|
|
451
599
|
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Benchmark methodology v2 favors robust statistics for skewed token
|
|
603
|
+
// distributions: median/IQR/MAD instead of average/stddev alone.
|
|
604
|
+
export function robustStatistic(values) {
|
|
605
|
+
const usable = sortedFinite(values);
|
|
606
|
+
if (usable.length === 0) {
|
|
607
|
+
return {
|
|
608
|
+
count: 0,
|
|
609
|
+
average: null,
|
|
610
|
+
minimum: null,
|
|
611
|
+
p25: null,
|
|
612
|
+
p50: null,
|
|
613
|
+
p75: null,
|
|
614
|
+
p90: null,
|
|
615
|
+
p95: null,
|
|
616
|
+
maximum: null,
|
|
617
|
+
iqr: null,
|
|
618
|
+
mad: null,
|
|
619
|
+
outlierCount: 0,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const q1 = percentileOf(usable, 0.25);
|
|
623
|
+
const median = percentileOf(usable, 0.5);
|
|
624
|
+
const q3 = percentileOf(usable, 0.75);
|
|
625
|
+
const iqr = q3 - q1;
|
|
626
|
+
const upperFence = q3 + 1.5 * iqr;
|
|
627
|
+
const deviations = usable.map((value) => Math.abs(value - median)).sort((a, b) => a - b);
|
|
452
628
|
return {
|
|
453
629
|
count: usable.length,
|
|
454
630
|
average: Number((usable.reduce((sum, value) => sum + value, 0) / usable.length).toFixed(4)),
|
|
455
|
-
p50: Number(percentile(0.5).toFixed(4)),
|
|
456
|
-
p95: Number(percentile(0.95).toFixed(4)),
|
|
457
631
|
minimum: usable[0],
|
|
632
|
+
p25: Number(q1.toFixed(4)),
|
|
633
|
+
p50: Number(median.toFixed(4)),
|
|
634
|
+
p75: Number(q3.toFixed(4)),
|
|
635
|
+
p90: Number(percentileOf(usable, 0.9).toFixed(4)),
|
|
636
|
+
p95: Number(percentileOf(usable, 0.95).toFixed(4)),
|
|
458
637
|
maximum: usable.at(-1),
|
|
638
|
+
iqr: Number(iqr.toFixed(4)),
|
|
639
|
+
mad: Number(percentileOf(deviations, 0.5).toFixed(4)),
|
|
640
|
+
outlierCount: usable.filter((value) => value > upperFence).length,
|
|
459
641
|
};
|
|
460
642
|
}
|
|
461
643
|
|
|
@@ -487,7 +669,18 @@ function comparablePair(left, right) {
|
|
|
487
669
|
return fields.every((field) => (left.metadata[field] ?? null) === (right.metadata[field] ?? null));
|
|
488
670
|
}
|
|
489
671
|
|
|
490
|
-
function
|
|
672
|
+
export function distributionDeltaPercent(baselineStats, candidateStats) {
|
|
673
|
+
if (!baselineStats || !candidateStats) return { p50: null, p95: null };
|
|
674
|
+
const p50Delta = (Number.isFinite(baselineStats.p50) && Number.isFinite(candidateStats.p50) && baselineStats.p50 > 0)
|
|
675
|
+
? Number((((candidateStats.p50 - baselineStats.p50) / baselineStats.p50) * 100).toFixed(4))
|
|
676
|
+
: null;
|
|
677
|
+
const p95Delta = (Number.isFinite(baselineStats.p95) && Number.isFinite(candidateStats.p95) && baselineStats.p95 > 0)
|
|
678
|
+
? Number((((candidateStats.p95 - baselineStats.p95) / baselineStats.p95) * 100).toFixed(4))
|
|
679
|
+
: null;
|
|
680
|
+
return { p50: p50Delta, p95: p95Delta };
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function legacyComparisonForMode(directRuns, modeRuns) {
|
|
491
684
|
const directByIndex = new Map(directRuns.map((run) => [run.runIndex, run]));
|
|
492
685
|
const pairs = modeRuns
|
|
493
686
|
.map((run) => ({ variant: run, direct: directByIndex.get(run.runIndex) }))
|
|
@@ -515,28 +708,113 @@ function comparisonForMode(directRuns, modeRuns) {
|
|
|
515
708
|
};
|
|
516
709
|
}
|
|
517
710
|
|
|
518
|
-
function
|
|
711
|
+
function comparisonForMode(directRuns, modeRuns) {
|
|
712
|
+
const directByIndex = new Map(directRuns.map((run) => [run.runIndex, run]));
|
|
713
|
+
const pairs = modeRuns
|
|
714
|
+
.map((run) => ({ variant: run, direct: directByIndex.get(run.runIndex) }))
|
|
715
|
+
.filter((pair) => pair.direct && comparablePair(pair.direct, pair.variant));
|
|
716
|
+
|
|
717
|
+
const validTokenPairs = pairs.filter(({ direct, variant }) => (
|
|
718
|
+
Number.isFinite(direct.usage?.totalTokens)
|
|
719
|
+
&& Number.isFinite(variant.usage?.totalTokens)
|
|
720
|
+
&& direct.usage.totalTokens > 0
|
|
721
|
+
));
|
|
722
|
+
const validTimePairs = pairs.filter(({ direct, variant }) => (
|
|
723
|
+
Number.isFinite(direct.wallClockMs)
|
|
724
|
+
&& Number.isFinite(variant.wallClockMs)
|
|
725
|
+
&& direct.wallClockMs > 0
|
|
726
|
+
));
|
|
727
|
+
|
|
728
|
+
const comparableDirectTokens = validTokenPairs.map(({ direct }) => direct.usage.totalTokens);
|
|
729
|
+
const comparableCandidateTokens = validTokenPairs.map(({ variant }) => variant.usage.totalTokens);
|
|
730
|
+
|
|
731
|
+
const comparableDirectStats = robustStatistic(comparableDirectTokens);
|
|
732
|
+
const comparableCandidateStats = robustStatistic(comparableCandidateTokens);
|
|
733
|
+
|
|
734
|
+
const distDelta = distributionDeltaPercent(comparableDirectStats, comparableCandidateStats);
|
|
735
|
+
|
|
736
|
+
const lowBaselineThreshold = (Number.isFinite(comparableDirectStats.p50) && comparableDirectStats.p50 > 0)
|
|
737
|
+
? Number((comparableDirectStats.p50 * LOW_BASELINE_RATIO).toFixed(4))
|
|
738
|
+
: null;
|
|
739
|
+
|
|
740
|
+
const pairedRuns = validTokenPairs.map(({ direct, variant }) => {
|
|
741
|
+
const directTokens = direct.usage.totalTokens;
|
|
742
|
+
const candidateTokens = variant.usage.totalTokens;
|
|
743
|
+
const pairedOverhead = Number((((candidateTokens - directTokens) / directTokens) * 100).toFixed(4));
|
|
744
|
+
const absoluteDelta = candidateTokens - directTokens;
|
|
745
|
+
const baselineRegime = (lowBaselineThreshold !== null && directTokens < lowBaselineThreshold)
|
|
746
|
+
? "LOW_BASELINE_TOKEN_REGIME"
|
|
747
|
+
: "NORMAL";
|
|
748
|
+
return {
|
|
749
|
+
runIndex: variant.runIndex,
|
|
750
|
+
directTokens,
|
|
751
|
+
candidateTokens,
|
|
752
|
+
absoluteDeltaTokens: absoluteDelta,
|
|
753
|
+
pairedOverheadPercent: pairedOverhead,
|
|
754
|
+
baselineRegime,
|
|
755
|
+
};
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
const tokenOverheads = validTokenPairs.map(({ direct, variant }) => (
|
|
759
|
+
((variant.usage.totalTokens - direct.usage.totalTokens) / direct.usage.totalTokens) * 100
|
|
760
|
+
));
|
|
761
|
+
const timeOverheads = validTimePairs.map(({ direct, variant }) => (
|
|
762
|
+
((variant.wallClockMs - direct.wallClockMs) / direct.wallClockMs) * 100
|
|
763
|
+
));
|
|
764
|
+
|
|
765
|
+
const tokenStats = statistic(tokenOverheads);
|
|
766
|
+
const timeStats = statistic(timeOverheads);
|
|
767
|
+
const claimAllowed = tokenStats.count > 0 && timeStats.count > 0;
|
|
768
|
+
|
|
769
|
+
const lowBaselinePairCount = pairedRuns.filter((p) => p.baselineRegime === "LOW_BASELINE_TOKEN_REGIME").length;
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
comparablePairs: pairs.length,
|
|
773
|
+
tokenComparablePairs: tokenStats.count,
|
|
774
|
+
timeComparablePairs: timeStats.count,
|
|
775
|
+
tokenOverheadPercent: { p50: tokenStats.p50, p95: tokenStats.p95 },
|
|
776
|
+
pairedOverheadPercent: { p50: tokenStats.p50, p95: tokenStats.p95 },
|
|
777
|
+
distributionDeltaPercent: distDelta,
|
|
778
|
+
timeOverheadPercent: { p50: timeStats.p50, p95: timeStats.p95 },
|
|
779
|
+
pairedRatioDiagnostics: {
|
|
780
|
+
pairCount: pairs.length,
|
|
781
|
+
baselineMinimum: comparableDirectStats.minimum,
|
|
782
|
+
baselineP25: comparableDirectStats.p25,
|
|
783
|
+
baselineP50: comparableDirectStats.p50,
|
|
784
|
+
lowBaselineThreshold,
|
|
785
|
+
lowBaselinePairCount,
|
|
786
|
+
},
|
|
787
|
+
pairedRuns,
|
|
788
|
+
claimStatus: claimAllowed ? "OBSERVATIONAL" : "NOT_COMPARABLE",
|
|
789
|
+
claimAllowed,
|
|
790
|
+
reason: claimAllowed
|
|
791
|
+
? "Trusted usage, actual timing, verification, and matching comparability metadata are present."
|
|
792
|
+
: "Efficiency claims require trusted usage, actual timing, PASS verification, positive comparable steps, and matching metadata.",
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function contextUsageAggregate(runs, statisticFn = statistic) {
|
|
519
797
|
const contextUsages = runs.map(contextUsageForRun);
|
|
520
798
|
const totals = contextUsages.map(measuredContextTokens);
|
|
521
799
|
return {
|
|
522
800
|
sources: [...new Set(contextUsages.map((contextUsage) => contextUsage.source))].sort(),
|
|
523
801
|
profiles: [...new Set(contextUsages.map((contextUsage) => contextUsage.profile).filter(Boolean))].sort(),
|
|
524
802
|
measuredRuns: totals.filter((value) => value !== null).length,
|
|
525
|
-
totalTokens:
|
|
803
|
+
totalTokens: statisticFn(totals),
|
|
526
804
|
items: Object.fromEntries(BENCHMARK_CONTEXT_USAGE_ITEMS.map((item) => [
|
|
527
805
|
item,
|
|
528
|
-
|
|
806
|
+
statisticFn(contextUsages.map((contextUsage) => contextUsage.items[item])),
|
|
529
807
|
])),
|
|
530
808
|
};
|
|
531
809
|
}
|
|
532
810
|
|
|
533
|
-
function qualityAggregate(runs) {
|
|
811
|
+
function qualityAggregate(runs, statisticFn = statistic) {
|
|
534
812
|
const quality = runs.map((run) => normalizeBenchmarkQuality(run.quality));
|
|
535
813
|
return {
|
|
536
814
|
sources: [...new Set(quality.map((item) => item.source))].sort(),
|
|
537
815
|
scores: Object.fromEntries(BENCHMARK_QUALITY_FIELDS.map((field) => [
|
|
538
816
|
field,
|
|
539
|
-
|
|
817
|
+
statisticFn(quality.map((item) => item.scores[field])),
|
|
540
818
|
])),
|
|
541
819
|
};
|
|
542
820
|
}
|
|
@@ -578,6 +856,138 @@ function contextInflationForScenario(balancedRuns, adaptiveRuns) {
|
|
|
578
856
|
};
|
|
579
857
|
}
|
|
580
858
|
|
|
859
|
+
// Deterministic benchmark-level outlier classification. The IQR rule is an
|
|
860
|
+
// analysis convention, not protocol truth; it never changes lifecycle state.
|
|
861
|
+
export function analyzeTokenOutliers(runsByMode) {
|
|
862
|
+
return {
|
|
863
|
+
policy: OUTLIER_POLICY,
|
|
864
|
+
minimumSamples: OUTLIER_MINIMUM_SAMPLES,
|
|
865
|
+
modes: Object.fromEntries(BENCHMARK_MODES.map((mode) => [
|
|
866
|
+
mode,
|
|
867
|
+
tokenOutlierAnalysisForMode(runsByMode[mode] ?? []),
|
|
868
|
+
])),
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function tokenOutlierAnalysisForMode(runs) {
|
|
873
|
+
const measured = runs.filter((run) => Number.isFinite(run.usage.totalTokens));
|
|
874
|
+
const notEnough = {
|
|
875
|
+
sampleCount: runs.length,
|
|
876
|
+
measuredCount: measured.length,
|
|
877
|
+
status: "NOT_ENOUGH_SAMPLES",
|
|
878
|
+
q1: null,
|
|
879
|
+
q3: null,
|
|
880
|
+
iqr: null,
|
|
881
|
+
median: null,
|
|
882
|
+
upperFence: null,
|
|
883
|
+
outliers: [],
|
|
884
|
+
};
|
|
885
|
+
if (measured.length < OUTLIER_MINIMUM_SAMPLES) return notEnough;
|
|
886
|
+
const stats = robustStatistic(measured.map((run) => run.usage.totalTokens));
|
|
887
|
+
const medianModelTurns = robustStatistic(measured.map((run) => run.diagnostics?.modelTurns ?? null)).p50;
|
|
888
|
+
const medianToolCalls = robustStatistic(measured.map((run) => run.diagnostics?.toolCalls ?? null)).p50;
|
|
889
|
+
const upperFence = stats.p75 + 1.5 * stats.iqr;
|
|
890
|
+
const outliers = measured
|
|
891
|
+
.filter((run) => run.usage.totalTokens > upperFence)
|
|
892
|
+
.map((run) => ({
|
|
893
|
+
runId: run.runId,
|
|
894
|
+
runIndex: run.runIndex,
|
|
895
|
+
totalTokens: run.usage.totalTokens,
|
|
896
|
+
scenarioMedianTokens: stats.p50,
|
|
897
|
+
ratioToMedian: stats.p50 > 0 ? Number((run.usage.totalTokens / stats.p50).toFixed(4)) : null,
|
|
898
|
+
reasons: ["TOKEN_IQR_OUTLIER"],
|
|
899
|
+
diagnosticSignals: evaluateRunawaySignals(run, { medianModelTurns, medianToolCalls, tokenOutlier: true }),
|
|
900
|
+
}))
|
|
901
|
+
.sort((left, right) => left.runIndex - right.runIndex);
|
|
902
|
+
return {
|
|
903
|
+
sampleCount: runs.length,
|
|
904
|
+
measuredCount: measured.length,
|
|
905
|
+
status: "MEASURED",
|
|
906
|
+
q1: stats.p25,
|
|
907
|
+
q3: stats.p75,
|
|
908
|
+
iqr: stats.iqr,
|
|
909
|
+
median: stats.p50,
|
|
910
|
+
upperFence: Number(upperFence.toFixed(4)),
|
|
911
|
+
outliers,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Observational tail stability status; the thresholds are benchmark policy,
|
|
916
|
+
// never lifecycle policy.
|
|
917
|
+
export function classifyTailStatus({
|
|
918
|
+
sampleCount,
|
|
919
|
+
p95TokenOverheadPercent,
|
|
920
|
+
outlierCount = 0,
|
|
921
|
+
sampleMinimum = TAIL_SAMPLE_MINIMUM,
|
|
922
|
+
regressionPercent = LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
|
|
923
|
+
} = {}) {
|
|
924
|
+
if (!Number.isInteger(sampleCount) || sampleCount < sampleMinimum) return "NOT_ENOUGH_SAMPLES";
|
|
925
|
+
if (p95TokenOverheadPercent === null || p95TokenOverheadPercent === undefined) return "NOT_ENOUGH_SAMPLES";
|
|
926
|
+
if (p95TokenOverheadPercent > regressionPercent) return "TAIL_REGRESSION";
|
|
927
|
+
if (outlierCount > 0) return "TAIL_WARNING";
|
|
928
|
+
return "TAIL_STABLE";
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export function classifyDistributionTailStatus({
|
|
932
|
+
sampleCount,
|
|
933
|
+
distributionP95DeltaPercent,
|
|
934
|
+
sampleMinimum = TAIL_SAMPLE_MINIMUM,
|
|
935
|
+
regressionPercent = LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
|
|
936
|
+
} = {}) {
|
|
937
|
+
if (!Number.isInteger(sampleCount) || sampleCount < sampleMinimum) return "NOT_ENOUGH_SAMPLES";
|
|
938
|
+
if (distributionP95DeltaPercent === null || distributionP95DeltaPercent === undefined) return "NOT_ENOUGH_SAMPLES";
|
|
939
|
+
if (distributionP95DeltaPercent > regressionPercent) return "TAIL_REGRESSION";
|
|
940
|
+
return "TAIL_ACCEPTABLE";
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export function classifyCombinedTailStatus({
|
|
944
|
+
pairedStatus,
|
|
945
|
+
distributionStatus,
|
|
946
|
+
lowBaselinePairCount = 0,
|
|
947
|
+
sampleCount,
|
|
948
|
+
sampleMinimum = TAIL_SAMPLE_MINIMUM,
|
|
949
|
+
} = {}) {
|
|
950
|
+
if (!Number.isInteger(sampleCount) || sampleCount < sampleMinimum) return "TAIL_UNRESOLVED";
|
|
951
|
+
if (pairedStatus === "NOT_ENOUGH_SAMPLES" || distributionStatus === "NOT_ENOUGH_SAMPLES") return "TAIL_UNRESOLVED";
|
|
952
|
+
if (distributionStatus === "TAIL_REGRESSION") return "TAIL_DISTRIBUTION_REGRESSION";
|
|
953
|
+
if (pairedStatus === "TAIL_REGRESSION" && distributionStatus === "TAIL_ACCEPTABLE" && lowBaselinePairCount > 0) {
|
|
954
|
+
return "TAIL_PAIRED_RATIO_SENSITIVE";
|
|
955
|
+
}
|
|
956
|
+
if (pairedStatus === "TAIL_STABLE" && distributionStatus === "TAIL_ACCEPTABLE") return "TAIL_CONSISTENT";
|
|
957
|
+
if (pairedStatus === "TAIL_WARNING" && distributionStatus === "TAIL_ACCEPTABLE") return "TAIL_CONSISTENT";
|
|
958
|
+
if (pairedStatus === distributionStatus) return "TAIL_CONSISTENT";
|
|
959
|
+
return "TAIL_UNRESOLVED";
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function diagnosticsAggregate(runs) {
|
|
963
|
+
return Object.fromEntries(BENCHMARK_DIAGNOSTIC_COUNT_FIELDS.map((field) => [
|
|
964
|
+
field,
|
|
965
|
+
robustStatistic(runs.map((run) => run.diagnostics?.[field] ?? null)),
|
|
966
|
+
]));
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function modeAggregateRobust(runs, { includeContextUsage = false, includeQuality = false } = {}) {
|
|
970
|
+
const verificationCount = runs.filter((run) => run.verification === "PASS").length;
|
|
971
|
+
const tokenPerStep = runs.map((run) => (
|
|
972
|
+
Number.isFinite(run.usage.totalTokens) && Number.isInteger(run.comparableSteps) && run.comparableSteps > 0
|
|
973
|
+
? run.usage.totalTokens / run.comparableSteps
|
|
974
|
+
: null
|
|
975
|
+
));
|
|
976
|
+
return {
|
|
977
|
+
runCount: runs.length,
|
|
978
|
+
verificationSuccessRate: runs.length > 0 ? Number((verificationCount / runs.length).toFixed(4)) : null,
|
|
979
|
+
usageSources: [...new Set(runs.map((run) => run.usage.source))].sort(),
|
|
980
|
+
totalTokens: robustStatistic(runs.map((run) => run.usage.totalTokens)),
|
|
981
|
+
wallClockMs: robustStatistic(runs.map((run) => run.wallClockMs)),
|
|
982
|
+
verificationCycles: robustStatistic(runs.map((run) => run.verificationCycles)),
|
|
983
|
+
comparableSteps: robustStatistic(runs.map((run) => run.comparableSteps)),
|
|
984
|
+
tokensPerComparableStep: robustStatistic(tokenPerStep),
|
|
985
|
+
diagnostics: diagnosticsAggregate(runs),
|
|
986
|
+
...(includeContextUsage ? { contextUsage: contextUsageAggregate(runs, robustStatistic) } : {}),
|
|
987
|
+
...(includeQuality ? { quality: qualityAggregate(runs, robustStatistic) } : {}),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
581
991
|
function modeAggregate(runs, { includeContextUsage = false, includeQuality = false } = {}) {
|
|
582
992
|
const verificationCount = runs.filter((run) => run.verification === "PASS").length;
|
|
583
993
|
const comparableSteps = runs.map((run) => run.comparableSteps);
|
|
@@ -600,6 +1010,21 @@ function modeAggregate(runs, { includeContextUsage = false, includeQuality = fal
|
|
|
600
1010
|
};
|
|
601
1011
|
}
|
|
602
1012
|
|
|
1013
|
+
function lightObjectivesFor(scenario, comparisons) {
|
|
1014
|
+
if (scenario.expectedProfile !== "light") return null;
|
|
1015
|
+
return {
|
|
1016
|
+
p50TokenOverheadPercent: LIGHT_EFFICIENCY_OBJECTIVES.p50TokenOverheadPercent,
|
|
1017
|
+
p95TokenOverheadPercent: LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
|
|
1018
|
+
status: comparisons.forgeloopAdaptive?.claimAllowed ? "OBSERVATIONAL" : "NOT_VERIFIED",
|
|
1019
|
+
p50Pass: comparisons.forgeloopAdaptive?.tokenOverheadPercent.p50 === null
|
|
1020
|
+
? null
|
|
1021
|
+
: comparisons.forgeloopAdaptive.tokenOverheadPercent.p50 <= LIGHT_EFFICIENCY_OBJECTIVES.p50TokenOverheadPercent,
|
|
1022
|
+
p95Pass: comparisons.forgeloopAdaptive?.tokenOverheadPercent.p95 === null
|
|
1023
|
+
? null
|
|
1024
|
+
: comparisons.forgeloopAdaptive.tokenOverheadPercent.p95 <= LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
603
1028
|
export function aggregateBenchmarkRuns({ scenario, runs } = {}) {
|
|
604
1029
|
assertBenchmarkScenario(scenario);
|
|
605
1030
|
if (!Array.isArray(runs) || runs.length === 0) throw benchmarkError("runs must contain at least one benchmark run");
|
|
@@ -620,33 +1045,81 @@ export function aggregateBenchmarkRuns({ scenario, runs } = {}) {
|
|
|
620
1045
|
duplicateKeys.add(key);
|
|
621
1046
|
}
|
|
622
1047
|
const directRuns = byMode.direct;
|
|
1048
|
+
const includeContextUsage = runs.some((run) => Object.prototype.hasOwnProperty.call(run, "contextUsage"));
|
|
1049
|
+
const includeQuality = runs.some((run) => Object.prototype.hasOwnProperty.call(run, "quality"));
|
|
1050
|
+
const legacy = runs.every((run) => run.benchmarkVersion === "1");
|
|
1051
|
+
if (legacy) {
|
|
1052
|
+
const comparisons = Object.fromEntries(BENCHMARK_MODES.map((mode) => [
|
|
1053
|
+
mode,
|
|
1054
|
+
mode === "direct" ? null : legacyComparisonForMode(directRuns, byMode[mode]),
|
|
1055
|
+
]));
|
|
1056
|
+
const lightObjectives = lightObjectivesFor(scenario, comparisons);
|
|
1057
|
+
return {
|
|
1058
|
+
schemaVersion: 1,
|
|
1059
|
+
benchmarkVersion: "1",
|
|
1060
|
+
runSetId: runs[0].runSetId,
|
|
1061
|
+
scenarioId: scenario.scenarioId,
|
|
1062
|
+
expectedProfile: scenario.expectedProfile,
|
|
1063
|
+
modeAggregates: Object.fromEntries(BENCHMARK_MODES.map((mode) => [mode, modeAggregate(byMode[mode], { includeContextUsage, includeQuality })])),
|
|
1064
|
+
comparisons,
|
|
1065
|
+
lightObjectives,
|
|
1066
|
+
sourcePolicy: "PROVIDER_REPORTED_OR_HOST_REPORTED_ONLY",
|
|
1067
|
+
claimsAllowed: Object.values(comparisons).some((comparison) => comparison?.claimAllowed === true),
|
|
1068
|
+
generatedFromRunCount: runs.length,
|
|
1069
|
+
...(includeContextUsage
|
|
1070
|
+
? { contextInflation: contextInflationForScenario(byMode.forgeloopBalanced, byMode.forgeloopAdaptive) }
|
|
1071
|
+
: {}),
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
623
1074
|
const comparisons = Object.fromEntries(BENCHMARK_MODES.map((mode) => [
|
|
624
1075
|
mode,
|
|
625
1076
|
mode === "direct" ? null : comparisonForMode(directRuns, byMode[mode]),
|
|
626
1077
|
]));
|
|
627
|
-
const lightObjectives = scenario
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
:
|
|
640
|
-
|
|
641
|
-
|
|
1078
|
+
const lightObjectives = lightObjectivesFor(scenario, comparisons);
|
|
1079
|
+
const outlierAnalysis = analyzeTokenOutliers(byMode);
|
|
1080
|
+
const comparisonsWithTail = Object.fromEntries(BENCHMARK_MODES.map((mode) => {
|
|
1081
|
+
const comparison = comparisons[mode];
|
|
1082
|
+
if (comparison === null) return [mode, null];
|
|
1083
|
+
const outlierCount = outlierAnalysis.modes[mode].outliers.length;
|
|
1084
|
+
const pairedStatus = classifyTailStatus({
|
|
1085
|
+
sampleCount: comparison.tokenComparablePairs,
|
|
1086
|
+
p95TokenOverheadPercent: comparison.tokenOverheadPercent.p95,
|
|
1087
|
+
outlierCount,
|
|
1088
|
+
});
|
|
1089
|
+
const distributionStatus = classifyDistributionTailStatus({
|
|
1090
|
+
sampleCount: comparison.tokenComparablePairs,
|
|
1091
|
+
distributionP95DeltaPercent: comparison.distributionDeltaPercent.p95,
|
|
1092
|
+
});
|
|
1093
|
+
const combinedInterpretation = classifyCombinedTailStatus({
|
|
1094
|
+
pairedStatus,
|
|
1095
|
+
distributionStatus,
|
|
1096
|
+
lowBaselinePairCount: comparison.pairedRatioDiagnostics?.lowBaselinePairCount ?? 0,
|
|
1097
|
+
sampleCount: comparison.tokenComparablePairs,
|
|
1098
|
+
});
|
|
1099
|
+
return [mode, {
|
|
1100
|
+
...comparison,
|
|
1101
|
+
tail: {
|
|
1102
|
+
sampleMinimum: TAIL_SAMPLE_MINIMUM,
|
|
1103
|
+
sampleCount: comparison.tokenComparablePairs,
|
|
1104
|
+
p95TokenOverheadPercent: comparison.tokenOverheadPercent.p95,
|
|
1105
|
+
pairedOverheadP95Percent: comparison.pairedOverheadPercent.p95,
|
|
1106
|
+
distributionP95DeltaPercent: comparison.distributionDeltaPercent.p95,
|
|
1107
|
+
outlierCount,
|
|
1108
|
+
status: pairedStatus,
|
|
1109
|
+
pairedStatus,
|
|
1110
|
+
distributionStatus,
|
|
1111
|
+
combinedInterpretation,
|
|
1112
|
+
},
|
|
1113
|
+
}];
|
|
1114
|
+
}));
|
|
642
1115
|
return {
|
|
643
1116
|
schemaVersion: 1,
|
|
644
1117
|
benchmarkVersion: BENCHMARK_VERSION,
|
|
645
1118
|
runSetId: runs[0].runSetId,
|
|
646
1119
|
scenarioId: scenario.scenarioId,
|
|
647
1120
|
expectedProfile: scenario.expectedProfile,
|
|
648
|
-
modeAggregates: Object.fromEntries(BENCHMARK_MODES.map((mode) => [mode,
|
|
649
|
-
comparisons,
|
|
1121
|
+
modeAggregates: Object.fromEntries(BENCHMARK_MODES.map((mode) => [mode, modeAggregateRobust(byMode[mode], { includeContextUsage, includeQuality })])),
|
|
1122
|
+
comparisons: comparisonsWithTail,
|
|
650
1123
|
lightObjectives,
|
|
651
1124
|
sourcePolicy: "PROVIDER_REPORTED_OR_HOST_REPORTED_ONLY",
|
|
652
1125
|
claimsAllowed: Object.values(comparisons).some((comparison) => comparison?.claimAllowed === true),
|
|
@@ -654,6 +1127,7 @@ export function aggregateBenchmarkRuns({ scenario, runs } = {}) {
|
|
|
654
1127
|
...(includeContextUsage
|
|
655
1128
|
? { contextInflation: contextInflationForScenario(byMode.forgeloopBalanced, byMode.forgeloopAdaptive) }
|
|
656
1129
|
: {}),
|
|
1130
|
+
outlierAnalysis,
|
|
657
1131
|
};
|
|
658
1132
|
}
|
|
659
1133
|
|