@aigne/afs-llm-bench 1.12.0-beta.5
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.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/bench.cjs +944 -0
- package/dist/bench.d.cts +89 -0
- package/dist/bench.d.cts.map +1 -0
- package/dist/bench.d.mts +89 -0
- package/dist/bench.d.mts.map +1 -0
- package/dist/bench.mjs +944 -0
- package/dist/bench.mjs.map +1 -0
- package/dist/errors.cjs +14 -0
- package/dist/errors.d.cts +13 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +13 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +14 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +38 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +10 -0
- package/dist/judge.cjs +189 -0
- package/dist/judge.d.cts +67 -0
- package/dist/judge.d.cts.map +1 -0
- package/dist/judge.d.mts +67 -0
- package/dist/judge.d.mts.map +1 -0
- package/dist/judge.mjs +185 -0
- package/dist/judge.mjs.map +1 -0
- package/dist/outlier.cjs +73 -0
- package/dist/outlier.d.cts +30 -0
- package/dist/outlier.d.cts.map +1 -0
- package/dist/outlier.d.mts +30 -0
- package/dist/outlier.d.mts.map +1 -0
- package/dist/outlier.mjs +73 -0
- package/dist/outlier.mjs.map +1 -0
- package/dist/patcher.cjs +296 -0
- package/dist/patcher.d.cts +119 -0
- package/dist/patcher.d.cts.map +1 -0
- package/dist/patcher.d.mts +119 -0
- package/dist/patcher.d.mts.map +1 -0
- package/dist/patcher.mjs +291 -0
- package/dist/patcher.mjs.map +1 -0
- package/dist/prompts.cjs +123 -0
- package/dist/prompts.d.cts +30 -0
- package/dist/prompts.d.cts.map +1 -0
- package/dist/prompts.d.mts +30 -0
- package/dist/prompts.d.mts.map +1 -0
- package/dist/prompts.mjs +121 -0
- package/dist/prompts.mjs.map +1 -0
- package/dist/reporter.cjs +322 -0
- package/dist/reporter.d.cts +27 -0
- package/dist/reporter.d.cts.map +1 -0
- package/dist/reporter.d.mts +27 -0
- package/dist/reporter.d.mts.map +1 -0
- package/dist/reporter.mjs +322 -0
- package/dist/reporter.mjs.map +1 -0
- package/dist/runner.cjs +710 -0
- package/dist/runner.d.cts +345 -0
- package/dist/runner.d.cts.map +1 -0
- package/dist/runner.d.mts +345 -0
- package/dist/runner.d.mts.map +1 -0
- package/dist/runner.mjs +697 -0
- package/dist/runner.mjs.map +1 -0
- package/dist/scoring.cjs +47 -0
- package/dist/scoring.d.cts +28 -0
- package/dist/scoring.d.cts.map +1 -0
- package/dist/scoring.d.mts +28 -0
- package/dist/scoring.d.mts.map +1 -0
- package/dist/scoring.mjs +46 -0
- package/dist/scoring.mjs.map +1 -0
- package/manifest.json +39 -0
- package/package.json +62 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.mjs";
|
|
2
|
+
import "./runner.mjs";
|
|
3
|
+
import { assertWithinResultsDir, validateResultsShape } from "./patcher.mjs";
|
|
4
|
+
import { promises } from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/reporter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Phase 2 (Run 4) — HTML / Markdown report generator (plan.md L1063-1068).
|
|
9
|
+
*
|
|
10
|
+
* Reads a results JSON and produces either a markdown or HTML report:
|
|
11
|
+
* - model × dimension heatmap (emoji thresholds for markdown, color-coded
|
|
12
|
+
* CSS classes for HTML)
|
|
13
|
+
* - per-policy ranking (computed from the same weight tables the patcher
|
|
14
|
+
* uses, resolved through `/dev/ai/score-weights/<name>` by the bench wiring)
|
|
15
|
+
* - delta vs a `previous` results JSON when supplied
|
|
16
|
+
* - per-domain table when domains are present
|
|
17
|
+
*
|
|
18
|
+
* Security invariants (plan.md L1098-1101):
|
|
19
|
+
* 1. All user-controlled strings (model id, judge id, version, etc.) are
|
|
20
|
+
* HTML-escaped before insertion in HTML mode. Markdown mode also escapes
|
|
21
|
+
* `<>` so a model name like `<script>alert(1)<\/script>` cannot be smuggled
|
|
22
|
+
* into a downstream HTML viewer that renders the markdown.
|
|
23
|
+
* 2. Vault credentials never end up in the report — `validateResultsShape`
|
|
24
|
+
* rejects sensitive field names before we ever reach the renderer.
|
|
25
|
+
* 3. Default `includeRaw: false` — raw trace[] is never embedded unless the
|
|
26
|
+
* caller explicitly asks; even then we serialise via JSON.stringify with
|
|
27
|
+
* no key reordering so audit diffs stay clean.
|
|
28
|
+
* 4. `assertWithinResultsDir` keeps the path inside the results root, same
|
|
29
|
+
* as patcher.ts.
|
|
30
|
+
*
|
|
31
|
+
* Read-only: never writes files, never mutates input.
|
|
32
|
+
*/
|
|
33
|
+
const DIMENSIONS = [
|
|
34
|
+
"exploration",
|
|
35
|
+
"tool_reliability",
|
|
36
|
+
"self_stop",
|
|
37
|
+
"parallelism",
|
|
38
|
+
"efficiency",
|
|
39
|
+
"instruction_following"
|
|
40
|
+
];
|
|
41
|
+
const THRESHOLDS = {
|
|
42
|
+
good: .75,
|
|
43
|
+
ok: .5
|
|
44
|
+
};
|
|
45
|
+
async function generateReport(input) {
|
|
46
|
+
const format = input.format ?? "markdown";
|
|
47
|
+
if (format !== "markdown" && format !== "html") throw new ValidationError(`unsupported format "${format}"; use "markdown" or "html"`);
|
|
48
|
+
const current = await loadResults(input.resultsPath, input.resultsRoot);
|
|
49
|
+
const previous = input.previousPath ? await loadResults(input.previousPath, input.resultsRoot) : void 0;
|
|
50
|
+
const weightsTable = await input.loadPolicyWeights();
|
|
51
|
+
if (!weightsTable || typeof weightsTable !== "object" || Array.isArray(weightsTable)) throw new ValidationError("loadPolicyWeights must return a plain object of { policy: weights }");
|
|
52
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
+
const data = assembleReport(current, previous, weightsTable);
|
|
54
|
+
return {
|
|
55
|
+
content: format === "markdown" ? renderMarkdown(data, generatedAt, input.includeRaw === true ? current.raw : void 0) : renderHtml(data, generatedAt, input.includeRaw === true ? current.raw : void 0),
|
|
56
|
+
format,
|
|
57
|
+
generatedAt
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function loadResults(p, root) {
|
|
61
|
+
const safe = assertWithinResultsDir(p, root);
|
|
62
|
+
let text;
|
|
63
|
+
try {
|
|
64
|
+
text = await promises.readFile(safe, "utf8");
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new ValidationError(`failed to read results: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(text);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new ValidationError(`results is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...validateResultsShape(parsed),
|
|
76
|
+
raw: parsed
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function assembleReport(current, previous, weightsTable) {
|
|
80
|
+
const judges = Array.isArray(current.judge_model) ? current.judge_model : current.judge_model ? [current.judge_model] : [];
|
|
81
|
+
const modelIds = Object.keys(current.results).sort();
|
|
82
|
+
const models = modelIds.map((m) => {
|
|
83
|
+
const r = current.results[m];
|
|
84
|
+
const scores = {};
|
|
85
|
+
for (const d of DIMENSIONS) scores[d] = r.scores[d];
|
|
86
|
+
return {
|
|
87
|
+
model: m,
|
|
88
|
+
scores,
|
|
89
|
+
overall: meanOf(Object.values(r.scores).filter(Number.isFinite))
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
const dims = models.some((m) => typeof m.scores.instruction_following === "number") ? DIMENSIONS : DIMENSIONS.filter((d) => d !== "instruction_following");
|
|
93
|
+
const policies = Object.entries(weightsTable).map(([policy, weights]) => {
|
|
94
|
+
return {
|
|
95
|
+
policy,
|
|
96
|
+
rows: modelIds.map((m) => ({
|
|
97
|
+
model: m,
|
|
98
|
+
score: computeWeightedScore(current.results[m], weights)
|
|
99
|
+
})).sort((a, b) => b.score - a.score)
|
|
100
|
+
};
|
|
101
|
+
}).sort((a, b) => a.policy.localeCompare(b.policy));
|
|
102
|
+
const domains = modelIds.map((m) => ({
|
|
103
|
+
model: m,
|
|
104
|
+
domains: current.results[m]?.domains ?? {}
|
|
105
|
+
})).filter((row) => Object.keys(row.domains).length > 0);
|
|
106
|
+
const delta = previous ? computeDelta(current, previous) : [];
|
|
107
|
+
return {
|
|
108
|
+
version: current.version,
|
|
109
|
+
judges,
|
|
110
|
+
dims,
|
|
111
|
+
models,
|
|
112
|
+
policies,
|
|
113
|
+
domains,
|
|
114
|
+
delta
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function meanOf(xs) {
|
|
118
|
+
if (xs.length === 0) return void 0;
|
|
119
|
+
let s = 0;
|
|
120
|
+
for (const x of xs) s += x;
|
|
121
|
+
return s / xs.length;
|
|
122
|
+
}
|
|
123
|
+
function computeDelta(current, previous) {
|
|
124
|
+
const rows = [];
|
|
125
|
+
const models = new Set([...Object.keys(current.results), ...Object.keys(previous.results)]);
|
|
126
|
+
for (const m of [...models].sort()) {
|
|
127
|
+
const cur = current.results[m]?.scores ?? {};
|
|
128
|
+
const prev = previous.results[m]?.scores ?? {};
|
|
129
|
+
for (const dim of DIMENSIONS) {
|
|
130
|
+
const c = cur[dim];
|
|
131
|
+
const p = prev[dim];
|
|
132
|
+
if (typeof c !== "number" && typeof p !== "number") continue;
|
|
133
|
+
if (c === p) continue;
|
|
134
|
+
const d = typeof c === "number" && typeof p === "number" ? c - p : void 0;
|
|
135
|
+
rows.push({
|
|
136
|
+
model: m,
|
|
137
|
+
dimension: dim,
|
|
138
|
+
prev: p,
|
|
139
|
+
curr: c,
|
|
140
|
+
delta: d
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return rows;
|
|
145
|
+
}
|
|
146
|
+
function computeWeightedScore(entry, weights) {
|
|
147
|
+
let total = 0;
|
|
148
|
+
for (const [key, w] of Object.entries(weights)) {
|
|
149
|
+
const v = getNested(entry, key);
|
|
150
|
+
if (typeof v === "number" && Number.isFinite(v)) total += v * w;
|
|
151
|
+
}
|
|
152
|
+
return total;
|
|
153
|
+
}
|
|
154
|
+
function getNested(obj, p) {
|
|
155
|
+
let cur = obj;
|
|
156
|
+
for (const k of p.split(".")) {
|
|
157
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
158
|
+
cur = cur[k];
|
|
159
|
+
}
|
|
160
|
+
return cur;
|
|
161
|
+
}
|
|
162
|
+
function renderMarkdown(data, generatedAt, raw) {
|
|
163
|
+
const lines = [];
|
|
164
|
+
lines.push("# LLM AFS Benchmark Report");
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`**Generated:** ${escMd(generatedAt)}`);
|
|
167
|
+
lines.push(`**Run version:** ${escMd(data.version)}`);
|
|
168
|
+
if (data.judges.length > 0) lines.push(`**Judge(s):** ${data.judges.map(escMd).join(", ")}`);
|
|
169
|
+
lines.push(`**Models:** ${data.models.length}`);
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push("## Model × Dimension Heatmap");
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(`| model | ${data.dims.join(" | ")} | overall |`);
|
|
174
|
+
lines.push(`|---|${data.dims.map(() => "---").join("|")}|---|`);
|
|
175
|
+
for (const row of data.models) {
|
|
176
|
+
const cells = data.dims.map((d) => formatCellMd(row.scores[d]));
|
|
177
|
+
const overall = typeof row.overall === "number" ? row.overall.toFixed(2) : "-";
|
|
178
|
+
lines.push(`| ${escMd(row.model)} | ${cells.join(" | ")} | ${overall} |`);
|
|
179
|
+
}
|
|
180
|
+
lines.push("");
|
|
181
|
+
if (data.policies.length > 0) {
|
|
182
|
+
lines.push("## Per-Policy Ranking");
|
|
183
|
+
lines.push("");
|
|
184
|
+
for (const p of data.policies) {
|
|
185
|
+
lines.push(`### ${escMd(p.policy)}`);
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push("| rank | model | score |");
|
|
188
|
+
lines.push("|---|---|---|");
|
|
189
|
+
p.rows.forEach((r, i) => {
|
|
190
|
+
lines.push(`| ${i + 1} | ${escMd(r.model)} | ${r.score.toFixed(3)} |`);
|
|
191
|
+
});
|
|
192
|
+
lines.push("");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (data.domains.length > 0) {
|
|
196
|
+
const allDomains = collectDomainKeys(data.domains);
|
|
197
|
+
lines.push("## Domains");
|
|
198
|
+
lines.push("");
|
|
199
|
+
lines.push(`| model | ${allDomains.join(" | ")} |`);
|
|
200
|
+
lines.push(`|---|${allDomains.map(() => "---").join("|")}|`);
|
|
201
|
+
for (const row of data.domains) {
|
|
202
|
+
const cells = allDomains.map((d) => formatCellMd(row.domains[d]));
|
|
203
|
+
lines.push(`| ${escMd(row.model)} | ${cells.join(" | ")} |`);
|
|
204
|
+
}
|
|
205
|
+
lines.push("");
|
|
206
|
+
}
|
|
207
|
+
if (data.delta.length > 0) {
|
|
208
|
+
lines.push("## Delta vs previous run");
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push("| model | dimension | prev | curr | delta |");
|
|
211
|
+
lines.push("|---|---|---|---|---|");
|
|
212
|
+
for (const d of data.delta) lines.push(`| ${escMd(d.model)} | ${escMd(d.dimension)} | ${formatNum(d.prev)} | ${formatNum(d.curr)} | ${formatDelta(d.delta)} |`);
|
|
213
|
+
lines.push("");
|
|
214
|
+
}
|
|
215
|
+
if (raw !== void 0) {
|
|
216
|
+
lines.push("## Raw trace");
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push("```json");
|
|
219
|
+
lines.push(JSON.stringify(raw, null, 2));
|
|
220
|
+
lines.push("```");
|
|
221
|
+
lines.push("");
|
|
222
|
+
}
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|
|
225
|
+
function escMd(s) {
|
|
226
|
+
return String(s).replace(/[<>|]/g, (c) => c === "<" ? "<" : c === ">" ? ">" : "\\|");
|
|
227
|
+
}
|
|
228
|
+
function formatCellMd(v) {
|
|
229
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return "-";
|
|
230
|
+
return `${v >= THRESHOLDS.good ? "✅" : v >= THRESHOLDS.ok ? "⚠️" : "❌"} ${v.toFixed(2)}`;
|
|
231
|
+
}
|
|
232
|
+
function formatNum(v) {
|
|
233
|
+
return typeof v === "number" && Number.isFinite(v) ? v.toFixed(2) : "-";
|
|
234
|
+
}
|
|
235
|
+
function formatDelta(v) {
|
|
236
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return "-";
|
|
237
|
+
return `${v > 0 ? "+" : ""}${v.toFixed(2)}`;
|
|
238
|
+
}
|
|
239
|
+
function renderHtml(data, generatedAt, raw) {
|
|
240
|
+
const out = [];
|
|
241
|
+
out.push("<!doctype html>");
|
|
242
|
+
out.push("<html lang=\"en\">");
|
|
243
|
+
out.push("<head>");
|
|
244
|
+
out.push("<meta charset=\"utf-8\">");
|
|
245
|
+
out.push("<title>LLM AFS Benchmark Report</title>");
|
|
246
|
+
out.push("<style>");
|
|
247
|
+
out.push("body{font-family:system-ui,sans-serif;margin:2rem;color:#222}", "table{border-collapse:collapse;margin-bottom:1.5rem}", "th,td{border:1px solid #ccc;padding:.4rem .8rem;text-align:left}", "th{background:#f4f4f4}", ".good{background:#c6f6d5}.ok{background:#fef3c7}.bad{background:#fee2e2}", ".pos{color:#15803d}.neg{color:#b91c1c}", "h2{margin-top:2rem}");
|
|
248
|
+
out.push("</style>");
|
|
249
|
+
out.push("</head>");
|
|
250
|
+
out.push("<body>");
|
|
251
|
+
out.push("<h1>LLM AFS Benchmark Report</h1>");
|
|
252
|
+
out.push(`<p><strong>Generated:</strong> ${esc(generatedAt)}</p>`);
|
|
253
|
+
out.push(`<p><strong>Run version:</strong> ${esc(data.version)}</p>`);
|
|
254
|
+
if (data.judges.length > 0) out.push(`<p><strong>Judge(s):</strong> ${data.judges.map(esc).join(", ")}</p>`);
|
|
255
|
+
out.push(`<p><strong>Models:</strong> ${data.models.length}</p>`);
|
|
256
|
+
out.push("<h2>Model × Dimension Heatmap</h2>");
|
|
257
|
+
out.push("<table>");
|
|
258
|
+
out.push(`<thead><tr><th>model</th>${data.dims.map((d) => `<th>${esc(d)}</th>`).join("")}<th>overall</th></tr></thead>`);
|
|
259
|
+
out.push("<tbody>");
|
|
260
|
+
for (const row of data.models) {
|
|
261
|
+
const cells = data.dims.map((d) => htmlCell(row.scores[d])).join("");
|
|
262
|
+
const overall = typeof row.overall === "number" ? row.overall.toFixed(2) : "-";
|
|
263
|
+
out.push(`<tr><td>${esc(row.model)}</td>${cells}<td>${overall}</td></tr>`);
|
|
264
|
+
}
|
|
265
|
+
out.push("</tbody></table>");
|
|
266
|
+
if (data.policies.length > 0) {
|
|
267
|
+
out.push("<h2>Per-Policy Ranking</h2>");
|
|
268
|
+
for (const p of data.policies) {
|
|
269
|
+
out.push(`<h3>${esc(p.policy)}</h3>`);
|
|
270
|
+
out.push("<table>");
|
|
271
|
+
out.push("<thead><tr><th>rank</th><th>model</th><th>score</th></tr></thead><tbody>");
|
|
272
|
+
p.rows.forEach((r, i) => {
|
|
273
|
+
out.push(`<tr><td>${i + 1}</td><td>${esc(r.model)}</td><td>${r.score.toFixed(3)}</td></tr>`);
|
|
274
|
+
});
|
|
275
|
+
out.push("</tbody></table>");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (data.domains.length > 0) {
|
|
279
|
+
const allDomains = collectDomainKeys(data.domains);
|
|
280
|
+
out.push("<h2>Domains</h2>");
|
|
281
|
+
out.push("<table>");
|
|
282
|
+
out.push(`<thead><tr><th>model</th>${allDomains.map((d) => `<th>${esc(d)}</th>`).join("")}</tr></thead>`);
|
|
283
|
+
out.push("<tbody>");
|
|
284
|
+
for (const row of data.domains) {
|
|
285
|
+
const cells = allDomains.map((d) => htmlCell(row.domains[d])).join("");
|
|
286
|
+
out.push(`<tr><td>${esc(row.model)}</td>${cells}</tr>`);
|
|
287
|
+
}
|
|
288
|
+
out.push("</tbody></table>");
|
|
289
|
+
}
|
|
290
|
+
if (data.delta.length > 0) {
|
|
291
|
+
out.push("<h2>Delta vs previous run</h2>");
|
|
292
|
+
out.push("<table>");
|
|
293
|
+
out.push("<thead><tr><th>model</th><th>dimension</th><th>prev</th><th>curr</th><th>delta</th></tr></thead><tbody>");
|
|
294
|
+
for (const d of data.delta) {
|
|
295
|
+
const sign = d.delta === void 0 ? "" : d.delta > 0 ? "pos" : d.delta < 0 ? "neg" : "";
|
|
296
|
+
out.push(`<tr><td>${esc(d.model)}</td><td>${esc(d.dimension)}</td><td>${formatNum(d.prev)}</td><td>${formatNum(d.curr)}</td><td class="${sign}">${formatDelta(d.delta)}</td></tr>`);
|
|
297
|
+
}
|
|
298
|
+
out.push("</tbody></table>");
|
|
299
|
+
}
|
|
300
|
+
if (raw !== void 0) {
|
|
301
|
+
out.push("<h2>Raw trace</h2>");
|
|
302
|
+
out.push(`<pre>${esc(JSON.stringify(raw, null, 2))}</pre>`);
|
|
303
|
+
}
|
|
304
|
+
out.push("</body></html>");
|
|
305
|
+
return out.join("\n");
|
|
306
|
+
}
|
|
307
|
+
function htmlCell(v) {
|
|
308
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return "<td>-</td>";
|
|
309
|
+
return `<td class="${v >= THRESHOLDS.good ? "good" : v >= THRESHOLDS.ok ? "ok" : "bad"}">${v.toFixed(2)}</td>`;
|
|
310
|
+
}
|
|
311
|
+
function esc(s) {
|
|
312
|
+
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
313
|
+
}
|
|
314
|
+
function collectDomainKeys(rows) {
|
|
315
|
+
const set = /* @__PURE__ */ new Set();
|
|
316
|
+
for (const r of rows) for (const k of Object.keys(r.domains)) set.add(k);
|
|
317
|
+
return [...set].sort();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
export { generateReport };
|
|
322
|
+
//# sourceMappingURL=reporter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.mjs","names":["fs"],"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Phase 2 (Run 4) — HTML / Markdown report generator (plan.md L1063-1068).\n *\n * Reads a results JSON and produces either a markdown or HTML report:\n * - model × dimension heatmap (emoji thresholds for markdown, color-coded\n * CSS classes for HTML)\n * - per-policy ranking (computed from the same weight tables the patcher\n * uses, resolved through `/dev/ai/score-weights/<name>` by the bench wiring)\n * - delta vs a `previous` results JSON when supplied\n * - per-domain table when domains are present\n *\n * Security invariants (plan.md L1098-1101):\n * 1. All user-controlled strings (model id, judge id, version, etc.) are\n * HTML-escaped before insertion in HTML mode. Markdown mode also escapes\n * `<>` so a model name like `<script>alert(1)</script>` cannot be smuggled\n * into a downstream HTML viewer that renders the markdown.\n * 2. Vault credentials never end up in the report — `validateResultsShape`\n * rejects sensitive field names before we ever reach the renderer.\n * 3. Default `includeRaw: false` — raw trace[] is never embedded unless the\n * caller explicitly asks; even then we serialise via JSON.stringify with\n * no key reordering so audit diffs stay clean.\n * 4. `assertWithinResultsDir` keeps the path inside the results root, same\n * as patcher.ts.\n *\n * Read-only: never writes files, never mutates input.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport {\n assertWithinResultsDir,\n type PolicyWeightsTable,\n type ValidatedResults,\n validateResultsShape,\n} from \"./patcher.js\";\nimport { ValidationError } from \"./runner.js\";\n\n// ─── Public types ────────────────────────────────────────────────────────\n\nexport type ReportFormat = \"markdown\" | \"html\";\n\nexport interface GenerateReportInput {\n resultsPath: string;\n resultsRoot: string;\n format?: ReportFormat;\n previousPath?: string;\n includeRaw?: boolean;\n /**\n * Resolve every policy → its weight map. Bench wires this through AFS\n * (`/dev/ai/score-weights/<name>`) so the reporter stays decoupled from\n * ai-device internals — same pattern as patcher.ts. Returning an empty\n * object means the report skips the per-policy ranking section.\n */\n loadPolicyWeights: () => Promise<PolicyWeightsTable>;\n}\n\nexport interface GeneratedReport {\n content: string;\n format: ReportFormat;\n generatedAt: string;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────\n\nconst DIMENSIONS = [\n \"exploration\",\n \"tool_reliability\",\n \"self_stop\",\n \"parallelism\",\n \"efficiency\",\n \"instruction_following\",\n] as const;\n\n// Emoji heatmap thresholds (markdown). HTML mode mirrors the same banding\n// but with CSS classes so a stylesheet can pick colours without us inlining\n// presentation here.\nconst THRESHOLDS = { good: 0.75, ok: 0.5 } as const;\n\n// ─── Entry point ─────────────────────────────────────────────────────────\n\nexport async function generateReport(input: GenerateReportInput): Promise<GeneratedReport> {\n const format: ReportFormat = input.format ?? \"markdown\";\n if (format !== \"markdown\" && format !== \"html\") {\n throw new ValidationError(`unsupported format \"${format}\"; use \"markdown\" or \"html\"`);\n }\n\n const current = await loadResults(input.resultsPath, input.resultsRoot);\n const previous = input.previousPath\n ? await loadResults(input.previousPath, input.resultsRoot)\n : undefined;\n\n const weightsTable = await input.loadPolicyWeights();\n if (!weightsTable || typeof weightsTable !== \"object\" || Array.isArray(weightsTable)) {\n throw new ValidationError(\n \"loadPolicyWeights must return a plain object of { policy: weights }\",\n );\n }\n\n const generatedAt = new Date().toISOString();\n const data = assembleReport(current, previous, weightsTable);\n\n const content =\n format === \"markdown\"\n ? renderMarkdown(data, generatedAt, input.includeRaw === true ? current.raw : undefined)\n : renderHtml(data, generatedAt, input.includeRaw === true ? current.raw : undefined);\n\n return { content, format, generatedAt };\n}\n\n// ─── Loading + validation ────────────────────────────────────────────────\n\ninterface LoadedResults extends ValidatedResults {\n /** Raw parsed JSON, unmodified — surfaced only when `includeRaw` is set. */\n raw: unknown;\n}\n\nasync function loadResults(p: string, root: string): Promise<LoadedResults> {\n const safe = assertWithinResultsDir(p, root);\n let text: string;\n try {\n text = await fs.readFile(safe, \"utf8\");\n } catch (err) {\n throw new ValidationError(\n `failed to read results: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new ValidationError(\n `results is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n const validated = validateResultsShape(parsed);\n return { ...validated, raw: parsed };\n}\n\n// ─── Data assembly ───────────────────────────────────────────────────────\n\ninterface ReportData {\n version: string;\n judges: string[];\n /** Dimensions to render — drops `instruction_following` when no model has it. */\n dims: readonly string[];\n models: ModelRow[];\n policies: PolicyRanking[];\n domains: DomainRow[];\n delta: DeltaRow[];\n}\n\ninterface ModelRow {\n model: string;\n scores: Record<string, number | undefined>;\n overall: number | undefined;\n}\n\ninterface PolicyRanking {\n policy: string;\n rows: Array<{ model: string; score: number }>;\n}\n\ninterface DomainRow {\n model: string;\n domains: Record<string, number>;\n}\n\ninterface DeltaRow {\n model: string;\n dimension: string;\n prev: number | undefined;\n curr: number | undefined;\n delta: number | undefined;\n}\n\nfunction assembleReport(\n current: LoadedResults,\n previous: LoadedResults | undefined,\n weightsTable: PolicyWeightsTable,\n): ReportData {\n const judges = Array.isArray(current.judge_model)\n ? current.judge_model\n : current.judge_model\n ? [current.judge_model]\n : [];\n\n const modelIds = Object.keys(current.results).sort();\n const models: ModelRow[] = modelIds.map((m) => {\n const r = current.results[m]!;\n const scores: Record<string, number | undefined> = {};\n for (const d of DIMENSIONS) scores[d] = r.scores[d];\n return {\n model: m,\n scores,\n overall: meanOf(Object.values(r.scores).filter(Number.isFinite)),\n };\n });\n\n // Drop the instruction_following column when no model produced it, so a\n // non-judged run isn't padded with a column of dashes.\n const dims = models.some((m) => typeof m.scores.instruction_following === \"number\")\n ? DIMENSIONS\n : DIMENSIONS.filter((d) => d !== \"instruction_following\");\n\n const policies: PolicyRanking[] = Object.entries(weightsTable)\n .map(([policy, weights]) => {\n const rows = modelIds\n .map((m) => ({ model: m, score: computeWeightedScore(current.results[m]!, weights) }))\n .sort((a, b) => b.score - a.score);\n return { policy, rows };\n })\n .sort((a, b) => a.policy.localeCompare(b.policy));\n\n const domains: DomainRow[] = modelIds\n .map((m) => ({ model: m, domains: current.results[m]?.domains ?? {} }))\n .filter((row) => Object.keys(row.domains).length > 0);\n\n const delta: DeltaRow[] = previous ? computeDelta(current, previous) : [];\n\n return { version: current.version, judges, dims, models, policies, domains, delta };\n}\n\nfunction meanOf(xs: number[]): number | undefined {\n if (xs.length === 0) return undefined;\n let s = 0;\n for (const x of xs) s += x;\n return s / xs.length;\n}\n\nfunction computeDelta(current: LoadedResults, previous: LoadedResults): DeltaRow[] {\n const rows: DeltaRow[] = [];\n const models = new Set([...Object.keys(current.results), ...Object.keys(previous.results)]);\n for (const m of [...models].sort()) {\n const cur: Record<string, number | undefined> = current.results[m]?.scores ?? {};\n const prev: Record<string, number | undefined> = previous.results[m]?.scores ?? {};\n for (const dim of DIMENSIONS) {\n const c = cur[dim];\n const p = prev[dim];\n if (typeof c !== \"number\" && typeof p !== \"number\") continue;\n // Only surface rows where the score actually moved — a flat 0.00 → 0.00\n // entry is noise for a maintainer scanning the diff.\n if (c === p) continue;\n const d = typeof c === \"number\" && typeof p === \"number\" ? c - p : undefined;\n rows.push({ model: m, dimension: dim, prev: p, curr: c, delta: d });\n }\n }\n return rows;\n}\n\n// Mirrors `computeWeightedScore` in patcher.ts — kept inline so reporter stays\n// independent (scope rule for Phase 2). Each weight key is a dot-path like\n// \"scores.parallelism\" or \"domains.coding\"; missing values contribute 0.\nfunction computeWeightedScore(entry: object, weights: Record<string, number>): number {\n let total = 0;\n for (const [key, w] of Object.entries(weights)) {\n const v = getNested(entry, key);\n if (typeof v === \"number\" && Number.isFinite(v)) total += v * w;\n }\n return total;\n}\n\nfunction getNested(obj: unknown, p: string): unknown {\n let cur: unknown = obj;\n for (const k of p.split(\".\")) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[k];\n }\n return cur;\n}\n\n// ─── Markdown rendering ──────────────────────────────────────────────────\n\nfunction renderMarkdown(data: ReportData, generatedAt: string, raw: unknown): string {\n const lines: string[] = [];\n lines.push(\"# LLM AFS Benchmark Report\");\n lines.push(\"\");\n lines.push(`**Generated:** ${escMd(generatedAt)}`);\n lines.push(`**Run version:** ${escMd(data.version)}`);\n if (data.judges.length > 0) {\n lines.push(`**Judge(s):** ${data.judges.map(escMd).join(\", \")}`);\n }\n lines.push(`**Models:** ${data.models.length}`);\n lines.push(\"\");\n\n lines.push(\"## Model × Dimension Heatmap\");\n lines.push(\"\");\n lines.push(`| model | ${data.dims.join(\" | \")} | overall |`);\n lines.push(`|---|${data.dims.map(() => \"---\").join(\"|\")}|---|`);\n for (const row of data.models) {\n const cells = data.dims.map((d) => formatCellMd(row.scores[d]));\n const overall = typeof row.overall === \"number\" ? row.overall.toFixed(2) : \"-\";\n lines.push(`| ${escMd(row.model)} | ${cells.join(\" | \")} | ${overall} |`);\n }\n lines.push(\"\");\n\n if (data.policies.length > 0) {\n lines.push(\"## Per-Policy Ranking\");\n lines.push(\"\");\n for (const p of data.policies) {\n lines.push(`### ${escMd(p.policy)}`);\n lines.push(\"\");\n lines.push(\"| rank | model | score |\");\n lines.push(\"|---|---|---|\");\n p.rows.forEach((r, i) => {\n lines.push(`| ${i + 1} | ${escMd(r.model)} | ${r.score.toFixed(3)} |`);\n });\n lines.push(\"\");\n }\n }\n\n if (data.domains.length > 0) {\n const allDomains = collectDomainKeys(data.domains);\n lines.push(\"## Domains\");\n lines.push(\"\");\n lines.push(`| model | ${allDomains.join(\" | \")} |`);\n lines.push(`|---|${allDomains.map(() => \"---\").join(\"|\")}|`);\n for (const row of data.domains) {\n const cells = allDomains.map((d) => formatCellMd(row.domains[d]));\n lines.push(`| ${escMd(row.model)} | ${cells.join(\" | \")} |`);\n }\n lines.push(\"\");\n }\n\n if (data.delta.length > 0) {\n lines.push(\"## Delta vs previous run\");\n lines.push(\"\");\n lines.push(\"| model | dimension | prev | curr | delta |\");\n lines.push(\"|---|---|---|---|---|\");\n for (const d of data.delta) {\n lines.push(\n `| ${escMd(d.model)} | ${escMd(d.dimension)} | ${formatNum(d.prev)} | ${formatNum(d.curr)} | ${formatDelta(d.delta)} |`,\n );\n }\n lines.push(\"\");\n }\n\n if (raw !== undefined) {\n lines.push(\"## Raw trace\");\n lines.push(\"\");\n lines.push(\"```json\");\n lines.push(JSON.stringify(raw, null, 2));\n lines.push(\"```\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n// Treat `<` and `>` as untrusted in markdown too — many markdown viewers\n// render raw HTML (CommonMark allows it), so a model name like\n// `<script>alert(1)</script>` would execute in those contexts. Pipe escaping\n// keeps multi-cell rows from collapsing when an id contains `|`.\nfunction escMd(s: string): string {\n return String(s).replace(/[<>|]/g, (c) => (c === \"<\" ? \"<\" : c === \">\" ? \">\" : \"\\\\|\"));\n}\n\nfunction formatCellMd(v: number | undefined): string {\n if (typeof v !== \"number\" || !Number.isFinite(v)) return \"-\";\n const emoji = v >= THRESHOLDS.good ? \"✅\" : v >= THRESHOLDS.ok ? \"⚠️\" : \"❌\";\n return `${emoji} ${v.toFixed(2)}`;\n}\n\nfunction formatNum(v: number | undefined): string {\n return typeof v === \"number\" && Number.isFinite(v) ? v.toFixed(2) : \"-\";\n}\n\nfunction formatDelta(v: number | undefined): string {\n if (typeof v !== \"number\" || !Number.isFinite(v)) return \"-\";\n const sign = v > 0 ? \"+\" : \"\";\n return `${sign}${v.toFixed(2)}`;\n}\n\n// ─── HTML rendering ──────────────────────────────────────────────────────\n\nfunction renderHtml(data: ReportData, generatedAt: string, raw: unknown): string {\n const out: string[] = [];\n out.push(\"<!doctype html>\");\n out.push('<html lang=\"en\">');\n out.push(\"<head>\");\n out.push('<meta charset=\"utf-8\">');\n out.push(\"<title>LLM AFS Benchmark Report</title>\");\n out.push(\"<style>\");\n out.push(\n \"body{font-family:system-ui,sans-serif;margin:2rem;color:#222}\",\n \"table{border-collapse:collapse;margin-bottom:1.5rem}\",\n \"th,td{border:1px solid #ccc;padding:.4rem .8rem;text-align:left}\",\n \"th{background:#f4f4f4}\",\n \".good{background:#c6f6d5}.ok{background:#fef3c7}.bad{background:#fee2e2}\",\n \".pos{color:#15803d}.neg{color:#b91c1c}\",\n \"h2{margin-top:2rem}\",\n );\n out.push(\"</style>\");\n out.push(\"</head>\");\n out.push(\"<body>\");\n\n out.push(\"<h1>LLM AFS Benchmark Report</h1>\");\n out.push(`<p><strong>Generated:</strong> ${esc(generatedAt)}</p>`);\n out.push(`<p><strong>Run version:</strong> ${esc(data.version)}</p>`);\n if (data.judges.length > 0) {\n out.push(`<p><strong>Judge(s):</strong> ${data.judges.map(esc).join(\", \")}</p>`);\n }\n out.push(`<p><strong>Models:</strong> ${data.models.length}</p>`);\n\n out.push(\"<h2>Model × Dimension Heatmap</h2>\");\n out.push(\"<table>\");\n out.push(\n `<thead><tr><th>model</th>${data.dims.map((d) => `<th>${esc(d)}</th>`).join(\"\")}<th>overall</th></tr></thead>`,\n );\n out.push(\"<tbody>\");\n for (const row of data.models) {\n const cells = data.dims.map((d) => htmlCell(row.scores[d])).join(\"\");\n const overall = typeof row.overall === \"number\" ? row.overall.toFixed(2) : \"-\";\n out.push(`<tr><td>${esc(row.model)}</td>${cells}<td>${overall}</td></tr>`);\n }\n out.push(\"</tbody></table>\");\n\n if (data.policies.length > 0) {\n out.push(\"<h2>Per-Policy Ranking</h2>\");\n for (const p of data.policies) {\n out.push(`<h3>${esc(p.policy)}</h3>`);\n out.push(\"<table>\");\n out.push(\"<thead><tr><th>rank</th><th>model</th><th>score</th></tr></thead><tbody>\");\n p.rows.forEach((r, i) => {\n out.push(\n `<tr><td>${i + 1}</td><td>${esc(r.model)}</td><td>${r.score.toFixed(3)}</td></tr>`,\n );\n });\n out.push(\"</tbody></table>\");\n }\n }\n\n if (data.domains.length > 0) {\n const allDomains = collectDomainKeys(data.domains);\n out.push(\"<h2>Domains</h2>\");\n out.push(\"<table>\");\n out.push(\n `<thead><tr><th>model</th>${allDomains.map((d) => `<th>${esc(d)}</th>`).join(\"\")}</tr></thead>`,\n );\n out.push(\"<tbody>\");\n for (const row of data.domains) {\n const cells = allDomains.map((d) => htmlCell(row.domains[d])).join(\"\");\n out.push(`<tr><td>${esc(row.model)}</td>${cells}</tr>`);\n }\n out.push(\"</tbody></table>\");\n }\n\n if (data.delta.length > 0) {\n out.push(\"<h2>Delta vs previous run</h2>\");\n out.push(\"<table>\");\n out.push(\n \"<thead><tr><th>model</th><th>dimension</th><th>prev</th><th>curr</th><th>delta</th></tr></thead><tbody>\",\n );\n for (const d of data.delta) {\n const sign = d.delta === undefined ? \"\" : d.delta > 0 ? \"pos\" : d.delta < 0 ? \"neg\" : \"\";\n out.push(\n `<tr><td>${esc(d.model)}</td><td>${esc(d.dimension)}</td><td>${formatNum(d.prev)}</td><td>${formatNum(d.curr)}</td><td class=\"${sign}\">${formatDelta(d.delta)}</td></tr>`,\n );\n }\n out.push(\"</tbody></table>\");\n }\n\n if (raw !== undefined) {\n out.push(\"<h2>Raw trace</h2>\");\n out.push(`<pre>${esc(JSON.stringify(raw, null, 2))}</pre>`);\n }\n\n out.push(\"</body></html>\");\n return out.join(\"\\n\");\n}\n\nfunction htmlCell(v: number | undefined): string {\n if (typeof v !== \"number\" || !Number.isFinite(v)) return \"<td>-</td>\";\n const cls = v >= THRESHOLDS.good ? \"good\" : v >= THRESHOLDS.ok ? \"ok\" : \"bad\";\n return `<td class=\"${cls}\">${v.toFixed(2)}</td>`;\n}\n\nfunction esc(s: string): string {\n return String(s)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction collectDomainKeys(rows: DomainRow[]): string[] {\n const set = new Set<string>();\n for (const r of rows) for (const k of Object.keys(r.domains)) set.add(k);\n return [...set].sort();\n}\n"],"mappings":"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"}
|