@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hona/openeval",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Code and LLM evaluations with isolated agents, recorded OpenCode data, and a results viewer",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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 ?? results.judgeRuns())
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 scores = results.readSnapshot(() =>
276
- benchmarkScores(
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
- results
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
- .evalRuns()
292
- .filter((candidate) => active.has(candidate.slotId)),
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 = results.judgeRuns(),
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);
@@ -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(url.searchParams.get("id") ?? "")
100
- : await reader.summary(url.searchParams.get("id") ?? ""),
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()