@crowandraven/evalkeeper 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +6 -2
- package/bin/evalkeeper.mjs +9 -0
- package/dist/attention.d.ts +49 -0
- package/dist/attention.js +48 -0
- package/dist/attention.js.map +1 -0
- package/dist/board.d.ts +55 -0
- package/dist/board.js +2 -0
- package/dist/board.js.map +1 -0
- package/dist/cli/addPages.d.ts +79 -0
- package/dist/cli/addPages.js +181 -0
- package/dist/cli/addPages.js.map +1 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +115 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/common.d.ts +72 -0
- package/dist/common.js +276 -0
- package/dist/common.js.map +1 -0
- package/dist/compare.d.ts +85 -0
- package/dist/compare.js +185 -0
- package/dist/compare.js.map +1 -0
- package/dist/decisions.d.ts +84 -0
- package/dist/decisions.js +146 -0
- package/dist/decisions.js.map +1 -0
- package/dist/feedback.d.ts +114 -0
- package/dist/feedback.js +116 -0
- package/dist/feedback.js.map +1 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.js +22 -0
- package/dist/format.js.map +1 -0
- package/dist/glossary.d.ts +46 -0
- package/dist/glossary.js +45 -0
- package/dist/glossary.js.map +1 -0
- package/dist/health.d.ts +40 -0
- package/dist/health.js +101 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +15 -0
- package/dist/labels.js.map +1 -0
- package/dist/legs.d.ts +23 -0
- package/dist/legs.js +22 -0
- package/dist/legs.js.map +1 -0
- package/dist/memoryStore.d.ts +21 -0
- package/dist/memoryStore.js +228 -0
- package/dist/memoryStore.js.map +1 -0
- package/dist/models.d.ts +50 -0
- package/dist/models.js +39 -0
- package/dist/models.js.map +1 -0
- package/dist/prune.d.ts +48 -0
- package/dist/prune.js +77 -0
- package/dist/prune.js.map +1 -0
- package/dist/quickSet.d.ts +26 -0
- package/dist/quickSet.js +44 -0
- package/dist/quickSet.js.map +1 -0
- package/dist/runner.d.ts +193 -0
- package/dist/runner.js +706 -0
- package/dist/runner.js.map +1 -0
- package/dist/settings.d.ts +157 -0
- package/dist/settings.js +183 -0
- package/dist/settings.js.map +1 -0
- package/dist/stableJson.d.ts +11 -0
- package/dist/stableJson.js +15 -0
- package/dist/stableJson.js.map +1 -0
- package/dist/stats.d.ts +98 -0
- package/dist/stats.js +119 -0
- package/dist/stats.js.map +1 -0
- package/dist/store.d.ts +146 -0
- package/dist/store.js +3 -0
- package/dist/store.js.map +1 -0
- package/dist/suggest.d.ts +75 -0
- package/dist/suggest.js +150 -0
- package/dist/suggest.js.map +1 -0
- package/dist/summary.d.ts +51 -0
- package/dist/summary.js +61 -0
- package/dist/summary.js.map +1 -0
- package/dist/testing.d.ts +26 -0
- package/dist/testing.js +341 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +2 -0
- package/dist/timeout.js +15 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/upkeepRules.d.ts +28 -0
- package/dist/upkeepRules.js +40 -0
- package/dist/upkeepRules.js.map +1 -0
- package/dist/util.d.ts +8 -0
- package/dist/util.js +18 -0
- package/dist/util.js.map +1 -0
- package/dist/weekly.d.ts +80 -0
- package/dist/weekly.js +111 -0
- package/dist/weekly.js.map +1 -0
- package/package.json +81 -4
- package/src/attention.ts +88 -0
- package/src/board.ts +56 -0
- package/src/cli/addPages.ts +256 -0
- package/src/cli/main.ts +125 -0
- package/src/common.ts +338 -0
- package/src/compare.ts +261 -0
- package/src/decisions.ts +230 -0
- package/src/feedback.ts +225 -0
- package/src/format.ts +19 -0
- package/src/glossary.ts +75 -0
- package/src/health.ts +147 -0
- package/src/index.ts +33 -0
- package/src/labels.ts +18 -0
- package/src/legs.ts +38 -0
- package/src/memoryStore.ts +268 -0
- package/src/models.ts +82 -0
- package/src/prune.ts +95 -0
- package/src/quickSet.ts +55 -0
- package/src/runner.ts +971 -0
- package/src/settings.ts +331 -0
- package/src/stableJson.ts +24 -0
- package/src/stats.ts +215 -0
- package/src/store.ts +167 -0
- package/src/suggest.ts +245 -0
- package/src/summary.ts +109 -0
- package/src/testing.ts +392 -0
- package/src/timeout.ts +14 -0
- package/src/types.ts +252 -0
- package/src/upkeepRules.ts +46 -0
- package/src/util.ts +17 -0
- package/src/weekly.ts +167 -0
- package/starter/index.ts +86 -0
- package/templates/app/api/cron/evals-weekly/route.ts +24 -0
- package/templates/app/evals/[suite]/page.tsx +449 -0
- package/templates/app/evals/[suite]/runs/[runId]/page.tsx +95 -0
- package/templates/app/evals/feedback/page.tsx +219 -0
- package/templates/app/evals/page.tsx +313 -0
- package/templates/components/evalkeeper/AddCase.tsx +62 -0
- package/templates/components/evalkeeper/AnswerKey.tsx +360 -0
- package/templates/components/evalkeeper/CaseMenu.tsx +122 -0
- package/templates/components/evalkeeper/Fields.tsx +110 -0
- package/templates/components/evalkeeper/RefreshWhile.tsx +25 -0
- package/templates/components/evalkeeper/SettingsHistory.tsx +191 -0
- package/templates/components/evalkeeper/Submit.tsx +58 -0
- package/templates/components/evalkeeper/Thumbs.tsx +117 -0
- package/templates/components/evalkeeper/Tip.tsx +98 -0
- package/templates/components/evalkeeper/WeeklyCheck.tsx +124 -0
- package/templates/components/evalkeeper/evalkeeper.css +71 -0
- package/templates/components/evalkeeper/ui.tsx +103 -0
- package/templates/lib/evalkeeper/actions.ts +263 -0
- package/templates/lib/evalkeeper/cronAuth.ts +18 -0
- package/templates/lib/evalkeeper/paths.ts +11 -0
- package/templates/lib/evalkeeper/time.ts +4 -0
- package/templates/lib/evalkeeper/weekly.ts +51 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules for the weekly eval check, pure so they're tested: when a quick-set
|
|
3
|
+
* score has drifted, when a model is about to retire, and when people's
|
|
4
|
+
* thumbs-down rate needs a look.
|
|
5
|
+
*/
|
|
6
|
+
/** A drop this big on the quick set is worth a look. */
|
|
7
|
+
export declare const DRIFT_DROP = 0.15;
|
|
8
|
+
/** Warn about a live or fallback model this long before it retires. */
|
|
9
|
+
export declare const RETIRE_WARN_DAYS = 60;
|
|
10
|
+
/** Thumbs: at least this many ratings, at least this share down. */
|
|
11
|
+
export declare const THUMBS_MIN = 5;
|
|
12
|
+
/**
|
|
13
|
+
* The share of thumbs down that flags a feature (with at least THUMBS_MIN ratings).
|
|
14
|
+
*/
|
|
15
|
+
export declare const THUMBS_DOWN_SHARE = 0.2;
|
|
16
|
+
/** This week's quick-set score against last week's: a drop worth a look. */
|
|
17
|
+
export declare function drifted(previous: number, latest: number): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Days until a "YYYY-MM" or "YYYY-MM-DD" retirement; a month counts from
|
|
20
|
+
* its first day. Negative once it's passed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function daysUntil(retiring: string, today: Date): number;
|
|
23
|
+
/** Whether a model retires within the warning window, or already has. */
|
|
24
|
+
export declare function retiresSoon(retiring: string | undefined, today: Date): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether a feature's ratings need a look: enough of them, and enough down.
|
|
27
|
+
*/
|
|
28
|
+
export declare function thumbsAlert(up: number, down: number): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules for the weekly eval check, pure so they're tested: when a quick-set
|
|
3
|
+
* score has drifted, when a model is about to retire, and when people's
|
|
4
|
+
* thumbs-down rate needs a look.
|
|
5
|
+
*/
|
|
6
|
+
/** A drop this big on the quick set is worth a look. */
|
|
7
|
+
export const DRIFT_DROP = 0.15;
|
|
8
|
+
/** Warn about a live or fallback model this long before it retires. */
|
|
9
|
+
export const RETIRE_WARN_DAYS = 60;
|
|
10
|
+
/** Thumbs: at least this many ratings, at least this share down. */
|
|
11
|
+
export const THUMBS_MIN = 5;
|
|
12
|
+
/**
|
|
13
|
+
* The share of thumbs down that flags a feature (with at least THUMBS_MIN ratings).
|
|
14
|
+
*/
|
|
15
|
+
export const THUMBS_DOWN_SHARE = 0.2;
|
|
16
|
+
/** This week's quick-set score against last week's: a drop worth a look. */
|
|
17
|
+
export function drifted(previous, latest) {
|
|
18
|
+
return previous - latest >= DRIFT_DROP;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Days until a "YYYY-MM" or "YYYY-MM-DD" retirement; a month counts from
|
|
22
|
+
* its first day. Negative once it's passed.
|
|
23
|
+
*/
|
|
24
|
+
export function daysUntil(retiring, today) {
|
|
25
|
+
const [y = 1970, m = 1, d = 1] = retiring.split("-").map(Number);
|
|
26
|
+
const when = Date.UTC(y, (m || 1) - 1, d || 1);
|
|
27
|
+
return Math.floor((when - today.getTime()) / 86_400_000);
|
|
28
|
+
}
|
|
29
|
+
/** Whether a model retires within the warning window, or already has. */
|
|
30
|
+
export function retiresSoon(retiring, today) {
|
|
31
|
+
return (retiring !== undefined && daysUntil(retiring, today) <= RETIRE_WARN_DAYS);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a feature's ratings need a look: enough of them, and enough down.
|
|
35
|
+
*/
|
|
36
|
+
export function thumbsAlert(up, down) {
|
|
37
|
+
const n = up + down;
|
|
38
|
+
return n >= THUMBS_MIN && down / n >= THUMBS_DOWN_SHARE;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=upkeepRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeepRules.js","sourceRoot":"","sources":["../src/upkeepRules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC;AAC/B,uEAAuE;AACvE,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,MAAc;IACtD,OAAO,QAAQ,GAAG,MAAM,IAAI,UAAU,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,KAAW;IACrD,MAAM,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC;AAC3D,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,WAAW,CAAC,QAA4B,EAAE,KAAW;IACnE,OAAO,CACL,QAAQ,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,gBAAgB,CACzE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU,EAAE,IAAY;IAClD,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,IAAI,UAAU,IAAI,IAAI,GAAG,CAAC,IAAI,iBAAiB,CAAC;AAC1D,CAAC"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The item at `i`, for indexes the caller has already checked (a loop
|
|
3
|
+
* bound, a non-empty list). Throws instead of returning undefined, so a
|
|
4
|
+
* wrong index fails loudly rather than as NaN or a missing row.
|
|
5
|
+
*/
|
|
6
|
+
export declare function at<T>(items: readonly T[], i: number): T;
|
|
7
|
+
/** A map's value for a key the caller knows is there. Throws otherwise. */
|
|
8
|
+
export declare function mustGet<K, V>(map: ReadonlyMap<K, V>, key: K): V;
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The item at `i`, for indexes the caller has already checked (a loop
|
|
3
|
+
* bound, a non-empty list). Throws instead of returning undefined, so a
|
|
4
|
+
* wrong index fails loudly rather than as NaN or a missing row.
|
|
5
|
+
*/
|
|
6
|
+
export function at(items, i) {
|
|
7
|
+
if (i < 0 || i >= items.length)
|
|
8
|
+
throw new RangeError(`Index ${i} is outside 0..${items.length - 1}`);
|
|
9
|
+
return items[i];
|
|
10
|
+
}
|
|
11
|
+
/** A map's value for a key the caller knows is there. Throws otherwise. */
|
|
12
|
+
export function mustGet(map, key) {
|
|
13
|
+
const v = map.get(key);
|
|
14
|
+
if (v === undefined)
|
|
15
|
+
throw new Error(`Missing ${String(key)}`);
|
|
16
|
+
return v;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,EAAE,CAAI,KAAmB,EAAE,CAAS;IAClD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM;QAC5B,MAAM,IAAI,UAAU,CAAC,SAAS,CAAC,kBAAkB,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,KAAK,CAAC,CAAC,CAAM,CAAC;AACvB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAO,GAAsB,EAAE,GAAM;IAC1D,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/dist/weekly.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ThumbsSummary } from "./feedback.js";
|
|
2
|
+
import { type ModelCatalog } from "./models.js";
|
|
3
|
+
import type { RunMetrics, RunRecord } from "./store.js";
|
|
4
|
+
/**
|
|
5
|
+
* The weekly check: each suite's quick set on its live model, with fresh
|
|
6
|
+
* answers, so a provider quietly changing a model under the same name shows
|
|
7
|
+
* up as drift. The summary also flags models retiring soon and features
|
|
8
|
+
* people rate poorly. It only reports; a person decides what to change.
|
|
9
|
+
* Pure.
|
|
10
|
+
*/
|
|
11
|
+
/** `startedBy` on tests the weekly check starts, so they're told apart. */
|
|
12
|
+
export declare const SCHEDULED = "schedule";
|
|
13
|
+
/**
|
|
14
|
+
* What the last weekly check found: drift, unfinished checks, retiring models, and
|
|
15
|
+
* thumbs-down, plus when it ran and what it cost.
|
|
16
|
+
*/
|
|
17
|
+
export type WeeklySummary = {
|
|
18
|
+
/** When the last weekly check started; null before the first. */
|
|
19
|
+
lastAt: Date | null;
|
|
20
|
+
/** What last week's quick sets cost (reused answers are free). */
|
|
21
|
+
costUsd: number;
|
|
22
|
+
/** Quick sets still running. */
|
|
23
|
+
running: number;
|
|
24
|
+
drift: {
|
|
25
|
+
suite: string;
|
|
26
|
+
label: string;
|
|
27
|
+
model: string;
|
|
28
|
+
previous: number;
|
|
29
|
+
latest: number;
|
|
30
|
+
}[];
|
|
31
|
+
/** Quick sets that didn't finish, and why. */
|
|
32
|
+
failed: {
|
|
33
|
+
suite: string;
|
|
34
|
+
label: string;
|
|
35
|
+
error: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** Live and fallback models retiring within the warning window. */
|
|
38
|
+
retiring: {
|
|
39
|
+
suite: string;
|
|
40
|
+
label: string;
|
|
41
|
+
model: string;
|
|
42
|
+
role: "live" | "fallback";
|
|
43
|
+
when: string;
|
|
44
|
+
days: number;
|
|
45
|
+
}[];
|
|
46
|
+
/** Features with a high thumbs-down share this week. */
|
|
47
|
+
thumbs: ThumbsSummary[];
|
|
48
|
+
/** Ran, and nothing needs a look. */
|
|
49
|
+
clear: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** A test's headline score: parts right where it gives partial credit. */
|
|
52
|
+
export declare function headline(m: RunMetrics | null): number | null;
|
|
53
|
+
/**
|
|
54
|
+
* What the last weekly check found, from every suite's tests.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const runs = (await Promise.all(SUITES.map((s) => store.listRuns(s.key, { limit: 50 })))).flat();
|
|
59
|
+
* const weekly = weeklySummary({ runs, suites: SUITES, routes, models: MODELS, thumbs });
|
|
60
|
+
* if (!weekly.clear) console.log(weekly.drift, weekly.retiring);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function weeklySummary(args: {
|
|
64
|
+
/** Every suite's tests, newest first or not. */
|
|
65
|
+
runs: RunRecord[];
|
|
66
|
+
suites: {
|
|
67
|
+
key: string;
|
|
68
|
+
label: string;
|
|
69
|
+
operation: string;
|
|
70
|
+
}[];
|
|
71
|
+
/** Each operation's live and fallback model. */
|
|
72
|
+
routes: Record<string, {
|
|
73
|
+
model: string;
|
|
74
|
+
fallbackModel: string | null;
|
|
75
|
+
}>;
|
|
76
|
+
models: ModelCatalog;
|
|
77
|
+
/** From thumbsSummary over the last week; only flagged ones are shown. */
|
|
78
|
+
thumbs?: ThumbsSummary[];
|
|
79
|
+
today?: Date;
|
|
80
|
+
}): WeeklySummary;
|
package/dist/weekly.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { retiresLabel } from "./models.js";
|
|
2
|
+
import { daysUntil, drifted, retiresSoon } from "./upkeepRules.js";
|
|
3
|
+
/**
|
|
4
|
+
* The weekly check: each suite's quick set on its live model, with fresh
|
|
5
|
+
* answers, so a provider quietly changing a model under the same name shows
|
|
6
|
+
* up as drift. The summary also flags models retiring soon and features
|
|
7
|
+
* people rate poorly. It only reports; a person decides what to change.
|
|
8
|
+
* Pure.
|
|
9
|
+
*/
|
|
10
|
+
/** `startedBy` on tests the weekly check starts, so they're told apart. */
|
|
11
|
+
export const SCHEDULED = "schedule";
|
|
12
|
+
const DAY = 86_400_000;
|
|
13
|
+
/** A test's headline score: parts right where it gives partial credit. */
|
|
14
|
+
export function headline(m) {
|
|
15
|
+
if (!m)
|
|
16
|
+
return null;
|
|
17
|
+
return m.units && m.units.total
|
|
18
|
+
? m.units.right / m.units.total
|
|
19
|
+
: (m.allPass.rate ?? 0);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What the last weekly check found, from every suite's tests.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const runs = (await Promise.all(SUITES.map((s) => store.listRuns(s.key, { limit: 50 })))).flat();
|
|
27
|
+
* const weekly = weeklySummary({ runs, suites: SUITES, routes, models: MODELS, thumbs });
|
|
28
|
+
* if (!weekly.clear) console.log(weekly.drift, weekly.retiring);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function weeklySummary(args) {
|
|
32
|
+
const today = args.today ?? new Date();
|
|
33
|
+
const label = (key) => args.suites.find((s) => s.key === key)?.label ?? key;
|
|
34
|
+
const scheduled = args.runs
|
|
35
|
+
.filter((r) => r.startedBy === SCHEDULED && r.set === "SMOKE")
|
|
36
|
+
.sort((a, b) => +b.startedAt - +a.startedAt);
|
|
37
|
+
const lastAt = scheduled[0]?.startedAt ?? null;
|
|
38
|
+
// The last weekly batch: everything started within a day of the newest.
|
|
39
|
+
const batch = lastAt
|
|
40
|
+
? scheduled.filter((r) => +lastAt - +r.startedAt < DAY)
|
|
41
|
+
: [];
|
|
42
|
+
const drift = [];
|
|
43
|
+
const failed = [];
|
|
44
|
+
for (const r of batch) {
|
|
45
|
+
if (r.status === "FAILED") {
|
|
46
|
+
failed.push({
|
|
47
|
+
suite: r.suite,
|
|
48
|
+
label: label(r.suite),
|
|
49
|
+
error: r.error ?? "unknown error",
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (r.status !== "SUCCEEDED")
|
|
54
|
+
continue;
|
|
55
|
+
// The last check of the same model, instructions, and grading, a day or
|
|
56
|
+
// more earlier: anything else changed for a known reason.
|
|
57
|
+
const prior = scheduled.find((p) => p.suite === r.suite &&
|
|
58
|
+
p.status === "SUCCEEDED" &&
|
|
59
|
+
p.model === r.model &&
|
|
60
|
+
p.promptVersion === r.promptVersion &&
|
|
61
|
+
p.graderVersion === r.graderVersion &&
|
|
62
|
+
+r.startedAt - +p.startedAt >= DAY);
|
|
63
|
+
const latest = headline(r.metrics);
|
|
64
|
+
const previous = headline(prior?.metrics ?? null);
|
|
65
|
+
if (latest !== null && previous !== null && drifted(previous, latest))
|
|
66
|
+
drift.push({
|
|
67
|
+
suite: r.suite,
|
|
68
|
+
label: label(r.suite),
|
|
69
|
+
model: args.models[r.model]?.label ?? r.model,
|
|
70
|
+
previous,
|
|
71
|
+
latest,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const retiring = [];
|
|
75
|
+
for (const s of args.suites) {
|
|
76
|
+
const route = args.routes[s.operation];
|
|
77
|
+
if (!route)
|
|
78
|
+
continue;
|
|
79
|
+
for (const [model, role] of [
|
|
80
|
+
[route.model, "live"],
|
|
81
|
+
[route.fallbackModel, "fallback"],
|
|
82
|
+
]) {
|
|
83
|
+
const when = model ? args.models[model]?.retiring : undefined;
|
|
84
|
+
if (model && when && retiresSoon(when, today))
|
|
85
|
+
retiring.push({
|
|
86
|
+
suite: s.key,
|
|
87
|
+
label: s.label,
|
|
88
|
+
model: args.models[model]?.label ?? model,
|
|
89
|
+
role,
|
|
90
|
+
when: retiresLabel(args.models, model) ?? when,
|
|
91
|
+
days: daysUntil(when, today),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const thumbs = (args.thumbs ?? []).filter((t) => t.alert);
|
|
96
|
+
return {
|
|
97
|
+
lastAt,
|
|
98
|
+
costUsd: batch.reduce((t, r) => t + (r.metrics?.costUsd ?? 0), 0),
|
|
99
|
+
running: batch.filter((r) => r.status === "RUNNING").length,
|
|
100
|
+
drift,
|
|
101
|
+
failed,
|
|
102
|
+
retiring,
|
|
103
|
+
thumbs,
|
|
104
|
+
clear: lastAt !== null &&
|
|
105
|
+
!drift.length &&
|
|
106
|
+
!failed.length &&
|
|
107
|
+
!retiring.length &&
|
|
108
|
+
!thumbs.length,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=weekly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weekly.js","sourceRoot":"","sources":["../src/weekly.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowandraven/evalkeeper",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Pure eval logic: suites, checks, stats, model comparisons, and model suggestions on evidence.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./runner": {
|
|
15
|
+
"types": "./dist/runner.d.ts",
|
|
16
|
+
"default": "./dist/runner.js"
|
|
17
|
+
},
|
|
18
|
+
"./memory": {
|
|
19
|
+
"types": "./dist/memoryStore.d.ts",
|
|
20
|
+
"default": "./dist/memoryStore.js"
|
|
21
|
+
},
|
|
22
|
+
"./testing": {
|
|
23
|
+
"types": "./dist/testing.d.ts",
|
|
24
|
+
"default": "./dist/testing.js"
|
|
25
|
+
},
|
|
26
|
+
"./cli": {
|
|
27
|
+
"types": "./dist/cli/main.d.ts",
|
|
28
|
+
"default": "./dist/cli/main.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"src",
|
|
34
|
+
"!src/**/*.test.ts",
|
|
35
|
+
"bin",
|
|
36
|
+
"templates",
|
|
37
|
+
"starter"
|
|
38
|
+
],
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "node scripts/build-templates.mjs && tsc -p tsconfig.build.json",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run --coverage"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"zod": "^3.25.0",
|
|
47
|
+
"vitest": ">=3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"zod": "^3.25.76"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"evals",
|
|
54
|
+
"llm",
|
|
55
|
+
"ai",
|
|
56
|
+
"testing",
|
|
57
|
+
"model-selection"
|
|
58
|
+
],
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"vitest": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"author": "Sean Hudson",
|
|
65
|
+
"homepage": "https://github.com/seanhudson/evalkeeper#readme",
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/seanhudson/evalkeeper/issues"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/seanhudson/evalkeeper.git",
|
|
72
|
+
"directory": "packages/core"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=20"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
},
|
|
80
|
+
"bin": {
|
|
81
|
+
"evalkeeper": "./bin/evalkeeper.mjs"
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/attention.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { BoardRow } from "./board.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What needs attention across suites, for the evals hub: the suite with
|
|
5
|
+
* the most room to improve, the slowest and costliest live models, and the
|
|
6
|
+
* answers waiting for review. From the same boards the suite pages show;
|
|
7
|
+
* only current, complete tests count. Pure.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** One suite's board, as attention reads it. */
|
|
11
|
+
export type SuiteBoard = {
|
|
12
|
+
key: string;
|
|
13
|
+
label: string;
|
|
14
|
+
rows: BoardRow[];
|
|
15
|
+
/** Cases waiting for a person: drafts, and provisional answers. */
|
|
16
|
+
toReview: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* What needs a look across suites: the most room to improve, the slowest and costliest
|
|
21
|
+
* live models, and the answers waiting for review.
|
|
22
|
+
*/
|
|
23
|
+
export type Attention = {
|
|
24
|
+
improve: { key: string; label: string; best: number; model: string } | null;
|
|
25
|
+
slowest: { key: string; label: string; model: string; ms: number } | null;
|
|
26
|
+
costliest: { key: string; label: string; model: string; usd: number } | null;
|
|
27
|
+
review: {
|
|
28
|
+
total: number;
|
|
29
|
+
key: string | null;
|
|
30
|
+
label: string | null;
|
|
31
|
+
most: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const current = (r: BoardRow) =>
|
|
36
|
+
r.test && !r.test.stale && !r.test.failedAnswers ? r.test : null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Finds what needs a look across suites, from their boards. Only current, complete tests count.
|
|
40
|
+
*/
|
|
41
|
+
export function attention(boards: SuiteBoard[]): Attention {
|
|
42
|
+
let improve: Attention["improve"] = null;
|
|
43
|
+
let slowest: Attention["slowest"] = null;
|
|
44
|
+
let costliest: Attention["costliest"] = null;
|
|
45
|
+
for (const b of boards) {
|
|
46
|
+
const tested = b.rows.filter((r) => current(r));
|
|
47
|
+
if (tested.length) {
|
|
48
|
+
const rateOf = (r: BoardRow) => {
|
|
49
|
+
const t = current(r);
|
|
50
|
+
return t ? t.right / t.total : -1;
|
|
51
|
+
};
|
|
52
|
+
const best = tested.reduce((a, r) => (rateOf(r) > rateOf(a) ? r : a));
|
|
53
|
+
const rate = rateOf(best);
|
|
54
|
+
if (!improve || rate < improve.best)
|
|
55
|
+
improve = { key: b.key, label: b.label, best: rate, model: best.label };
|
|
56
|
+
}
|
|
57
|
+
const live = b.rows.find((r) => r.live && current(r));
|
|
58
|
+
const t = live && current(live);
|
|
59
|
+
if (live && t?.avgMs && (!slowest || t.avgMs > slowest.ms))
|
|
60
|
+
slowest = { key: b.key, label: b.label, model: live.label, ms: t.avgMs };
|
|
61
|
+
if (
|
|
62
|
+
live &&
|
|
63
|
+
t?.costPerAnswer &&
|
|
64
|
+
(!costliest || t.costPerAnswer > costliest.usd)
|
|
65
|
+
)
|
|
66
|
+
costliest = {
|
|
67
|
+
key: b.key,
|
|
68
|
+
label: b.label,
|
|
69
|
+
model: live.label,
|
|
70
|
+
usd: t.costPerAnswer,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const most = boards.reduce<SuiteBoard | null>(
|
|
74
|
+
(a, b) => (b.toReview > (a?.toReview ?? 0) ? b : a),
|
|
75
|
+
null,
|
|
76
|
+
);
|
|
77
|
+
return {
|
|
78
|
+
improve,
|
|
79
|
+
slowest,
|
|
80
|
+
costliest,
|
|
81
|
+
review: {
|
|
82
|
+
total: boards.reduce((t, b) => t + b.toReview, 0),
|
|
83
|
+
key: most?.key ?? null,
|
|
84
|
+
label: most?.label ?? null,
|
|
85
|
+
most: most?.toReview ?? 0,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
package/src/board.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Verdict } from "./compare.js";
|
|
2
|
+
import type { SuiteMetrics } from "./stats.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One row of a suite's model board: a model (and reasoning setting) with
|
|
6
|
+
* its latest test on every case in use, and how it compares with the live
|
|
7
|
+
* model. Storage adapters build these; health, attention, and suggest read
|
|
8
|
+
* them.
|
|
9
|
+
*/
|
|
10
|
+
export type BoardRow = {
|
|
11
|
+
/** Model and reasoning setting together. */
|
|
12
|
+
key: string;
|
|
13
|
+
model: string;
|
|
14
|
+
/** The reasoning setting; null: the model's own default. */
|
|
15
|
+
effort: string | null;
|
|
16
|
+
/** At a setting other than the operation's (a "Test a setting" row). */
|
|
17
|
+
variant: boolean;
|
|
18
|
+
label: string;
|
|
19
|
+
live: boolean;
|
|
20
|
+
fallback: boolean;
|
|
21
|
+
testing: boolean;
|
|
22
|
+
/** The newest test failed: why, in a line. Older results still show. */
|
|
23
|
+
failed: string | null;
|
|
24
|
+
/** Estimated from real usage, for models not tested yet. */
|
|
25
|
+
estCostPerAnswer: number | null;
|
|
26
|
+
test: {
|
|
27
|
+
runId: string;
|
|
28
|
+
at: Date;
|
|
29
|
+
/** Parts right of parts expected (or answers, where a suite has no parts). */
|
|
30
|
+
right: number;
|
|
31
|
+
total: number;
|
|
32
|
+
/** Answers with every check passed. */
|
|
33
|
+
answersRight: number;
|
|
34
|
+
answers: number;
|
|
35
|
+
/** Pass rate of each check, for the breakdown table. */
|
|
36
|
+
byCheck: SuiteMetrics["byCheck"];
|
|
37
|
+
/** Per check, probability measures (decision suites); empty otherwise. */
|
|
38
|
+
measures: NonNullable<SuiteMetrics["measures"]>;
|
|
39
|
+
avgMs: number | null;
|
|
40
|
+
costPerAnswer: number | null;
|
|
41
|
+
/** Cases, answers, or instructions changed since: test again. */
|
|
42
|
+
stale: boolean;
|
|
43
|
+
/** Answers that failed (no score), and the usual reason. */
|
|
44
|
+
failedAnswers: number;
|
|
45
|
+
failureReason: string | null;
|
|
46
|
+
} | null;
|
|
47
|
+
/** Against the live model's test, when both are current. */
|
|
48
|
+
vsLive: Verdict | null;
|
|
49
|
+
canMakeLive: boolean;
|
|
50
|
+
/** Tested, current, and from another provider than the live model. */
|
|
51
|
+
canMakeFallback: boolean;
|
|
52
|
+
/** Can be live but not the fallback (a model that only answers typed questions). */
|
|
53
|
+
liveOnly: boolean;
|
|
54
|
+
/** Can be the fallback but not live. */
|
|
55
|
+
fallbackOnly: boolean;
|
|
56
|
+
};
|