@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
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { notFound } from "next/navigation";
|
|
3
|
+
import {
|
|
4
|
+
FAVOR_TEXT,
|
|
5
|
+
checkBar,
|
|
6
|
+
meanCalibrationError,
|
|
7
|
+
money,
|
|
8
|
+
probabilityQuality,
|
|
9
|
+
perAnswer,
|
|
10
|
+
type ReasoningEffort,
|
|
11
|
+
retiresLabel,
|
|
12
|
+
seconds,
|
|
13
|
+
seriousMistakes,
|
|
14
|
+
suggest,
|
|
15
|
+
} from "@crowandraven/evalkeeper";
|
|
16
|
+
import { AnswerKey } from "@/components/evalkeeper/AnswerKey";
|
|
17
|
+
import { RefreshWhile } from "@/components/evalkeeper/RefreshWhile";
|
|
18
|
+
import { IconSubmit, Spinner, Submit } from "@/components/evalkeeper/Submit";
|
|
19
|
+
import { RotateCw } from "lucide-react";
|
|
20
|
+
import { BackLink, Card, Chip, Score, pct } from "@/components/evalkeeper/ui";
|
|
21
|
+
import {
|
|
22
|
+
MODELS,
|
|
23
|
+
requireAdmin,
|
|
24
|
+
runner,
|
|
25
|
+
settings,
|
|
26
|
+
SUITE_BY_KEY,
|
|
27
|
+
} from "@/lib/evalkeeper";
|
|
28
|
+
import { SettingsHistory } from "@/components/evalkeeper/SettingsHistory";
|
|
29
|
+
import {
|
|
30
|
+
makeFallback,
|
|
31
|
+
makeLive,
|
|
32
|
+
rescore,
|
|
33
|
+
testAll,
|
|
34
|
+
testModel,
|
|
35
|
+
useSuggestion,
|
|
36
|
+
} from "@/lib/evalkeeper/actions";
|
|
37
|
+
import { InfoTip, Tip } from "@/components/evalkeeper/Tip";
|
|
38
|
+
import { EVALS_BASE, evalsPath } from "@/lib/evalkeeper/paths";
|
|
39
|
+
|
|
40
|
+
export const dynamic = "force-dynamic";
|
|
41
|
+
|
|
42
|
+
/** A share as a whole percent, or a dash when there's nothing to show. */
|
|
43
|
+
const asPct = (x: number | null) => (x === null ? "—" : pct(x));
|
|
44
|
+
|
|
45
|
+
const VS: Record<string, string> = {
|
|
46
|
+
BETTER: "More accurate",
|
|
47
|
+
SAME: "About as accurate",
|
|
48
|
+
WORSE: "Less accurate",
|
|
49
|
+
TOO_FEW: "Too few cases to tell",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default async function SuitePage({
|
|
53
|
+
params,
|
|
54
|
+
}: {
|
|
55
|
+
params: Promise<{ suite: string }>;
|
|
56
|
+
}) {
|
|
57
|
+
await requireAdmin();
|
|
58
|
+
const { suite: key } = await params;
|
|
59
|
+
if (!SUITE_BY_KEY[key]) notFound();
|
|
60
|
+
const b = await runner.board(key);
|
|
61
|
+
// Records a deploy if the instructions changed under the live settings.
|
|
62
|
+
await settings.syncPrompt(b.suite.operation);
|
|
63
|
+
const { suite, rows } = b;
|
|
64
|
+
const s = suggest(rows, suite.checks, suite.priority, MODELS);
|
|
65
|
+
const testing = rows.some((r) => r.testing);
|
|
66
|
+
const [history, liveSettings] = await Promise.all([
|
|
67
|
+
settings.history(suite.operation),
|
|
68
|
+
settings.current(suite.operation),
|
|
69
|
+
]);
|
|
70
|
+
// What testing each model would cost now, counting answers it would reuse.
|
|
71
|
+
const estimates = new Map(
|
|
72
|
+
await Promise.all(
|
|
73
|
+
rows.map(
|
|
74
|
+
async (r) =>
|
|
75
|
+
[
|
|
76
|
+
r.key,
|
|
77
|
+
await runner.estimateRun(key, r.model, "ALL", 1, {
|
|
78
|
+
reasoningEffort: r.effort as ReasoningEffort | null,
|
|
79
|
+
}),
|
|
80
|
+
] as const,
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
const live = rows.find((r) => r.live);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="space-y-8">
|
|
88
|
+
<RefreshWhile active={testing} />
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<BackLink href={EVALS_BASE}>Evals</BackLink>
|
|
91
|
+
<h1 className="text-2xl font-semibold">{suite.label}</h1>
|
|
92
|
+
<p className="readable text-muted">{suite.description}</p>
|
|
93
|
+
<p className="readable text-sm text-muted">
|
|
94
|
+
Priority: {FAVOR_TEXT[suite.priority.favor]}.
|
|
95
|
+
<InfoTip term="priority" />
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{s && !s.same && (
|
|
100
|
+
<Card className="space-y-3">
|
|
101
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
102
|
+
<h2 className="font-semibold">
|
|
103
|
+
Suggested
|
|
104
|
+
<InfoTip term="suggestion" />
|
|
105
|
+
</h2>
|
|
106
|
+
<form action={useSuggestion}>
|
|
107
|
+
<input type="hidden" name="suite" value={key} />
|
|
108
|
+
<Submit>Use these</Submit>
|
|
109
|
+
</form>
|
|
110
|
+
</div>
|
|
111
|
+
<p>
|
|
112
|
+
Live: <strong>{s.live.label}</strong>
|
|
113
|
+
{s.fallback && (
|
|
114
|
+
<>
|
|
115
|
+
{" "}
|
|
116
|
+
· Fallback: <strong>{s.fallback.label}</strong>
|
|
117
|
+
</>
|
|
118
|
+
)}
|
|
119
|
+
</p>
|
|
120
|
+
<ul className="readable list-disc space-y-1 pl-5 text-sm text-muted">
|
|
121
|
+
{s.reasons.map((r) => (
|
|
122
|
+
<li key={r}>{r}</li>
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
</Card>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
<section className="space-y-3">
|
|
129
|
+
<div className="flex items-center justify-between gap-4">
|
|
130
|
+
<h2 className="text-lg font-semibold">Models</h2>
|
|
131
|
+
<form action={testAll}>
|
|
132
|
+
<input type="hidden" name="suite" value={key} />
|
|
133
|
+
<Submit variant="quiet">Test every model</Submit>
|
|
134
|
+
</form>
|
|
135
|
+
</div>
|
|
136
|
+
<Card className="overflow-x-auto p-0 lg:overflow-visible">
|
|
137
|
+
<table className="w-full text-sm">
|
|
138
|
+
<thead className="text-left text-muted">
|
|
139
|
+
<tr className="border-b border-line">
|
|
140
|
+
<th className="p-3 font-normal">Model</th>
|
|
141
|
+
<th className="p-3 font-normal">
|
|
142
|
+
<Tip term="score" side="bottom">
|
|
143
|
+
Score
|
|
144
|
+
</Tip>
|
|
145
|
+
</th>
|
|
146
|
+
<th className="p-3 font-normal">
|
|
147
|
+
<Tip term="seriousMistakes" side="bottom">
|
|
148
|
+
Serious mistakes
|
|
149
|
+
</Tip>
|
|
150
|
+
</th>
|
|
151
|
+
<th className="p-3 font-normal">
|
|
152
|
+
<Tip term="time" side="bottom" align="end">
|
|
153
|
+
Time
|
|
154
|
+
</Tip>
|
|
155
|
+
</th>
|
|
156
|
+
{suite.decisions && (
|
|
157
|
+
<>
|
|
158
|
+
<th className="p-3 font-normal">
|
|
159
|
+
<Tip term="probabilityQuality" side="bottom">
|
|
160
|
+
Probability quality
|
|
161
|
+
</Tip>
|
|
162
|
+
</th>
|
|
163
|
+
<th className="p-3 font-normal">
|
|
164
|
+
<Tip term="calibrationError" side="bottom" align="end">
|
|
165
|
+
Calibration error
|
|
166
|
+
</Tip>
|
|
167
|
+
</th>
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
<th className="p-3 font-normal">
|
|
171
|
+
<Tip term="cost" side="bottom" align="end">
|
|
172
|
+
Cost
|
|
173
|
+
</Tip>
|
|
174
|
+
</th>
|
|
175
|
+
<th className="p-3 font-normal">
|
|
176
|
+
<Tip term="againstLive" side="bottom" align="end">
|
|
177
|
+
Against live
|
|
178
|
+
</Tip>
|
|
179
|
+
</th>
|
|
180
|
+
<th className="p-3" />
|
|
181
|
+
</tr>
|
|
182
|
+
</thead>
|
|
183
|
+
<tbody>
|
|
184
|
+
{rows.map((r) => (
|
|
185
|
+
<tr key={r.key} className="border-b border-line last:border-0">
|
|
186
|
+
<td className="p-3">
|
|
187
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
188
|
+
<span
|
|
189
|
+
className={r.live ? "font-semibold" : "font-medium"}
|
|
190
|
+
>
|
|
191
|
+
{r.label}
|
|
192
|
+
</span>
|
|
193
|
+
{r.live && (
|
|
194
|
+
<Tip term="live" side="bottom" id={`live-${r.key}`}>
|
|
195
|
+
<Chip tone="live">Live</Chip>
|
|
196
|
+
</Tip>
|
|
197
|
+
)}
|
|
198
|
+
{r.fallback && (
|
|
199
|
+
<Tip
|
|
200
|
+
term="fallback"
|
|
201
|
+
side="bottom"
|
|
202
|
+
id={`fallback-${r.key}`}
|
|
203
|
+
>
|
|
204
|
+
<Chip tone="fallback">Fallback</Chip>
|
|
205
|
+
</Tip>
|
|
206
|
+
)}
|
|
207
|
+
{retiresLabel(MODELS, r.model) && (
|
|
208
|
+
<Tip
|
|
209
|
+
term="retiring"
|
|
210
|
+
side="bottom"
|
|
211
|
+
id={`retiring-${r.key}`}
|
|
212
|
+
>
|
|
213
|
+
<Chip tone="warn">
|
|
214
|
+
Retires {retiresLabel(MODELS, r.model)}
|
|
215
|
+
</Chip>
|
|
216
|
+
</Tip>
|
|
217
|
+
)}
|
|
218
|
+
{suite.decisions && MODELS[r.model]?.decisions && (
|
|
219
|
+
<Tip
|
|
220
|
+
term={
|
|
221
|
+
MODELS[r.model]?.decisions === "native"
|
|
222
|
+
? "nativeProbabilities"
|
|
223
|
+
: "promptedProbabilities"
|
|
224
|
+
}
|
|
225
|
+
side="bottom"
|
|
226
|
+
id={`decisions-${r.key}`}
|
|
227
|
+
>
|
|
228
|
+
<Chip tone="neutral">
|
|
229
|
+
{MODELS[r.model]?.decisions === "native"
|
|
230
|
+
? "Native probabilities"
|
|
231
|
+
: "Prompted"}
|
|
232
|
+
</Chip>
|
|
233
|
+
</Tip>
|
|
234
|
+
)}
|
|
235
|
+
{r.test?.stale && (
|
|
236
|
+
<Tip
|
|
237
|
+
term="outOfDate"
|
|
238
|
+
side="bottom"
|
|
239
|
+
id={`stale-${r.key}`}
|
|
240
|
+
>
|
|
241
|
+
<Chip tone="neutral">Out of date</Chip>
|
|
242
|
+
</Tip>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
</td>
|
|
246
|
+
<td className="p-3">
|
|
247
|
+
{r.testing ? (
|
|
248
|
+
<span
|
|
249
|
+
role="status"
|
|
250
|
+
title="Testing"
|
|
251
|
+
className="inline-flex text-accent"
|
|
252
|
+
>
|
|
253
|
+
<Spinner />
|
|
254
|
+
</span>
|
|
255
|
+
) : r.test ? (
|
|
256
|
+
<Link href={evalsPath(key, "runs", r.test.runId)}>
|
|
257
|
+
<Score right={r.test.right} total={r.test.total} />
|
|
258
|
+
</Link>
|
|
259
|
+
) : (
|
|
260
|
+
<span className="text-muted">
|
|
261
|
+
{r.failed ?? "Not tested"}
|
|
262
|
+
</span>
|
|
263
|
+
)}
|
|
264
|
+
</td>
|
|
265
|
+
<td className="p-3 tabular-nums">
|
|
266
|
+
{r.test ? seriousMistakes(r, suite.checks) : "—"}
|
|
267
|
+
</td>
|
|
268
|
+
<td className="p-3 tabular-nums">
|
|
269
|
+
{r.test ? seconds(r.test.avgMs) : "—"}
|
|
270
|
+
</td>
|
|
271
|
+
{suite.decisions && (
|
|
272
|
+
<>
|
|
273
|
+
<td className="p-3 tabular-nums">
|
|
274
|
+
{asPct(
|
|
275
|
+
r.test ? probabilityQuality(r.test.measures) : null,
|
|
276
|
+
)}
|
|
277
|
+
</td>
|
|
278
|
+
<td className="p-3 tabular-nums">
|
|
279
|
+
{asPct(
|
|
280
|
+
r.test ? meanCalibrationError(r.test.measures) : null,
|
|
281
|
+
)}
|
|
282
|
+
</td>
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
285
|
+
<td className="p-3 tabular-nums">
|
|
286
|
+
{r.test ? perAnswer(r.test.costPerAnswer) : "—"}
|
|
287
|
+
</td>
|
|
288
|
+
<td className="p-3">{r.vsLive ? VS[r.vsLive] : "—"}</td>
|
|
289
|
+
<td className="p-3">
|
|
290
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
291
|
+
<div className="flex justify-end gap-2">
|
|
292
|
+
{r.canMakeFallback && (
|
|
293
|
+
<form action={makeFallback}>
|
|
294
|
+
<input type="hidden" name="suite" value={key} />
|
|
295
|
+
<input type="hidden" name="model" value={r.model} />
|
|
296
|
+
<Submit variant="quiet">Make fallback</Submit>
|
|
297
|
+
</form>
|
|
298
|
+
)}
|
|
299
|
+
{!r.live && r.canMakeLive && (
|
|
300
|
+
<form action={makeLive}>
|
|
301
|
+
<input type="hidden" name="suite" value={key} />
|
|
302
|
+
<input type="hidden" name="model" value={r.model} />
|
|
303
|
+
<Submit variant="quiet">Make live</Submit>
|
|
304
|
+
</form>
|
|
305
|
+
)}
|
|
306
|
+
<form action={testModel}>
|
|
307
|
+
<input type="hidden" name="suite" value={key} />
|
|
308
|
+
<input type="hidden" name="model" value={r.model} />
|
|
309
|
+
<Tip
|
|
310
|
+
control
|
|
311
|
+
id={`test-${r.key}`}
|
|
312
|
+
text={
|
|
313
|
+
r.test
|
|
314
|
+
? "Test this model again on every case in use."
|
|
315
|
+
: "Test this model on every case in use."
|
|
316
|
+
}
|
|
317
|
+
side="bottom"
|
|
318
|
+
align="end"
|
|
319
|
+
>
|
|
320
|
+
<IconSubmit
|
|
321
|
+
label="Test"
|
|
322
|
+
icon={
|
|
323
|
+
<RotateCw aria-hidden className="h-4 w-4" />
|
|
324
|
+
}
|
|
325
|
+
/>
|
|
326
|
+
</Tip>
|
|
327
|
+
</form>
|
|
328
|
+
</div>
|
|
329
|
+
<EstimateNote
|
|
330
|
+
id={r.key}
|
|
331
|
+
estimate={estimates.get(r.key)}
|
|
332
|
+
/>
|
|
333
|
+
</div>
|
|
334
|
+
</td>
|
|
335
|
+
</tr>
|
|
336
|
+
))}
|
|
337
|
+
</tbody>
|
|
338
|
+
</table>
|
|
339
|
+
</Card>
|
|
340
|
+
</section>
|
|
341
|
+
|
|
342
|
+
<SettingsHistory
|
|
343
|
+
suite={key}
|
|
344
|
+
entries={history}
|
|
345
|
+
liveId={liveSettings.version?.id ?? null}
|
|
346
|
+
label={(m) => MODELS[m]?.label ?? m}
|
|
347
|
+
defaults={liveSettings.settings}
|
|
348
|
+
/>
|
|
349
|
+
|
|
350
|
+
<section className="space-y-3">
|
|
351
|
+
<h2 className="text-lg font-semibold">
|
|
352
|
+
Checks
|
|
353
|
+
<InfoTip term="checks" />
|
|
354
|
+
</h2>
|
|
355
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
356
|
+
{suite.checks.map((c) => {
|
|
357
|
+
const rate = live?.test?.byCheck[c.key];
|
|
358
|
+
return (
|
|
359
|
+
<Card key={c.key} className="space-y-1">
|
|
360
|
+
<div className="flex justify-between gap-4">
|
|
361
|
+
<h3 className="font-medium">{c.label}</h3>
|
|
362
|
+
<Tip term={c.severity} id={`severity-${c.key}`}>
|
|
363
|
+
<Chip
|
|
364
|
+
tone={c.severity === "serious" ? "serious" : "neutral"}
|
|
365
|
+
>
|
|
366
|
+
{c.severity}
|
|
367
|
+
</Chip>
|
|
368
|
+
</Tip>
|
|
369
|
+
</div>
|
|
370
|
+
<p className="readable text-sm text-muted">{c.description}</p>
|
|
371
|
+
<p className="text-sm">
|
|
372
|
+
{rate && rate.n > 0 && (
|
|
373
|
+
<>
|
|
374
|
+
Live:{" "}
|
|
375
|
+
<span
|
|
376
|
+
className={
|
|
377
|
+
rate.passed / rate.n >= checkBar(c)
|
|
378
|
+
? "text-good"
|
|
379
|
+
: "text-warn"
|
|
380
|
+
}
|
|
381
|
+
>
|
|
382
|
+
{pct(rate.passed / rate.n)}
|
|
383
|
+
</span>{" "}
|
|
384
|
+
({rate.passed}/{rate.n}) ·{" "}
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
387
|
+
<Tip term="bar" id={`bar-${c.key}`}>
|
|
388
|
+
<span className="text-muted">Bar: {pct(checkBar(c))}</span>
|
|
389
|
+
</Tip>
|
|
390
|
+
</p>
|
|
391
|
+
</Card>
|
|
392
|
+
);
|
|
393
|
+
})}
|
|
394
|
+
</div>
|
|
395
|
+
</section>
|
|
396
|
+
|
|
397
|
+
<section className="space-y-3">
|
|
398
|
+
<AnswerKey
|
|
399
|
+
suite={suite}
|
|
400
|
+
rows={rows}
|
|
401
|
+
heading={
|
|
402
|
+
<h2 className="text-lg font-semibold">
|
|
403
|
+
Answer key ({b.cases} cases in use)
|
|
404
|
+
<InfoTip term="answerKey" />
|
|
405
|
+
</h2>
|
|
406
|
+
}
|
|
407
|
+
tools={
|
|
408
|
+
<form action={rescore}>
|
|
409
|
+
<input type="hidden" name="suite" value={key} />
|
|
410
|
+
<Tip control term="rescore" side="bottom" align="end">
|
|
411
|
+
<IconSubmit
|
|
412
|
+
label="Rescore tests"
|
|
413
|
+
icon={<RotateCw aria-hidden className="h-4 w-4" />}
|
|
414
|
+
/>
|
|
415
|
+
</Tip>
|
|
416
|
+
</form>
|
|
417
|
+
}
|
|
418
|
+
/>
|
|
419
|
+
</section>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** "Free: all 16 reused" or "~$0.03, 4 of 16 reused", under a model's buttons. */
|
|
425
|
+
function EstimateNote({
|
|
426
|
+
id,
|
|
427
|
+
estimate,
|
|
428
|
+
}: {
|
|
429
|
+
id: string;
|
|
430
|
+
estimate:
|
|
431
|
+
{ answers: number; reused: number; costUsd: number | null } | undefined;
|
|
432
|
+
}) {
|
|
433
|
+
if (!estimate || estimate.answers === 0) return null;
|
|
434
|
+
const { answers, reused, costUsd } = estimate;
|
|
435
|
+
const text =
|
|
436
|
+
reused === answers
|
|
437
|
+
? `Free: all ${answers} reused`
|
|
438
|
+
: `${costUsd === null ? "Cost unknown" : `~${money(costUsd)}`}${reused ? `, ${reused} of ${answers} reused` : ""}`;
|
|
439
|
+
return (
|
|
440
|
+
<Tip
|
|
441
|
+
text="What testing this model costs now. Answers an identical request gave in the last 7 days are reused for free."
|
|
442
|
+
id={`estimate-${id}`}
|
|
443
|
+
side="bottom"
|
|
444
|
+
align="end"
|
|
445
|
+
>
|
|
446
|
+
<span className="whitespace-nowrap text-xs text-muted">{text}</span>
|
|
447
|
+
</Tip>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { allPassed, money, summarize } from "@crowandraven/evalkeeper";
|
|
3
|
+
import { BackLink, Card, pct } from "@/components/evalkeeper/ui";
|
|
4
|
+
import { MODELS, requireAdmin, runner, store } from "@/lib/evalkeeper";
|
|
5
|
+
import { evalsPath } from "@/lib/evalkeeper/paths";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
|
|
9
|
+
/** One test: every answer, with what each check found. */
|
|
10
|
+
export default async function RunPage({
|
|
11
|
+
params,
|
|
12
|
+
}: {
|
|
13
|
+
params: Promise<{ suite: string; runId: string }>;
|
|
14
|
+
}) {
|
|
15
|
+
await requireAdmin();
|
|
16
|
+
const { suite: key, runId } = await params;
|
|
17
|
+
const run = await store.getRun(runId);
|
|
18
|
+
if (!run || run.suite !== key) notFound();
|
|
19
|
+
const suite = runner.suiteOrThrow(key);
|
|
20
|
+
const [outputs, cases] = await Promise.all([
|
|
21
|
+
store.listOutputs(runId),
|
|
22
|
+
store.listCases(key).then((cs) => new Map(cs.map((c) => [c.id, c]))),
|
|
23
|
+
]);
|
|
24
|
+
const titleOf = (caseId: string) => {
|
|
25
|
+
const c = cases.get(caseId);
|
|
26
|
+
return c ? suite.title(suite.input.parse(c.input)) : "A retired case";
|
|
27
|
+
};
|
|
28
|
+
const metrics = run.metrics;
|
|
29
|
+
const summary = metrics ? summarize(metrics, suite.checks) : null;
|
|
30
|
+
const label = (k: string) =>
|
|
31
|
+
suite.checks.find((c) => c.key === k)?.label ?? k;
|
|
32
|
+
type Check = { key: string; pass: boolean | null; note?: string };
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="space-y-6">
|
|
36
|
+
<BackLink href={evalsPath(key)}>{suite.label}</BackLink>
|
|
37
|
+
<div className="space-y-1">
|
|
38
|
+
<h1 className="text-2xl font-semibold">
|
|
39
|
+
{MODELS[run.model]?.label ?? run.model} on {suite.label}
|
|
40
|
+
</h1>
|
|
41
|
+
<p className="text-sm text-muted">
|
|
42
|
+
{run.startedAt.toLocaleString()} · {run.status.toLowerCase()}
|
|
43
|
+
{metrics && ` · spent ${money(metrics.costUsd)}`}
|
|
44
|
+
</p>
|
|
45
|
+
{run.error && <p className="readable text-bad">{run.error}</p>}
|
|
46
|
+
</div>
|
|
47
|
+
{summary && (
|
|
48
|
+
<Card className="space-y-2">
|
|
49
|
+
<p className="text-lg">
|
|
50
|
+
{summary.right} of {summary.total} answers fully right (
|
|
51
|
+
{pct(summary.total ? summary.right / summary.total : 0)})
|
|
52
|
+
</p>
|
|
53
|
+
<p className="readable text-sm text-muted">{summary.trust}</p>
|
|
54
|
+
{summary.problems.length > 0 && (
|
|
55
|
+
<ul className="readable list-disc pl-5 text-sm">
|
|
56
|
+
{summary.problems.map((p) => (
|
|
57
|
+
<li key={p.key}>
|
|
58
|
+
{p.label}: failed {p.failed} of {p.of}
|
|
59
|
+
</li>
|
|
60
|
+
))}
|
|
61
|
+
</ul>
|
|
62
|
+
)}
|
|
63
|
+
</Card>
|
|
64
|
+
)}
|
|
65
|
+
<Card className="space-y-3">
|
|
66
|
+
{outputs.map((o) => {
|
|
67
|
+
const checks = (o.scores as Check[] | null) ?? null;
|
|
68
|
+
const ok = allPassed(checks);
|
|
69
|
+
return (
|
|
70
|
+
<div key={o.id} className="rounded-md bg-tint p-3 text-sm">
|
|
71
|
+
<div className="flex justify-between gap-4">
|
|
72
|
+
<span className="font-medium">{titleOf(o.caseId)}</span>
|
|
73
|
+
<span
|
|
74
|
+
className={
|
|
75
|
+
o.error ? "text-bad" : ok ? "text-good" : "text-warn"
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
{o.error ? "No answer" : ok ? "Right" : "Wrong"}
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
{o.error && <p className="text-bad">{o.error}</p>}
|
|
82
|
+
{checks
|
|
83
|
+
?.filter((c) => c.pass === false)
|
|
84
|
+
.map((c) => (
|
|
85
|
+
<p key={c.key} className="readable text-muted">
|
|
86
|
+
{label(c.key)}: {c.note ?? "failed"}
|
|
87
|
+
</p>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
92
|
+
</Card>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|