@crowandraven/evalkeeper 0.0.0-stage → 0.2.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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/bin/evalkeeper.mjs +9 -0
  4. package/dist/attention.d.ts +49 -0
  5. package/dist/attention.js +49 -0
  6. package/dist/attention.js.map +1 -0
  7. package/dist/board.d.ts +57 -0
  8. package/dist/board.js +2 -0
  9. package/dist/board.js.map +1 -0
  10. package/dist/cli/addPages.d.ts +79 -0
  11. package/dist/cli/addPages.js +181 -0
  12. package/dist/cli/addPages.js.map +1 -0
  13. package/dist/cli/main.d.ts +18 -0
  14. package/dist/cli/main.js +115 -0
  15. package/dist/cli/main.js.map +1 -0
  16. package/dist/common.d.ts +72 -0
  17. package/dist/common.js +276 -0
  18. package/dist/common.js.map +1 -0
  19. package/dist/compare.d.ts +85 -0
  20. package/dist/compare.js +185 -0
  21. package/dist/compare.js.map +1 -0
  22. package/dist/decisions.d.ts +84 -0
  23. package/dist/decisions.js +146 -0
  24. package/dist/decisions.js.map +1 -0
  25. package/dist/feedback.d.ts +114 -0
  26. package/dist/feedback.js +116 -0
  27. package/dist/feedback.js.map +1 -0
  28. package/dist/format.d.ts +6 -0
  29. package/dist/format.js +22 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/glossary.d.ts +46 -0
  32. package/dist/glossary.js +45 -0
  33. package/dist/glossary.js.map +1 -0
  34. package/dist/health.d.ts +40 -0
  35. package/dist/health.js +101 -0
  36. package/dist/health.js.map +1 -0
  37. package/dist/index.d.ts +31 -0
  38. package/dist/index.js +34 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/labels.d.ts +8 -0
  41. package/dist/labels.js +15 -0
  42. package/dist/labels.js.map +1 -0
  43. package/dist/legs.d.ts +23 -0
  44. package/dist/legs.js +22 -0
  45. package/dist/legs.js.map +1 -0
  46. package/dist/memoryStore.d.ts +21 -0
  47. package/dist/memoryStore.js +228 -0
  48. package/dist/memoryStore.js.map +1 -0
  49. package/dist/models.d.ts +76 -0
  50. package/dist/models.js +56 -0
  51. package/dist/models.js.map +1 -0
  52. package/dist/prune.d.ts +48 -0
  53. package/dist/prune.js +77 -0
  54. package/dist/prune.js.map +1 -0
  55. package/dist/quickSet.d.ts +26 -0
  56. package/dist/quickSet.js +44 -0
  57. package/dist/quickSet.js.map +1 -0
  58. package/dist/runner.d.ts +202 -0
  59. package/dist/runner.js +734 -0
  60. package/dist/runner.js.map +1 -0
  61. package/dist/settings.d.ts +196 -0
  62. package/dist/settings.js +272 -0
  63. package/dist/settings.js.map +1 -0
  64. package/dist/stableJson.d.ts +11 -0
  65. package/dist/stableJson.js +15 -0
  66. package/dist/stableJson.js.map +1 -0
  67. package/dist/stats.d.ts +98 -0
  68. package/dist/stats.js +119 -0
  69. package/dist/stats.js.map +1 -0
  70. package/dist/store.d.ts +151 -0
  71. package/dist/store.js +3 -0
  72. package/dist/store.js.map +1 -0
  73. package/dist/suggest.d.ts +75 -0
  74. package/dist/suggest.js +152 -0
  75. package/dist/suggest.js.map +1 -0
  76. package/dist/summary.d.ts +51 -0
  77. package/dist/summary.js +61 -0
  78. package/dist/summary.js.map +1 -0
  79. package/dist/testing.d.ts +26 -0
  80. package/dist/testing.js +362 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/timeout.d.ts +2 -0
  83. package/dist/timeout.js +15 -0
  84. package/dist/timeout.js.map +1 -0
  85. package/dist/types.d.ts +225 -0
  86. package/dist/types.js +46 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/upkeepRules.d.ts +28 -0
  89. package/dist/upkeepRules.js +40 -0
  90. package/dist/upkeepRules.js.map +1 -0
  91. package/dist/util.d.ts +8 -0
  92. package/dist/util.js +18 -0
  93. package/dist/util.js.map +1 -0
  94. package/dist/weekly.d.ts +80 -0
  95. package/dist/weekly.js +113 -0
  96. package/dist/weekly.js.map +1 -0
  97. package/package.json +81 -4
  98. package/src/attention.ts +89 -0
  99. package/src/board.ts +58 -0
  100. package/src/cli/addPages.ts +256 -0
  101. package/src/cli/main.ts +125 -0
  102. package/src/common.ts +338 -0
  103. package/src/compare.ts +261 -0
  104. package/src/decisions.ts +230 -0
  105. package/src/feedback.ts +225 -0
  106. package/src/format.ts +19 -0
  107. package/src/glossary.ts +75 -0
  108. package/src/health.ts +149 -0
  109. package/src/index.ts +33 -0
  110. package/src/labels.ts +18 -0
  111. package/src/legs.ts +38 -0
  112. package/src/memoryStore.ts +268 -0
  113. package/src/models.ts +127 -0
  114. package/src/prune.ts +95 -0
  115. package/src/quickSet.ts +55 -0
  116. package/src/runner.ts +1010 -0
  117. package/src/settings.ts +441 -0
  118. package/src/stableJson.ts +24 -0
  119. package/src/stats.ts +215 -0
  120. package/src/store.ts +172 -0
  121. package/src/suggest.ts +247 -0
  122. package/src/summary.ts +109 -0
  123. package/src/testing.ts +415 -0
  124. package/src/timeout.ts +14 -0
  125. package/src/types.ts +258 -0
  126. package/src/upkeepRules.ts +46 -0
  127. package/src/util.ts +17 -0
  128. package/src/weekly.ts +169 -0
  129. package/starter/index.ts +86 -0
  130. package/templates/app/api/cron/evals-weekly/route.ts +24 -0
  131. package/templates/app/evals/[suite]/page.tsx +449 -0
  132. package/templates/app/evals/[suite]/runs/[runId]/page.tsx +95 -0
  133. package/templates/app/evals/feedback/page.tsx +219 -0
  134. package/templates/app/evals/page.tsx +313 -0
  135. package/templates/components/evalkeeper/AddCase.tsx +62 -0
  136. package/templates/components/evalkeeper/AnswerKey.tsx +360 -0
  137. package/templates/components/evalkeeper/CaseMenu.tsx +122 -0
  138. package/templates/components/evalkeeper/Fields.tsx +110 -0
  139. package/templates/components/evalkeeper/RefreshWhile.tsx +25 -0
  140. package/templates/components/evalkeeper/SettingsHistory.tsx +191 -0
  141. package/templates/components/evalkeeper/Submit.tsx +58 -0
  142. package/templates/components/evalkeeper/Thumbs.tsx +117 -0
  143. package/templates/components/evalkeeper/Tip.tsx +98 -0
  144. package/templates/components/evalkeeper/WeeklyCheck.tsx +124 -0
  145. package/templates/components/evalkeeper/evalkeeper.css +71 -0
  146. package/templates/components/evalkeeper/ui.tsx +103 -0
  147. package/templates/lib/evalkeeper/actions.ts +263 -0
  148. package/templates/lib/evalkeeper/cronAuth.ts +18 -0
  149. package/templates/lib/evalkeeper/paths.ts +11 -0
  150. package/templates/lib/evalkeeper/time.ts +4 -0
  151. package/templates/lib/evalkeeper/weekly.ts +51 -0
@@ -0,0 +1,98 @@
1
+ import type { CheckResult } from "./types.js";
2
+ /**
3
+ * Summary numbers for a run: pass rates per check with a 95% range, so a
4
+ * small dataset doesn't look more certain than it is. Pure; no database.
5
+ */
6
+ /** A pass rate with its 95% range; null when nothing was measured. */
7
+ export type Rate = {
8
+ passed: number;
9
+ /** Outputs the check applied to. */
10
+ n: number;
11
+ rate: number | null;
12
+ /** 95% Wilson interval. */
13
+ low: number | null;
14
+ high: number | null;
15
+ };
16
+ /** Wilson score interval: honest for small n and rates near 0 or 1. */
17
+ export declare function wilson(passed: number, n: number, z?: number): Rate;
18
+ /** One graded answer, as the metrics read it. */
19
+ export type ScoredOutput = {
20
+ caseId: string;
21
+ repeat: number;
22
+ /** null when the call failed. */
23
+ checks: CheckResult[] | null;
24
+ costUsd: number;
25
+ ms: number;
26
+ reused: boolean;
27
+ };
28
+ /**
29
+ * A test's numbers: answers right, partial credit, each check's pass rate, consistency,
30
+ * cost, time, and reuse.
31
+ */
32
+ export type SuiteMetrics = {
33
+ cases: number;
34
+ outputs: number;
35
+ failed: number;
36
+ /** Every applicable check passed. Failed calls count as not passing. */
37
+ allPass: Rate;
38
+ /**
39
+ * Partial credit summed over answers (e.g. items right of items
40
+ * expected), where the suite gives it; the headline when present.
41
+ */
42
+ units: {
43
+ right: number;
44
+ total: number;
45
+ } | null;
46
+ byCheck: Record<string, Rate>;
47
+ /**
48
+ * Of cases run more than once, the share whose all-pass result was the
49
+ * same every time. Low: the model is inconsistent on these cases.
50
+ */
51
+ consistency: Rate | null;
52
+ /** What grading's judge spent, apart from the model under test (runner). */
53
+ judgeCostUsd?: number;
54
+ /** The error most failed answers share (runner). */
55
+ failureReason?: string | null;
56
+ /**
57
+ * Per check, the mean of each measure its answers carried (brier,
58
+ * logLoss, …), and, where answers carried a confidence, the calibration
59
+ * error. Only checks with measures appear.
60
+ */
61
+ measures?: Record<string, CheckMeasures>;
62
+ /** What this run spent: reused outputs cost nothing. */
63
+ costUsd: number;
64
+ /** What one answer costs on this model, reused or not. */
65
+ costPerAnswer: number | null;
66
+ /** How long one answer takes on this model. */
67
+ avgMs: number;
68
+ reused: number;
69
+ };
70
+ /**
71
+ * Whether an answer passed every check that applied. A failed answer (null) didn't.
72
+ */
73
+ export declare function allPassed(checks: CheckResult[] | null): boolean;
74
+ /** Sums each answer's partial credit; null when the suite gives none. */
75
+ export declare function sumUnits(outputs: {
76
+ checks: CheckResult[] | null;
77
+ }[]): {
78
+ right: number;
79
+ total: number;
80
+ } | null;
81
+ /** One answer's score from 0 to 1: its partial credit, else all-or-nothing. */
82
+ export declare function answerScore(checks: CheckResult[] | null): number;
83
+ /** A test's numbers from its graded answers. */
84
+ /** One check's measures across a test. */
85
+ export type CheckMeasures = {
86
+ /** Answers that carried measures. */
87
+ n: number;
88
+ /** The mean of each measure, over the answers that carried it. */
89
+ mean: Record<string, number>;
90
+ /** Expected calibration error, from confidence and correctness; null without both. */
91
+ calibrationError: number | null;
92
+ };
93
+ /** One check's measures across a test's answers; null when none carried any. */
94
+ export declare function measuresFor(outputs: ScoredOutput[], key: string): CheckMeasures | null;
95
+ /**
96
+ *
97
+ */
98
+ export declare function suiteMetrics(outputs: ScoredOutput[], checkKeys: string[]): SuiteMetrics;
package/dist/stats.js ADDED
@@ -0,0 +1,119 @@
1
+ import { calibrationError } from "./decisions.js";
2
+ /** Wilson score interval: honest for small n and rates near 0 or 1. */
3
+ export function wilson(passed, n, z = 1.96) {
4
+ if (n === 0)
5
+ return { passed, n, rate: null, low: null, high: null };
6
+ const p = passed / n;
7
+ const denom = 1 + (z * z) / n;
8
+ const center = (p + (z * z) / (2 * n)) / denom;
9
+ const half = (z * Math.sqrt((p * (1 - p)) / n + (z * z) / (4 * n * n))) / denom;
10
+ return {
11
+ passed,
12
+ n,
13
+ rate: p,
14
+ low: Math.max(0, center - half),
15
+ high: Math.min(1, center + half),
16
+ };
17
+ }
18
+ /**
19
+ * Whether an answer passed every check that applied. A failed answer (null) didn't.
20
+ */
21
+ export function allPassed(checks) {
22
+ return checks !== null && checks.every((c) => c.pass !== false);
23
+ }
24
+ /** Sums each answer's partial credit; null when the suite gives none. */
25
+ export function sumUnits(outputs) {
26
+ let right = 0;
27
+ let total = 0;
28
+ let any = false;
29
+ for (const o of outputs)
30
+ for (const c of o.checks ?? [])
31
+ if (c.units) {
32
+ any = true;
33
+ right += c.units.right;
34
+ total += c.units.total;
35
+ }
36
+ return any ? { right, total } : null;
37
+ }
38
+ /** One answer's score from 0 to 1: its partial credit, else all-or-nothing. */
39
+ export function answerScore(checks) {
40
+ if (!checks)
41
+ return 0;
42
+ const u = checks.find((c) => c.units)?.units;
43
+ if (u)
44
+ return u.total ? u.right / u.total : 1;
45
+ return allPassed(checks) ? 1 : 0;
46
+ }
47
+ /** One check's measures across a test's answers; null when none carried any. */
48
+ export function measuresFor(outputs, key) {
49
+ const all = outputs
50
+ .map((o) => o.checks?.find((c) => c.key === key)?.measures)
51
+ .filter((m) => m !== undefined);
52
+ if (!all.length)
53
+ return null;
54
+ const sums = new Map();
55
+ for (const m of all)
56
+ for (const [name, v] of Object.entries(m)) {
57
+ const s = sums.get(name) ?? { total: 0, n: 0 };
58
+ s.total += v;
59
+ s.n++;
60
+ sums.set(name, s);
61
+ }
62
+ const pairs = all.flatMap((m) => m.confidence !== undefined && m.correct !== undefined
63
+ ? [{ confidence: m.confidence, correct: m.correct === 1 }]
64
+ : []);
65
+ return {
66
+ n: all.length,
67
+ mean: Object.fromEntries([...sums].map(([k, s]) => [k, s.total / s.n])),
68
+ calibrationError: calibrationError(pairs),
69
+ };
70
+ }
71
+ /**
72
+ *
73
+ */
74
+ export function suiteMetrics(outputs, checkKeys) {
75
+ const byCheck = {};
76
+ for (const key of checkKeys) {
77
+ const applied = outputs
78
+ .map((o) => o.checks?.find((c) => c.key === key))
79
+ .filter((c) => c !== undefined && c.pass !== null);
80
+ byCheck[key] = wilson(applied.filter((c) => c.pass).length, applied.length);
81
+ }
82
+ const measures = {};
83
+ for (const key of checkKeys) {
84
+ const m = measuresFor(outputs, key);
85
+ if (m)
86
+ measures[key] = m;
87
+ }
88
+ const byCase = new Map();
89
+ for (const o of outputs)
90
+ byCase.set(o.caseId, [
91
+ ...(byCase.get(o.caseId) ?? []),
92
+ allPassed(o.checks),
93
+ ]);
94
+ const repeated = [...byCase.values()].filter((r) => r.length > 1);
95
+ const called = outputs.filter((o) => !o.reused);
96
+ // Answers with a recorded time (older reused rows have none).
97
+ const timed = outputs.filter((o) => o.checks && o.ms > 0);
98
+ return {
99
+ cases: byCase.size,
100
+ outputs: outputs.length,
101
+ failed: outputs.filter((o) => !o.checks).length,
102
+ allPass: wilson(outputs.filter((o) => allPassed(o.checks)).length, outputs.length),
103
+ units: sumUnits(outputs),
104
+ byCheck,
105
+ ...(Object.keys(measures).length ? { measures } : {}),
106
+ consistency: repeated.length
107
+ ? wilson(repeated.filter((r) => r.every((x) => x === r[0])).length, repeated.length)
108
+ : null,
109
+ costUsd: called.reduce((t, o) => t + o.costUsd, 0),
110
+ costPerAnswer: timed.length
111
+ ? timed.reduce((t, o) => t + o.costUsd, 0) / timed.length
112
+ : null,
113
+ avgMs: timed.length
114
+ ? Math.round(timed.reduce((t, o) => t + o.ms, 0) / timed.length)
115
+ : 0,
116
+ reused: outputs.length - called.length,
117
+ };
118
+ }
119
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAkBlD,uEAAuE;AACvE,MAAM,UAAU,MAAM,CAAC,MAAc,EAAE,CAAS,EAAE,CAAC,GAAG,IAAI;IACxD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrE,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IACrB,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAC/C,MAAM,IAAI,GACR,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IACrE,OAAO;QACL,MAAM;QACN,CAAC;QACD,IAAI,EAAE,CAAC;QACP,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACjC,CAAC;AACJ,CAAC;AAqDD;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,MAA4B;IACpD,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;AAClE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CACtB,OAA2C;IAE3C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,OAAO;QACrB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE;YAC5B,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACZ,GAAG,GAAG,IAAI,CAAC;gBACX,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;gBACvB,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,CAAC;IACL,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IAC7C,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC;AAaD,gFAAgF;AAChF,MAAM,UAAU,WAAW,CACzB,OAAuB,EACvB,GAAW;IAEX,MAAM,GAAG,GAAG,OAAO;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,QAAQ,CAAC;SAC1D,MAAM,CAAC,CAAC,CAAC,EAA+B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAwC,CAAC;IAC7D,KAAK,MAAM,CAAC,IAAI,GAAG;QACjB,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/C,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;YACb,CAAC,CAAC,CAAC,EAAE,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpB,CAAC;IACH,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9B,CAAC,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;QACnD,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC1D,CAAC,CAAC,EAAE,CACP,CAAC;IACF,OAAO;QACL,CAAC,EAAE,GAAG,CAAC,MAAM;QACb,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,gBAAgB,EAAE,gBAAgB,CAAC,KAAK,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAuB,EACvB,SAAmB;IAEnB,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,OAAO;aACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;aAChD,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,QAAQ,GAAkC,EAAE,CAAC;IACnD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,OAAO;QACrB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE;YACnB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC/B,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;SACpB,CAAC,CAAC;IACL,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAChD,8DAA8D;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC,MAAM;QACvB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM;QAC/C,OAAO,EAAE,MAAM,CACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EACjD,OAAO,CAAC,MAAM,CACf;QACD,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;QACxB,OAAO;QACP,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,WAAW,EAAE,QAAQ,CAAC,MAAM;YAC1B,CAAC,CAAC,MAAM,CACJ,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EACzD,QAAQ,CAAC,MAAM,CAChB;YACH,CAAC,CAAC,IAAI;QACR,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,aAAa,EAAE,KAAK,CAAC,MAAM;YACzB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM;YACzD,CAAC,CAAC,IAAI;QACR,KAAK,EAAE,KAAK,CAAC,MAAM;YACjB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,151 @@
1
+ import type { CheckResult } from "./types.js";
2
+ import type { SuiteMetrics } from "./stats.js";
3
+ /**
4
+ * Where Evalkeeper keeps cases, answers, tests, and outputs. The runner
5
+ * talks only to this interface: `@crowandraven/evalkeeper-prisma` implements it on your
6
+ * database, and `memoryStore()` keeps everything in memory for tests and
7
+ * demos. Every method is a plain read or write; the rules live in the
8
+ * runner.
9
+ */
10
+ /** ALL: every case in use. SMOKE: the quick set. DEV and TEST: by split. */
11
+ export type EvalSet = "ALL" | "SMOKE" | "DEV" | "TEST";
12
+ /** Every set of cases a test can run on. */
13
+ export declare const EVAL_SETS: EvalSet[];
14
+ /**
15
+ * Where an answer stands: pending review, confirmed, provisional, or rejected.
16
+ */
17
+ export type LabelStatus = "PENDING" | "CONFIRMED" | "PROVISIONAL" | "REJECTED";
18
+ /**
19
+ * An answer to a case. EXPECTED is what a good answer looks like; a case's
20
+ * newest CONFIRMED one is what tests grade against. PENDING answers (from a
21
+ * model, or not yet reviewed) are never graded against; PROVISIONAL ones are
22
+ * put to use before review and say so.
23
+ */
24
+ export type LabelRecord = {
25
+ id: string;
26
+ kind: "EXPECTED";
27
+ status: LabelStatus;
28
+ value: unknown;
29
+ /** Who gave it: "OWNER", "MODEL", a reviewer's role. */
30
+ raterRole: string;
31
+ raterId: string | null;
32
+ note: string | null;
33
+ createdAt: Date;
34
+ updatedAt: Date;
35
+ };
36
+ /** A stored case: a frozen input, its tags and set, and its answers. */
37
+ export type CaseRecord = {
38
+ id: string;
39
+ suite: string;
40
+ /** The suite's input, frozen. */
41
+ input: unknown;
42
+ tags: string[];
43
+ split: "DEV" | "TEST";
44
+ status: "DRAFT" | "ACTIVE" | "RETIRED";
45
+ /** Where it came from: "OWNER", "MODEL_DRAFT", "FEEDBACK", … */
46
+ source: string;
47
+ /** What it was made from (an id in your app), to avoid adding it twice. */
48
+ sourceRef: string | null;
49
+ note: string | null;
50
+ createdAt: Date;
51
+ updatedAt: Date;
52
+ labels: LabelRecord[];
53
+ };
54
+ /** Where a test stands. */
55
+ export type RunStatus = "RUNNING" | "SUCCEEDED" | "FAILED";
56
+ /** What the runner stores on a finished test. */
57
+ export type RunMetrics = SuiteMetrics & {
58
+ judgeCostUsd: number;
59
+ failureReason: string | null;
60
+ };
61
+ /**
62
+ * A stored test: one suite on one model, with what it ran with and its metrics.
63
+ */
64
+ export type RunRecord = {
65
+ id: string;
66
+ suite: string;
67
+ model: string;
68
+ /** Hash of the instructions it ran with (see `instructionsHash`). */
69
+ promptVersion: string;
70
+ /** The reasoning setting tested; null: the model's own default. */
71
+ reasoningEffort: string | null;
72
+ /**
73
+ * The temperature tested; null or unset: none sent. Stores keep it, so a
74
+ * test at another temperature than the live one shows as out of date.
75
+ */
76
+ temperature?: number | null;
77
+ set: EvalSet;
78
+ repeats: number;
79
+ /** Changes when the cases or their answers do, so tests compare. */
80
+ datasetVersion: string | null;
81
+ /** The suite's grader version when last graded. */
82
+ graderVersion: string;
83
+ status: RunStatus;
84
+ metrics: RunMetrics | null;
85
+ error: string | null;
86
+ estimatedCostUsd: number | null;
87
+ /** Who started it; null for a scheduled check. */
88
+ startedBy: string | null;
89
+ startedAt: Date;
90
+ finishedAt: Date | null;
91
+ };
92
+ /** One stored answer in a test, with its grades, cost, and time. */
93
+ export type OutputRecord = {
94
+ id: string;
95
+ runId: string;
96
+ caseId: string;
97
+ repeat: number;
98
+ /** The request's hash, so an identical one is reused, not paid twice. */
99
+ requestHash: string;
100
+ output: unknown;
101
+ /** Why the answer failed; null when it came back. */
102
+ error: string | null;
103
+ scores: CheckResult[] | null;
104
+ costUsd: number;
105
+ ms: number;
106
+ /** Copied from an identical earlier request: cost nothing this time. */
107
+ reused: boolean;
108
+ createdAt: Date;
109
+ };
110
+ /** A test to record; the store fills in the id, times, and status. */
111
+ export type NewRun = Omit<RunRecord, "id" | "startedAt" | "finishedAt" | "status" | "metrics" | "error">;
112
+ /** An answer to record. */
113
+ export type NewOutput = Omit<OutputRecord, "id" | "createdAt">;
114
+ /** An answer to add to a case. */
115
+ export type NewLabel = Omit<LabelRecord, "id" | "createdAt" | "updatedAt">;
116
+ /** A case to create, with its first answers. */
117
+ export type NewCase = Omit<CaseRecord, "id" | "createdAt" | "updatedAt" | "labels"> & {
118
+ labels?: NewLabel[];
119
+ };
120
+ /**
121
+ * Where Evalkeeper keeps cases, answers, tests, and outputs. Implement it for another
122
+ * database, and run describeStoreContract against it.
123
+ */
124
+ export interface EvalStore {
125
+ /** A suite's cases that aren't retired, with their answers, oldest first. */
126
+ listCases(suite: string): Promise<CaseRecord[]>;
127
+ createCase(data: NewCase): Promise<CaseRecord>;
128
+ /** Adds an answer to a case (answers are never overwritten). */
129
+ addLabel(caseId: string, label: NewLabel): Promise<LabelRecord>;
130
+ /** Changes an answer's status, e.g. a person confirming a draft. */
131
+ setLabelStatus(labelId: string, status: LabelStatus): Promise<void>;
132
+ /** Changes a case's status, e.g. retiring it. */
133
+ setCaseStatus(caseId: string, status: CaseRecord["status"]): Promise<void>;
134
+ getRun(id: string): Promise<RunRecord | null>;
135
+ /** Newest first. */
136
+ listRuns(suite: string, opts?: {
137
+ status?: RunStatus;
138
+ limit?: number;
139
+ }): Promise<RunRecord[]>;
140
+ createRun(data: NewRun): Promise<RunRecord>;
141
+ updateRun(id: string, patch: Partial<Omit<RunRecord, "id">>,
142
+ /** Only if the run still has this status (a guard against races). */
143
+ onlyIf?: {
144
+ status: RunStatus;
145
+ }): Promise<void>;
146
+ listOutputs(runId: string): Promise<OutputRecord[]>;
147
+ /** The newest original (not reused, no error) output with this hash. */
148
+ findReusableOutput(requestHash: string, since: Date): Promise<OutputRecord | null>;
149
+ createOutput(data: NewOutput): Promise<OutputRecord>;
150
+ setOutputScores(id: string, scores: CheckResult[]): Promise<void>;
151
+ }
package/dist/store.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Every set of cases a test can run on. */
2
+ export const EVAL_SETS = ["ALL", "SMOKE", "DEV", "TEST"];
3
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAaA,4CAA4C;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { type ModelCatalog } from "./models.js";
2
+ import type { Rate } from "./stats.js";
3
+ import type { CheckDef, SuitePriority } from "./types.js";
4
+ import type { Verdict } from "./compare.js";
5
+ /**
6
+ * Which model to make live and which the fallback, from a suite's current
7
+ * tests, by fixed rules so the reason is always the real one. Switching
8
+ * live is conservative: the candidate must score at least as high as live
9
+ * and make no more serious mistakes, and then needs a real reason: clearly
10
+ * more accurate, at least two fewer serious mistakes, or, by the suite's
11
+ * priority, at least 30% faster (speed) or cheaper (cost). One exception,
12
+ * for suites that favor cost or speed: a model that scores a little lower
13
+ * (within 10 points, and no clear difference) with no more serious
14
+ * mistakes qualifies when it costs or takes at most half as much. A suite's
15
+ * time limit rules out slower models, live included. Otherwise live stays, and the best-scoring other
16
+ * model is named as one to watch. The fallback is the most accurate model
17
+ * from another provider; it needn't beat live, it only runs in an outage.
18
+ * Models the provider is retiring are never suggested, and a retiring live
19
+ * model is replaced by the best-scoring one that isn't. Pure.
20
+ */
21
+ /** One model's row, as the suggestion reads it. */
22
+ export type SuggestRow = {
23
+ key: string;
24
+ model: string;
25
+ label: string;
26
+ variant: boolean;
27
+ live: boolean;
28
+ fallback: boolean;
29
+ vsLive: Verdict | null;
30
+ /** May only be live (e.g. a model that can't stand in for a text model). */
31
+ liveOnly?: boolean;
32
+ /** May only be the fallback. */
33
+ fallbackOnly?: boolean;
34
+ test: {
35
+ right: number;
36
+ total: number;
37
+ byCheck: Record<string, Rate>;
38
+ avgMs: number | null;
39
+ costPerAnswer: number | null;
40
+ stale: boolean;
41
+ /** Answers that failed; a test with any is incomplete. */
42
+ failedAnswers?: number;
43
+ } | null;
44
+ };
45
+ /**
46
+ * Which model to make live and which the fallback, with the reasons in plain sentences.
47
+ */
48
+ export type Suggestion<R extends SuggestRow = SuggestRow> = {
49
+ live: R;
50
+ fallback: R | null;
51
+ /** Live stays as it is. */
52
+ keepLive: boolean;
53
+ /** Nothing to change at all: live stays and the fallback is already set. */
54
+ same: boolean;
55
+ /** Why, in plain sentences. */
56
+ reasons: string[];
57
+ };
58
+ /**
59
+ * How many serious checks a model's test failed; Infinity without a test.
60
+ */
61
+ export declare function seriousMistakes(row: SuggestRow, checks: CheckDef[]): number;
62
+ /**
63
+ * Suggests the live and fallback models from a suite's board, by fixed rules, so the
64
+ * reason given is always the real one. Null with nothing current to go on.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const board = await runner.board("ticket_triage");
69
+ * const s = suggest(board.rows, suite.checks, suite.priority, MODELS);
70
+ * if (s && !s.same) console.log(s.live.label, s.fallback?.label, s.reasons);
71
+ * ```
72
+ */
73
+ export declare function suggest<R extends SuggestRow>(rows: R[], checks: CheckDef[], priority?: SuitePriority,
74
+ /** Your model catalog: providers (for the fallback) and retirements. */
75
+ models?: ModelCatalog): Suggestion<R> | null;
@@ -0,0 +1,152 @@
1
+ import { isBaseline, retiresLabel } from "./models.js";
2
+ import { at } from "./util.js";
3
+ /**
4
+ * How many serious checks a model's test failed; Infinity without a test.
5
+ */
6
+ export function seriousMistakes(row, checks) {
7
+ const test = row.test;
8
+ if (!test)
9
+ return Infinity;
10
+ return checks
11
+ .filter((c) => c.severity === "serious")
12
+ .reduce((t, c) => {
13
+ const r = test.byCheck[c.key];
14
+ return t + (r ? r.n - r.passed : 0);
15
+ }, 0);
16
+ }
17
+ const mistakes = (n) => `${n} serious mistake${n === 1 ? "" : "s"}`;
18
+ const rate = (r) => (r.test ? r.test.right / r.test.total : 0);
19
+ const pct = (x) => `${Math.round(x * 100)}%`;
20
+ const cost = (r) => r.test?.costPerAnswer ?? Infinity;
21
+ const ms = (r) => r.test?.avgMs ?? Infinity;
22
+ /** A cost or speed trade-off may give up at most this much accuracy. */
23
+ const MAX_TRADE_DROP = 0.1;
24
+ /** ...and must cost or take at most this share of live's. */
25
+ const HALF = 0.5;
26
+ /** At least 30% lower. */
27
+ const clearlyLess = (a, b) => Number.isFinite(a) && Number.isFinite(b) && a <= b * 0.7;
28
+ /**
29
+ * Suggests the live and fallback models from a suite's board, by fixed rules, so the
30
+ * reason given is always the real one. Null with nothing current to go on.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const board = await runner.board("ticket_triage");
35
+ * const s = suggest(board.rows, suite.checks, suite.priority, MODELS);
36
+ * if (s && !s.same) console.log(s.live.label, s.fallback?.label, s.reasons);
37
+ * ```
38
+ */
39
+ export function suggest(rows, checks, priority = { favor: "quality" },
40
+ /** Your model catalog: providers (for the fallback) and retirements. */
41
+ models = {}) {
42
+ const provider = (model) => models[model]?.provider ?? model;
43
+ const retiring = (r) => retiresLabel(models, r.model);
44
+ const limitMs = priority.maxSeconds ? priority.maxSeconds * 1000 : Infinity;
45
+ const tooSlow = (r) => ms(r) !== Infinity && ms(r) > limitMs;
46
+ // Baselines are code, compared on the board but never suggested.
47
+ const tested = rows.filter((r) => !isBaseline(models, r.model) &&
48
+ r.test &&
49
+ !r.test.stale &&
50
+ !r.test.failedAnswers &&
51
+ !retiring(r) &&
52
+ !tooSlow(r));
53
+ if (!tested.length)
54
+ return null;
55
+ const serious = (r) => seriousMistakes(r, checks);
56
+ const live = tested.find((r) => r.live) ?? null;
57
+ const reasons = [];
58
+ const liveRow = rows.find((r) => r.live);
59
+ const liveRetires = liveRow ? retiring(liveRow) : null;
60
+ const liveSlow = liveRow?.test && !liveRow.test.stale && tooSlow(liveRow)
61
+ ? `${liveRow.label} takes ${(ms(liveRow) / 1000).toFixed(1)}s, over this suite's ${priority.maxSeconds}s limit.`
62
+ : null;
63
+ // Live: switch only for a real, safe improvement.
64
+ let next;
65
+ const liveable = tested.filter((r) => !r.fallbackOnly);
66
+ if (!live) {
67
+ if (!liveable.length)
68
+ return null;
69
+ next = at([...liveable].sort((a, b) => rate(b) - rate(a) || serious(a) - serious(b) || cost(a) - cost(b)), 0);
70
+ reasons.push(liveRetires || liveSlow
71
+ ? `${liveRetires && liveRow ? `${liveRow.label} retires in ${liveRetires}.` : liveSlow} ${next.label} scores highest of the rest (${pct(rate(next))}) with ${mistakes(serious(next))}.`
72
+ : `${next.label} scores highest (${pct(rate(next))}) with ${mistakes(serious(next))}. The live model has no up-to-date test to compare with.`);
73
+ }
74
+ else {
75
+ const why = (r) => {
76
+ if (rate(r) < rate(live))
77
+ return trade(r);
78
+ if (r.vsLive === "BETTER")
79
+ return `${r.label} is clearly more accurate than ${live.label}: ${pct(rate(r))} against ${pct(rate(live))}.`;
80
+ if (serious(r) <= serious(live) - 2)
81
+ return `${r.label} makes fewer serious mistakes than ${live.label} (${serious(r)} against ${serious(live)}), and scores at least as well (${pct(rate(r))} against ${pct(rate(live))}).`;
82
+ if (priority.favor === "cost" && clearlyLess(cost(r), cost(live)))
83
+ return `${r.label} scores at least as well as ${live.label} (${pct(rate(r))} against ${pct(rate(live))}) at ${Math.round((1 - cost(r) / cost(live)) * 100)}% less per answer.`;
84
+ if (priority.favor === "speed" && clearlyLess(ms(r), ms(live)))
85
+ return `${r.label} scores at least as well as ${live.label} (${pct(rate(r))} against ${pct(rate(live))}) and answers faster (${(ms(r) / 1000).toFixed(1)}s against ${(ms(live) / 1000).toFixed(1)}s).`;
86
+ return null;
87
+ };
88
+ // Cost and speed suites: a little less accurate, no clear difference,
89
+ // and at most half the cost (or time).
90
+ const trade = (r) => {
91
+ if (priority.favor === "quality" ||
92
+ r.vsLive !== "SAME" ||
93
+ rate(live) - rate(r) > MAX_TRADE_DROP ||
94
+ serious(r) > serious(live))
95
+ return null;
96
+ const close = `${r.label} is about as accurate as ${live.label} (${pct(rate(r))} against ${pct(rate(live))}: closer than these cases can tell apart), with no more serious mistakes`;
97
+ if (priority.favor === "cost" && cost(r) <= cost(live) * HALF)
98
+ return `${close}, at ${Math.round((1 - cost(r) / cost(live)) * 100)}% less per answer. This suite favors cost.`;
99
+ if (priority.favor === "speed" && ms(r) <= ms(live) * HALF)
100
+ return `${close}, and answers in ${(ms(r) / 1000).toFixed(1)}s against ${(ms(live) / 1000).toFixed(1)}s. This suite favors speed.`;
101
+ return null;
102
+ };
103
+ const candidates = liveable
104
+ .filter((r) => !r.live &&
105
+ r.vsLive !== "WORSE" &&
106
+ serious(r) <= serious(live) &&
107
+ why(r))
108
+ .sort((a, b) => Number(b.vsLive === "BETTER") - Number(a.vsLive === "BETTER") ||
109
+ // A trade-off only when nothing scores as well.
110
+ Number(rate(b) >= rate(live)) - Number(rate(a) >= rate(live)) ||
111
+ rate(b) - rate(a) ||
112
+ serious(a) - serious(b) ||
113
+ (priority.favor === "speed"
114
+ ? ms(a) - ms(b) || cost(a) - cost(b)
115
+ : cost(a) - cost(b) || ms(a) - ms(b)));
116
+ if (candidates.length) {
117
+ next = at(candidates, 0);
118
+ const reason = why(next);
119
+ if (reason)
120
+ reasons.push(reason);
121
+ }
122
+ else {
123
+ next = live;
124
+ reasons.push(`Keep ${live.label}: no other model is safely better on these cases.`);
125
+ // The one to watch: scored higher, but not by enough to tell.
126
+ const watch = tested
127
+ .filter((r) => !r.live && rate(r) > rate(live))
128
+ .sort((a, b) => rate(b) - rate(a))[0];
129
+ if (watch)
130
+ reasons.push(`${watch.label} scored highest (${pct(rate(watch))} against ${pct(rate(live))}), but with this many cases the gap can't be told from noise${serious(watch) > serious(live) ? `, and it makes more serious mistakes (${serious(watch)} against ${serious(live)})` : ""}. Add cases, then test again.`);
131
+ }
132
+ }
133
+ // Fallback: the most accurate from another provider, at the operation's
134
+ // setting (the setting is shared, so setting rows don't qualify).
135
+ const fallback = tested
136
+ .filter((r) => !r.variant &&
137
+ !r.liveOnly &&
138
+ r.key !== next.key &&
139
+ provider(r.model) !== provider(next.model))
140
+ .sort((a, b) => rate(b) - rate(a) || serious(a) - serious(b) || cost(a) - cost(b))[0] ?? null;
141
+ if (fallback)
142
+ reasons.push(`Fallback: ${fallback.label} is the most accurate from another provider (${pct(rate(fallback))}, ${mistakes(serious(fallback))}).`);
143
+ const keepLive = next.live;
144
+ return {
145
+ live: next,
146
+ fallback,
147
+ keepLive,
148
+ same: keepLive && (!fallback || fallback.fallback),
149
+ reasons,
150
+ };
151
+ }
152
+ //# sourceMappingURL=suggest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.js","sourceRoot":"","sources":["../src/suggest.ts"],"names":[],"mappings":"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"}