@namzu/sdk 62.12.0 → 62.13.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/connector/index.d.ts +1 -1
  3. package/dist/connector/index.d.ts.map +1 -1
  4. package/dist/connector/index.js +1 -1
  5. package/dist/connector/index.js.map +1 -1
  6. package/dist/connector/mcp/client.d.ts +96 -0
  7. package/dist/connector/mcp/client.d.ts.map +1 -1
  8. package/dist/connector/mcp/client.js +488 -13
  9. package/dist/connector/mcp/client.js.map +1 -1
  10. package/dist/connector/mcp/index.d.ts +1 -0
  11. package/dist/connector/mcp/index.d.ts.map +1 -1
  12. package/dist/connector/mcp/index.js +1 -0
  13. package/dist/connector/mcp/index.js.map +1 -1
  14. package/dist/connector/mcp/tasks.d.ts +40 -0
  15. package/dist/connector/mcp/tasks.d.ts.map +1 -0
  16. package/dist/connector/mcp/tasks.js +55 -0
  17. package/dist/connector/mcp/tasks.js.map +1 -0
  18. package/dist/constants/mcp/index.d.ts +1 -1
  19. package/dist/constants/mcp/index.d.ts.map +1 -1
  20. package/dist/constants/mcp/index.js +5 -0
  21. package/dist/constants/mcp/index.js.map +1 -1
  22. package/dist/eval/bootstrap.d.ts +122 -0
  23. package/dist/eval/bootstrap.d.ts.map +1 -0
  24. package/dist/eval/bootstrap.js +227 -0
  25. package/dist/eval/bootstrap.js.map +1 -0
  26. package/dist/eval/compare.d.ts +109 -0
  27. package/dist/eval/compare.d.ts.map +1 -0
  28. package/dist/eval/compare.js +324 -0
  29. package/dist/eval/compare.js.map +1 -0
  30. package/dist/eval/disclosure.d.ts +70 -0
  31. package/dist/eval/disclosure.d.ts.map +1 -0
  32. package/dist/eval/disclosure.js +64 -0
  33. package/dist/eval/disclosure.js.map +1 -0
  34. package/dist/eval/experiment.d.ts +23 -0
  35. package/dist/eval/experiment.d.ts.map +1 -1
  36. package/dist/eval/experiment.js +87 -15
  37. package/dist/eval/experiment.js.map +1 -1
  38. package/dist/eval/from-turn.d.ts.map +1 -1
  39. package/dist/eval/from-turn.js +5 -0
  40. package/dist/eval/from-turn.js.map +1 -1
  41. package/dist/eval/index.d.ts +13 -2
  42. package/dist/eval/index.d.ts.map +1 -1
  43. package/dist/eval/index.js +6 -1
  44. package/dist/eval/index.js.map +1 -1
  45. package/dist/eval/report-stats.d.ts +77 -0
  46. package/dist/eval/report-stats.d.ts.map +1 -0
  47. package/dist/eval/report-stats.js +184 -0
  48. package/dist/eval/report-stats.js.map +1 -0
  49. package/dist/eval/run-options.d.ts +30 -0
  50. package/dist/eval/run-options.d.ts.map +1 -0
  51. package/dist/eval/run-options.js +20 -0
  52. package/dist/eval/run-options.js.map +1 -0
  53. package/dist/eval/run-summary.d.ts +36 -0
  54. package/dist/eval/run-summary.d.ts.map +1 -0
  55. package/dist/eval/run-summary.js +57 -0
  56. package/dist/eval/run-summary.js.map +1 -0
  57. package/dist/eval/scorers.d.ts +32 -0
  58. package/dist/eval/scorers.d.ts.map +1 -1
  59. package/dist/eval/scorers.js +77 -0
  60. package/dist/eval/scorers.js.map +1 -1
  61. package/dist/eval/types.d.ts +74 -0
  62. package/dist/eval/types.d.ts.map +1 -1
  63. package/dist/manager/session/turn-recorder.d.ts +3 -0
  64. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  65. package/dist/manager/session/turn-recorder.js +18 -2
  66. package/dist/manager/session/turn-recorder.js.map +1 -1
  67. package/dist/public-runtime.d.ts +5 -2
  68. package/dist/public-runtime.d.ts.map +1 -1
  69. package/dist/public-runtime.js +7 -2
  70. package/dist/public-runtime.js.map +1 -1
  71. package/dist/public-types.d.ts +2 -0
  72. package/dist/public-types.d.ts.map +1 -1
  73. package/dist/registry/command/kernel-commands.d.ts +3 -0
  74. package/dist/registry/command/kernel-commands.d.ts.map +1 -1
  75. package/dist/registry/command/kernel-commands.js +76 -5
  76. package/dist/registry/command/kernel-commands.js.map +1 -1
  77. package/dist/runtime/query/index.d.ts.map +1 -1
  78. package/dist/runtime/query/index.js +15 -0
  79. package/dist/runtime/query/index.js.map +1 -1
  80. package/dist/runtime/query/iteration/index.d.ts +18 -1
  81. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  82. package/dist/runtime/query/iteration/index.js +80 -1
  83. package/dist/runtime/query/iteration/index.js.map +1 -1
  84. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  85. package/dist/runtime/query/iteration/phases/tool-review.js +18 -1
  86. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  87. package/dist/runtime/query/turn/answer-claims.d.ts +52 -0
  88. package/dist/runtime/query/turn/answer-claims.d.ts.map +1 -0
  89. package/dist/runtime/query/turn/answer-claims.js +348 -0
  90. package/dist/runtime/query/turn/answer-claims.js.map +1 -0
  91. package/dist/runtime/query/turn/outcome-ledger.d.ts +7 -0
  92. package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -1
  93. package/dist/runtime/query/turn/outcome-ledger.js +13 -0
  94. package/dist/runtime/query/turn/outcome-ledger.js.map +1 -1
  95. package/dist/store/goal/index.d.ts +3 -1
  96. package/dist/store/goal/index.d.ts.map +1 -1
  97. package/dist/store/goal/index.js +6 -0
  98. package/dist/store/goal/index.js.map +1 -1
  99. package/dist/telemetry/runtime-accessors.d.ts +14 -0
  100. package/dist/telemetry/runtime-accessors.d.ts.map +1 -1
  101. package/dist/telemetry/runtime-accessors.js +32 -0
  102. package/dist/telemetry/runtime-accessors.js.map +1 -1
  103. package/dist/tools/goal/confirmation.d.ts +154 -0
  104. package/dist/tools/goal/confirmation.d.ts.map +1 -0
  105. package/dist/tools/goal/confirmation.js +290 -0
  106. package/dist/tools/goal/confirmation.js.map +1 -0
  107. package/dist/tools/goal/index.d.ts +4 -1
  108. package/dist/tools/goal/index.d.ts.map +1 -1
  109. package/dist/tools/goal/index.js +27 -2
  110. package/dist/tools/goal/index.js.map +1 -1
  111. package/dist/tools/hand-back/index.d.ts +48 -0
  112. package/dist/tools/hand-back/index.d.ts.map +1 -0
  113. package/dist/tools/hand-back/index.js +129 -0
  114. package/dist/tools/hand-back/index.js.map +1 -0
  115. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
  116. package/dist/tools/schedules/schedule-tool.js +15 -1
  117. package/dist/tools/schedules/schedule-tool.js.map +1 -1
  118. package/dist/tools/schedules/types.d.ts +10 -0
  119. package/dist/tools/schedules/types.d.ts.map +1 -1
  120. package/dist/types/connector/mcp.d.ts +18 -0
  121. package/dist/types/connector/mcp.d.ts.map +1 -1
  122. package/dist/types/goal/index.d.ts +12 -0
  123. package/dist/types/goal/index.d.ts.map +1 -1
  124. package/dist/types/session/config.d.ts +8 -0
  125. package/dist/types/session/config.d.ts.map +1 -1
  126. package/dist/types/session/hand-back.d.ts +29 -0
  127. package/dist/types/session/hand-back.d.ts.map +1 -0
  128. package/dist/types/session/hand-back.js +11 -0
  129. package/dist/types/session/hand-back.js.map +1 -0
  130. package/dist/types/session/outcome.d.ts +37 -0
  131. package/dist/types/session/outcome.d.ts.map +1 -1
  132. package/dist/types/session/outcome.js.map +1 -1
  133. package/dist/types/session/turn.d.ts +19 -1
  134. package/dist/types/session/turn.d.ts.map +1 -1
  135. package/dist/types/session/turn.js.map +1 -1
  136. package/package.json +1 -1
  137. package/src/connector/index.ts +1 -0
  138. package/src/connector/mcp/client.ts +531 -14
  139. package/src/connector/mcp/index.ts +1 -0
  140. package/src/connector/mcp/tasks.ts +81 -0
  141. package/src/constants/mcp/index.ts +6 -1
  142. package/src/eval/bootstrap.ts +314 -0
  143. package/src/eval/compare.ts +473 -0
  144. package/src/eval/disclosure.ts +136 -0
  145. package/src/eval/experiment.ts +127 -15
  146. package/src/eval/from-turn.ts +5 -0
  147. package/src/eval/index.ts +51 -0
  148. package/src/eval/report-stats.ts +257 -0
  149. package/src/eval/run-options.ts +49 -0
  150. package/src/eval/run-summary.ts +104 -0
  151. package/src/eval/scorers.ts +83 -0
  152. package/src/eval/types.ts +76 -0
  153. package/src/manager/session/turn-recorder.ts +23 -2
  154. package/src/public-runtime.ts +53 -0
  155. package/src/public-types.ts +9 -0
  156. package/src/registry/command/kernel-commands.ts +94 -6
  157. package/src/runtime/query/index.ts +18 -0
  158. package/src/runtime/query/iteration/index.ts +85 -1
  159. package/src/runtime/query/iteration/phases/tool-review.ts +24 -1
  160. package/src/runtime/query/turn/answer-claims.ts +413 -0
  161. package/src/runtime/query/turn/outcome-ledger.ts +12 -0
  162. package/src/store/goal/index.ts +11 -0
  163. package/src/telemetry/runtime-accessors.ts +29 -0
  164. package/src/tools/goal/confirmation.ts +375 -0
  165. package/src/tools/goal/index.ts +37 -1
  166. package/src/tools/hand-back/index.ts +150 -0
  167. package/src/tools/schedules/schedule-tool.ts +19 -1
  168. package/src/tools/schedules/types.ts +10 -0
  169. package/src/types/connector/mcp.ts +18 -0
  170. package/src/types/goal/index.ts +13 -0
  171. package/src/types/session/config.ts +8 -0
  172. package/src/types/session/hand-back.ts +31 -0
  173. package/src/types/session/outcome.ts +42 -0
  174. package/src/types/session/turn.ts +19 -1
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Intervals for an eval, by resampling cases.
3
+ *
4
+ * A pass rate printed alone invites conclusions the data cannot carry: on 80
5
+ * tasks a difference of -1.25 points came with a 95% interval from -10.0 to
6
+ * +7.5, which says almost nothing. The interval here is a percentile
7
+ * bootstrap over CASES. A case is the unit that was sampled from the world, so
8
+ * a case is what gets resampled, and all of a case's repeats travel with it:
9
+ * repeats of one case are not independent evidence about the suite, and
10
+ * treating them as such would narrow the interval for free.
11
+ *
12
+ * Nothing in here reads the clock or an unseeded random source. The generator
13
+ * is a small deterministic one fed by a seed that the report records, so the
14
+ * same cases and the same seed give the same interval.
15
+ */
16
+ /** Fewest cases an interval is computed for. Below it the report says why. */
17
+ export const MIN_CASES_FOR_INTERVAL = 5;
18
+ /** Resamples drawn per interval. */
19
+ export const BOOTSTRAP_RESAMPLES = 2000;
20
+ /** The seed used when none is given. A fixed number, so a report is reproducible. */
21
+ export const DEFAULT_EVAL_SEED = 1;
22
+ /** Coverage of every interval in this module. */
23
+ export const INTERVAL_LEVEL = 0.95;
24
+ /**
25
+ * A bad `--repeat` or `--seed`. A RangeError, so existing handlers still see
26
+ * one, and marked with a code so a CLI can tell it from a RangeError a suite's
27
+ * own code threw (and from one thrown by another copy of this class).
28
+ */
29
+ export class EvalUsageError extends RangeError {
30
+ code = 'EVAL_USAGE';
31
+ }
32
+ /** True for an {@link EvalUsageError}, even one built by another copy of the SDK. */
33
+ export function isEvalUsageError(err) {
34
+ return err instanceof RangeError && err.code === 'EVAL_USAGE';
35
+ }
36
+ /**
37
+ * Validate a seed: an integer that fits in 32 bits, unsigned.
38
+ * Thrown as an EvalUsageError so a CLI can turn it into a usage error.
39
+ */
40
+ export function validateSeed(value) {
41
+ if (value === undefined)
42
+ return DEFAULT_EVAL_SEED;
43
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
44
+ throw new EvalUsageError(`seed must be an integer from 0 to 4294967295, or omitted; received ${String(value)}`);
45
+ }
46
+ return value;
47
+ }
48
+ /**
49
+ * mulberry32: a 32-bit generator with one word of state. Not for secrets;
50
+ * its job is to be the same on every machine for a given seed.
51
+ */
52
+ export function createSeededRandom(seed) {
53
+ let state = validateSeed(seed) >>> 0;
54
+ return () => {
55
+ state = (state + 0x6d2b79f5) >>> 0;
56
+ let t = state;
57
+ t = Math.imul(t ^ (t >>> 15), t | 1);
58
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
59
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
60
+ };
61
+ }
62
+ function quantile(sorted, p) {
63
+ const position = p * (sorted.length - 1);
64
+ const lower = Math.floor(position);
65
+ const upper = Math.ceil(position);
66
+ const low = sorted[lower];
67
+ const high = sorted[upper];
68
+ return low + (high - low) * (position - lower);
69
+ }
70
+ /**
71
+ * Draw `resamples` samples of `n` indices with replacement, hand each to
72
+ * `statistics`, and return the 2.5th and 97.5th percentiles of the two numbers
73
+ * it returns. Both statistics see the same draw, so the two intervals come
74
+ * from one set of resampled suites.
75
+ */
76
+ function bootstrap(n, seed, statistics) {
77
+ const random = createSeededRandom(seed);
78
+ const draw = new Int32Array(n);
79
+ const means = new Float64Array(BOOTSTRAP_RESAMPLES);
80
+ const rates = new Float64Array(BOOTSTRAP_RESAMPLES);
81
+ for (let b = 0; b < BOOTSTRAP_RESAMPLES; b++) {
82
+ for (let i = 0; i < n; i++)
83
+ draw[i] = Math.floor(random() * n);
84
+ const [mean, rate] = statistics(draw);
85
+ means[b] = mean;
86
+ rates[b] = rate;
87
+ }
88
+ means.sort();
89
+ rates.sort();
90
+ const lowP = (1 - INTERVAL_LEVEL) / 2;
91
+ const highP = 1 - lowP;
92
+ return {
93
+ mean: [quantile(means, lowP), quantile(means, highP)],
94
+ passRate: [quantile(rates, lowP), quantile(rates, highP)],
95
+ };
96
+ }
97
+ function methodOf(seed, cases) {
98
+ return {
99
+ kind: 'percentile bootstrap over cases, repeats kept together',
100
+ level: INTERVAL_LEVEL,
101
+ resamples: BOOTSTRAP_RESAMPLES,
102
+ seed,
103
+ cases,
104
+ minimumCases: MIN_CASES_FOR_INTERVAL,
105
+ };
106
+ }
107
+ function tooFew(one, many, n) {
108
+ return `only ${n} ${n === 1 ? one : many}; an interval needs at least ${MIN_CASES_FOR_INTERVAL}`;
109
+ }
110
+ export function pooledMeanAndRate(tallies, draw) {
111
+ let score = 0;
112
+ let passes = 0;
113
+ let judged = 0;
114
+ const count = draw ? draw.length : tallies.length;
115
+ for (let i = 0; i < count; i++) {
116
+ const t = tallies[draw ? draw[i] : i];
117
+ score += t.scoreSum;
118
+ passes += t.passes;
119
+ judged += t.judged;
120
+ }
121
+ return judged === 0 ? [Number.NaN, Number.NaN] : [score / judged, passes / judged];
122
+ }
123
+ /**
124
+ * 95% intervals for the mean score and the pass rate over a set of cases.
125
+ *
126
+ * Each resample draws as many cases as there are, with replacement, and pools
127
+ * their runs. Cases without a judged run must not be passed in.
128
+ */
129
+ export function bootstrapCases(tallies, seed) {
130
+ const checked = validateSeed(seed);
131
+ const n = tallies.length;
132
+ const method = methodOf(checked, n);
133
+ const [meanEstimate, rateEstimate] = n === 0 ? [null, null] : pooledMeanAndRate(tallies);
134
+ if (n < MIN_CASES_FOR_INTERVAL) {
135
+ const reason = tooFew('case with a judged run', 'cases with a judged run', n);
136
+ return {
137
+ method,
138
+ mean: { computed: false, estimate: meanEstimate, reason },
139
+ passRate: { computed: false, estimate: rateEstimate, reason },
140
+ };
141
+ }
142
+ const result = bootstrap(n, checked, (draw) => pooledMeanAndRate(tallies, draw));
143
+ return {
144
+ method,
145
+ mean: {
146
+ computed: true,
147
+ estimate: meanEstimate,
148
+ low: result.mean[0],
149
+ high: result.mean[1],
150
+ },
151
+ passRate: {
152
+ computed: true,
153
+ estimate: rateEstimate,
154
+ low: result.passRate[0],
155
+ high: result.passRate[1],
156
+ },
157
+ };
158
+ }
159
+ /**
160
+ * 95% intervals for the difference of two runs over the same cases, `b` minus
161
+ * `a`. A resample draws PAIRS, so the two sides of a case always travel
162
+ * together; that is what makes the interval a paired one, and it is much
163
+ * narrower than two separate intervals when the cases are the thing that
164
+ * varies.
165
+ */
166
+ export function bootstrapPairedDifference(pairs, seed) {
167
+ const checked = validateSeed(seed);
168
+ const n = pairs.length;
169
+ const method = methodOf(checked, n);
170
+ const aSide = pairs.map((p) => p.a);
171
+ const bSide = pairs.map((p) => p.b);
172
+ const difference = (draw) => {
173
+ const [bMean, bRate] = pooledMeanAndRate(bSide, draw);
174
+ const [aMean, aRate] = pooledMeanAndRate(aSide, draw);
175
+ return [bMean - aMean, bRate - aRate];
176
+ };
177
+ const [meanEstimate, rateEstimate] = n === 0 ? [null, null] : difference();
178
+ if (n < MIN_CASES_FOR_INTERVAL) {
179
+ const reason = tooFew('paired case', 'paired cases', n);
180
+ return {
181
+ method,
182
+ mean: { computed: false, estimate: meanEstimate, reason },
183
+ passRate: { computed: false, estimate: rateEstimate, reason },
184
+ };
185
+ }
186
+ const result = bootstrap(n, checked, (draw) => difference(draw));
187
+ return {
188
+ method,
189
+ mean: {
190
+ computed: true,
191
+ estimate: meanEstimate,
192
+ low: result.mean[0],
193
+ high: result.mean[1],
194
+ },
195
+ passRate: {
196
+ computed: true,
197
+ estimate: rateEstimate,
198
+ low: result.passRate[0],
199
+ high: result.passRate[1],
200
+ },
201
+ };
202
+ }
203
+ /**
204
+ * Exact two-sided sign test on discordant pairs: under no difference each flip
205
+ * is equally likely to go either way, so the count going one way is
206
+ * Binomial(n, 1/2). Returns twice the smaller tail, capped at 1; with no flips
207
+ * at all the answer is 1.
208
+ */
209
+ export function exactSignTestP(oneWay, otherWay) {
210
+ const n = oneWay + otherWay;
211
+ if (n === 0)
212
+ return 1;
213
+ const k = Math.min(oneWay, otherWay);
214
+ const logFactorial = new Float64Array(n + 1);
215
+ for (let i = 2; i <= n; i++)
216
+ logFactorial[i] = logFactorial[i - 1] + Math.log(i);
217
+ let tail = 0;
218
+ for (let i = 0; i <= k; i++) {
219
+ const logTerm = logFactorial[n] -
220
+ logFactorial[i] -
221
+ logFactorial[n - i] -
222
+ n * Math.LN2;
223
+ tail += Math.exp(logTerm);
224
+ }
225
+ return Math.min(1, 2 * tail);
226
+ }
227
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/eval/bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { type DifferenceIntervals } from './bootstrap.js';
2
+ import { type CaseRuns } from './report-stats.js';
3
+ /**
4
+ * Two eval reports, side by side, paired by suite and case id.
5
+ *
6
+ * Two pass rates a few points apart read as a difference, and at the size of
7
+ * a hand-built suite they are usually not one. The comparison pairs the cases
8
+ * both sides ran, takes the difference over those, and puts an interval on it
9
+ * by resampling the pairs. It says "no difference can be claimed at this
10
+ * size" whenever that interval includes zero, and it says what else differs
11
+ * between the two runs, because a difference that comes with a new model and
12
+ * a new harness version and a new effort cannot be credited to any of them.
13
+ */
14
+ /** One suite of a loaded report file. */
15
+ export interface LoadedSuite {
16
+ readonly id: string;
17
+ readonly cases: readonly CaseRuns[];
18
+ readonly repeats: number;
19
+ /** Whatever the file said about how the suite ran; absent in an older file. */
20
+ readonly facts: Readonly<Record<string, unknown>> | undefined;
21
+ }
22
+ export interface LoadedReportFile {
23
+ readonly label: string;
24
+ readonly suites: readonly LoadedSuite[];
25
+ }
26
+ /**
27
+ * Read the JSON `namzu eval --out` writes. Tolerant of a file from an older
28
+ * version: only `suites[].suite` and each case's name, mean and status are
29
+ * required; repeats, splits and disclosure are read when they are there.
30
+ * Throws an Error whose message names what is wrong, for a person to read.
31
+ */
32
+ export declare function loadEvalReportFile(json: unknown, label: string): LoadedReportFile;
33
+ export type DisclosureDimension = 'harness' | 'suites' | 'model' | 'route' | 'effort' | 'repeats';
34
+ export interface DisclosureDifference {
35
+ readonly dimension: DisclosureDimension;
36
+ readonly key: string;
37
+ readonly a: string;
38
+ readonly b: string;
39
+ }
40
+ export interface DisclosureUnknown {
41
+ readonly dimension: DisclosureDimension;
42
+ readonly key: string;
43
+ /** Which side does not say; the other side's value is given. */
44
+ readonly missingIn: 'a' | 'b';
45
+ readonly value: string;
46
+ }
47
+ export interface DisclosureComparison {
48
+ readonly differences: readonly DisclosureDifference[];
49
+ /** Facts one side states and the other does not. */
50
+ readonly unknown: readonly DisclosureUnknown[];
51
+ /** Dimensions in which at least one stated fact differs, in a fixed order. */
52
+ readonly differingDimensions: readonly DisclosureDimension[];
53
+ /** Set when more than one dimension differs. */
54
+ readonly warning?: string;
55
+ }
56
+ export interface CaseRef {
57
+ readonly suite: string;
58
+ readonly case: string;
59
+ }
60
+ export interface SideNumbers {
61
+ /** Over the paired cases only. */
62
+ readonly mean: number | null;
63
+ readonly passRate: number | null;
64
+ }
65
+ export interface Flips {
66
+ /** Cases that passed in every run on side B and not on side A. */
67
+ readonly onlyBPasses: number;
68
+ /** The reverse: passed in every run on side A and not on side B. */
69
+ readonly onlyAPasses: number;
70
+ readonly bothPass: number;
71
+ readonly bothFail: number;
72
+ /** Exact two-sided sign test on the two flip counts; 1 with no flips. */
73
+ readonly pValue: number;
74
+ readonly test: 'exact two-sided sign test on the cases that flipped';
75
+ }
76
+ export interface EvalComparison {
77
+ readonly seed: number;
78
+ readonly a: {
79
+ readonly label: string;
80
+ readonly cases: number;
81
+ };
82
+ readonly b: {
83
+ readonly label: string;
84
+ readonly cases: number;
85
+ };
86
+ readonly onlyInA: readonly CaseRef[];
87
+ readonly onlyInB: readonly CaseRef[];
88
+ /** Present on both sides but with no judged run on one of them. */
89
+ readonly notJudged: readonly CaseRef[];
90
+ readonly paired: number;
91
+ readonly sideA: SideNumbers;
92
+ readonly sideB: SideNumbers;
93
+ /** B minus A. */
94
+ readonly difference: DifferenceIntervals;
95
+ readonly flips: Flips;
96
+ readonly verdict: {
97
+ readonly claim: 'better' | 'worse' | 'none';
98
+ /** The one sentence. */
99
+ readonly sentence: string;
100
+ /** Why, in plain words. */
101
+ readonly detail: string;
102
+ };
103
+ readonly disclosure: DisclosureComparison;
104
+ }
105
+ export declare function compareEvalReports(a: LoadedReportFile, b: LoadedReportFile, options?: {
106
+ seed?: number;
107
+ }): EvalComparison;
108
+ export declare function formatEvalComparison(c: EvalComparison): string;
109
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/eval/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,324 @@
1
+ import { bootstrapPairedDifference, exactSignTestP, pooledMeanAndRate, validateSeed, } from './bootstrap.js';
2
+ import { UNKNOWN } from './disclosure.js';
3
+ import { duplicateRun, groupRuns, passedEveryRun, tallyOf } from './report-stats.js';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ /**
8
+ * Read the JSON `namzu eval --out` writes. Tolerant of a file from an older
9
+ * version: only `suites[].suite` and each case's name, mean and status are
10
+ * required; repeats, splits and disclosure are read when they are there.
11
+ * Throws an Error whose message names what is wrong, for a person to read.
12
+ */
13
+ export function loadEvalReportFile(json, label) {
14
+ if (!isRecord(json) || !Array.isArray(json.suites)) {
15
+ throw new Error(`${label} is not an eval report: it has no "suites" list.`);
16
+ }
17
+ const suites = [];
18
+ for (const [index, entry] of json.suites.entries()) {
19
+ if (!isRecord(entry) || typeof entry.suite !== 'string' || !isRecord(entry.report)) {
20
+ throw new Error(`${label}: suites[${index}] needs a "suite" id and a "report".`);
21
+ }
22
+ const report = entry.report;
23
+ if (!Array.isArray(report.cases)) {
24
+ throw new Error(`${label}: suite "${entry.suite}" has no "cases" list.`);
25
+ }
26
+ const runs = [];
27
+ for (const [caseIndex, run] of report.cases.entries()) {
28
+ if (!isRecord(run) || typeof run.case !== 'string' || !Number.isFinite(run.mean)) {
29
+ throw new Error(`${label}: suite "${entry.suite}", case ${caseIndex} needs a name in "case" and a finite number in "mean".`);
30
+ }
31
+ runs.push(run);
32
+ }
33
+ const twice = duplicateRun(runs);
34
+ if (twice !== undefined) {
35
+ throw new Error(`${label}: suite "${entry.suite}" has the case "${twice}" more than once, so its runs cannot be told from one case's repeats. A case is named by its id; the report needs each case once per repeat.`);
36
+ }
37
+ suites.push({
38
+ id: entry.suite,
39
+ cases: groupRuns(runs),
40
+ repeats: typeof report.repeats === 'number' ? report.repeats : 1,
41
+ facts: isRecord(report.disclosure) ? report.disclosure : undefined,
42
+ });
43
+ }
44
+ return { label, suites };
45
+ }
46
+ const DIMENSION_ORDER = [
47
+ 'harness',
48
+ 'suites',
49
+ 'model',
50
+ 'route',
51
+ 'effort',
52
+ 'repeats',
53
+ ];
54
+ function known(value) {
55
+ return typeof value === 'string' && value !== UNKNOWN && value.length > 0 ? value : null;
56
+ }
57
+ function canonical(value) {
58
+ if (value === undefined || value === null || value === UNKNOWN)
59
+ return null;
60
+ if (typeof value === 'string')
61
+ return value;
62
+ return JSON.stringify(value, (_key, v) => isRecord(v)
63
+ ? Object.fromEntries(Object.entries(v).sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)))
64
+ : v);
65
+ }
66
+ function rolesFact(value) {
67
+ if (!isRecord(value))
68
+ return null;
69
+ const parts = [];
70
+ for (const role of Object.keys(value).sort()) {
71
+ const who = value[role];
72
+ const provider = isRecord(who) ? known(who.provider) : null;
73
+ const model = isRecord(who) ? known(who.model) : null;
74
+ if (provider === null || model === null)
75
+ return null;
76
+ parts.push(`${role}=${provider}/${model}`);
77
+ }
78
+ return parts.length === 0 ? 'no model' : parts.join('; ');
79
+ }
80
+ function firstKnown(suites, field) {
81
+ for (const suite of suites) {
82
+ const value = known(suite.facts?.[field]);
83
+ if (value !== null)
84
+ return value;
85
+ }
86
+ return null;
87
+ }
88
+ function factsOf(file, suiteIds) {
89
+ const facts = [
90
+ {
91
+ dimension: 'harness',
92
+ key: 'harness version',
93
+ value: firstKnown(file.suites, 'harnessVersion'),
94
+ },
95
+ { dimension: 'harness', key: 'sdk version', value: firstKnown(file.suites, 'sdkVersion') },
96
+ { dimension: 'harness', key: 'commit', value: firstKnown(file.suites, 'commit') },
97
+ ];
98
+ for (const id of suiteIds) {
99
+ const suite = file.suites.find((s) => s.id === id);
100
+ if (!suite)
101
+ continue;
102
+ const f = suite.facts;
103
+ facts.push({ dimension: 'suites', key: `${id} version`, value: known(f?.suiteVersion) }, { dimension: 'model', key: `${id} mode`, value: known(f?.mode) }, { dimension: 'model', key: `${id} models`, value: rolesFact(f?.roles) }, { dimension: 'route', key: `${id} routing`, value: canonical(f?.routing) }, { dimension: 'effort', key: `${id} reasoning effort`, value: known(f?.reasoningEffort) },
104
+ // A file from before repeats existed ran every case once.
105
+ { dimension: 'repeats', key: `${id} repeats`, value: String(suite.repeats) });
106
+ }
107
+ return facts;
108
+ }
109
+ function compareDisclosure(a, b, sharedSuites) {
110
+ const left = new Map(factsOf(a, sharedSuites).map((f) => [f.key, f]));
111
+ const right = new Map(factsOf(b, sharedSuites).map((f) => [f.key, f]));
112
+ const differences = [];
113
+ const unknown = [];
114
+ for (const [key, fa] of left) {
115
+ const fb = right.get(key);
116
+ if (fa.value !== null && fb.value !== null) {
117
+ if (fa.value !== fb.value) {
118
+ differences.push({ dimension: fa.dimension, key, a: fa.value, b: fb.value });
119
+ }
120
+ }
121
+ else if (fa.value !== null || fb.value !== null) {
122
+ unknown.push({
123
+ dimension: fa.dimension,
124
+ key,
125
+ missingIn: fa.value === null ? 'a' : 'b',
126
+ value: (fa.value ?? fb.value),
127
+ });
128
+ }
129
+ }
130
+ const differingDimensions = DIMENSION_ORDER.filter((d) => differences.some((x) => x.dimension === d));
131
+ return {
132
+ differences,
133
+ unknown,
134
+ differingDimensions,
135
+ ...(differingDimensions.length > 1
136
+ ? {
137
+ warning: `More than one thing differs between these two runs (${differingDimensions.join(', ')}), so a difference in the scores cannot be attributed to any one of them.`,
138
+ }
139
+ : {}),
140
+ };
141
+ }
142
+ function sorted(refs) {
143
+ return refs.sort((x, y) => x.suite === y.suite ? (x.case < y.case ? -1 : 1) : x.suite < y.suite ? -1 : 1);
144
+ }
145
+ function signed(value, digits = 3) {
146
+ const text = value.toFixed(digits);
147
+ return value > 0 ? `+${text}` : text;
148
+ }
149
+ export function compareEvalReports(a, b, options = {}) {
150
+ const seed = validateSeed(options.seed);
151
+ const index = (file) => {
152
+ const map = new Map();
153
+ for (const suite of file.suites) {
154
+ for (const c of suite.cases) {
155
+ map.set(`${suite.id}\u0000${c.name}`, { ref: { suite: suite.id, case: c.name }, runs: c });
156
+ }
157
+ }
158
+ return map;
159
+ };
160
+ const left = index(a);
161
+ const right = index(b);
162
+ const onlyInA = [];
163
+ const onlyInB = [];
164
+ const notJudged = [];
165
+ const pairs = [];
166
+ let onlyBPasses = 0;
167
+ let onlyAPasses = 0;
168
+ let bothPass = 0;
169
+ let bothFail = 0;
170
+ for (const [key, l] of left) {
171
+ const r = right.get(key);
172
+ if (!r) {
173
+ onlyInA.push(l.ref);
174
+ continue;
175
+ }
176
+ const ta = tallyOf(l.runs.runs);
177
+ const tb = tallyOf(r.runs.runs);
178
+ if (!ta || !tb) {
179
+ notJudged.push(l.ref);
180
+ continue;
181
+ }
182
+ pairs.push({ a: ta, b: tb });
183
+ const passA = passedEveryRun(l.runs.runs);
184
+ const passB = passedEveryRun(r.runs.runs);
185
+ if (passA && passB)
186
+ bothPass++;
187
+ else if (!passA && !passB)
188
+ bothFail++;
189
+ else if (passB)
190
+ onlyBPasses++;
191
+ else
192
+ onlyAPasses++;
193
+ }
194
+ for (const [key, r] of right)
195
+ if (!left.has(key))
196
+ onlyInB.push(r.ref);
197
+ const aSide = pairs.map((p) => p.a);
198
+ const bSide = pairs.map((p) => p.b);
199
+ const numbers = (tallies) => {
200
+ if (tallies.length === 0)
201
+ return { mean: null, passRate: null };
202
+ const [mean, passRate] = pooledMeanAndRate(tallies);
203
+ return { mean, passRate };
204
+ };
205
+ const difference = bootstrapPairedDifference(pairs, seed);
206
+ const flips = {
207
+ onlyBPasses,
208
+ onlyAPasses,
209
+ bothPass,
210
+ bothFail,
211
+ pValue: exactSignTestP(onlyBPasses, onlyAPasses),
212
+ test: 'exact two-sided sign test on the cases that flipped',
213
+ };
214
+ const verdict = decide(difference.mean, pairs.length, flips);
215
+ const sharedSuites = a.suites.map((s) => s.id).filter((id) => b.suites.some((s) => s.id === id));
216
+ return {
217
+ seed,
218
+ a: { label: a.label, cases: left.size },
219
+ b: { label: b.label, cases: right.size },
220
+ onlyInA: sorted(onlyInA),
221
+ onlyInB: sorted(onlyInB),
222
+ notJudged: sorted(notJudged),
223
+ paired: pairs.length,
224
+ sideA: numbers(aSide),
225
+ sideB: numbers(bSide),
226
+ difference,
227
+ flips,
228
+ verdict,
229
+ disclosure: compareDisclosure(a, b, sharedSuites),
230
+ };
231
+ }
232
+ /** The sign test must be at least this convincing before the sentence names a winner. */
233
+ const CLAIM_P = 0.05;
234
+ /** Bounds are judged at the precision they are printed at. */
235
+ function atPrintedPrecision(value) {
236
+ return Math.round(value * 1000) / 1000 || 0;
237
+ }
238
+ function decide(mean, paired, flips) {
239
+ const none = 'no difference can be claimed at this size';
240
+ if (!mean.computed) {
241
+ return { claim: 'none', sentence: none, detail: `no interval: ${mean.reason}.` };
242
+ }
243
+ const range = `[${mean.low.toFixed(3)}, ${mean.high.toFixed(3)}]`;
244
+ // A bound is zero when it prints as zero: sums that differ by rounding only
245
+ // (the same scores added in another order) must not read as a difference.
246
+ const low = atPrintedPrecision(mean.low);
247
+ const high = atPrintedPrecision(mean.high);
248
+ const excludesZero = (low > 0 || high < 0) && atPrintedPrecision(mean.estimate) !== 0;
249
+ if (!excludesZero) {
250
+ return {
251
+ claim: 'none',
252
+ sentence: none,
253
+ detail: `the 95% interval ${range} for the difference in mean score over ${paired} paired cases includes zero.`,
254
+ };
255
+ }
256
+ const flipped = flips.onlyAPasses + flips.onlyBPasses;
257
+ if (flipped > 0 && flips.pValue >= CLAIM_P) {
258
+ return {
259
+ claim: 'none',
260
+ sentence: none,
261
+ detail: `the 95% interval ${range} over ${paired} paired cases excludes zero, but the exact test on the ${flipped} case${flipped === 1 ? '' : 's'} that flipped gives p = ${flips.pValue.toFixed(4)}, not below ${CLAIM_P}; two checks that disagree are not a finding.`,
262
+ };
263
+ }
264
+ const better = low > 0;
265
+ const size = Math.abs(mean.estimate).toFixed(3);
266
+ return {
267
+ claim: better ? 'better' : 'worse',
268
+ sentence: `B is ${better ? 'better' : 'worse'} than A by ${size} in mean score, 95% interval ${range}`,
269
+ detail: `the interval over ${paired} paired cases excludes zero${flipped > 0 ? ` and the exact test on the flips agrees (p = ${flips.pValue.toFixed(4)})` : ''}.`,
270
+ };
271
+ }
272
+ function listCases(title, refs) {
273
+ if (refs.length === 0)
274
+ return [`${title}: none`];
275
+ const shown = refs.slice(0, 20).map((r) => ` ${r.suite} / ${r.case}`);
276
+ if (refs.length > shown.length)
277
+ shown.push(` and ${refs.length - shown.length} more`);
278
+ return [`${title} (${refs.length}):`, ...shown];
279
+ }
280
+ function intervalText(interval, show) {
281
+ if (!interval.computed)
282
+ return `no interval: ${interval.reason}`;
283
+ return `95% paired interval [${show(interval.low)}, ${show(interval.high)}]`;
284
+ }
285
+ export function formatEvalComparison(c) {
286
+ const score = (n) => (n === null ? 'none' : n.toFixed(3));
287
+ const rate = (n) => (n === null ? 'none' : `${(n * 100).toFixed(1)}%`);
288
+ const points = (n) => `${signed(n * 100, 1)} points`;
289
+ const lines = [
290
+ `A: ${c.a.label} (${c.a.cases} cases)`,
291
+ `B: ${c.b.label} (${c.b.cases} cases)`,
292
+ `Cases are paired by suite and id; ${c.paired} paired${c.notJudged.length > 0 ? `, ${c.notJudged.length} more present on both but not judged on one side` : ''}. Seed ${c.seed}.`,
293
+ '',
294
+ ...listCases('Only in A', c.onlyInA),
295
+ ...listCases('Only in B', c.onlyInB),
296
+ '',
297
+ `Mean score: A ${score(c.sideA.mean)}, B ${score(c.sideB.mean)}${c.difference.mean.estimate !== null ? `, difference ${signed(c.difference.mean.estimate)}` : ''}; ${intervalText(c.difference.mean, (n) => signed(n))}`,
298
+ `Pass rate: A ${rate(c.sideA.passRate)}, B ${rate(c.sideB.passRate)}${c.difference.passRate.estimate !== null ? `, difference ${points(c.difference.passRate.estimate)}` : ''}; ${intervalText(c.difference.passRate, (n) => points(n))}`,
299
+ `Flips: ${c.flips.onlyBPasses} case${c.flips.onlyBPasses === 1 ? '' : 's'} passed on B and not on A, ${c.flips.onlyAPasses} passed on A and not on B (${c.flips.bothPass} passed on both, ${c.flips.bothFail} failed on both). A case passes when every one of its runs passed. ${c.flips.test}: p = ${c.flips.pValue.toFixed(4)}`,
300
+ '',
301
+ `${c.verdict.sentence}.`,
302
+ ` (${c.verdict.detail})`,
303
+ '',
304
+ ];
305
+ const d = c.disclosure;
306
+ if (d.differences.length === 0) {
307
+ lines.push('What the two reports say about how they were produced: no stated difference.');
308
+ }
309
+ else {
310
+ lines.push('What differs in how the two reports were produced:');
311
+ for (const x of d.differences)
312
+ lines.push(` ${x.key}: A ${x.a}, B ${x.b}`);
313
+ }
314
+ if (d.unknown.length > 0) {
315
+ lines.push('Stated on one side only, so it cannot be compared:');
316
+ for (const x of d.unknown) {
317
+ lines.push(` ${x.key}: ${x.missingIn === 'a' ? 'A does not say' : 'B does not say'}, the other says ${x.value}`);
318
+ }
319
+ }
320
+ if (d.warning)
321
+ lines.push('', `Warning: ${d.warning}`);
322
+ return lines.join('\n');
323
+ }
324
+ //# sourceMappingURL=compare.js.map