@hackerrank/astra-cli 0.1.5 → 0.1.7
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/README.md +41 -9
- package/package.json +1 -1
- package/src/agent.js +37 -6
- package/src/bench.js +65 -8
- package/src/cli.js +132 -6
- package/src/ledger.js +68 -0
- package/src/model.js +8 -1
- package/src/models.js +1 -0
- package/src/project-bench.js +154 -0
- package/src/project.js +89 -0
- package/src/prompts.js +2 -0
- package/src/report.html +77 -30
- package/src/report.js +106 -0
- package/src/result-contract.js +58 -0
- package/src/verifier-runner.js +159 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { matrixCells } from "./project.js";
|
|
5
|
+
import { runCell as defaultRunCell, writeMetrics } from "./bench.js";
|
|
6
|
+
import { candidateTreeHash, mergeRunResult } from "./result-contract.js";
|
|
7
|
+
import { loadLedger, claimCell, selectWork, transitionCell } from "./ledger.js";
|
|
8
|
+
import { runVerifier as defaultRunVerifier } from "./verifier-runner.js";
|
|
9
|
+
import { newSessionId } from "./session.js";
|
|
10
|
+
import { refreshReport } from "./report.js";
|
|
11
|
+
|
|
12
|
+
function writeJson(file, value) {
|
|
13
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
14
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runProjectBench({ project, apiKey, baseUrl, quiet = false, resume = false, resumeSessionId = null, runCellFn = defaultRunCell, verifierFn = defaultRunVerifier, onCell = () => {} } = {}) {
|
|
18
|
+
const root = project.outputRoot;
|
|
19
|
+
const ledger = loadLedger(path.join(root, "benchmark.json"));
|
|
20
|
+
const desired = matrixCells({ project });
|
|
21
|
+
const work = selectWork(ledger, desired, Date.now(), { force: resume, sessionId: resumeSessionId });
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
for (const cell of work) {
|
|
25
|
+
const record = claimCell(ledger, cell);
|
|
26
|
+
const runDir = path.join(root, record.runPath);
|
|
27
|
+
const candidateDir = path.join(runDir, "workspace");
|
|
28
|
+
const reuseFrozen = ["candidate-frozen", "verifying"].includes(record.status);
|
|
29
|
+
const isResume = Boolean(record.sessionId && ["generating", "interrupted"].includes(record.status));
|
|
30
|
+
const sessionId = record.sessionId || newSessionId();
|
|
31
|
+
|
|
32
|
+
let generation;
|
|
33
|
+
if (reuseFrozen) {
|
|
34
|
+
const generationPath = path.join(runDir, "generation.json");
|
|
35
|
+
try {
|
|
36
|
+
generation = JSON.parse(fs.readFileSync(generationPath, "utf8"));
|
|
37
|
+
if (!fs.existsSync(candidateDir)) throw new Error("frozen candidate workspace is missing");
|
|
38
|
+
const actualHash = candidateTreeHash(candidateDir);
|
|
39
|
+
if (record.candidateSha256 && actualHash !== record.candidateSha256) {
|
|
40
|
+
throw new Error("frozen candidate workspace hash does not match the ledger");
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const failure = { status: "error", failureOwner: "infrastructure", error: String(error?.message || error) };
|
|
44
|
+
transitionCell(ledger, cell.cellKey, "failed", { failureOwner: "infrastructure", error: failure.error });
|
|
45
|
+
results.push({ cell, generation: { status: "error" }, verification: failure });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
onCell({ phase: "candidate-frozen", cell, record });
|
|
49
|
+
} else {
|
|
50
|
+
transitionCell(ledger, cell.cellKey, "generating", { sessionId, leaseAt: new Date().toISOString() });
|
|
51
|
+
onCell({ phase: "generating", cell, record });
|
|
52
|
+
try {
|
|
53
|
+
generation = await runCellFn({
|
|
54
|
+
model: cell.model,
|
|
55
|
+
reasoning: cell.reasoning,
|
|
56
|
+
apiKey,
|
|
57
|
+
baseUrl,
|
|
58
|
+
task: project.taskText,
|
|
59
|
+
taskPath: project.workspace,
|
|
60
|
+
projectMetadata: project.metadata,
|
|
61
|
+
root,
|
|
62
|
+
runDir,
|
|
63
|
+
sessionId,
|
|
64
|
+
resume: isResume,
|
|
65
|
+
cell,
|
|
66
|
+
steps: project.bench.steps,
|
|
67
|
+
wall: project.bench.wall,
|
|
68
|
+
timeout: project.bench.timeout,
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const failure = { status: "error", failureOwner: "infrastructure", error: String(error?.message || error) };
|
|
72
|
+
transitionCell(ledger, cell.cellKey, "failed", { failureOwner: "infrastructure", error: failure.error });
|
|
73
|
+
results.push({ cell, generation: { status: "error" }, verification: failure });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const candidateSha256 = candidateTreeHash(candidateDir);
|
|
78
|
+
writeJson(path.join(runDir, "generation.json"), generation);
|
|
79
|
+
transitionCell(ledger, cell.cellKey, "candidate-frozen", { candidateSha256, sessionId });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const candidateSha256 = record.candidateSha256 || candidateTreeHash(candidateDir);
|
|
83
|
+
|
|
84
|
+
let verification = {
|
|
85
|
+
status: "not_configured",
|
|
86
|
+
failureOwner: null,
|
|
87
|
+
score: null,
|
|
88
|
+
solvedScore: "NA",
|
|
89
|
+
reason: "task verifier is not present or not configured",
|
|
90
|
+
};
|
|
91
|
+
if (project.verification && generation.status === "completed") {
|
|
92
|
+
transitionCell(ledger, cell.cellKey, "verifying", { candidateSha256 });
|
|
93
|
+
try {
|
|
94
|
+
verification = await verifierFn({
|
|
95
|
+
project,
|
|
96
|
+
cell,
|
|
97
|
+
runDir,
|
|
98
|
+
candidateDir,
|
|
99
|
+
config: project.verification,
|
|
100
|
+
expected: { taskId: project.id, taskVersion: project.version, candidateSha256 },
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
verification = {
|
|
104
|
+
status: "error",
|
|
105
|
+
failureOwner: "infrastructure",
|
|
106
|
+
score: null,
|
|
107
|
+
solvedScore: "NA",
|
|
108
|
+
error: String(error?.message || error),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (verification.score && typeof verification.score.percentage === "number") {
|
|
112
|
+
verification.solvedScore = verification.score.percentage;
|
|
113
|
+
}
|
|
114
|
+
writeJson(path.join(runDir, "verifier.json"), verification);
|
|
115
|
+
} else if (generation.status !== "completed") {
|
|
116
|
+
verification = {
|
|
117
|
+
status: "not_run",
|
|
118
|
+
failureOwner: "candidate",
|
|
119
|
+
score: null,
|
|
120
|
+
solvedScore: "NA",
|
|
121
|
+
reason: "generation did not submit a candidate; verification was skipped",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const merged = mergeRunResult({
|
|
126
|
+
task: { id: project.id, version: project.version, type: project.type || "brownfield" },
|
|
127
|
+
cell,
|
|
128
|
+
generation: { ...generation, candidateSha256 },
|
|
129
|
+
verification,
|
|
130
|
+
artifacts: { runPath: record.runPath, candidateSha256 },
|
|
131
|
+
});
|
|
132
|
+
writeMetrics({
|
|
133
|
+
runDir,
|
|
134
|
+
root,
|
|
135
|
+
metrics: {
|
|
136
|
+
...generation,
|
|
137
|
+
model: cell.model,
|
|
138
|
+
reasoning: cell.reasoning,
|
|
139
|
+
verification_status: verification.status,
|
|
140
|
+
verification_score: verification.score?.percentage ?? "",
|
|
141
|
+
solved_score: verification.solvedScore ?? "NA",
|
|
142
|
+
verification_hard_pass: verification.score?.hardFailPassed ?? "",
|
|
143
|
+
failure_owner: verification.failureOwner ?? "",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
writeJson(path.join(runDir, "result.json"), merged);
|
|
147
|
+
transitionCell(ledger, cell.cellKey, "completed", { candidateSha256, resultPath: path.join(record.runPath, "result.json"), failureOwner: verification.failureOwner });
|
|
148
|
+
results.push(merged);
|
|
149
|
+
try { refreshReport(root); } catch { /* report regeneration is best-effort per cell */ }
|
|
150
|
+
onCell({ phase: "completed", cell, record, result: merged });
|
|
151
|
+
}
|
|
152
|
+
try { refreshReport(root); } catch { /* report regeneration is best-effort */ }
|
|
153
|
+
return results;
|
|
154
|
+
}
|
package/src/project.js
CHANGED
|
@@ -19,6 +19,9 @@ const DEFAULT_BENCH = {
|
|
|
19
19
|
command_timeout_seconds: 60,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const DEFAULT_VERIFICATION = null;
|
|
23
|
+
const TASK_TYPES = new Set(["brownfield", "greenfield"]);
|
|
24
|
+
|
|
22
25
|
function requireObject(value, name) {
|
|
23
26
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
24
27
|
throw new ProjectConfigError(`${name} must be a TOML table`);
|
|
@@ -49,6 +52,42 @@ function stringArray(value, name, fallback) {
|
|
|
49
52
|
return value;
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
function repeatOverrides(value) {
|
|
56
|
+
if (value === undefined) return {};
|
|
57
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
58
|
+
throw new ProjectConfigError("bench.repeats must be a TOML table");
|
|
59
|
+
}
|
|
60
|
+
const result = {};
|
|
61
|
+
for (const [key, count] of Object.entries(value)) {
|
|
62
|
+
if (!key.trim()) throw new ProjectConfigError("bench.repeats keys must be non-empty");
|
|
63
|
+
result[key] = positiveInteger(count, `bench.repeats.${key}`, 1);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function verificationConfig(value) {
|
|
69
|
+
if (value === undefined) return DEFAULT_VERIFICATION;
|
|
70
|
+
const verification = requireObject(value, "verification");
|
|
71
|
+
return {
|
|
72
|
+
command: requireString(verification.command, "verification.command"),
|
|
73
|
+
startCommand: verification.start_command === undefined ? null : requireString(verification.start_command, "verification.start_command"),
|
|
74
|
+
stopCommand: verification.stop_command === undefined ? null : requireString(verification.stop_command, "verification.stop_command"),
|
|
75
|
+
baseUrl: verification.base_url === undefined ? null : requireString(verification.base_url, "verification.base_url"),
|
|
76
|
+
readinessUrl: verification.readiness_url === undefined ? null : requireString(verification.readiness_url, "verification.readiness_url"),
|
|
77
|
+
tokenEnv: verification.token_env === undefined ? null : requireString(verification.token_env, "verification.token_env"),
|
|
78
|
+
timeoutSeconds: positiveInteger(verification.timeout_seconds, "verification.timeout_seconds", 3600),
|
|
79
|
+
report: verification.report === undefined ? "verifier-report.json" : requireString(verification.report, "verification.report"),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function taskType(project) {
|
|
84
|
+
const value = project.type ?? project.profile ?? "brownfield";
|
|
85
|
+
if (typeof value !== "string" || !TASK_TYPES.has(value)) {
|
|
86
|
+
throw new ProjectConfigError("project.type must be either brownfield or greenfield");
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
52
91
|
function resolveInside(root, relative, name) {
|
|
53
92
|
const candidate = path.resolve(root, requireString(relative, name));
|
|
54
93
|
if (candidate !== root && !candidate.startsWith(root + path.sep)) {
|
|
@@ -114,12 +153,14 @@ export function loadProject(projectPath) {
|
|
|
114
153
|
|
|
115
154
|
const project = requireObject(config.project, "project");
|
|
116
155
|
const bench = requireObject(config.bench ?? {}, "bench");
|
|
156
|
+
let verification = verificationConfig(config.verification);
|
|
117
157
|
const output = requireObject(config.output ?? {}, "output");
|
|
118
158
|
const templates = requireObject(config.templates ?? {}, "templates");
|
|
119
159
|
const extensionConfig = requireObject(config.extensions ?? {}, "extensions");
|
|
120
160
|
const provenance = requireObject(config.provenance ?? {}, "provenance");
|
|
121
161
|
const id = requireString(project.id, "project.id");
|
|
122
162
|
const version = positiveInteger(project.version, "project.version", undefined);
|
|
163
|
+
const type = taskType(project);
|
|
123
164
|
|
|
124
165
|
const instructionPath = resolveInside(root, project.instruction, "project.instruction");
|
|
125
166
|
const workspace = resolveInside(root, project.workspace, "project.workspace");
|
|
@@ -136,6 +177,23 @@ export function loadProject(projectPath) {
|
|
|
136
177
|
.filter(Boolean)
|
|
137
178
|
.join("\n\n");
|
|
138
179
|
|
|
180
|
+
// Verifiers are task-owned. A conventional runner is enough metadata for
|
|
181
|
+
// Astra to invoke it; all domain behavior remains under verifier/.
|
|
182
|
+
const conventionalVerifier = path.join(root, "verifier", "run_verifier.py");
|
|
183
|
+
if (!verification && fs.existsSync(conventionalVerifier)) {
|
|
184
|
+
verification = {
|
|
185
|
+
command: `python3 verifier/run_verifier.py --report "$ASTRA_VERIFIER_REPORT"`,
|
|
186
|
+
startCommand: null,
|
|
187
|
+
stopCommand: null,
|
|
188
|
+
baseUrl: null,
|
|
189
|
+
readinessUrl: null,
|
|
190
|
+
tokenEnv: null,
|
|
191
|
+
timeoutSeconds: 3600,
|
|
192
|
+
report: "verifier-report.json",
|
|
193
|
+
discovered: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
139
197
|
const baselineSha256 = provenance.baseline_sha256;
|
|
140
198
|
if (baselineSha256 !== undefined && (typeof baselineSha256 !== "string" || !/^[a-f0-9]{64}$/.test(baselineSha256))) {
|
|
141
199
|
throw new ProjectConfigError("provenance.baseline_sha256 must be a lowercase SHA-256 hash");
|
|
@@ -145,6 +203,7 @@ export function loadProject(projectPath) {
|
|
|
145
203
|
root,
|
|
146
204
|
id,
|
|
147
205
|
version,
|
|
206
|
+
type,
|
|
148
207
|
instructionPath,
|
|
149
208
|
instruction,
|
|
150
209
|
instructionSha256: sha256(instruction),
|
|
@@ -169,9 +228,39 @@ export function loadProject(projectPath) {
|
|
|
169
228
|
models: stringArray(bench.models, "bench.models", DEFAULT_BENCH.models),
|
|
170
229
|
reasoning: stringArray(bench.reasoning, "bench.reasoning", DEFAULT_BENCH.reasoning),
|
|
171
230
|
repeat: positiveInteger(bench.repeat, "bench.repeat", DEFAULT_BENCH.repeat),
|
|
231
|
+
repeats: repeatOverrides(bench.repeats),
|
|
172
232
|
steps: positiveInteger(bench.steps, "bench.steps", DEFAULT_BENCH.steps, { allowZero: true }),
|
|
173
233
|
wall: positiveInteger(bench.wall_seconds, "bench.wall_seconds", DEFAULT_BENCH.wall_seconds, { allowZero: true }),
|
|
174
234
|
timeout: positiveInteger(bench.command_timeout_seconds, "bench.command_timeout_seconds", DEFAULT_BENCH.command_timeout_seconds),
|
|
175
235
|
},
|
|
236
|
+
verification,
|
|
176
237
|
};
|
|
177
238
|
}
|
|
239
|
+
|
|
240
|
+
export function matrixCells({ project, models, reasonings, repeat } = {}) {
|
|
241
|
+
const id = requireString(project?.id, "project.id");
|
|
242
|
+
const version = positiveInteger(project?.version, "project.version", undefined);
|
|
243
|
+
const bench = project?.bench ?? {};
|
|
244
|
+
const selectedModels = models ?? bench.models ?? [];
|
|
245
|
+
const selectedReasonings = reasonings ?? bench.reasoning ?? [""];
|
|
246
|
+
const defaultRepeat = repeat ?? bench.repeat ?? 1;
|
|
247
|
+
const overrides = bench.repeats ?? {};
|
|
248
|
+
const cells = [];
|
|
249
|
+
for (const model of selectedModels) {
|
|
250
|
+
for (const reasoningValue of selectedReasonings) {
|
|
251
|
+
const reasoning = reasoningValue || "none";
|
|
252
|
+
const displayKey = `${model}-${reasoning}`;
|
|
253
|
+
const count = overrides[displayKey] ?? defaultRepeat;
|
|
254
|
+
for (let repeatIndex = 1; repeatIndex <= count; repeatIndex += 1) {
|
|
255
|
+
cells.push({
|
|
256
|
+
cellKey: `${id}:${version}:${model}:${reasoning}:${repeatIndex}`,
|
|
257
|
+
displayKey,
|
|
258
|
+
model,
|
|
259
|
+
reasoning,
|
|
260
|
+
repeatIndex,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return cells;
|
|
266
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -37,6 +37,8 @@ Autonomous mode:
|
|
|
37
37
|
- Do NOT ask the user questions; work autonomously until the task is done.
|
|
38
38
|
- You are permitted and expected to read and use any available credentials,
|
|
39
39
|
environment variables, and config files needed to complete the task.
|
|
40
|
+
- In autonomous mode, you may provide multiple fenced bash blocks when they
|
|
41
|
+
form one coherent sequence; Astra executes them together in order.
|
|
40
42
|
- When completely finished, run this and NOTHING else:
|
|
41
43
|
\`echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT\`
|
|
42
44
|
After that command you cannot continue.`;
|
package/src/report.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
:root { --ink:#11131a; --muted:#69707d; --wash:#f5f6f8; --line:#e4e7eb; --lime:#a7ff63; --blue:#5285f7; --purple:#9160d8; --green:#39ae76; --orange:#f3a021; --red:#d85668; font-family:Satoshi,"Avenir Next",Arial,sans-serif; }
|
|
10
10
|
* { box-sizing:border-box; } body { margin:0; background:#fff; color:var(--ink); font-size:14px; line-height:1.45; } .wrap { max-width:1240px; margin:auto; padding:28px 32px 68px; }
|
|
11
11
|
header.top { display:flex; justify-content:space-between; gap:20px; align-items:flex-start; padding-bottom:28px; border-bottom:1px solid var(--line); } header.top h1 { font-size:32px; letter-spacing:-.045em; line-height:1; margin:8px 0 0; font-weight:700; } .eyebrow { display:inline-block; padding:5px 14px; border-radius:99px; background:var(--lime); font-size:12px; font-weight:700; } header.top .meta { color:var(--muted); font-size:12px; text-align:right; }
|
|
12
|
-
.kpis { display:grid; grid-template-columns:repeat(
|
|
12
|
+
.kpis { display:grid; grid-template-columns:repeat(5,1fr); margin:24px 0 52px; } .kpi { min-height:96px; padding:8px 18px; border-left:1px solid var(--line); } .kpi:first-child { border:0; padding-left:0; } .kpi .v { font-size:28px; letter-spacing:-.045em; font-weight:700; } .kpi .l { color:var(--muted); font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; margin-top:8px; }
|
|
13
13
|
section { margin:56px 0; } section > h2 { font-size:20px; letter-spacing:-.035em; margin:0 0 22px; } section > h2 .sub { color:var(--muted); font-size:13px; font-weight:400; letter-spacing:0; } .grid2 { display:grid; grid-template-columns:1.1fr 1fr; gap:44px; } .panel { padding:0; } .panel h3 { color:var(--muted); letter-spacing:.07em; text-transform:uppercase; font-size:12px; margin:0 0 16px; }
|
|
14
14
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
15
15
|
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
|
@@ -47,6 +47,15 @@
|
|
|
47
47
|
.toolbar { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
|
|
48
48
|
.toolbar select, .toolbar input { background: var(--wash); border:0; color:var(--ink); border-radius:4px; padding:7px 9px; font-size:12px; } .model-filter { align-items:end; } .model-filter > span { color:var(--muted); font-size:11px; } .model-menu { position:relative; } .model-menu summary { cursor:pointer; list-style:none; min-width:220px; padding:9px 32px 9px 11px; background:var(--wash); border-radius:5px; font-size:12px; position:relative; } .model-menu summary::-webkit-details-marker { display:none; } .model-menu summary::after { content:"⌄"; position:absolute; right:11px; color:var(--muted); } .model-options { position:absolute; z-index:2; top:calc(100% + 6px); left:0; width:260px; max-height:240px; overflow:auto; padding:6px; background:#fff; border:1px solid var(--line); box-shadow:0 10px 24px rgba(17,19,26,.1); } .model-option { display:flex; gap:9px; align-items:center; padding:8px; font-size:12px; cursor:pointer; } .model-option:hover { background:var(--wash); } .model-option input { accent-color:var(--blue); }
|
|
49
49
|
.toolbar label { color: var(--muted); font-size: 12px; }
|
|
50
|
+
.model-pills { display:flex; gap:8px; flex-wrap:wrap; margin:-6px 0 16px; }
|
|
51
|
+
.model-pill { border:1px solid var(--line); background:#fff; color:var(--ink); border-radius:999px; padding:7px 12px; font-size:12px; cursor:pointer; }
|
|
52
|
+
.model-pill.active { background:var(--ink); color:#fff; border-color:var(--ink); }
|
|
53
|
+
.criterion-cell { min-width:112px; text-align:center; }
|
|
54
|
+
.criterion-cell .rate { font-weight:700; }
|
|
55
|
+
.criterion-cell .counts { display:block; color:var(--muted); font-size:11px; margin-top:2px; }
|
|
56
|
+
.criterion-cell.pass { background:rgba(57,174,118,.10); }
|
|
57
|
+
.criterion-cell.fail { background:rgba(216,86,104,.10); }
|
|
58
|
+
.criterion-cell.na { color:var(--muted); }
|
|
50
59
|
footer { color: var(--muted); font-size: 11.5px; margin-top: 40px; text-align: center; }
|
|
51
60
|
svg text { fill: var(--muted); font-size: 10.5px; }
|
|
52
61
|
.axis line, .axis path { stroke: var(--line); }
|
|
@@ -56,13 +65,13 @@
|
|
|
56
65
|
<body>
|
|
57
66
|
<div class="wrap">
|
|
58
67
|
<header class="top">
|
|
59
|
-
<div><h1>Benchmark report</h1><div class="meta" id="generated"></div></div>
|
|
68
|
+
<div><h1>Benchmark report</h1><div class="meta" id="generated"></div><div class="meta" id="schema"></div></div>
|
|
60
69
|
</header>
|
|
61
70
|
|
|
62
71
|
<div class="kpis" id="kpis"></div>
|
|
63
72
|
|
|
64
73
|
<section>
|
|
65
|
-
<h2>
|
|
74
|
+
<h2>Run outcomes <span class="sub">generation completion versus verified correctness</span></h2>
|
|
66
75
|
<div class="toolbar model-filter">
|
|
67
76
|
<span>Models</span>
|
|
68
77
|
<details class="model-menu">
|
|
@@ -76,24 +85,17 @@
|
|
|
76
85
|
</section>
|
|
77
86
|
|
|
78
87
|
<section>
|
|
79
|
-
<h2>
|
|
80
|
-
<div class="
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<div id="chart-scatter"></div>
|
|
84
|
-
</div>
|
|
85
|
-
<div class="panel">
|
|
86
|
-
<h3>Cost vs. average steps</h3>
|
|
87
|
-
<div id="chart-scatter-steps"></div>
|
|
88
|
-
</div>
|
|
88
|
+
<h2>Test-case results <span class="sub">passed, failed, blocked, and errored criteria by model</span></h2>
|
|
89
|
+
<div id="verifier-model-pills" class="model-pills"></div>
|
|
90
|
+
<div class="panel">
|
|
91
|
+
<table id="tbl-verifier-matrix"></table>
|
|
89
92
|
</div>
|
|
90
93
|
</section>
|
|
91
94
|
|
|
92
95
|
<section>
|
|
93
|
-
<h2>
|
|
96
|
+
<h2>Run details <span class="sub">generation and benchmarking status for each attempt</span></h2>
|
|
94
97
|
<div class="panel">
|
|
95
|
-
<
|
|
96
|
-
<div class="legend" id="legend-tokens"></div>
|
|
98
|
+
<table id="tbl-runs"></table>
|
|
97
99
|
</div>
|
|
98
100
|
</section>
|
|
99
101
|
|
|
@@ -112,7 +114,7 @@
|
|
|
112
114
|
<div class="legend" id="legend-series"></div>
|
|
113
115
|
</section>
|
|
114
116
|
|
|
115
|
-
<footer
|
|
117
|
+
<footer></footer>
|
|
116
118
|
</div>
|
|
117
119
|
|
|
118
120
|
<script>
|
|
@@ -181,6 +183,7 @@
|
|
|
181
183
|
var k = DATA.kpis || {};
|
|
182
184
|
var cards = [
|
|
183
185
|
["Models Benchmarked", fmt(k.models)],
|
|
186
|
+
["Average score", k.average_score == null ? "n/a" : fmt1(k.average_score) + "%"],
|
|
184
187
|
["Total duration", fmt(k.elapsed_seconds) + "s"],
|
|
185
188
|
["Total cost", fmtUsd(k.cost_usd) + (k.cost_source === "estimated" ? "~" : "")],
|
|
186
189
|
["Tokens used", fmt(k.tokens)],
|
|
@@ -279,9 +282,12 @@
|
|
|
279
282
|
var options = document.getElementById("leaderboard-model-options");
|
|
280
283
|
var filterLabel = document.getElementById("model-filter-label");
|
|
281
284
|
var columns = [
|
|
282
|
-
{ key: "
|
|
283
|
-
{ key: "reasoning", label: "Reasoning", render: function (r) { return esc(r.reasoning); } },
|
|
285
|
+
{ key: "slug", label: "Model · reasoning", render: function (r) { return esc(r.slug); } },
|
|
284
286
|
{ key: "runs", label: "Runs", render: function (r) { return fmt(r.runs); } },
|
|
287
|
+
{ key: "average_score", label: "Score", render: function (r) { return r.average_score == null ? '<span class="n-a">n/a</span>' : fmt1(r.average_score) + "%"; } },
|
|
288
|
+
{ key: "criteria_pass_rate", label: "Test cases", render: function (r) { return r.criteria_pass_rate == null ? '<span class="n-a">n/a</span>' : pct(r.criteria_pass_rate); } },
|
|
289
|
+
{ key: "verification_runs", label: "Verified", render: function (r) { return fmt(r.verification_runs) + " / " + fmt(r.runs); } },
|
|
290
|
+
{ key: "hard_passes", label: "Hard passes", render: function (r) { return fmt(r.hard_passes); } },
|
|
285
291
|
{ key: "sum_steps", label: "Total steps", render: function (r) { return fmt(r.sum_steps); } },
|
|
286
292
|
{ key: "sum_elapsed_seconds", label: "Time taken", render: function (r) { return fmt(r.sum_elapsed_seconds) + "s"; } },
|
|
287
293
|
{ key: "sum_tokens", label: "Tokens", render: function (r) { return fmt(r.sum_tokens); } },
|
|
@@ -303,10 +309,46 @@
|
|
|
303
309
|
draw();
|
|
304
310
|
})();
|
|
305
311
|
|
|
312
|
+
// ---------------------------------------------------------------- verifier test-case matrix
|
|
313
|
+
(function () {
|
|
314
|
+
var container = document.getElementById("tbl-verifier-matrix");
|
|
315
|
+
var pills = document.getElementById("verifier-model-pills");
|
|
316
|
+
if (!container) return;
|
|
317
|
+
var matrix = DATA.verifier_matrix || { models: [], criteria: [] };
|
|
318
|
+
var active = matrix.models.slice();
|
|
319
|
+
function cellHtml(cell) {
|
|
320
|
+
if (!cell) return '<div class="criterion-cell na">NA</div>';
|
|
321
|
+
var rate = cell.total ? Math.round(cell.passed / cell.total * 100) : 0;
|
|
322
|
+
var cls = rate === 100 ? "pass" : (cell.failed || cell.blocked || cell.error ? "fail" : "na");
|
|
323
|
+
return '<div class="criterion-cell ' + cls + '"><span class="rate">' + rate + '%</span><span class="counts">' + cell.passed + ' pass · ' + (cell.failed + cell.blocked + cell.error) + ' fail</span></div>';
|
|
324
|
+
}
|
|
325
|
+
function draw() {
|
|
326
|
+
var visible = matrix.models.filter(function (model) { return active.indexOf(model) !== -1; });
|
|
327
|
+
container.innerHTML = '<thead><tr><th>Test case</th>' + visible.map(function (model) { return '<th><span class="mono">' + esc(model) + '</span></th>'; }).join('') + '</tr></thead>' +
|
|
328
|
+
'<tbody>' + matrix.criteria.map(function (criterion) { return '<tr><td><div class="mono">' + esc(criterion.id) + '</div><div class="sub">' + esc(criterion.title || criterion.id) + '</div></td>' + visible.map(function (model) { return '<td>' + cellHtml(criterion.cells[model]) + '</td>'; }).join('') + '</tr>'; }).join('') + '</tbody>';
|
|
329
|
+
}
|
|
330
|
+
matrix.models.forEach(function (model, index) {
|
|
331
|
+
var pill = el("button", { class: "model-pill active", type: "button" }, esc(model));
|
|
332
|
+
pill.addEventListener("click", function () {
|
|
333
|
+
var pos = active.indexOf(model);
|
|
334
|
+
if (pos === -1) { active.push(model); pill.classList.add("active"); }
|
|
335
|
+
else if (active.length > 1) { active.splice(pos, 1); pill.classList.remove("active"); }
|
|
336
|
+
draw();
|
|
337
|
+
});
|
|
338
|
+
pills.appendChild(pill);
|
|
339
|
+
});
|
|
340
|
+
if (!matrix.criteria.length) {
|
|
341
|
+
container.innerHTML = '<tbody><tr><td class="muted">No verifier criteria have produced results yet.</td></tr></tbody>';
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
draw();
|
|
345
|
+
})();
|
|
346
|
+
|
|
306
347
|
// ---------------------------------------------------------------- token mix stacked bar
|
|
307
348
|
(function () {
|
|
308
349
|
var host = document.getElementById("chart-tokens");
|
|
309
350
|
var legend = document.getElementById("legend-tokens");
|
|
351
|
+
if (!host || !legend) return;
|
|
310
352
|
var keys = ["prompt", "completion", "reasoning", "cached"];
|
|
311
353
|
var colors = { prompt: "#5b9dff", completion: "#3ecf8e", reasoning: "#b57bff", cached: "#8b93a7" };
|
|
312
354
|
var maxTotal = Math.max(1, Math.max.apply(null, (DATA.leaderboard || []).map(function (g) {
|
|
@@ -333,6 +375,7 @@
|
|
|
333
375
|
// ---------------------------------------------------------------- scatter: cost vs duration
|
|
334
376
|
(function () {
|
|
335
377
|
var host = document.getElementById("chart-scatter");
|
|
378
|
+
if (!host) return;
|
|
336
379
|
var W = host.clientWidth || 460, H = 260, pad = { l: 42, r: 16, t: 14, b: 30 };
|
|
337
380
|
var svg = svgEl("svg", { width: "100%", height: H, viewBox: "0 0 " + W + " " + H });
|
|
338
381
|
var data = (DATA.leaderboard || []).filter(function (g) { return g.cost_usd != null; });
|
|
@@ -367,6 +410,7 @@
|
|
|
367
410
|
// ---------------------------------------------------------------- scatter: cost vs average steps
|
|
368
411
|
(function () {
|
|
369
412
|
var host = document.getElementById("chart-scatter-steps");
|
|
413
|
+
if (!host) return;
|
|
370
414
|
var W = host.clientWidth || 460, H = 260, pad = { l: 42, r: 16, t: 14, b: 30 };
|
|
371
415
|
var svg = svgEl("svg", { width: "100%", height: H, viewBox: "0 0 " + W + " " + H });
|
|
372
416
|
var data = (DATA.leaderboard || []).filter(function (g) { return g.cost_usd != null; });
|
|
@@ -491,25 +535,28 @@
|
|
|
491
535
|
var fModel = document.getElementById("filter-model");
|
|
492
536
|
var fTask = document.getElementById("filter-task");
|
|
493
537
|
var fOutcome = document.getElementById("filter-outcome");
|
|
494
|
-
if (!container
|
|
538
|
+
if (!container) return;
|
|
495
539
|
|
|
496
540
|
function uniq(fn) {
|
|
497
541
|
var seen = {}, out = [];
|
|
498
542
|
runs.forEach(function (r) { var v = fn(r); if (v && !seen[v]) { seen[v] = 1; out.push(v); } });
|
|
499
543
|
return out.sort();
|
|
500
544
|
}
|
|
501
|
-
uniq(function (r) { return r.slug; }).forEach(function (v) { fModel.appendChild(el("option", { value: v }, esc(v))); });
|
|
502
|
-
uniq(function (r) { return r.task_id; }).forEach(function (v) { fTask.appendChild(el("option", { value: v }, esc(v))); });
|
|
503
|
-
uniq(function (r) { return r.status; }).forEach(function (v) { fOutcome.appendChild(el("option", { value: v }, esc(v))); });
|
|
545
|
+
if (fModel) uniq(function (r) { return r.slug; }).forEach(function (v) { fModel.appendChild(el("option", { value: v }, esc(v))); });
|
|
546
|
+
if (fTask) uniq(function (r) { return r.task_id; }).forEach(function (v) { fTask.appendChild(el("option", { value: v }, esc(v))); });
|
|
547
|
+
if (fOutcome) uniq(function (r) { return r.status; }).forEach(function (v) { fOutcome.appendChild(el("option", { value: v }, esc(v))); });
|
|
504
548
|
|
|
505
549
|
var columns = [
|
|
506
550
|
{ key: "slug", label: "Model", render: function (r) { return '<span class="mono">' + esc(r.slug) + "</span>"; } },
|
|
507
|
-
{ key: "
|
|
508
|
-
{ key: "run_id", label: "Run", render: function (r) { return esc(r.run_id); } },
|
|
509
|
-
{ key: "status", label: "Status", render: function (r) {
|
|
551
|
+
{ key: "status", label: "generation_status", render: function (r) {
|
|
510
552
|
var cls = r.completed ? "ok" : (r.status === "request_error" ? "bad" : "warn");
|
|
511
553
|
return '<span class="pill ' + cls + '">' + esc(r.status || "Unknown") + "</span>";
|
|
512
554
|
} },
|
|
555
|
+
{ key: "verifier_status", label: "benchmarking_status", render: function (r) {
|
|
556
|
+
var cls = r.verifier_status === "passed" ? "ok" : (r.verifier_status === "not_configured" ? "warn" : "bad");
|
|
557
|
+
return '<span class="pill ' + cls + '">' + esc(r.verifier_status || "NA") + "</span>";
|
|
558
|
+
} },
|
|
559
|
+
{ key: "solved_score", label: "Solved", render: function (r) { return typeof r.solved_score === "number" ? fmt1(r.solved_score) + "%" : '<span class="n-a">NA</span>'; } },
|
|
513
560
|
{ key: "steps", label: "Steps", render: function (r) { return fmt(r.steps); } },
|
|
514
561
|
{ key: "elapsed_seconds", label: "Time", render: function (r) { return fmt(r.elapsed_seconds) + "s"; } },
|
|
515
562
|
{ key: "tokens", label: "Tokens", render: function (r) { return fmt(r.tokens.total); } },
|
|
@@ -539,9 +586,9 @@
|
|
|
539
586
|
|
|
540
587
|
function draw() {
|
|
541
588
|
var filtered = runs.filter(function (r) {
|
|
542
|
-
if (fModel.value && r.slug !== fModel.value) return false;
|
|
543
|
-
if (fTask.value && r.task_id !== fTask.value) return false;
|
|
544
|
-
if (fOutcome.value && r.status !== fOutcome.value) return false;
|
|
589
|
+
if (fModel && fModel.value && r.slug !== fModel.value) return false;
|
|
590
|
+
if (fTask && fTask.value && r.task_id !== fTask.value) return false;
|
|
591
|
+
if (fOutcome && fOutcome.value && r.status !== fOutcome.value) return false;
|
|
545
592
|
return true;
|
|
546
593
|
});
|
|
547
594
|
sortableTable(container, columns, filtered, {
|
|
@@ -571,7 +618,7 @@
|
|
|
571
618
|
},
|
|
572
619
|
});
|
|
573
620
|
}
|
|
574
|
-
[fModel, fTask, fOutcome].forEach(function (elm) { elm.addEventListener("change", draw); });
|
|
621
|
+
[fModel, fTask, fOutcome].filter(Boolean).forEach(function (elm) { elm.addEventListener("change", draw); });
|
|
575
622
|
draw();
|
|
576
623
|
})();
|
|
577
624
|
})();
|