@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.
- 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 +49 -0
- package/dist/attention.js.map +1 -0
- package/dist/board.d.ts +57 -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 +76 -0
- package/dist/models.js +56 -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 +202 -0
- package/dist/runner.js +734 -0
- package/dist/runner.js.map +1 -0
- package/dist/settings.d.ts +196 -0
- package/dist/settings.js +272 -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 +151 -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 +152 -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 +362 -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 +225 -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 +113 -0
- package/dist/weekly.js.map +1 -0
- package/package.json +81 -4
- package/src/attention.ts +89 -0
- package/src/board.ts +58 -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 +149 -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 +127 -0
- package/src/prune.ts +95 -0
- package/src/quickSet.ts +55 -0
- package/src/runner.ts +1010 -0
- package/src/settings.ts +441 -0
- package/src/stableJson.ts +24 -0
- package/src/stats.ts +215 -0
- package/src/store.ts +172 -0
- package/src/suggest.ts +247 -0
- package/src/summary.ts +109 -0
- package/src/testing.ts +415 -0
- package/src/timeout.ts +14 -0
- package/src/types.ts +258 -0
- package/src/upkeepRules.ts +46 -0
- package/src/util.ts +17 -0
- package/src/weekly.ts +169 -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
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { ReasoningEffort } from "./models.js";
|
|
3
|
+
/**
|
|
4
|
+
* An eval suite: how to run one AI operation on a frozen case and how to
|
|
5
|
+
* grade what comes back. Code says how to evaluate; the database holds the
|
|
6
|
+
* cases and labels (EvalCase, EvalCaseLabel). See docs/evals.md.
|
|
7
|
+
*/
|
|
8
|
+
/** One check on one output. null: doesn't apply to this case. */
|
|
9
|
+
export type CheckResult = {
|
|
10
|
+
key: string;
|
|
11
|
+
pass: boolean | null;
|
|
12
|
+
/** Why it failed (or a detail worth showing), in plain words. */
|
|
13
|
+
note?: string | undefined;
|
|
14
|
+
/** For matching checks: which expected item matched which output, and why. */
|
|
15
|
+
matches?: {
|
|
16
|
+
expected: string;
|
|
17
|
+
actual: string;
|
|
18
|
+
why: string;
|
|
19
|
+
}[] | undefined;
|
|
20
|
+
/** "meaning": a judge model matched them; "words": shared words did. */
|
|
21
|
+
matchedBy?: "meaning" | "words" | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Partial credit: of the parts this answer should have (e.g. expected
|
|
24
|
+
* items), how many it got right. The headline score sums these.
|
|
25
|
+
*/
|
|
26
|
+
units?: {
|
|
27
|
+
right: number;
|
|
28
|
+
total: number;
|
|
29
|
+
} | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Numbers measured on this answer, averaged across a test: a decision's
|
|
32
|
+
* `brier`, `logLoss`, `confidence`, and `correct` (see decisionCheck).
|
|
33
|
+
*/
|
|
34
|
+
measures?: Record<string, number> | undefined;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* One check a suite runs: what it checks, whether it needs an answer, and how serious a
|
|
38
|
+
* miss is.
|
|
39
|
+
*/
|
|
40
|
+
export type CheckDef = {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
/** What it checks, for the page that explains the suite. */
|
|
44
|
+
description: string;
|
|
45
|
+
/** "code": needs no label. "label": compares with a confirmed label. */
|
|
46
|
+
kind: "code" | "label";
|
|
47
|
+
/**
|
|
48
|
+
* Serious: changes what the product does (which queue a ticket lands
|
|
49
|
+
* in, what a summary claims). Minor: a fair difference of judgment (a category, how finely
|
|
50
|
+
* to split). Models are ranked on serious mistakes.
|
|
51
|
+
*/
|
|
52
|
+
severity: "serious" | "minor";
|
|
53
|
+
/** Pass rate it should reach; default by severity (checkBar). */
|
|
54
|
+
bar?: number;
|
|
55
|
+
};
|
|
56
|
+
/** The pass rate a check should reach: its own, else 95% serious, 75% minor. */
|
|
57
|
+
export declare function checkBar(c: CheckDef): number;
|
|
58
|
+
/** Meets the bar, is within 10 points of it, or is further below. */
|
|
59
|
+
export declare function barStatus(rate: number, bar: number): "meets" | "close" | "below";
|
|
60
|
+
/**
|
|
61
|
+
* What a suite cares about once accuracy holds. Accuracy is always the
|
|
62
|
+
* floor; this is the one thing worth switching models for beyond it.
|
|
63
|
+
*/
|
|
64
|
+
export type SuitePriority = {
|
|
65
|
+
/** speed: someone waits on it. cost: runs in the background, often.
|
|
66
|
+
* quality: people read and act on it, so only accuracy counts. */
|
|
67
|
+
favor: "speed" | "cost" | "quality";
|
|
68
|
+
/** Models slower than this on average are never suggested. */
|
|
69
|
+
maxSeconds?: number;
|
|
70
|
+
};
|
|
71
|
+
/** Each priority, explained for the suite page. */
|
|
72
|
+
export declare const FAVOR_TEXT: Record<SuitePriority["favor"], string>;
|
|
73
|
+
/**
|
|
74
|
+
* An eval suite: how to call one AI feature on a model and how to grade what comes back.
|
|
75
|
+
* Code says how to evaluate; the store holds the cases and answers.
|
|
76
|
+
*/
|
|
77
|
+
export type Suite<Input, Output, Label> = {
|
|
78
|
+
key: string;
|
|
79
|
+
/** The AI feature it tests, as your app names it ("summarize_ticket"). */
|
|
80
|
+
operation: string;
|
|
81
|
+
priority: SuitePriority;
|
|
82
|
+
label: string;
|
|
83
|
+
description: string;
|
|
84
|
+
/** Bump when checks change, so runs say which grading they used. */
|
|
85
|
+
graderVersion: string;
|
|
86
|
+
input: z.ZodType<Input>;
|
|
87
|
+
/** The EXPECTED label's value. */
|
|
88
|
+
labelValue: z.ZodType<Label>;
|
|
89
|
+
checks: CheckDef[];
|
|
90
|
+
/** Calls the operation on exactly this model, recorded as an eval. */
|
|
91
|
+
run(input: Input, ctx: {
|
|
92
|
+
model: string;
|
|
93
|
+
/** A reasoning setting to test; undefined: the operation's own. */
|
|
94
|
+
reasoningEffort?: ReasoningEffort | null;
|
|
95
|
+
/**
|
|
96
|
+
* The temperature the test runs at: the operation's live one when the
|
|
97
|
+
* test was started. Null: send none. Undefined from runners before
|
|
98
|
+
* temperature was versioned.
|
|
99
|
+
*/
|
|
100
|
+
temperature?: number | null;
|
|
101
|
+
onCost: (usd: number) => void;
|
|
102
|
+
}): Promise<Output>;
|
|
103
|
+
/**
|
|
104
|
+
* Grades one output. `label` is the confirmed one, or null. A check that
|
|
105
|
+
* asks a judge model reports its cost through `ctx.onCost`.
|
|
106
|
+
*/
|
|
107
|
+
grade(input: Input, output: Output, label: Label | null, ctx?: {
|
|
108
|
+
onCost: (usd: number) => void;
|
|
109
|
+
}): Promise<CheckResult[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Checks that compare a test's answers with each other, such as ranking.
|
|
112
|
+
* Gets every graded answer of one repeat; returns extra checks for each,
|
|
113
|
+
* in the same order. Pure; runs after a test and on every regrade.
|
|
114
|
+
*/
|
|
115
|
+
gradeTogether?(answers: {
|
|
116
|
+
input: Input;
|
|
117
|
+
output: Output;
|
|
118
|
+
label: Label | null;
|
|
119
|
+
}[]): CheckResult[][];
|
|
120
|
+
/** Reads a case's input from the add/edit form; throws on bad input. */
|
|
121
|
+
inputFromForm(form: FormData): Input;
|
|
122
|
+
/** Reads an answer from the add/edit form; throws on bad input. */
|
|
123
|
+
labelFromForm?(form: FormData): Label;
|
|
124
|
+
/**
|
|
125
|
+
* The operation's output as an answer, so a thumbs-up can propose it
|
|
126
|
+
* (pending until a person confirms it). Omit when outputs and answers
|
|
127
|
+
* differ too much to propose one.
|
|
128
|
+
*/
|
|
129
|
+
labelFromOutput?(output: Output): Label;
|
|
130
|
+
/**
|
|
131
|
+
* The form's fields, so pages can render one for any suite. Every field
|
|
132
|
+
* has a plain label and help: people confirming answers weeks later
|
|
133
|
+
* won't remember what a field means.
|
|
134
|
+
*/
|
|
135
|
+
fields?: {
|
|
136
|
+
input: FormField[];
|
|
137
|
+
label: FormField[];
|
|
138
|
+
};
|
|
139
|
+
/** Drafted cases to start from, added as drafts with PENDING labels. */
|
|
140
|
+
starter?: {
|
|
141
|
+
input: Input;
|
|
142
|
+
label: Label;
|
|
143
|
+
tags: string[];
|
|
144
|
+
split: "DEV" | "TEST";
|
|
145
|
+
smoke?: boolean;
|
|
146
|
+
note: string;
|
|
147
|
+
}[];
|
|
148
|
+
/**
|
|
149
|
+
* Drafts new cases aimed at what the answer key is missing, for a person
|
|
150
|
+
* to review (they arrive with PENDING answers).
|
|
151
|
+
*/
|
|
152
|
+
draftMore?(args: {
|
|
153
|
+
existing: {
|
|
154
|
+
input: Input;
|
|
155
|
+
tags: string[];
|
|
156
|
+
}[];
|
|
157
|
+
count: number;
|
|
158
|
+
focus: string | null;
|
|
159
|
+
}): Promise<{
|
|
160
|
+
input: Input;
|
|
161
|
+
label: Label;
|
|
162
|
+
tags: string[];
|
|
163
|
+
note: string;
|
|
164
|
+
}[]>;
|
|
165
|
+
/**
|
|
166
|
+
* How long one answer may take before it counts as failed. Default 60s;
|
|
167
|
+
* multi-step agent turns need more. Keep it under the run budget.
|
|
168
|
+
*/
|
|
169
|
+
answerTimeoutMs?: number;
|
|
170
|
+
/**
|
|
171
|
+
* How many answers a test asks for at once. Default 6; slow operations
|
|
172
|
+
* need more to finish before the platform's time limit.
|
|
173
|
+
*/
|
|
174
|
+
concurrency?: number;
|
|
175
|
+
/**
|
|
176
|
+
* Graded as decisions (typed answers with probabilities, as from the AI
|
|
177
|
+
* SDK's `experimental_decide`), so pages show probability quality and
|
|
178
|
+
* calibration next to accuracy, and which models' probabilities are
|
|
179
|
+
* native or prompted.
|
|
180
|
+
*/
|
|
181
|
+
decisions?: boolean;
|
|
182
|
+
/** What the headline counts: "items", "tickets", … Default "answers". */
|
|
183
|
+
unitNoun?: string;
|
|
184
|
+
/**
|
|
185
|
+
* "typed": cases are text someone types (added by hand, or drafted).
|
|
186
|
+
* "imported": cases are frozen from the app's own data, then rated by a
|
|
187
|
+
* person.
|
|
188
|
+
* "fixture": cases are structured inputs, shown with describe() and
|
|
189
|
+
* edited as JSON.
|
|
190
|
+
*/
|
|
191
|
+
caseKind?: "typed" | "imported" | "fixture";
|
|
192
|
+
/** A short line naming the case in lists. */
|
|
193
|
+
title(input: Input): string;
|
|
194
|
+
/** Fixture suites: the whole input, readable, for reviewing a case. */
|
|
195
|
+
describe?(input: Input): string;
|
|
196
|
+
/** Fixture suites: what the expected answer's fields mean. */
|
|
197
|
+
labelHelp?: string;
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* One field of a suite's add/edit form. `name` is the form key (and the
|
|
201
|
+
* input or answer property it fills). "lines": one item per line, read as
|
|
202
|
+
* a list.
|
|
203
|
+
*/
|
|
204
|
+
export type FormField = {
|
|
205
|
+
name: string;
|
|
206
|
+
label: string;
|
|
207
|
+
help: string;
|
|
208
|
+
kind: "text" | "textarea" | "select" | "lines";
|
|
209
|
+
/** For "select". */
|
|
210
|
+
options?: readonly string[];
|
|
211
|
+
required?: boolean;
|
|
212
|
+
};
|
|
213
|
+
/** A value as plain text: strings as they are, other values as JSON. */
|
|
214
|
+
export declare function asText(v: unknown): string;
|
|
215
|
+
/** A form value as text: "" when missing or a file. */
|
|
216
|
+
export declare function formText(form: FormData, name: string): string;
|
|
217
|
+
/** A field's value from a stored input or answer, as the form shows it. */
|
|
218
|
+
export declare function fieldValue(field: FormField, value: unknown): string;
|
|
219
|
+
/** A "lines" field's value as a list: one item per non-empty line. */
|
|
220
|
+
export declare function linesOf(form: FormData, name: string): string[];
|
|
221
|
+
/**
|
|
222
|
+
* A suite of any shape, for registries. Its methods take and return
|
|
223
|
+
* `unknown`; a suite's own code keeps its precise types.
|
|
224
|
+
*/
|
|
225
|
+
export type AnySuite = Suite<unknown, unknown, unknown>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** The pass rate a check should reach: its own, else 95% serious, 75% minor. */
|
|
2
|
+
export function checkBar(c) {
|
|
3
|
+
return c.bar ?? (c.severity === "serious" ? 0.95 : 0.75);
|
|
4
|
+
}
|
|
5
|
+
/** Meets the bar, is within 10 points of it, or is further below. */
|
|
6
|
+
export function barStatus(rate, bar) {
|
|
7
|
+
return rate >= bar ? "meets" : rate >= bar - 0.1 ? "close" : "below";
|
|
8
|
+
}
|
|
9
|
+
/** Each priority, explained for the suite page. */
|
|
10
|
+
export const FAVOR_TEXT = {
|
|
11
|
+
speed: "speed: someone waits on this, so a model that scores as well and answers clearly faster is worth switching to, and so is one about as accurate (within 10 points) in half the time",
|
|
12
|
+
cost: "cost: this runs in the background, often, so a model that scores as well and is clearly cheaper is worth switching to, and so is one about as accurate (within 10 points) at half the cost",
|
|
13
|
+
quality: "quality: people read and act on this, so only clearly better accuracy or fewer serious mistakes is worth switching for",
|
|
14
|
+
};
|
|
15
|
+
/** A value as plain text: strings as they are, other values as JSON. */
|
|
16
|
+
export function asText(v) {
|
|
17
|
+
if (v === undefined || v === null)
|
|
18
|
+
return "";
|
|
19
|
+
if (typeof v === "string")
|
|
20
|
+
return v;
|
|
21
|
+
if (typeof v === "number" || typeof v === "boolean")
|
|
22
|
+
return String(v);
|
|
23
|
+
return JSON.stringify(v);
|
|
24
|
+
}
|
|
25
|
+
/** A form value as text: "" when missing or a file. */
|
|
26
|
+
export function formText(form, name) {
|
|
27
|
+
const v = form.get(name);
|
|
28
|
+
return typeof v === "string" ? v.trim() : "";
|
|
29
|
+
}
|
|
30
|
+
/** A field's value from a stored input or answer, as the form shows it. */
|
|
31
|
+
export function fieldValue(field, value) {
|
|
32
|
+
const v = value && typeof value === "object"
|
|
33
|
+
? value[field.name]
|
|
34
|
+
: undefined;
|
|
35
|
+
if (Array.isArray(v))
|
|
36
|
+
return v.map(asText).join("\n");
|
|
37
|
+
return asText(v);
|
|
38
|
+
}
|
|
39
|
+
/** A "lines" field's value as a list: one item per non-empty line. */
|
|
40
|
+
export function linesOf(form, name) {
|
|
41
|
+
return formText(form, name)
|
|
42
|
+
.split("\n")
|
|
43
|
+
.map((l) => l.trim())
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoDA,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,CAAW;IAClC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,GAAW;IAEX,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;AACvE,CAAC;AAcD,mDAAmD;AACnD,MAAM,CAAC,MAAM,UAAU,GAA2C;IAChE,KAAK,EACH,oLAAoL;IACtL,IAAI,EAAE,4LAA4L;IAClM,OAAO,EACL,wHAAwH;CAC3H,CAAC;AAyIF,wEAAwE;AACxE,MAAM,UAAU,MAAM,CAAC,CAAU;IAC/B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,IAAY;IACnD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,KAAgB,EAAE,KAAc;IACzD,MAAM,CAAC,GACL,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAChC,CAAC,CAAE,KAAiC,CAAC,KAAK,CAAC,IAAI,CAAC;QAChD,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,OAAO,CAAC,IAAc,EAAE,IAAY;IAClD,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;SACxB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -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,113 @@
|
|
|
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, temperature, instructions, and
|
|
56
|
+
// grading, a day or more earlier: anything else changed for a known
|
|
57
|
+
// reason.
|
|
58
|
+
const prior = scheduled.find((p) => p.suite === r.suite &&
|
|
59
|
+
p.status === "SUCCEEDED" &&
|
|
60
|
+
p.model === r.model &&
|
|
61
|
+
(p.temperature ?? null) === (r.temperature ?? null) &&
|
|
62
|
+
p.promptVersion === r.promptVersion &&
|
|
63
|
+
p.graderVersion === r.graderVersion &&
|
|
64
|
+
+r.startedAt - +p.startedAt >= DAY);
|
|
65
|
+
const latest = headline(r.metrics);
|
|
66
|
+
const previous = headline(prior?.metrics ?? null);
|
|
67
|
+
if (latest !== null && previous !== null && drifted(previous, latest))
|
|
68
|
+
drift.push({
|
|
69
|
+
suite: r.suite,
|
|
70
|
+
label: label(r.suite),
|
|
71
|
+
model: args.models[r.model]?.label ?? r.model,
|
|
72
|
+
previous,
|
|
73
|
+
latest,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const retiring = [];
|
|
77
|
+
for (const s of args.suites) {
|
|
78
|
+
const route = args.routes[s.operation];
|
|
79
|
+
if (!route)
|
|
80
|
+
continue;
|
|
81
|
+
for (const [model, role] of [
|
|
82
|
+
[route.model, "live"],
|
|
83
|
+
[route.fallbackModel, "fallback"],
|
|
84
|
+
]) {
|
|
85
|
+
const when = model ? args.models[model]?.retiring : undefined;
|
|
86
|
+
if (model && when && retiresSoon(when, today))
|
|
87
|
+
retiring.push({
|
|
88
|
+
suite: s.key,
|
|
89
|
+
label: s.label,
|
|
90
|
+
model: args.models[model]?.label ?? model,
|
|
91
|
+
role,
|
|
92
|
+
when: retiresLabel(args.models, model) ?? when,
|
|
93
|
+
days: daysUntil(when, today),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const thumbs = (args.thumbs ?? []).filter((t) => t.alert);
|
|
98
|
+
return {
|
|
99
|
+
lastAt,
|
|
100
|
+
costUsd: batch.reduce((t, r) => t + (r.metrics?.costUsd ?? 0), 0),
|
|
101
|
+
running: batch.filter((r) => r.status === "RUNNING").length,
|
|
102
|
+
drift,
|
|
103
|
+
failed,
|
|
104
|
+
retiring,
|
|
105
|
+
thumbs,
|
|
106
|
+
clear: lastAt !== null &&
|
|
107
|
+
!drift.length &&
|
|
108
|
+
!failed.length &&
|
|
109
|
+
!retiring.length &&
|
|
110
|
+
!thumbs.length,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=weekly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weekly.js","sourceRoot":"","sources":["../src/weekly.ts"],"names":[],"mappings":"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"}
|