@hona/openeval 0.5.2 → 0.5.3
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/package.json +1 -1
- package/src/app/read-results.ts +23 -16
- package/src/app/serve-results.ts +16 -4
- package/src/infra/sqlite/index.ts +7 -0
package/package.json
CHANGED
package/src/app/read-results.ts
CHANGED
|
@@ -72,7 +72,22 @@ export const evalResultId = (benchmarkId: string, evalId: string) =>
|
|
|
72
72
|
export class ResultReader {
|
|
73
73
|
private paths = new Map<string, string>();
|
|
74
74
|
private runtimes = new Map<string, RuntimeInterval[]>();
|
|
75
|
+
private finishedJudges = new Map<string, Map<string, JudgeRun>>();
|
|
75
76
|
constructor(readonly root: string) {}
|
|
77
|
+
/** Every judge run; parses only running or unseen records, since finalized runs never change. */
|
|
78
|
+
private judgeRunsOf(results: Results) {
|
|
79
|
+
const cache =
|
|
80
|
+
this.finishedJudges.get(results.path) ?? new Map<string, JudgeRun>();
|
|
81
|
+
this.finishedJudges.set(results.path, cache);
|
|
82
|
+
return results.judgeRunStates().flatMap(({ id }) => {
|
|
83
|
+
const cached = cache.get(id);
|
|
84
|
+
if (cached) return [cached];
|
|
85
|
+
const run = results.judgeRun(id);
|
|
86
|
+
if (!run) return [];
|
|
87
|
+
if (run.state !== "running") cache.set(id, run);
|
|
88
|
+
return [run];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
76
91
|
private runtime(
|
|
77
92
|
run: EvalRun | JudgeRun,
|
|
78
93
|
results: Results,
|
|
@@ -93,7 +108,7 @@ export class ResultReader {
|
|
|
93
108
|
scheduled(slot, benchmark, now);
|
|
94
109
|
if (current && "evalRunId" in run.input)
|
|
95
110
|
current =
|
|
96
|
-
(judges ??
|
|
111
|
+
(judges ?? this.judgeRunsOf(results))
|
|
97
112
|
.filter(
|
|
98
113
|
(judge) =>
|
|
99
114
|
judge.input.evalRunId === candidate!.id &&
|
|
@@ -272,25 +287,17 @@ export class ResultReader {
|
|
|
272
287
|
(item) => !evalId || item.id === evalId,
|
|
273
288
|
);
|
|
274
289
|
if (!evals.length) throw new Error("Unknown eval");
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
run.definition,
|
|
278
|
-
results.slots(),
|
|
279
|
-
results.judgeRuns(),
|
|
280
|
-
evalId,
|
|
281
|
-
),
|
|
290
|
+
const [slots, allJudges] = results.readSnapshot(
|
|
291
|
+
() => [results.slots(), this.judgeRunsOf(results)] as const,
|
|
282
292
|
);
|
|
293
|
+
const scores = benchmarkScores(run.definition, slots, allJudges, evalId);
|
|
283
294
|
// Runtime and cost describe the work behind the active selections, not retired history.
|
|
284
295
|
const active = new Set(
|
|
285
|
-
|
|
286
|
-
.slots()
|
|
287
|
-
.filter((slot) => slot.active)
|
|
288
|
-
.map((slot) => slot.id),
|
|
296
|
+
slots.filter((slot) => slot.active).map((slot) => slot.id),
|
|
289
297
|
);
|
|
290
298
|
const candidates = results
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
allJudges = results.judgeRuns();
|
|
299
|
+
.evalRuns()
|
|
300
|
+
.filter((candidate) => active.has(candidate.slotId));
|
|
294
301
|
const now = Date.now(),
|
|
295
302
|
candidateIndex = new Map(candidates.map((run) => [run.id, run]));
|
|
296
303
|
const evalRuntime = new Map<string, RuntimeInterval[]>();
|
|
@@ -380,7 +387,7 @@ export class ResultReader {
|
|
|
380
387
|
private liveRuns(results: Results, evalIds: readonly string[]) {
|
|
381
388
|
const benchmark = results.benchmark!,
|
|
382
389
|
executions = results.evalRuns(),
|
|
383
|
-
judges =
|
|
390
|
+
judges = this.judgeRunsOf(results),
|
|
384
391
|
now = Date.now();
|
|
385
392
|
const byId = new Map(executions.map((run) => [run.id, run]));
|
|
386
393
|
const bySlot = Map.groupBy(executions, (run) => run.slotId);
|
package/src/app/serve-results.ts
CHANGED
|
@@ -16,6 +16,16 @@ export async function serveResults(options: {
|
|
|
16
16
|
await mkdir(root, { recursive: true });
|
|
17
17
|
const reader = new ResultReader(root),
|
|
18
18
|
encoder = new TextEncoder();
|
|
19
|
+
// Open tabs refresh on every change. Share one recent read so polling cannot saturate the server.
|
|
20
|
+
const recent = new Map<string, { at: number; value: Promise<unknown> }>();
|
|
21
|
+
const shared = <T>(key: string, read: () => Promise<T>): Promise<T> => {
|
|
22
|
+
const hit = recent.get(key);
|
|
23
|
+
if (hit && Date.now() - hit.at < 2000) return hit.value as Promise<T>;
|
|
24
|
+
const value = read();
|
|
25
|
+
recent.set(key, { at: Date.now(), value });
|
|
26
|
+
value.catch(() => recent.delete(key));
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
19
29
|
const clients = new Set<ReadableStreamDefaultController<Uint8Array>>(),
|
|
20
30
|
streams = new Set<() => void>();
|
|
21
31
|
let changed: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -44,7 +54,7 @@ export async function serveResults(options: {
|
|
|
44
54
|
if (url.pathname === "/api/results")
|
|
45
55
|
return Response.json(await reader.index());
|
|
46
56
|
if (url.pathname === "/api/activity")
|
|
47
|
-
return Response.json(await reader.activity());
|
|
57
|
+
return Response.json(await shared("activity", () => reader.activity()));
|
|
48
58
|
if (url.pathname === "/api/judge-checks")
|
|
49
59
|
return Response.json(
|
|
50
60
|
await reader.judgeAudit(
|
|
@@ -93,12 +103,14 @@ export async function serveResults(options: {
|
|
|
93
103
|
),
|
|
94
104
|
);
|
|
95
105
|
}
|
|
96
|
-
if (url.pathname === "/api/result")
|
|
106
|
+
if (url.pathname === "/api/result") {
|
|
107
|
+
const id = url.searchParams.get("id") ?? "";
|
|
97
108
|
return Response.json(
|
|
98
109
|
url.searchParams.get("format") === "runs"
|
|
99
|
-
? await reader.evalRuns(
|
|
100
|
-
: await reader.summary(
|
|
110
|
+
? await shared(`runs:${id}`, () => reader.evalRuns(id))
|
|
111
|
+
: await shared(`summary:${id}`, () => reader.summary(id)),
|
|
101
112
|
);
|
|
113
|
+
}
|
|
102
114
|
if (url.pathname === "/api/events") {
|
|
103
115
|
let owner: ReadableStreamDefaultController<Uint8Array>;
|
|
104
116
|
return new Response(
|
|
@@ -166,6 +166,13 @@ export class Results {
|
|
|
166
166
|
.all()
|
|
167
167
|
.map((row) => row.value);
|
|
168
168
|
}
|
|
169
|
+
/** IDs and states in judgeRuns() order. Triggers keep finalized runs immutable, so readers can cache them. */
|
|
170
|
+
judgeRunStates() {
|
|
171
|
+
return this.sqlite.query("SELECT id, state FROM judge_runs").all() as Array<{
|
|
172
|
+
id: string;
|
|
173
|
+
state: JudgeRun["state"];
|
|
174
|
+
}>;
|
|
175
|
+
}
|
|
169
176
|
evalRun(id: string) {
|
|
170
177
|
return this.db
|
|
171
178
|
.select()
|