@graphorin/evals 0.5.0 → 0.6.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/CHANGELOG.md +34 -0
- package/README.md +32 -6
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/loaders/dmr.js +1 -1
- package/dist/loaders/dmr.js.map +1 -1
- package/dist/loaders/from-traces.js +1 -1
- package/dist/loaders/from-traces.js.map +1 -1
- package/dist/loaders/iterable.js.map +1 -1
- package/dist/loaders/locomo.d.ts +1 -1
- package/dist/loaders/locomo.js +2 -2
- package/dist/loaders/locomo.js.map +1 -1
- package/dist/loaders/longmemeval.js +1 -1
- package/dist/loaders/longmemeval.js.map +1 -1
- package/dist/loaders/memory-eval.d.ts +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/regression.d.ts.map +1 -1
- package/dist/regression.js +29 -7
- package/dist/regression.js.map +1 -1
- package/dist/reporters/html.js.map +1 -1
- package/dist/reporters/markdown.js +1 -1
- package/dist/reporters/markdown.js.map +1 -1
- package/dist/reporters/terminal.js +1 -1
- package/dist/reporters/terminal.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +25 -1
- package/dist/runner.js.map +1 -1
- package/dist/scorers/code/exact-match.js.map +1 -1
- package/dist/scorers/code/json-path.js.map +1 -1
- package/dist/scorers/code/predicate.js.map +1 -1
- package/dist/scorers/code/regex.js.map +1 -1
- package/dist/scorers/llm/judge.d.ts +3 -3
- package/dist/scorers/llm/judge.js +4 -4
- package/dist/scorers/llm/judge.js.map +1 -1
- package/dist/scorers/trajectory/argument-validity.js.map +1 -1
- package/dist/scorers/trajectory/correct-tool-selected.js.map +1 -1
- package/dist/scorers/trajectory/final-state-correct.js.map +1 -1
- package/dist/scorers/trajectory/recovery-after-error.js.map +1 -1
- package/dist/scorers/trajectory/redundant-call-detection.js.map +1 -1
- package/dist/scorers/trajectory/types.d.ts +2 -2
- package/dist/scorers/trajectory/util.js +1 -1
- package/dist/scorers/trajectory/util.js.map +1 -1
- package/dist/stats.d.ts +115 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +180 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +20 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//#region src/stats.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared eval statistics (audit 2026-07-04, E8; closes the evals-05 /
|
|
4
|
+
* evals-08 gap): confidence intervals, pass^k, and paired significance
|
|
5
|
+
* for benchmark deltas. Everything here is pure and dependency-free so
|
|
6
|
+
* the runner, the regression gate, and the benchmarks can share one
|
|
7
|
+
* implementation instead of each inlining its own mean/stddev.
|
|
8
|
+
*
|
|
9
|
+
* Statistical choices, briefly:
|
|
10
|
+
* - **Wilson score interval** for pass-rate CIs. Eval suites are small
|
|
11
|
+
* (n in the tens) and pass rates hug 0/1, exactly where the normal
|
|
12
|
+
* approximation interval collapses; Wilson stays sane there.
|
|
13
|
+
* - **pass^k** ("pass-hat-k", the fraction of base cases that pass in
|
|
14
|
+
* ALL k repeat iterations) as the stability metric for `iterations>1`
|
|
15
|
+
* runs - a mean pass rate hides a case that flips every other run.
|
|
16
|
+
* - **McNemar's test** (continuity-corrected normal approximation) for
|
|
17
|
+
* "did the pass rate really change vs the baseline": the same cases
|
|
18
|
+
* ran in both reports, so a PAIRED test on the discordant cases has
|
|
19
|
+
* far more power than comparing two independent proportions - and it
|
|
20
|
+
* is sample-size aware, which the fixed-tolerance regression gate is
|
|
21
|
+
* not.
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Arithmetic mean; `0` for an empty list.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
declare function mean(values: ReadonlyArray<number>): number;
|
|
31
|
+
/**
|
|
32
|
+
* Sample standard deviation (n-1 denominator); `0` for n < 2.
|
|
33
|
+
*
|
|
34
|
+
* @stable
|
|
35
|
+
*/
|
|
36
|
+
declare function sampleStddev(values: ReadonlyArray<number>): number;
|
|
37
|
+
/**
|
|
38
|
+
* Wilson score interval for a binomial proportion.
|
|
39
|
+
*
|
|
40
|
+
* @param passed number of successes
|
|
41
|
+
* @param total number of trials
|
|
42
|
+
* @param z normal quantile (default 1.96 = 95% two-sided)
|
|
43
|
+
* @returns `{ lo, hi }` clamped to [0, 1]; `{ lo: 0, hi: 1 }` when `total` is 0
|
|
44
|
+
*
|
|
45
|
+
* @stable
|
|
46
|
+
*/
|
|
47
|
+
declare function wilsonInterval(passed: number, total: number, z?: number): {
|
|
48
|
+
readonly lo: number;
|
|
49
|
+
readonly hi: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Strip the `-iter-N` disambiguation suffix the runner appends under
|
|
53
|
+
* `iterations > 1` (EB-6), recovering the base case id.
|
|
54
|
+
*
|
|
55
|
+
* @stable
|
|
56
|
+
*/
|
|
57
|
+
declare function stripIterationSuffix(caseId: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* pass^k over per-iteration case outcomes: group by base case id and
|
|
60
|
+
* report the fraction of base cases whose EVERY iteration passed.
|
|
61
|
+
*
|
|
62
|
+
* @param outcomes one entry per executed case iteration
|
|
63
|
+
* @returns `k` = the largest group size observed, `baseCases` = number of
|
|
64
|
+
* distinct base cases, `value` = the pass^k fraction (0 when no cases)
|
|
65
|
+
*
|
|
66
|
+
* @stable
|
|
67
|
+
*/
|
|
68
|
+
declare function passHatK(outcomes: ReadonlyArray<{
|
|
69
|
+
readonly caseId: string;
|
|
70
|
+
readonly pass: boolean;
|
|
71
|
+
}>): {
|
|
72
|
+
readonly k: number;
|
|
73
|
+
readonly baseCases: number;
|
|
74
|
+
readonly value: number;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Result of {@link pairedPassSignificance}.
|
|
78
|
+
*
|
|
79
|
+
* @stable
|
|
80
|
+
*/
|
|
81
|
+
interface PairedSignificance {
|
|
82
|
+
/** Cases present in BOTH runs (the paired sample). */
|
|
83
|
+
readonly pairs: number;
|
|
84
|
+
/** baseline-pass -> current-fail count (the regressions). */
|
|
85
|
+
readonly regressed: number;
|
|
86
|
+
/** baseline-fail -> current-pass count (the improvements). */
|
|
87
|
+
readonly improved: number;
|
|
88
|
+
/**
|
|
89
|
+
* Two-sided p-value from McNemar's test (continuity-corrected normal
|
|
90
|
+
* approximation over the discordant pairs). `1` when there are no
|
|
91
|
+
* discordant pairs - identical outcomes are never "significant".
|
|
92
|
+
*/
|
|
93
|
+
readonly pValue: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* McNemar's paired test on per-case pass outcomes of two eval runs.
|
|
97
|
+
* Only cases present in both maps are compared (by base case id).
|
|
98
|
+
*
|
|
99
|
+
* @stable
|
|
100
|
+
*/
|
|
101
|
+
declare function pairedPassSignificance(current: ReadonlyMap<string, boolean>, baseline: ReadonlyMap<string, boolean>): PairedSignificance;
|
|
102
|
+
/**
|
|
103
|
+
* Collapse per-iteration outcomes to per-base-case pass (a base case
|
|
104
|
+
* passes when EVERY iteration passed) - the paired unit for
|
|
105
|
+
* {@link pairedPassSignificance}.
|
|
106
|
+
*
|
|
107
|
+
* @stable
|
|
108
|
+
*/
|
|
109
|
+
declare function passByBaseCase(outcomes: ReadonlyArray<{
|
|
110
|
+
readonly caseId: string;
|
|
111
|
+
readonly pass: boolean;
|
|
112
|
+
}>): ReadonlyMap<string, boolean>;
|
|
113
|
+
//#endregion
|
|
114
|
+
export { PairedSignificance, mean, pairedPassSignificance, passByBaseCase, passHatK, sampleStddev, stripIterationSuffix, wilsonInterval };
|
|
115
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","names":[],"sources":["../src/stats.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAYA;AAkBA;AAyBA;AAcA;AA0BA;AAqBA;;;;;AA6BA;;;;;;;;;;;;;;;;iBAjJgB,IAAA,SAAa;;;;;;iBAYb,YAAA,SAAqB;;;;;;;;;;;iBAkBrB,cAAA;;;;;;;;;;iBAyBA,oBAAA;;;;;;;;;;;iBAcA,QAAA,WACJ;;;;;;;;;;;;;UAyBK,kBAAA;;;;;;;;;;;;;;;;;;;;iBAqBD,sBAAA,UACL,wCACC,+BACT;;;;;;;;iBA0Ba,cAAA,WACJ;;;KACT"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
//#region src/stats.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared eval statistics (audit 2026-07-04, E8; closes the evals-05 /
|
|
4
|
+
* evals-08 gap): confidence intervals, pass^k, and paired significance
|
|
5
|
+
* for benchmark deltas. Everything here is pure and dependency-free so
|
|
6
|
+
* the runner, the regression gate, and the benchmarks can share one
|
|
7
|
+
* implementation instead of each inlining its own mean/stddev.
|
|
8
|
+
*
|
|
9
|
+
* Statistical choices, briefly:
|
|
10
|
+
* - **Wilson score interval** for pass-rate CIs. Eval suites are small
|
|
11
|
+
* (n in the tens) and pass rates hug 0/1, exactly where the normal
|
|
12
|
+
* approximation interval collapses; Wilson stays sane there.
|
|
13
|
+
* - **pass^k** ("pass-hat-k", the fraction of base cases that pass in
|
|
14
|
+
* ALL k repeat iterations) as the stability metric for `iterations>1`
|
|
15
|
+
* runs - a mean pass rate hides a case that flips every other run.
|
|
16
|
+
* - **McNemar's test** (continuity-corrected normal approximation) for
|
|
17
|
+
* "did the pass rate really change vs the baseline": the same cases
|
|
18
|
+
* ran in both reports, so a PAIRED test on the discordant cases has
|
|
19
|
+
* far more power than comparing two independent proportions - and it
|
|
20
|
+
* is sample-size aware, which the fixed-tolerance regression gate is
|
|
21
|
+
* not.
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Arithmetic mean; `0` for an empty list.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
function mean(values) {
|
|
31
|
+
if (values.length === 0) return 0;
|
|
32
|
+
let sum = 0;
|
|
33
|
+
for (const v of values) sum += v;
|
|
34
|
+
return sum / values.length;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sample standard deviation (n-1 denominator); `0` for n < 2.
|
|
38
|
+
*
|
|
39
|
+
* @stable
|
|
40
|
+
*/
|
|
41
|
+
function sampleStddev(values) {
|
|
42
|
+
if (values.length < 2) return 0;
|
|
43
|
+
const m = mean(values);
|
|
44
|
+
let acc = 0;
|
|
45
|
+
for (const v of values) acc += (v - m) * (v - m);
|
|
46
|
+
return Math.sqrt(acc / (values.length - 1));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wilson score interval for a binomial proportion.
|
|
50
|
+
*
|
|
51
|
+
* @param passed number of successes
|
|
52
|
+
* @param total number of trials
|
|
53
|
+
* @param z normal quantile (default 1.96 = 95% two-sided)
|
|
54
|
+
* @returns `{ lo, hi }` clamped to [0, 1]; `{ lo: 0, hi: 1 }` when `total` is 0
|
|
55
|
+
*
|
|
56
|
+
* @stable
|
|
57
|
+
*/
|
|
58
|
+
function wilsonInterval(passed, total, z = 1.96) {
|
|
59
|
+
if (total <= 0) return {
|
|
60
|
+
lo: 0,
|
|
61
|
+
hi: 1
|
|
62
|
+
};
|
|
63
|
+
const p = passed / total;
|
|
64
|
+
const z2 = z * z;
|
|
65
|
+
const denom = 1 + z2 / total;
|
|
66
|
+
const centre = p + z2 / (2 * total);
|
|
67
|
+
const margin = z * Math.sqrt(p * (1 - p) / total + z2 / (4 * total * total));
|
|
68
|
+
return {
|
|
69
|
+
lo: Math.max(0, (centre - margin) / denom),
|
|
70
|
+
hi: Math.min(1, (centre + margin) / denom)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const ITERATION_SUFFIX_RE = /-iter-\d+$/;
|
|
74
|
+
/**
|
|
75
|
+
* Strip the `-iter-N` disambiguation suffix the runner appends under
|
|
76
|
+
* `iterations > 1` (EB-6), recovering the base case id.
|
|
77
|
+
*
|
|
78
|
+
* @stable
|
|
79
|
+
*/
|
|
80
|
+
function stripIterationSuffix(caseId) {
|
|
81
|
+
return caseId.replace(ITERATION_SUFFIX_RE, "");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* pass^k over per-iteration case outcomes: group by base case id and
|
|
85
|
+
* report the fraction of base cases whose EVERY iteration passed.
|
|
86
|
+
*
|
|
87
|
+
* @param outcomes one entry per executed case iteration
|
|
88
|
+
* @returns `k` = the largest group size observed, `baseCases` = number of
|
|
89
|
+
* distinct base cases, `value` = the pass^k fraction (0 when no cases)
|
|
90
|
+
*
|
|
91
|
+
* @stable
|
|
92
|
+
*/
|
|
93
|
+
function passHatK(outcomes) {
|
|
94
|
+
const groups = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const o of outcomes) {
|
|
96
|
+
const base = stripIterationSuffix(o.caseId);
|
|
97
|
+
const g = groups.get(base) ?? {
|
|
98
|
+
total: 0,
|
|
99
|
+
passed: 0
|
|
100
|
+
};
|
|
101
|
+
g.total += 1;
|
|
102
|
+
if (o.pass) g.passed += 1;
|
|
103
|
+
groups.set(base, g);
|
|
104
|
+
}
|
|
105
|
+
if (groups.size === 0) return {
|
|
106
|
+
k: 0,
|
|
107
|
+
baseCases: 0,
|
|
108
|
+
value: 0
|
|
109
|
+
};
|
|
110
|
+
let k = 0;
|
|
111
|
+
let allPass = 0;
|
|
112
|
+
for (const g of groups.values()) {
|
|
113
|
+
if (g.total > k) k = g.total;
|
|
114
|
+
if (g.passed === g.total) allPass += 1;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
k,
|
|
118
|
+
baseCases: groups.size,
|
|
119
|
+
value: allPass / groups.size
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* McNemar's paired test on per-case pass outcomes of two eval runs.
|
|
124
|
+
* Only cases present in both maps are compared (by base case id).
|
|
125
|
+
*
|
|
126
|
+
* @stable
|
|
127
|
+
*/
|
|
128
|
+
function pairedPassSignificance(current, baseline) {
|
|
129
|
+
let pairs = 0;
|
|
130
|
+
let regressed = 0;
|
|
131
|
+
let improved = 0;
|
|
132
|
+
for (const [caseId, basePass] of baseline) {
|
|
133
|
+
const curPass = current.get(caseId);
|
|
134
|
+
if (curPass === void 0) continue;
|
|
135
|
+
pairs += 1;
|
|
136
|
+
if (basePass && !curPass) regressed += 1;
|
|
137
|
+
else if (!basePass && curPass) improved += 1;
|
|
138
|
+
}
|
|
139
|
+
const discordant = regressed + improved;
|
|
140
|
+
if (discordant === 0) return {
|
|
141
|
+
pairs,
|
|
142
|
+
regressed,
|
|
143
|
+
improved,
|
|
144
|
+
pValue: 1
|
|
145
|
+
};
|
|
146
|
+
const z = (Math.abs(regressed - improved) - 1) / Math.sqrt(discordant);
|
|
147
|
+
const pValue = z <= 0 ? 1 : Math.min(1, 2 * (1 - normalCdf(z)));
|
|
148
|
+
return {
|
|
149
|
+
pairs,
|
|
150
|
+
regressed,
|
|
151
|
+
improved,
|
|
152
|
+
pValue
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Collapse per-iteration outcomes to per-base-case pass (a base case
|
|
157
|
+
* passes when EVERY iteration passed) - the paired unit for
|
|
158
|
+
* {@link pairedPassSignificance}.
|
|
159
|
+
*
|
|
160
|
+
* @stable
|
|
161
|
+
*/
|
|
162
|
+
function passByBaseCase(outcomes) {
|
|
163
|
+
const out = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const o of outcomes) {
|
|
165
|
+
const base = stripIterationSuffix(o.caseId);
|
|
166
|
+
const prev = out.get(base);
|
|
167
|
+
out.set(base, prev === void 0 ? o.pass : prev && o.pass);
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
/** Standard normal CDF via the Abramowitz-Stegun 7.1.26 erf approximation. */
|
|
172
|
+
function normalCdf(x) {
|
|
173
|
+
const t = 1 / (1 + .3275911 * (Math.abs(x) / Math.SQRT2));
|
|
174
|
+
const erf = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - .284496736) * t + .254829592) * t * Math.exp(-(x * x) / 2);
|
|
175
|
+
return x >= 0 ? .5 * (1 + erf) : .5 * (1 - erf);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { mean, pairedPassSignificance, passByBaseCase, passHatK, sampleStddev, stripIterationSuffix, wilsonInterval };
|
|
180
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","names":[],"sources":["../src/stats.ts"],"sourcesContent":["/**\n * Shared eval statistics (audit 2026-07-04, E8; closes the evals-05 /\n * evals-08 gap): confidence intervals, pass^k, and paired significance\n * for benchmark deltas. Everything here is pure and dependency-free so\n * the runner, the regression gate, and the benchmarks can share one\n * implementation instead of each inlining its own mean/stddev.\n *\n * Statistical choices, briefly:\n * - **Wilson score interval** for pass-rate CIs. Eval suites are small\n * (n in the tens) and pass rates hug 0/1, exactly where the normal\n * approximation interval collapses; Wilson stays sane there.\n * - **pass^k** (\"pass-hat-k\", the fraction of base cases that pass in\n * ALL k repeat iterations) as the stability metric for `iterations>1`\n * runs - a mean pass rate hides a case that flips every other run.\n * - **McNemar's test** (continuity-corrected normal approximation) for\n * \"did the pass rate really change vs the baseline\": the same cases\n * ran in both reports, so a PAIRED test on the discordant cases has\n * far more power than comparing two independent proportions - and it\n * is sample-size aware, which the fixed-tolerance regression gate is\n * not.\n *\n * @packageDocumentation\n */\n\n/**\n * Arithmetic mean; `0` for an empty list.\n *\n * @stable\n */\nexport function mean(values: ReadonlyArray<number>): number {\n if (values.length === 0) return 0;\n let sum = 0;\n for (const v of values) sum += v;\n return sum / values.length;\n}\n\n/**\n * Sample standard deviation (n-1 denominator); `0` for n < 2.\n *\n * @stable\n */\nexport function sampleStddev(values: ReadonlyArray<number>): number {\n if (values.length < 2) return 0;\n const m = mean(values);\n let acc = 0;\n for (const v of values) acc += (v - m) * (v - m);\n return Math.sqrt(acc / (values.length - 1));\n}\n\n/**\n * Wilson score interval for a binomial proportion.\n *\n * @param passed number of successes\n * @param total number of trials\n * @param z normal quantile (default 1.96 = 95% two-sided)\n * @returns `{ lo, hi }` clamped to [0, 1]; `{ lo: 0, hi: 1 }` when `total` is 0\n *\n * @stable\n */\nexport function wilsonInterval(\n passed: number,\n total: number,\n z = 1.96,\n): { readonly lo: number; readonly hi: number } {\n if (total <= 0) return { lo: 0, hi: 1 };\n const p = passed / total;\n const z2 = z * z;\n const denom = 1 + z2 / total;\n const centre = p + z2 / (2 * total);\n const margin = z * Math.sqrt((p * (1 - p)) / total + z2 / (4 * total * total));\n return {\n lo: Math.max(0, (centre - margin) / denom),\n hi: Math.min(1, (centre + margin) / denom),\n };\n}\n\nconst ITERATION_SUFFIX_RE = /-iter-\\d+$/;\n\n/**\n * Strip the `-iter-N` disambiguation suffix the runner appends under\n * `iterations > 1` (EB-6), recovering the base case id.\n *\n * @stable\n */\nexport function stripIterationSuffix(caseId: string): string {\n return caseId.replace(ITERATION_SUFFIX_RE, '');\n}\n\n/**\n * pass^k over per-iteration case outcomes: group by base case id and\n * report the fraction of base cases whose EVERY iteration passed.\n *\n * @param outcomes one entry per executed case iteration\n * @returns `k` = the largest group size observed, `baseCases` = number of\n * distinct base cases, `value` = the pass^k fraction (0 when no cases)\n *\n * @stable\n */\nexport function passHatK(\n outcomes: ReadonlyArray<{ readonly caseId: string; readonly pass: boolean }>,\n): { readonly k: number; readonly baseCases: number; readonly value: number } {\n const groups = new Map<string, { total: number; passed: number }>();\n for (const o of outcomes) {\n const base = stripIterationSuffix(o.caseId);\n const g = groups.get(base) ?? { total: 0, passed: 0 };\n g.total += 1;\n if (o.pass) g.passed += 1;\n groups.set(base, g);\n }\n if (groups.size === 0) return { k: 0, baseCases: 0, value: 0 };\n let k = 0;\n let allPass = 0;\n for (const g of groups.values()) {\n if (g.total > k) k = g.total;\n if (g.passed === g.total) allPass += 1;\n }\n return { k, baseCases: groups.size, value: allPass / groups.size };\n}\n\n/**\n * Result of {@link pairedPassSignificance}.\n *\n * @stable\n */\nexport interface PairedSignificance {\n /** Cases present in BOTH runs (the paired sample). */\n readonly pairs: number;\n /** baseline-pass -> current-fail count (the regressions). */\n readonly regressed: number;\n /** baseline-fail -> current-pass count (the improvements). */\n readonly improved: number;\n /**\n * Two-sided p-value from McNemar's test (continuity-corrected normal\n * approximation over the discordant pairs). `1` when there are no\n * discordant pairs - identical outcomes are never \"significant\".\n */\n readonly pValue: number;\n}\n\n/**\n * McNemar's paired test on per-case pass outcomes of two eval runs.\n * Only cases present in both maps are compared (by base case id).\n *\n * @stable\n */\nexport function pairedPassSignificance(\n current: ReadonlyMap<string, boolean>,\n baseline: ReadonlyMap<string, boolean>,\n): PairedSignificance {\n let pairs = 0;\n let regressed = 0;\n let improved = 0;\n for (const [caseId, basePass] of baseline) {\n const curPass = current.get(caseId);\n if (curPass === undefined) continue;\n pairs += 1;\n if (basePass && !curPass) regressed += 1;\n else if (!basePass && curPass) improved += 1;\n }\n const discordant = regressed + improved;\n if (discordant === 0) return { pairs, regressed, improved, pValue: 1 };\n // Continuity-corrected normal approximation to the binomial(discordant, 0.5).\n const z = (Math.abs(regressed - improved) - 1) / Math.sqrt(discordant);\n const pValue = z <= 0 ? 1 : Math.min(1, 2 * (1 - normalCdf(z)));\n return { pairs, regressed, improved, pValue };\n}\n\n/**\n * Collapse per-iteration outcomes to per-base-case pass (a base case\n * passes when EVERY iteration passed) - the paired unit for\n * {@link pairedPassSignificance}.\n *\n * @stable\n */\nexport function passByBaseCase(\n outcomes: ReadonlyArray<{ readonly caseId: string; readonly pass: boolean }>,\n): ReadonlyMap<string, boolean> {\n const out = new Map<string, boolean>();\n for (const o of outcomes) {\n const base = stripIterationSuffix(o.caseId);\n const prev = out.get(base);\n out.set(base, prev === undefined ? o.pass : prev && o.pass);\n }\n return out;\n}\n\n/** Standard normal CDF via the Abramowitz-Stegun 7.1.26 erf approximation. */\nfunction normalCdf(x: number): number {\n const t = 1 / (1 + 0.3275911 * (Math.abs(x) / Math.SQRT2));\n const erf =\n 1 -\n ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) *\n t *\n Math.exp(-(x * x) / 2);\n return x >= 0 ? 0.5 * (1 + erf) : 0.5 * (1 - erf);\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Case as Case$1, Dataset as Dataset$1, EvalCaseResult, EvalReport, EvalR
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Agent shape consumed by the runner. Anything with a `run(input)`
|
|
7
|
-
* method satisfies the contract
|
|
7
|
+
* method satisfies the contract - the framework's own `Agent` type
|
|
8
8
|
* matches by structural typing.
|
|
9
9
|
*
|
|
10
10
|
* @stable
|
|
@@ -38,7 +38,7 @@ interface RunOptions<I, O> {
|
|
|
38
38
|
/**
|
|
39
39
|
* How to handle `signal` abort. Default (`false`): stop dispatching and
|
|
40
40
|
* resolve with a **partial** {@link EvalReport} (`aborted: true`) covering
|
|
41
|
-
* the cases that finished
|
|
41
|
+
* the cases that finished - a long judged run shouldn't lose all completed
|
|
42
42
|
* work to a Ctrl+C. Set `true` to throw the abort reason instead.
|
|
43
43
|
*/
|
|
44
44
|
readonly throwOnAbort?: boolean;
|
|
@@ -74,6 +74,18 @@ interface RegressionOptions {
|
|
|
74
74
|
* absolute duration gate; leave unset to ignore duration entirely.
|
|
75
75
|
*/
|
|
76
76
|
readonly maxAvgDurationIncreaseMs?: number;
|
|
77
|
+
/**
|
|
78
|
+
* E8 (evals-05/08): when `true`, a `pass-rate-drop` finding is only kept if
|
|
79
|
+
* McNemar's paired test over the shared cases rejects "no real change" at
|
|
80
|
+
* {@link significanceAlpha} - a fixed percentage tolerance is blind to
|
|
81
|
+
* sample size (a 5pp drop is one case in a 20-case suite). Off by default
|
|
82
|
+
* so existing gates keep their exact behavior; the computed `pValue` is
|
|
83
|
+
* attached to the finding either way whenever both reports carry per-case
|
|
84
|
+
* results.
|
|
85
|
+
*/
|
|
86
|
+
readonly requireSignificance?: boolean;
|
|
87
|
+
/** Significance level for {@link requireSignificance}. Default `0.05`. */
|
|
88
|
+
readonly significanceAlpha?: number;
|
|
77
89
|
}
|
|
78
90
|
/**
|
|
79
91
|
* Result of {@link detectRegressions}.
|
|
@@ -92,6 +104,12 @@ interface RegressionFinding {
|
|
|
92
104
|
readonly scorer?: string;
|
|
93
105
|
readonly message: string;
|
|
94
106
|
readonly delta: number;
|
|
107
|
+
/**
|
|
108
|
+
* Two-sided McNemar p-value over the cases shared by both reports
|
|
109
|
+
* (E8; only on `pass-rate-drop` findings, and only when both reports
|
|
110
|
+
* carry per-case results to pair on).
|
|
111
|
+
*/
|
|
112
|
+
readonly pValue?: number;
|
|
95
113
|
}
|
|
96
114
|
//#endregion
|
|
97
115
|
export { AgentLike, type Case$1 as Case, type Dataset$1 as Dataset, type EvalCaseResult, type EvalReport$1 as EvalReport, ProgressEvent, RegressionFinding, RegressionOptions, RegressionReport, type RunEvalOptions, RunOptions, type ScoreResult$1 as ScoreResult, type Scorer$1 as Scorer };
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAiD4C,UAnB3B,SAmB2B,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EAAV,GAAA,CAAA,KAAA,EAlBrB,CAkBqB,EAAA,GAKd,CALc,EAAA;IAAd,MAAA,CAAA,EAlBa,WAkBb;EAKA,CAAA,CAAA,EAvB6B,OAuB7B,CAvBqC,CAuBrC,CAAA;;;AAgBpB;AAaA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAiD4C,UAnB3B,SAmB2B,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EAAV,GAAA,CAAA,KAAA,EAlBrB,CAkBqB,EAAA,GAKd,CALc,EAAA;IAAd,MAAA,CAAA,EAlBa,WAkBb;EAKA,CAAA,CAAA,EAvB6B,OAuB7B,CAvBqC,CAuBrC,CAAA;;;AAgBpB;AAaA;AAgCA;;AAEqB,UA9EJ,UA8EI,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EACU,SAAA,KAAA,EA9Eb,SA8Ea,CA9EH,CA8EG,EA9EA,CA8EA,CAAA;EAAG,SAAA,OAAA,EAAA;IAAd,SAAA,KAAA,EA5EA,aA4EA,CAAA;MACY,SAAA,EAAA,CAAA,EAAA,MAAA;MAAG,SAAA,KAAA,EA3Eb,CA2Ea;MAAd,SAAA,QAAA,CAAA,EA1EK,CA0EL;MAAU,SAAA,QAAA,CAAA,EAzEL,QAyEK,CAzEI,MAyEJ,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA;IAId,CAAA,CAAA;;oBA1EG,cAAc,OAAO,GAAG;;;;;oBAKxB;;;;;;;;;;;;gCAYY;;;UAIf,aAAA;;;;;;;;;;;;UAaA,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAgCA;;qBAEI,cAAc;oBACf,WAAW,GAAG;qBACb,WAAW,GAAG;;;UAIlB,iBAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphorin/evals",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Eval framework for the Graphorin framework. Ships scorer libraries (`code/` exact-match, regex, JSON path, predicates; `llm/` LLM-judge; `prebuilt/` toxicity / factuality / helpfulness), dataset loaders (JSONL, CSV, from-traces, generic iterable), reporters (terminal, markdown, JSON, JUnit XML), a parallel runner with `concurrency` config, and regression detection that compares the current run against a stored baseline. Builds on the eval primitives shipped from `@graphorin/observability` (RB-17 / DEC-152
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Eval framework for the Graphorin framework. Ships scorer libraries (`code/` exact-match, regex, JSON path, predicates; `llm/` LLM-judge; `prebuilt/` toxicity / factuality / helpfulness), dataset loaders (JSONL, CSV, from-traces, generic iterable), reporters (terminal, markdown, JSON, JUnit XML), a parallel runner with `concurrency` config, and regression detection that compares the current run against a stored baseline. Builds on the eval primitives shipped from `@graphorin/observability` (RB-17 / DEC-152 - full orchestrator lives here, post-MVP, decoupled from the core observability bundle so consumers do not pay the dataset / reporter cost when only the inline runner is needed). Created and maintained by Oleksiy Stepurenko.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Oleksiy Stepurenko",
|
|
7
7
|
"homepage": "https://github.com/o-stepper/graphorin/tree/main/packages/evals",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"LICENSE"
|
|
65
65
|
],
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@graphorin/core": "0.
|
|
68
|
-
"@graphorin/observability": "0.
|
|
67
|
+
"@graphorin/core": "0.6.1",
|
|
68
|
+
"@graphorin/observability": "0.6.1"
|
|
69
69
|
},
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public",
|