@tangle-network/agent-bench 0.4.1 → 0.4.4
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/CHANGELOG.md +14 -0
- package/dist/adapters.d.ts +4 -11
- package/dist/adapters.js +78 -41
- package/dist/adapters.js.map +1 -1
- package/dist/benchmarks/_harness.d.ts +48 -66
- package/dist/benchmarks/_harness.js +329 -33
- package/dist/benchmarks/_harness.js.map +1 -1
- package/dist/benchmarks/aec-bench.d.ts +4 -25
- package/dist/benchmarks/aec-bench.js +242 -7
- package/dist/benchmarks/aec-bench.js.map +1 -1
- package/dist/benchmarks/agentbench.d.ts +5 -13
- package/dist/benchmarks/agentbench.js +114 -9
- package/dist/benchmarks/agentbench.js.map +1 -1
- package/dist/benchmarks/appworld.d.ts +9 -29
- package/dist/benchmarks/appworld.js +317 -12
- package/dist/benchmarks/appworld.js.map +1 -1
- package/dist/benchmarks/bfcl.d.ts +5 -15
- package/dist/benchmarks/bfcl.js +264 -9
- package/dist/benchmarks/bfcl.js.map +1 -1
- package/dist/benchmarks/cad-design.d.ts +16 -41
- package/dist/benchmarks/cad-design.js +512 -6
- package/dist/benchmarks/cad-design.js.map +1 -1
- package/dist/benchmarks/cadbench.d.ts +4 -17
- package/dist/benchmarks/cadbench.js +2 -8
- package/dist/benchmarks/cadgenbench.d.ts +4 -20
- package/dist/benchmarks/cadgenbench.js +2 -8
- package/dist/benchmarks/commit0.d.ts +5 -27
- package/dist/benchmarks/commit0.js +187 -9
- package/dist/benchmarks/commit0.js.map +1 -1
- package/dist/benchmarks/crag.d.ts +4 -12
- package/dist/benchmarks/crag.js +110 -8
- package/dist/benchmarks/crag.js.map +1 -1
- package/dist/benchmarks/dabstep.d.ts +5 -15
- package/dist/benchmarks/dabstep.js +177 -9
- package/dist/benchmarks/dabstep.js.map +1 -1
- package/dist/benchmarks/enterpriseops-gym.d.ts +5 -34
- package/dist/benchmarks/enterpriseops-gym.js +236 -9
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -1
- package/dist/benchmarks/finresearchbench.d.ts +4 -13
- package/dist/benchmarks/finresearchbench.js +218 -7
- package/dist/benchmarks/finresearchbench.js.map +1 -1
- package/dist/benchmarks/finsearchcomp.d.ts +8 -39
- package/dist/benchmarks/finsearchcomp.js +267 -6
- package/dist/benchmarks/finsearchcomp.js.map +1 -1
- package/dist/benchmarks/frames.d.ts +15 -37
- package/dist/benchmarks/frames.js +408 -11
- package/dist/benchmarks/frames.js.map +1 -1
- package/dist/benchmarks/hotpotqa.d.ts +4 -24
- package/dist/benchmarks/hotpotqa.js +250 -14
- package/dist/benchmarks/hotpotqa.js.map +1 -1
- package/dist/benchmarks/humaneval.d.ts +19 -37
- package/dist/benchmarks/humaneval.js +279 -16
- package/dist/benchmarks/humaneval.js.map +1 -1
- package/dist/benchmarks/mind2web.d.ts +7 -34
- package/dist/benchmarks/mind2web.js +257 -8
- package/dist/benchmarks/mind2web.js.map +1 -1
- package/dist/benchmarks/nomiracl.d.ts +4 -13
- package/dist/benchmarks/nomiracl.js +146 -8
- package/dist/benchmarks/nomiracl.js.map +1 -1
- package/dist/benchmarks/open-rag-bench.d.ts +4 -12
- package/dist/benchmarks/open-rag-bench.js +110 -8
- package/dist/benchmarks/open-rag-bench.js.map +1 -1
- package/dist/benchmarks/programbench.d.ts +5 -29
- package/dist/benchmarks/programbench.js +161 -9
- package/dist/benchmarks/programbench.js.map +1 -1
- package/dist/benchmarks/rag-shared.d.ts +21 -20
- package/dist/benchmarks/rag-shared.js +245 -38
- package/dist/benchmarks/rag-shared.js.map +1 -1
- package/dist/benchmarks/ragbench.d.ts +4 -14
- package/dist/benchmarks/ragbench.js +127 -8
- package/dist/benchmarks/ragbench.js.map +1 -1
- package/dist/benchmarks/simpleqa.d.ts +17 -44
- package/dist/benchmarks/simpleqa.js +293 -10
- package/dist/benchmarks/simpleqa.js.map +1 -1
- package/dist/benchmarks/swe-bench.d.ts +23 -36
- package/dist/benchmarks/swe-bench.js +234 -13
- package/dist/benchmarks/swe-bench.js.map +1 -1
- package/dist/benchmarks/t2-ragbench.d.ts +4 -12
- package/dist/benchmarks/t2-ragbench.js +118 -8
- package/dist/benchmarks/t2-ragbench.js.map +1 -1
- package/dist/benchmarks/tau-bench-shared.d.ts +14 -22
- package/dist/benchmarks/tau-bench-shared.js +169 -9
- package/dist/benchmarks/tau-bench-shared.js.map +1 -1
- package/dist/benchmarks/tau2-bench.d.ts +5 -5
- package/dist/benchmarks/tau2-bench.js +28 -10
- package/dist/benchmarks/tau2-bench.js.map +1 -1
- package/dist/benchmarks/tau3-banking.d.ts +4 -13
- package/dist/benchmarks/tau3-banking.js +28 -8
- package/dist/benchmarks/tau3-banking.js.map +1 -1
- package/dist/benchmarks/terminal-bench.d.ts +4 -22
- package/dist/benchmarks/terminal-bench.js +140 -7
- package/dist/benchmarks/terminal-bench.js.map +1 -1
- package/dist/benchmarks/toollm.d.ts +5 -13
- package/dist/benchmarks/toollm.js +184 -9
- package/dist/benchmarks/toollm.js.map +1 -1
- package/dist/benchmarks/trata-hedge.d.ts +4 -30
- package/dist/benchmarks/trata-hedge.js +336 -6
- package/dist/benchmarks/trata-hedge.js.map +1 -1
- package/dist/benchmarks/types.d.ts +85 -94
- package/dist/benchmarks/types.js +1 -1
- package/dist/benchmarks/webarena-verified.d.ts +5 -13
- package/dist/benchmarks/webarena-verified.js +152 -9
- package/dist/benchmarks/webarena-verified.js.map +1 -1
- package/dist/cadbench-DpQWZHp4.js +285 -0
- package/dist/cadbench-DpQWZHp4.js.map +1 -0
- package/dist/cadgenbench-DRhczfsG.js +151 -0
- package/dist/cadgenbench-DRhczfsG.js.map +1 -0
- package/dist/index.d.ts +245 -293
- package/dist/index.js +1669 -1791
- package/dist/index.js.map +1 -1
- package/package.json +18 -15
- package/scripts/verify-packed-consumer.mjs +20 -17
- package/src/corpus.test.mts +13 -0
- package/src/corpus.ts +4 -0
- package/src/pier-trial-controller.test.mts +15 -7
- package/src/profile-coordinates.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.mts +5 -1
- package/src/rollout-ledger/settle-capture.mts +7 -1
- package/src/search-bench/profiles.ts +1 -1
- package/src/skill-sandbox-smoke.mts +2 -1
- package/src/swe-arena/gepa-seat.mts +1 -1
- package/src/swe-arena/ledger-orphans.test.mts +36 -34
- package/dist/benchmarks/cadbench.js.map +0 -1
- package/dist/benchmarks/cadgenbench.js.map +0 -1
- package/dist/benchmarks/types.js.map +0 -1
- package/dist/chunk-53UPUNBZ.js +0 -325
- package/dist/chunk-53UPUNBZ.js.map +0 -1
- package/dist/chunk-5FEQDSCT.js +0 -211
- package/dist/chunk-5FEQDSCT.js.map +0 -1
- package/dist/chunk-5H5XV76F.js +0 -240
- package/dist/chunk-5H5XV76F.js.map +0 -1
- package/dist/chunk-67ACKDCX.js +0 -118
- package/dist/chunk-67ACKDCX.js.map +0 -1
- package/dist/chunk-7FKBWOQT.js +0 -147
- package/dist/chunk-7FKBWOQT.js.map +0 -1
- package/dist/chunk-BEN6IF2X.js +0 -221
- package/dist/chunk-BEN6IF2X.js.map +0 -1
- package/dist/chunk-BZY5QARD.js +0 -120
- package/dist/chunk-BZY5QARD.js.map +0 -1
- package/dist/chunk-C7T7WEK2.js +0 -103
- package/dist/chunk-C7T7WEK2.js.map +0 -1
- package/dist/chunk-CLIKAXKH.js +0 -276
- package/dist/chunk-CLIKAXKH.js.map +0 -1
- package/dist/chunk-CWIOBFSP.js +0 -197
- package/dist/chunk-CWIOBFSP.js.map +0 -1
- package/dist/chunk-CXDUTWQE.js +0 -318
- package/dist/chunk-CXDUTWQE.js.map +0 -1
- package/dist/chunk-DWALFME7.js +0 -182
- package/dist/chunk-DWALFME7.js.map +0 -1
- package/dist/chunk-EEOC6QPJ.js +0 -144
- package/dist/chunk-EEOC6QPJ.js.map +0 -1
- package/dist/chunk-EIETHPD5.js +0 -321
- package/dist/chunk-EIETHPD5.js.map +0 -1
- package/dist/chunk-GC2EPS6L.js +0 -130
- package/dist/chunk-GC2EPS6L.js.map +0 -1
- package/dist/chunk-GCHL6XPM.js +0 -169
- package/dist/chunk-GCHL6XPM.js.map +0 -1
- package/dist/chunk-HQ5HCCKF.js +0 -142
- package/dist/chunk-HQ5HCCKF.js.map +0 -1
- package/dist/chunk-HVW25KSX.js +0 -208
- package/dist/chunk-HVW25KSX.js.map +0 -1
- package/dist/chunk-INNOYXCP.js +0 -387
- package/dist/chunk-INNOYXCP.js.map +0 -1
- package/dist/chunk-J6BU3NTM.js +0 -251
- package/dist/chunk-J6BU3NTM.js.map +0 -1
- package/dist/chunk-JSQOUKXS.js +0 -149
- package/dist/chunk-JSQOUKXS.js.map +0 -1
- package/dist/chunk-JTHWEDEW.js +0 -32
- package/dist/chunk-JTHWEDEW.js.map +0 -1
- package/dist/chunk-NRMGT25X.js +0 -116
- package/dist/chunk-NRMGT25X.js.map +0 -1
- package/dist/chunk-PA2ZKHJC.js +0 -230
- package/dist/chunk-PA2ZKHJC.js.map +0 -1
- package/dist/chunk-PUIRNYI7.js +0 -189
- package/dist/chunk-PUIRNYI7.js.map +0 -1
- package/dist/chunk-QZZEAHWJ.js +0 -136
- package/dist/chunk-QZZEAHWJ.js.map +0 -1
- package/dist/chunk-SEVJPLZC.js +0 -260
- package/dist/chunk-SEVJPLZC.js.map +0 -1
- package/dist/chunk-TBKU5XQI.js +0 -228
- package/dist/chunk-TBKU5XQI.js.map +0 -1
- package/dist/chunk-UPAMRDX4.js +0 -233
- package/dist/chunk-UPAMRDX4.js.map +0 -1
- package/dist/chunk-VQRS7VUC.js +0 -342
- package/dist/chunk-VQRS7VUC.js.map +0 -1
- package/dist/chunk-WG7TM7UV.js +0 -30
- package/dist/chunk-WG7TM7UV.js.map +0 -1
- package/dist/chunk-X3BTXCJ4.js +0 -262
- package/dist/chunk-X3BTXCJ4.js.map +0 -1
- package/dist/chunk-XXFF3RRD.js +0 -162
- package/dist/chunk-XXFF3RRD.js.map +0 -1
- package/dist/chunk-ZFNOM7WR.js +0 -27
- package/dist/chunk-ZFNOM7WR.js.map +0 -1
- package/dist/chunk-ZNCCYTFG.js +0 -170
- package/dist/chunk-ZNCCYTFG.js.map +0 -1
package/dist/chunk-PA2ZKHJC.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// src/benchmarks/hotpotqa.ts
|
|
2
|
-
import { execFile } from "child_process";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { promisify } from "util";
|
|
7
|
-
var execFileAsync = promisify(execFile);
|
|
8
|
-
var BENCH_ROOT = fileURLToPath(new URL("../..", import.meta.url));
|
|
9
|
-
var PY = join(BENCH_ROOT, ".venv", "bin", "python");
|
|
10
|
-
var FIXTURES = join(BENCH_ROOT, "fixtures", "hotpotqa.json");
|
|
11
|
-
var DATASET = "hotpotqa/hotpot_qa";
|
|
12
|
-
var DATASET_CONFIG = "distractor";
|
|
13
|
-
var DATASET_SPLIT = "validation";
|
|
14
|
-
var FINAL_ANSWER_SENTINEL = "FINAL ANSWER:";
|
|
15
|
-
var DEFAULT_F1_PASS = 0.6;
|
|
16
|
-
var WORKER_CONTRACT = [
|
|
17
|
-
"",
|
|
18
|
-
"Answer this multi-hop question. Reason across the facts you need, then commit to a single short answer.",
|
|
19
|
-
`End your response with a single final line: \`${FINAL_ANSWER_SENTINEL} <answer>\``,
|
|
20
|
-
"The answer after the sentinel must be the bare value only (no explanation on that line)."
|
|
21
|
-
].join("\n");
|
|
22
|
-
async function py(script, args = []) {
|
|
23
|
-
const { stdout } = await execFileAsync(PY, ["-c", script, ...args], {
|
|
24
|
-
maxBuffer: 1024 * 1024 * 256
|
|
25
|
-
});
|
|
26
|
-
return stdout;
|
|
27
|
-
}
|
|
28
|
-
var ARTICLES = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
29
|
-
function normalizeAnswer(input) {
|
|
30
|
-
const lower = input.toLowerCase();
|
|
31
|
-
const noPunct = lower.replace(/[^\w\s]/g, " ");
|
|
32
|
-
const tokens = noPunct.split(/\s+/).filter((t) => t.length > 0).filter((t) => !ARTICLES.has(t));
|
|
33
|
-
return tokens.join(" ").trim();
|
|
34
|
-
}
|
|
35
|
-
function answerTokens(input) {
|
|
36
|
-
const n = normalizeAnswer(input);
|
|
37
|
-
return n.length === 0 ? [] : n.split(" ");
|
|
38
|
-
}
|
|
39
|
-
function exactMatch(prediction, gold) {
|
|
40
|
-
return normalizeAnswer(prediction) === normalizeAnswer(gold);
|
|
41
|
-
}
|
|
42
|
-
function tokenF1(prediction, gold) {
|
|
43
|
-
const predTokens = answerTokens(prediction);
|
|
44
|
-
const goldTokens = answerTokens(gold);
|
|
45
|
-
if (predTokens.length === 0 || goldTokens.length === 0) {
|
|
46
|
-
return predTokens.length === 0 && goldTokens.length === 0 ? 1 : 0;
|
|
47
|
-
}
|
|
48
|
-
const goldCounts = /* @__PURE__ */ new Map();
|
|
49
|
-
for (const t of goldTokens) goldCounts.set(t, (goldCounts.get(t) ?? 0) + 1);
|
|
50
|
-
let common = 0;
|
|
51
|
-
for (const t of predTokens) {
|
|
52
|
-
const left = goldCounts.get(t);
|
|
53
|
-
if (left !== void 0 && left > 0) {
|
|
54
|
-
common += 1;
|
|
55
|
-
goldCounts.set(t, left - 1);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (common === 0) return 0;
|
|
59
|
-
const precision = common / predTokens.length;
|
|
60
|
-
const recall = common / goldTokens.length;
|
|
61
|
-
return 2 * precision * recall / (precision + recall);
|
|
62
|
-
}
|
|
63
|
-
function f1PassThreshold() {
|
|
64
|
-
const raw = process.env.HOTPOTQA_F1_PASS;
|
|
65
|
-
if (raw === void 0 || raw.length === 0) return DEFAULT_F1_PASS;
|
|
66
|
-
const v = Number(raw);
|
|
67
|
-
if (!Number.isFinite(v) || v < 0 || v > 1) {
|
|
68
|
-
throw new Error(`HOTPOTQA_F1_PASS must be a number in [0,1], got ${JSON.stringify(raw)}`);
|
|
69
|
-
}
|
|
70
|
-
return v;
|
|
71
|
-
}
|
|
72
|
-
function parseFinalAnswer(artifact) {
|
|
73
|
-
const lines = artifact.split(/\r?\n/);
|
|
74
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
75
|
-
const line = lines[i] ?? "";
|
|
76
|
-
const idx = line.toUpperCase().indexOf(FINAL_ANSWER_SENTINEL);
|
|
77
|
-
if (idx !== -1) return line.slice(idx + FINAL_ANSWER_SENTINEL.length).trim();
|
|
78
|
-
}
|
|
79
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
80
|
-
const t = (lines[i] ?? "").trim();
|
|
81
|
-
if (t.length > 0) return t;
|
|
82
|
-
}
|
|
83
|
-
return "";
|
|
84
|
-
}
|
|
85
|
-
function rowToTask(row) {
|
|
86
|
-
const meta = {
|
|
87
|
-
gold: row.answer,
|
|
88
|
-
supportingFacts: row.supporting_facts ?? { title: [], sent_id: [] },
|
|
89
|
-
type: row.type ?? "",
|
|
90
|
-
level: row.level ?? "",
|
|
91
|
-
rawQuestion: row.question
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
id: `hotpotqa-${row.id}`,
|
|
95
|
-
split: DATASET_SPLIT,
|
|
96
|
-
prompt: row.question + WORKER_CONTRACT,
|
|
97
|
-
metadata: meta
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
function readMeta(task) {
|
|
101
|
-
const md = task.metadata;
|
|
102
|
-
if (!md || typeof md.gold !== "string") {
|
|
103
|
-
throw new Error(`HotpotQA task ${task.id} missing metadata.gold \u2014 loadTasks did not populate it`);
|
|
104
|
-
}
|
|
105
|
-
return md;
|
|
106
|
-
}
|
|
107
|
-
async function loadFixtures(opts) {
|
|
108
|
-
const rows = JSON.parse(await readFile(FIXTURES, "utf8"));
|
|
109
|
-
console.warn(
|
|
110
|
-
`[hotpotqa] HOTPOTQA_FIXTURES=1 \u2014 loading ${rows.length} committed fixtures from ${FIXTURES} (no HF download)`
|
|
111
|
-
);
|
|
112
|
-
let tasks = rows.map(rowToTask);
|
|
113
|
-
if (opts.ids) {
|
|
114
|
-
const want = new Set(opts.ids);
|
|
115
|
-
tasks = tasks.filter((t) => want.has(t.id));
|
|
116
|
-
} else if (opts.limit !== void 0) {
|
|
117
|
-
tasks = tasks.slice(0, opts.limit);
|
|
118
|
-
}
|
|
119
|
-
return tasks;
|
|
120
|
-
}
|
|
121
|
-
function createHotpotqaAdapter() {
|
|
122
|
-
const fixturesMode = process.env.HOTPOTQA_FIXTURES === "1";
|
|
123
|
-
f1PassThreshold();
|
|
124
|
-
return {
|
|
125
|
-
name: "hotpotqa",
|
|
126
|
-
async preflight() {
|
|
127
|
-
if (fixturesMode) {
|
|
128
|
-
await readFile(FIXTURES, "utf8").catch((err) => {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`HOTPOTQA_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
await py(
|
|
137
|
-
`from datasets import load_dataset
|
|
138
|
-
load_dataset(${JSON.stringify(DATASET)}, ${JSON.stringify(DATASET_CONFIG)}, split=${JSON.stringify(DATASET_SPLIT)})
|
|
139
|
-
print('ok')`
|
|
140
|
-
);
|
|
141
|
-
} catch (err) {
|
|
142
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
143
|
-
throw new Error(
|
|
144
|
-
`hotpotqa preflight failed: ${msg}
|
|
145
|
-
Fix: (1) python3 -m venv bench/.venv && bench/.venv/bin/pip install datasets ; (2) ensure network access to Hugging Face for ${DATASET} (${DATASET_CONFIG}) ; or set HOTPOTQA_FIXTURES=1 to run against the committed fixtures offline.`
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
async loadTasks(opts = {}) {
|
|
150
|
-
if (fixturesMode) return loadFixtures(opts);
|
|
151
|
-
const limit = opts.limit ?? 10;
|
|
152
|
-
const script = `
|
|
153
|
-
import json, sys
|
|
154
|
-
from datasets import load_dataset
|
|
155
|
-
ds = load_dataset(${JSON.stringify(DATASET)}, ${JSON.stringify(DATASET_CONFIG)}, split=${JSON.stringify(DATASET_SPLIT)})
|
|
156
|
-
ids = set(json.loads(sys.argv[1])) if len(sys.argv) > 1 and sys.argv[1] else None
|
|
157
|
-
out = []
|
|
158
|
-
for r in ds:
|
|
159
|
-
rid = f"hotpotqa-{r['id']}"
|
|
160
|
-
if ids is not None and rid not in ids:
|
|
161
|
-
continue
|
|
162
|
-
sf = r.get('supporting_facts', {}) or {}
|
|
163
|
-
out.append({
|
|
164
|
-
"id": r["id"],
|
|
165
|
-
"question": r.get("question", ""),
|
|
166
|
-
"answer": r.get("answer", ""),
|
|
167
|
-
"type": str(r.get("type", "")),
|
|
168
|
-
"level": str(r.get("level", "")),
|
|
169
|
-
"supporting_facts": {
|
|
170
|
-
"title": list(sf.get("title", [])),
|
|
171
|
-
"sent_id": [int(x) for x in sf.get("sent_id", [])],
|
|
172
|
-
},
|
|
173
|
-
})
|
|
174
|
-
if ids is None and len(out) >= ${limit}:
|
|
175
|
-
break
|
|
176
|
-
print(json.dumps(out))
|
|
177
|
-
`;
|
|
178
|
-
const stdout = await py(script, [opts.ids ? JSON.stringify(opts.ids) : ""]);
|
|
179
|
-
const rows = JSON.parse(stdout);
|
|
180
|
-
return rows.map(rowToTask);
|
|
181
|
-
},
|
|
182
|
-
async goldArtifact(task) {
|
|
183
|
-
const meta = readMeta(task);
|
|
184
|
-
return `${FINAL_ANSWER_SENTINEL} ${meta.gold}`;
|
|
185
|
-
},
|
|
186
|
-
async judge(task, artifact) {
|
|
187
|
-
const meta = readMeta(task);
|
|
188
|
-
const finalAnswer = parseFinalAnswer(artifact);
|
|
189
|
-
if (finalAnswer.length === 0) {
|
|
190
|
-
return {
|
|
191
|
-
resolved: false,
|
|
192
|
-
score: 0,
|
|
193
|
-
detail: JSON.stringify({
|
|
194
|
-
reason: "no parseable answer",
|
|
195
|
-
em: false,
|
|
196
|
-
f1: 0,
|
|
197
|
-
normalizedGold: normalizeAnswer(meta.gold)
|
|
198
|
-
})
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
const em = exactMatch(finalAnswer, meta.gold);
|
|
202
|
-
const f1 = tokenF1(finalAnswer, meta.gold);
|
|
203
|
-
const threshold = f1PassThreshold();
|
|
204
|
-
const resolved = em || f1 >= threshold;
|
|
205
|
-
return {
|
|
206
|
-
resolved,
|
|
207
|
-
score: f1,
|
|
208
|
-
detail: JSON.stringify({
|
|
209
|
-
em,
|
|
210
|
-
f1,
|
|
211
|
-
threshold,
|
|
212
|
-
normalizedAnswer: normalizeAnswer(finalAnswer),
|
|
213
|
-
normalizedGold: normalizeAnswer(meta.gold),
|
|
214
|
-
type: meta.type,
|
|
215
|
-
level: meta.level,
|
|
216
|
-
supportingFacts: meta.supportingFacts
|
|
217
|
-
})
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export {
|
|
224
|
-
normalizeAnswer,
|
|
225
|
-
exactMatch,
|
|
226
|
-
tokenF1,
|
|
227
|
-
parseFinalAnswer,
|
|
228
|
-
createHotpotqaAdapter
|
|
229
|
-
};
|
|
230
|
-
//# sourceMappingURL=chunk-PA2ZKHJC.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/benchmarks/hotpotqa.ts"],"sourcesContent":["/**\n * HotpotQA adapter (hotpotqa/hotpot_qa, config 'distractor', split 'validation').\n * Multi-hop factoid QA. Worker artifact = a single free-text final answer string.\n *\n * Judge is the official HotpotQA / SQuAD-style metric, FULLY DETERMINISTIC — no\n * LLM. Both the predicted final answer and the gold are normalized (lowercase,\n * strip articles a/an/the, strip punctuation, collapse whitespace), then scored\n * by exact-match (EM) and token-level F1. resolved = EM OR F1 >= HOTPOTQA_F1_PASS\n * (default 0.6); score = the F1 (0..1). detail carries em + f1. This gives the\n * suite a judge that needs no model tokens at all.\n *\n * metadata carries the gold answer + supporting_facts (the title/sent_id pairs of\n * the gold supporting sentences) — a SOFT retrieval signal for the loop's critic,\n * never part of the score.\n *\n * Requires for a live run: the bench `.venv` with `datasets` installed + network\n * to Hugging Face. For offline/CI verification set HOTPOTQA_FIXTURES=1 to load the\n * committed fixtures (bench/fixtures/hotpotqa.json) — no HF download.\n */\n\nimport { execFile } from 'node:child_process'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { promisify } from 'node:util'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst execFileAsync = promisify(execFile)\nconst BENCH_ROOT = fileURLToPath(new URL('../..', import.meta.url))\nconst PY = join(BENCH_ROOT, '.venv', 'bin', 'python')\nconst FIXTURES = join(BENCH_ROOT, 'fixtures', 'hotpotqa.json')\n\nconst DATASET = 'hotpotqa/hotpot_qa'\nconst DATASET_CONFIG = 'distractor'\nconst DATASET_SPLIT = 'validation'\nconst FINAL_ANSWER_SENTINEL = 'FINAL ANSWER:'\n\n/** Default F1 pass threshold; HotpotQA's leaderboard reports EM and F1 separately. */\nconst DEFAULT_F1_PASS = 0.6\n\n/** Worker contract appended to every task prompt. The judge keys off the sentinel. */\nconst WORKER_CONTRACT = [\n '',\n 'Answer this multi-hop question. Reason across the facts you need, then commit to a single short answer.',\n `End your response with a single final line: \\`${FINAL_ANSWER_SENTINEL} <answer>\\``,\n 'The answer after the sentinel must be the bare value only (no explanation on that line).',\n].join('\\n')\n\ninterface SupportingFacts {\n title: string[]\n sent_id: number[]\n}\n\ninterface HotpotRow {\n id: string\n question: string\n answer: string\n type: string\n level: string\n supporting_facts: SupportingFacts\n}\n\ninterface HotpotMeta {\n gold: string\n supportingFacts: SupportingFacts\n type: string\n level: string\n rawQuestion: string\n}\n\n/** Run the bench venv python with a script on stdin; return stdout (throws on nonzero). */\nasync function py(script: string, args: string[] = []): Promise<string> {\n const { stdout } = await execFileAsync(PY, ['-c', script, ...args], {\n maxBuffer: 1024 * 1024 * 256,\n })\n return stdout\n}\n\nconst ARTICLES = new Set(['a', 'an', 'the'])\n\n/**\n * The official HotpotQA / SQuAD `normalize_answer`: lowercase, strip punctuation,\n * drop articles (a/an/the), collapse whitespace. Token comparisons run on the\n * output of this exactly as the published evaluator does.\n */\nexport function normalizeAnswer(input: string): string {\n const lower = input.toLowerCase()\n // strip punctuation: keep word chars + whitespace only\n const noPunct = lower.replace(/[^\\w\\s]/g, ' ')\n const tokens = noPunct\n .split(/\\s+/)\n .filter((t) => t.length > 0)\n .filter((t) => !ARTICLES.has(t))\n return tokens.join(' ').trim()\n}\n\n/** Normalized-token list (the F1 bag-of-tokens unit). */\nfunction answerTokens(input: string): string[] {\n const n = normalizeAnswer(input)\n return n.length === 0 ? [] : n.split(' ')\n}\n\n/** Exact match on the normalized strings. */\nexport function exactMatch(prediction: string, gold: string): boolean {\n return normalizeAnswer(prediction) === normalizeAnswer(gold)\n}\n\n/**\n * Token-level F1, the SQuAD/HotpotQA definition. Multiset (bag) intersection of\n * normalized tokens. Mirrors the published evaluator's special-case handling of\n * yes/no/noanswer and empty bags: if either side is empty, F1 is 1 only when both\n * are empty, else 0.\n */\nexport function tokenF1(prediction: string, gold: string): number {\n const predTokens = answerTokens(prediction)\n const goldTokens = answerTokens(gold)\n if (predTokens.length === 0 || goldTokens.length === 0) {\n return predTokens.length === 0 && goldTokens.length === 0 ? 1 : 0\n }\n const goldCounts = new Map<string, number>()\n for (const t of goldTokens) goldCounts.set(t, (goldCounts.get(t) ?? 0) + 1)\n let common = 0\n for (const t of predTokens) {\n const left = goldCounts.get(t)\n if (left !== undefined && left > 0) {\n common += 1\n goldCounts.set(t, left - 1)\n }\n }\n if (common === 0) return 0\n const precision = common / predTokens.length\n const recall = common / goldTokens.length\n return (2 * precision * recall) / (precision + recall)\n}\n\n/** Read the configured F1 pass threshold; fail loud on a malformed override. */\nfunction f1PassThreshold(): number {\n const raw = process.env.HOTPOTQA_F1_PASS\n if (raw === undefined || raw.length === 0) return DEFAULT_F1_PASS\n const v = Number(raw)\n if (!Number.isFinite(v) || v < 0 || v > 1) {\n throw new Error(`HOTPOTQA_F1_PASS must be a number in [0,1], got ${JSON.stringify(raw)}`)\n }\n return v\n}\n\n/**\n * Parse the worker artifact into the final answer string.\n * Order: the `FINAL ANSWER:` sentinel first (deterministic-extraction discipline);\n * fall back to the trimmed last non-empty line. Returns '' when nothing is\n * parseable (fail-closed — never guess), which judge() counts as resolved=false.\n */\nexport function parseFinalAnswer(artifact: string): string {\n const lines = artifact.split(/\\r?\\n/)\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const line = lines[i] ?? ''\n const idx = line.toUpperCase().indexOf(FINAL_ANSWER_SENTINEL)\n if (idx !== -1) return line.slice(idx + FINAL_ANSWER_SENTINEL.length).trim()\n }\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const t = (lines[i] ?? '').trim()\n if (t.length > 0) return t\n }\n return ''\n}\n\nfunction rowToTask(row: HotpotRow): BenchTask {\n const meta: HotpotMeta = {\n gold: row.answer,\n supportingFacts: row.supporting_facts ?? { title: [], sent_id: [] },\n type: row.type ?? '',\n level: row.level ?? '',\n rawQuestion: row.question,\n }\n return {\n id: `hotpotqa-${row.id}`,\n split: DATASET_SPLIT,\n prompt: row.question + WORKER_CONTRACT,\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask): HotpotMeta {\n const md = task.metadata\n if (!md || typeof md.gold !== 'string') {\n throw new Error(`HotpotQA task ${task.id} missing metadata.gold — loadTasks did not populate it`)\n }\n return md as unknown as HotpotMeta\n}\n\nasync function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as HotpotRow[]\n console.warn(\n `[hotpotqa] HOTPOTQA_FIXTURES=1 — loading ${rows.length} committed fixtures from ${FIXTURES} (no HF download)`,\n )\n let tasks = rows.map(rowToTask)\n if (opts.ids) {\n const want = new Set(opts.ids)\n tasks = tasks.filter((t) => want.has(t.id))\n } else if (opts.limit !== undefined) {\n tasks = tasks.slice(0, opts.limit)\n }\n return tasks\n}\n\nexport function createHotpotqaAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.HOTPOTQA_FIXTURES === '1'\n // Validate the threshold at construction so a malformed env fails before any run.\n f1PassThreshold()\n\n return {\n name: 'hotpotqa',\n\n async preflight() {\n if (fixturesMode) {\n await readFile(FIXTURES, 'utf8').catch((err) => {\n throw new Error(\n `HOTPOTQA_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`,\n )\n })\n return\n }\n try {\n await py(\n `from datasets import load_dataset\nload_dataset(${JSON.stringify(DATASET)}, ${JSON.stringify(DATASET_CONFIG)}, split=${JSON.stringify(DATASET_SPLIT)})\nprint('ok')`,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(\n `hotpotqa preflight failed: ${msg}\\n` +\n `Fix: (1) python3 -m venv bench/.venv && bench/.venv/bin/pip install datasets ; ` +\n `(2) ensure network access to Hugging Face for ${DATASET} (${DATASET_CONFIG}) ; ` +\n `or set HOTPOTQA_FIXTURES=1 to run against the committed fixtures offline.`,\n )\n }\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n if (fixturesMode) return loadFixtures(opts)\n const limit = opts.limit ?? 10\n const script = `\nimport json, sys\nfrom datasets import load_dataset\nds = load_dataset(${JSON.stringify(DATASET)}, ${JSON.stringify(DATASET_CONFIG)}, split=${JSON.stringify(DATASET_SPLIT)})\nids = set(json.loads(sys.argv[1])) if len(sys.argv) > 1 and sys.argv[1] else None\nout = []\nfor r in ds:\n rid = f\"hotpotqa-{r['id']}\"\n if ids is not None and rid not in ids:\n continue\n sf = r.get('supporting_facts', {}) or {}\n out.append({\n \"id\": r[\"id\"],\n \"question\": r.get(\"question\", \"\"),\n \"answer\": r.get(\"answer\", \"\"),\n \"type\": str(r.get(\"type\", \"\")),\n \"level\": str(r.get(\"level\", \"\")),\n \"supporting_facts\": {\n \"title\": list(sf.get(\"title\", [])),\n \"sent_id\": [int(x) for x in sf.get(\"sent_id\", [])],\n },\n })\n if ids is None and len(out) >= ${limit}:\n break\nprint(json.dumps(out))\n`\n const stdout = await py(script, [opts.ids ? JSON.stringify(opts.ids) : ''])\n const rows = JSON.parse(stdout) as HotpotRow[]\n return rows.map(rowToTask)\n },\n\n async goldArtifact(task: BenchTask) {\n // Gold artifact = the worker-contract serialization of the gold answer, so\n // verify-judge proves gold→resolved through the SAME parse path the real\n // artifact takes.\n const meta = readMeta(task)\n return `${FINAL_ANSWER_SENTINEL} ${meta.gold}`\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = readMeta(task)\n const finalAnswer = parseFinalAnswer(artifact)\n\n if (finalAnswer.length === 0) {\n // Fail-closed: distinguish prompt-adherence failure from a wrong answer.\n return {\n resolved: false,\n score: 0,\n detail: JSON.stringify({\n reason: 'no parseable answer',\n em: false,\n f1: 0,\n normalizedGold: normalizeAnswer(meta.gold),\n }),\n }\n }\n\n const em = exactMatch(finalAnswer, meta.gold)\n const f1 = tokenF1(finalAnswer, meta.gold)\n const threshold = f1PassThreshold()\n const resolved = em || f1 >= threshold\n return {\n resolved,\n score: f1,\n detail: JSON.stringify({\n em,\n f1,\n threshold,\n normalizedAnswer: normalizeAnswer(finalAnswer),\n normalizedGold: normalizeAnswer(meta.gold),\n type: meta.type,\n level: meta.level,\n supportingFacts: meta.supportingFacts,\n }),\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-PUIRNYI7.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// src/benchmarks/cadbench.ts
|
|
2
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
3
|
-
|
|
4
|
-
// src/worker-blender.ts
|
|
5
|
-
import { execFile } from "child_process";
|
|
6
|
-
import { mkdtemp, readFile, rm, writeFile } from "fs/promises";
|
|
7
|
-
import { tmpdir } from "os";
|
|
8
|
-
import { join } from "path";
|
|
9
|
-
import { promisify } from "util";
|
|
10
|
-
import { routerChatWithUsage } from "@tangle-network/agent-runtime/loops";
|
|
11
|
-
var execFileAsync = promisify(execFile);
|
|
12
|
-
async function runLocal(cmd, args, cwd, timeoutMs = 18e4) {
|
|
13
|
-
try {
|
|
14
|
-
const { stdout, stderr } = await execFileAsync(cmd, args, { cwd, maxBuffer: 1 << 26, timeout: timeoutMs });
|
|
15
|
-
return { code: 0, stdout, stderr };
|
|
16
|
-
} catch (err) {
|
|
17
|
-
const e = err;
|
|
18
|
-
return { code: typeof e.code === "number" ? e.code : 1, stdout: e.stdout ?? "", stderr: e.stderr ?? e.message ?? String(err) };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
var RUNNER_PY = `
|
|
22
|
-
import bpy, sys, math, mathutils, traceback, os
|
|
23
|
-
agent_script, outdir, nviews = sys.argv[-3], sys.argv[-2], int(sys.argv[-1])
|
|
24
|
-
bpy.ops.object.select_all(action='SELECT'); bpy.ops.object.delete()
|
|
25
|
-
ok=True
|
|
26
|
-
try:
|
|
27
|
-
g={'bpy':bpy,'math':math,'mathutils':mathutils,'__name__':'__main__'}
|
|
28
|
-
exec(compile(open(agent_script).read(), agent_script, 'exec'), g)
|
|
29
|
-
except Exception as e:
|
|
30
|
-
traceback.print_exc(); print('AGENT_SCRIPT_ERROR:'+repr(e)); ok=False
|
|
31
|
-
meshes=[o for o in bpy.context.scene.objects if o.type=='MESH']
|
|
32
|
-
if not meshes:
|
|
33
|
-
print('NO_MESH'); sys.exit(0 if ok else 3)
|
|
34
|
-
mn=[1e18]*3; mx=[-1e18]*3
|
|
35
|
-
for o in meshes:
|
|
36
|
-
for c in o.bound_box:
|
|
37
|
-
w=o.matrix_world @ mathutils.Vector(c)
|
|
38
|
-
for i in range(3): mn[i]=min(mn[i],w[i]); mx[i]=max(mx[i],w[i])
|
|
39
|
-
center=mathutils.Vector(((mn[0]+mx[0])/2,(mn[1]+mx[1])/2,(mn[2]+mx[2])/2))
|
|
40
|
-
size=max(mx[i]-mn[i] for i in range(3)) or 1.0
|
|
41
|
-
# standardize: drop any agent-added cameras/lights
|
|
42
|
-
for o in list(bpy.context.scene.objects):
|
|
43
|
-
if o.type in ('CAMERA','LIGHT'): bpy.data.objects.remove(o, do_unlink=True)
|
|
44
|
-
w=bpy.context.scene.world or bpy.data.worlds.new('W'); bpy.context.scene.world=w
|
|
45
|
-
w.use_nodes=True
|
|
46
|
-
try: w.node_tree.nodes['Background'].inputs[1].default_value=0.6
|
|
47
|
-
except Exception: pass
|
|
48
|
-
bpy.ops.object.light_add(type='SUN'); sun=bpy.context.object; sun.data.energy=4.0; sun.rotation_euler=mathutils.Euler((0.6,0.2,0.4))
|
|
49
|
-
bpy.ops.object.camera_add(); cam=bpy.context.object; bpy.context.scene.camera=cam
|
|
50
|
-
sc=bpy.context.scene
|
|
51
|
-
sc.render.engine='CYCLES'; sc.cycles.samples=20; sc.cycles.device='CPU'; sc.cycles.use_denoising=False
|
|
52
|
-
sc.render.resolution_x=640; sc.render.resolution_y=640; sc.render.film_transparent=False
|
|
53
|
-
dist=size*2.4
|
|
54
|
-
el=math.radians(58)
|
|
55
|
-
for v in range(nviews):
|
|
56
|
-
az=math.radians(40 + v*360.0/nviews)
|
|
57
|
-
cam.location=center+mathutils.Vector((math.cos(az)*math.sin(el), math.sin(az)*math.sin(el), math.cos(el)))*dist
|
|
58
|
-
d=(center-cam.location); cam.rotation_euler=d.to_track_quat('-Z','Y').to_euler()
|
|
59
|
-
sc.render.filepath=os.path.join(outdir, 'view_%d.png'%v); bpy.ops.render.render(write_still=True)
|
|
60
|
-
print('RENDER_DONE')
|
|
61
|
-
`.trim();
|
|
62
|
-
async function renderBpy(script, opts = {}) {
|
|
63
|
-
const views = Math.max(1, opts.views ?? 4);
|
|
64
|
-
const dir = await mkdtemp(join(tmpdir(), "blender-judge-"));
|
|
65
|
-
const runnerPath = join(dir, "runner.py");
|
|
66
|
-
const scriptPath = join(dir, "model.py");
|
|
67
|
-
try {
|
|
68
|
-
await writeFile(runnerPath, RUNNER_PY);
|
|
69
|
-
await writeFile(scriptPath, script);
|
|
70
|
-
const run = await runLocal("xvfb-run", ["-a", "blender", "--background", "--python", runnerPath, "--", scriptPath, dir, String(views)], dir);
|
|
71
|
-
const out = `${run.stdout}
|
|
72
|
-
${run.stderr}`;
|
|
73
|
-
const built = /RENDER_DONE/.test(out);
|
|
74
|
-
if (!built) return { built: false, renders: [], error: /(AGENT_SCRIPT_ERROR:.*|NO_MESH)/.exec(out)?.[0] ?? out.trim().slice(-400) };
|
|
75
|
-
const renders = [];
|
|
76
|
-
for (let v = 0; v < views; v++) {
|
|
77
|
-
const buf = await readFile(join(dir, `view_${v}.png`)).catch(() => void 0);
|
|
78
|
-
if (buf) renders.push(`data:image/png;base64,${buf.toString("base64")}`);
|
|
79
|
-
}
|
|
80
|
-
return { built: renders.length > 0, renders };
|
|
81
|
-
} finally {
|
|
82
|
-
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/benchmarks/cadbench.ts
|
|
88
|
-
function must(name) {
|
|
89
|
-
const v = process.env[name];
|
|
90
|
-
if (!v) throw new Error(`env ${name} is required for the CADBench judge`);
|
|
91
|
-
return v;
|
|
92
|
-
}
|
|
93
|
-
async function judgeCriteria(instruction, script, criteria, renders) {
|
|
94
|
-
const base = (process.env.ROUTER_BASE ?? "https://router.tangle.tools/v1").replace(/\/$/, "");
|
|
95
|
-
const key = must("TANGLE_API_KEY");
|
|
96
|
-
const model = process.env.JUDGE_MODEL ?? "deepseek-v4-flash";
|
|
97
|
-
const numbered = criteria.map((c, i) => `${i + 1}. ${c}`).join("\n");
|
|
98
|
-
const text = `You are strictly grading a 3D model that was built by a Blender bpy script for this instruction:
|
|
99
|
-
"${instruction}"
|
|
100
|
-
|
|
101
|
-
Below are ${renders.length} rendered views of the produced model, and the script that built it. For EACH numbered criterion, decide whether it is satisfied (judge geometry/shape/proportion/structure from the IMAGES; judge color/size/material reasonableness from the SCRIPT where the images are ambiguous). Return ONLY a JSON array of exactly ${criteria.length} booleans (true=satisfied, false=not), in order, no prose.
|
|
102
|
-
|
|
103
|
-
CRITERIA:
|
|
104
|
-
${numbered}
|
|
105
|
-
|
|
106
|
-
SCRIPT:
|
|
107
|
-
\`\`\`python
|
|
108
|
-
${script.slice(0, 6e3)}
|
|
109
|
-
\`\`\``;
|
|
110
|
-
const content = [{ type: "text", text }];
|
|
111
|
-
for (const url of renders) content.push({ type: "image_url", image_url: { url } });
|
|
112
|
-
const res = await fetch(`${base}/chat/completions`, {
|
|
113
|
-
method: "POST",
|
|
114
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
|
115
|
-
body: JSON.stringify({ model, max_tokens: 1500, temperature: 0, messages: [{ role: "user", content }] })
|
|
116
|
-
});
|
|
117
|
-
if (!res.ok) throw new Error(`judge ${model} ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
118
|
-
const data = await res.json();
|
|
119
|
-
const raw = data.choices?.[0]?.message?.content ?? "";
|
|
120
|
-
const m = /\[\s*(?:true|false)[\s\S]*?\]/i.exec(raw);
|
|
121
|
-
if (!m) return { passed: criteria.map(() => false), note: `judge returned no parseable verdict: ${raw.slice(0, 80)}` };
|
|
122
|
-
let arr;
|
|
123
|
-
try {
|
|
124
|
-
arr = JSON.parse(m[0].toLowerCase());
|
|
125
|
-
} catch {
|
|
126
|
-
return { passed: criteria.map(() => false), note: "judge verdict not valid JSON" };
|
|
127
|
-
}
|
|
128
|
-
const bools = Array.isArray(arr) ? arr.map((x) => x === true) : [];
|
|
129
|
-
const passed = criteria.map((_, i) => bools[i] === true);
|
|
130
|
-
return { passed, note: `${passed.filter(Boolean).length}/${criteria.length} criteria` };
|
|
131
|
-
}
|
|
132
|
-
function createCadBenchAdapter() {
|
|
133
|
-
let cache = null;
|
|
134
|
-
async function load() {
|
|
135
|
-
if (cache) return cache;
|
|
136
|
-
const path = process.env.CADBENCH_PATH;
|
|
137
|
-
if (!path) throw new Error("CADBENCH_PATH must point at the cleaned CADBench JSONL ({id,instruction,type,criteria:[]} per line)");
|
|
138
|
-
const text = await readFile2(path, "utf8");
|
|
139
|
-
cache = text.split("\n").filter((l) => l.trim()).map((l) => {
|
|
140
|
-
const r = JSON.parse(l);
|
|
141
|
-
return { id: r.id, instruction: r.instruction, meta: { name: r.name ?? "", type: r.type ?? "", criteria: r.criteria } };
|
|
142
|
-
});
|
|
143
|
-
return cache;
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
name: "cadbench",
|
|
147
|
-
async preflight() {
|
|
148
|
-
const { execFile: execFile2 } = await import("child_process");
|
|
149
|
-
const { promisify: promisify2 } = await import("util");
|
|
150
|
-
const exec = promisify2(execFile2);
|
|
151
|
-
try {
|
|
152
|
-
await exec("xvfb-run", ["-a", "blender", "--version"], { timeout: 3e4 });
|
|
153
|
-
} catch (err) {
|
|
154
|
-
throw new Error(
|
|
155
|
-
`cadbench preflight failed: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}
|
|
156
|
-
Fix: install Blender + Xvfb (sudo apt-get install -y blender xvfb). The judge runs \`xvfb-run -a blender --background --python\`.`
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
await load();
|
|
160
|
-
},
|
|
161
|
-
async loadTasks(opts = {}) {
|
|
162
|
-
let rows = await load();
|
|
163
|
-
if (opts.ids) rows = rows.filter((r) => opts.ids.includes(r.id));
|
|
164
|
-
const t = process.env.CADBENCH_TYPE;
|
|
165
|
-
if (t) rows = rows.filter((r) => r.meta.type.toLowerCase() === t.toLowerCase());
|
|
166
|
-
if (opts.limit != null) rows = rows.slice(0, opts.limit);
|
|
167
|
-
return rows.map((r) => ({ id: r.id, prompt: r.instruction, metadata: r.meta }));
|
|
168
|
-
},
|
|
169
|
-
async goldArtifact() {
|
|
170
|
-
return void 0;
|
|
171
|
-
},
|
|
172
|
-
async judge(task, artifact) {
|
|
173
|
-
const meta = task.metadata;
|
|
174
|
-
const criteria = meta.criteria ?? [];
|
|
175
|
-
if (!artifact.trim()) return { resolved: false, score: 0, detail: "empty artifact" };
|
|
176
|
-
if (criteria.length === 0) return { resolved: false, score: 0, detail: "task has no criteria" };
|
|
177
|
-
const r = await renderBpy(artifact, { views: 4 });
|
|
178
|
-
if (!r.built) return { resolved: false, score: 0, detail: `did not build/render: ${r.error ?? "no mesh"}` };
|
|
179
|
-
const { passed, note } = await judgeCriteria(task.prompt, artifact, criteria, r.renders);
|
|
180
|
-
const score = passed.filter(Boolean).length / criteria.length;
|
|
181
|
-
return { resolved: score === 1, score, detail: note };
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export {
|
|
187
|
-
createCadBenchAdapter
|
|
188
|
-
};
|
|
189
|
-
//# sourceMappingURL=chunk-PUIRNYI7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/benchmarks/cadbench.ts","../src/worker-blender.ts"],"sourcesContent":["/**\n * CADBench / BlenderLLM adapter (FreedomIntelligence/CADBench, arXiv:2412.14203).\n * Task = NL instruction → a Blender `bpy` script. Score = the paper's criteria\n * eval: render the produced model to standardized views, then a vision judge\n * (GPT-4o-class) marks each per-task criterion bullet pass/fail against the\n * rendered images + the script text. score = fraction of criteria satisfied.\n *\n * Data: the published dataset's `criteria` flattened to a bullet list (700 tasks,\n * 500 Simulative + 200 Wild). Point CADBENCH_PATH at the cleaned JSONL\n * ({id,name,instruction,type,criteria:string[]} per line). Judge creds from\n * TANGLE_API_KEY / ROUTER_BASE / JUDGE_MODEL (default gpt-4o).\n */\n\nimport { readFile } from 'node:fs/promises'\nimport type { BenchScore, BenchTask, BenchmarkAdapter, LoadOptions } from './types'\nimport { renderBpy } from '../worker-blender'\n\ninterface CadBenchMeta {\n name: string\n type: string\n criteria: string[]\n}\n\nfunction must(name: string): string {\n const v = process.env[name]\n if (!v) throw new Error(`env ${name} is required for the CADBench judge`)\n return v\n}\n\n/** One batched vision call: rendered views + the bpy script + the numbered\n * criteria → a JSON array of booleans (true = satisfied). Faithful to the\n * paper's combined image+script evaluation. Throws on transport failure (never\n * a silent zero); a parse miss falls back to \"all fail\" with a note. */\nasync function judgeCriteria(\n instruction: string,\n script: string,\n criteria: string[],\n renders: string[],\n): Promise<{ passed: boolean[]; note: string }> {\n const base = (process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1').replace(/\\/$/, '')\n const key = must('TANGLE_API_KEY')\n const model = process.env.JUDGE_MODEL ?? 'deepseek-v4-flash'\n const numbered = criteria.map((c, i) => `${i + 1}. ${c}`).join('\\n')\n const text =\n `You are strictly grading a 3D model that was built by a Blender bpy script for this instruction:\\n\"${instruction}\"\\n\\n` +\n `Below are ${renders.length} rendered views of the produced model, and the script that built it. ` +\n `For EACH numbered criterion, decide whether it is satisfied (judge geometry/shape/proportion/structure from the IMAGES; judge color/size/material reasonableness from the SCRIPT where the images are ambiguous). ` +\n `Return ONLY a JSON array of exactly ${criteria.length} booleans (true=satisfied, false=not), in order, no prose.\\n\\nCRITERIA:\\n${numbered}\\n\\nSCRIPT:\\n\\`\\`\\`python\\n${script.slice(0, 6000)}\\n\\`\\`\\``\n const content: unknown[] = [{ type: 'text', text }]\n for (const url of renders) content.push({ type: 'image_url', image_url: { url } })\n const res = await fetch(`${base}/chat/completions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },\n body: JSON.stringify({ model, max_tokens: 1500, temperature: 0, messages: [{ role: 'user', content }] }),\n })\n if (!res.ok) throw new Error(`judge ${model} ${res.status}: ${(await res.text()).slice(0, 200)}`)\n const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }\n const raw = data.choices?.[0]?.message?.content ?? ''\n const m = /\\[\\s*(?:true|false)[\\s\\S]*?\\]/i.exec(raw)\n if (!m) return { passed: criteria.map(() => false), note: `judge returned no parseable verdict: ${raw.slice(0, 80)}` }\n let arr: unknown\n try {\n arr = JSON.parse(m[0].toLowerCase())\n } catch {\n return { passed: criteria.map(() => false), note: 'judge verdict not valid JSON' }\n }\n const bools = Array.isArray(arr) ? arr.map((x) => x === true) : []\n // Pad/truncate to criteria length (a short array scores the missing as fail).\n const passed = criteria.map((_, i) => bools[i] === true)\n return { passed, note: `${passed.filter(Boolean).length}/${criteria.length} criteria` }\n}\n\nexport function createCadBenchAdapter(): BenchmarkAdapter {\n let cache: Array<{ id: string; instruction: string; meta: CadBenchMeta }> | null = null\n\n async function load(): Promise<typeof cache & object> {\n if (cache) return cache\n const path = process.env.CADBENCH_PATH\n if (!path) throw new Error('CADBENCH_PATH must point at the cleaned CADBench JSONL ({id,instruction,type,criteria:[]} per line)')\n const text = await readFile(path, 'utf8')\n cache = text\n .split('\\n')\n .filter((l) => l.trim())\n .map((l) => {\n const r = JSON.parse(l) as { id: string; name?: string; instruction: string; type?: string; criteria: string[] }\n return { id: r.id, instruction: r.instruction, meta: { name: r.name ?? '', type: r.type ?? '', criteria: r.criteria } }\n })\n return cache\n }\n\n return {\n name: 'cadbench',\n\n async preflight() {\n const { execFile } = await import('node:child_process')\n const { promisify } = await import('node:util')\n const exec = promisify(execFile)\n try {\n await exec('xvfb-run', ['-a', 'blender', '--version'], { timeout: 30_000 })\n } catch (err) {\n throw new Error(\n `cadbench preflight failed: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}\\n` +\n `Fix: install Blender + Xvfb (sudo apt-get install -y blender xvfb). The judge runs \\`xvfb-run -a blender --background --python\\`.`,\n )\n }\n await load()\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n let rows = await load()\n if (opts.ids) rows = rows.filter((r) => opts.ids!.includes(r.id))\n // TYPE filter (Simulative|Wild) via env, applied before limit.\n const t = process.env.CADBENCH_TYPE\n if (t) rows = rows.filter((r) => r.meta.type.toLowerCase() === t.toLowerCase())\n if (opts.limit != null) rows = rows.slice(0, opts.limit)\n return rows.map((r): BenchTask => ({ id: r.id, prompt: r.instruction, metadata: r.meta as unknown as Record<string, unknown> }))\n },\n\n async goldArtifact() {\n return undefined // no reference bpy script ships with the benchmark\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = task.metadata as unknown as CadBenchMeta\n const criteria = meta.criteria ?? []\n if (!artifact.trim()) return { resolved: false, score: 0, detail: 'empty artifact' }\n if (criteria.length === 0) return { resolved: false, score: 0, detail: 'task has no criteria' }\n const r = await renderBpy(artifact, { views: 4 })\n if (!r.built) return { resolved: false, score: 0, detail: `did not build/render: ${r.error ?? 'no mesh'}` }\n const { passed, note } = await judgeCriteria(task.prompt, artifact, criteria, r.renders)\n const score = passed.filter(Boolean).length / criteria.length\n return { resolved: score === 1, score, detail: note }\n },\n }\n}\n","/**\n * BlenderLLM / CADBench worker. The deliverable for a CADBench task is a Blender\n * `bpy` Python script that builds the described 3D model. We author it via the\n * router, execute it headless in Blender (Cycles CPU, no GPU), auto-frame the\n * produced geometry, and render N standardized views — the images the CADBench\n * criteria judge scores. The authoring directive is the GEPA-optimizable surface.\n *\n * Requires `blender` + `xvfb-run` on PATH (apt blender 4.x). No GPU: Cycles CPU\n * with denoising off (the apt build ships without OpenImageDenoise).\n */\n\nimport { execFile } from 'node:child_process'\nimport { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { promisify } from 'node:util'\nimport type { Span } from '@tangle-network/agent-eval'\nimport type { BenchTask } from './benchmarks/types'\nimport { DEFAULT_BLENDER_DIRECTIVE } from './directives'\nimport { runRefineLoop } from './refine-loop'\nimport { routerChatWithUsage } from '@tangle-network/agent-runtime/loops'\n\nexport { DEFAULT_BLENDER_DIRECTIVE } from './directives'\n\nconst execFileAsync = promisify(execFile)\n\nasync function runLocal(cmd: string, args: string[], cwd: string, timeoutMs = 180_000): Promise<{ code: number; stdout: string; stderr: string }> {\n try {\n const { stdout, stderr } = await execFileAsync(cmd, args, { cwd, maxBuffer: 1 << 26, timeout: timeoutMs })\n return { code: 0, stdout, stderr }\n } catch (err) {\n const e = err as { code?: number; stdout?: string; stderr?: string; message?: string }\n return { code: typeof e.code === 'number' ? e.code : 1, stdout: e.stdout ?? '', stderr: e.stderr ?? e.message ?? String(err) }\n }\n}\n\n\n/** Strip markdown fences so we keep just the Python. */\nfunction extractPy(text: string): string {\n const fence = /```(?:python|py)?\\s*\\n([\\s\\S]*?)```/i.exec(text)\n return (fence ? fence[1] : text).trim()\n}\n\n/**\n * The standardized Blender runner (written to a temp file per run). It clears the\n * scene, executes the agent's bpy script, auto-frames the produced meshes, sets\n * up neutral lighting, and renders N azimuth views with Cycles CPU.\n */\nconst RUNNER_PY = `\nimport bpy, sys, math, mathutils, traceback, os\nagent_script, outdir, nviews = sys.argv[-3], sys.argv[-2], int(sys.argv[-1])\nbpy.ops.object.select_all(action='SELECT'); bpy.ops.object.delete()\nok=True\ntry:\n g={'bpy':bpy,'math':math,'mathutils':mathutils,'__name__':'__main__'}\n exec(compile(open(agent_script).read(), agent_script, 'exec'), g)\nexcept Exception as e:\n traceback.print_exc(); print('AGENT_SCRIPT_ERROR:'+repr(e)); ok=False\nmeshes=[o for o in bpy.context.scene.objects if o.type=='MESH']\nif not meshes:\n print('NO_MESH'); sys.exit(0 if ok else 3)\nmn=[1e18]*3; mx=[-1e18]*3\nfor o in meshes:\n for c in o.bound_box:\n w=o.matrix_world @ mathutils.Vector(c)\n for i in range(3): mn[i]=min(mn[i],w[i]); mx[i]=max(mx[i],w[i])\ncenter=mathutils.Vector(((mn[0]+mx[0])/2,(mn[1]+mx[1])/2,(mn[2]+mx[2])/2))\nsize=max(mx[i]-mn[i] for i in range(3)) or 1.0\n# standardize: drop any agent-added cameras/lights\nfor o in list(bpy.context.scene.objects):\n if o.type in ('CAMERA','LIGHT'): bpy.data.objects.remove(o, do_unlink=True)\nw=bpy.context.scene.world or bpy.data.worlds.new('W'); bpy.context.scene.world=w\nw.use_nodes=True\ntry: w.node_tree.nodes['Background'].inputs[1].default_value=0.6\nexcept Exception: pass\nbpy.ops.object.light_add(type='SUN'); sun=bpy.context.object; sun.data.energy=4.0; sun.rotation_euler=mathutils.Euler((0.6,0.2,0.4))\nbpy.ops.object.camera_add(); cam=bpy.context.object; bpy.context.scene.camera=cam\nsc=bpy.context.scene\nsc.render.engine='CYCLES'; sc.cycles.samples=20; sc.cycles.device='CPU'; sc.cycles.use_denoising=False\nsc.render.resolution_x=640; sc.render.resolution_y=640; sc.render.film_transparent=False\ndist=size*2.4\nel=math.radians(58)\nfor v in range(nviews):\n az=math.radians(40 + v*360.0/nviews)\n cam.location=center+mathutils.Vector((math.cos(az)*math.sin(el), math.sin(az)*math.sin(el), math.cos(el)))*dist\n d=(center-cam.location); cam.rotation_euler=d.to_track_quat('-Z','Y').to_euler()\n sc.render.filepath=os.path.join(outdir, 'view_%d.png'%v); bpy.ops.render.render(write_still=True)\nprint('RENDER_DONE')\n`.trim()\n\n/** Execute a bpy script headless + render N standardized views — no authoring.\n * Used by the CADBench judge to render an artifact before vision-scoring it. */\nexport async function renderBpy(script: string, opts: { views?: number } = {}): Promise<{ built: boolean; renders: string[]; error?: string }> {\n const views = Math.max(1, opts.views ?? 4)\n const dir = await mkdtemp(join(tmpdir(), 'blender-judge-'))\n const runnerPath = join(dir, 'runner.py')\n const scriptPath = join(dir, 'model.py')\n try {\n await writeFile(runnerPath, RUNNER_PY)\n await writeFile(scriptPath, script)\n const run = await runLocal('xvfb-run', ['-a', 'blender', '--background', '--python', runnerPath, '--', scriptPath, dir, String(views)], dir)\n const out = `${run.stdout}\\n${run.stderr}`\n const built = /RENDER_DONE/.test(out)\n if (!built) return { built: false, renders: [], error: (/(AGENT_SCRIPT_ERROR:.*|NO_MESH)/.exec(out)?.[0] ?? out.trim().slice(-400)) }\n const renders: string[] = []\n for (let v = 0; v < views; v++) {\n const buf = await readFile(join(dir, `view_${v}.png`)).catch(() => undefined)\n if (buf) renders.push(`data:image/png;base64,${buf.toString('base64')}`)\n }\n return { built: renders.length > 0, renders }\n } finally {\n await rm(dir, { recursive: true, force: true }).catch(() => {})\n }\n}\n\nexport interface BlenderLocalConfig {\n routerBaseUrl: string\n routerKey: string\n model: string\n rounds?: number\n /** N standardized views to render (CADBench uses 4). Default 4. */\n views?: number\n /** The bpy authoring directive — the GEPA-optimizable surface. */\n directive?: string\n}\n\nexport interface BlenderShot {\n /** The bpy script the agent wrote — the artifact. */\n artifact: string\n /** Rendered view PNGs as data URIs (the images the criteria judge scores). */\n renders: string[]\n trace: Span[]\n usage: { input: number; output: number }\n ok: boolean\n /** True if the script executed and produced at least one mesh. */\n built: boolean\n detail?: string\n}\n\n/**\n * Author a bpy script for the task via the router, execute + render it headless\n * in Blender, refine on execution errors across rounds. Returns the script, the\n * rendered views, a screenshot-rich trace, and real token usage.\n */\nexport async function solveBlenderLocal(task: BenchTask, cfg: BlenderLocalConfig): Promise<BlenderShot> {\n const rounds = Math.max(1, cfg.rounds ?? 2)\n const views = Math.max(1, cfg.views ?? 4)\n const directive = cfg.directive ?? DEFAULT_BLENDER_DIRECTIVE\n const trace: Span[] = []\n const runId = `cadbench-${task.id}`\n let ts = Date.now()\n const tick = () => (ts += 1)\n const usage = { input: 0, output: 0 }\n // Carried across rounds in closures (the round Artifact is the bpy script; the\n // render PNGs + built flag + lastErr persist outside the loop). usage is REAL.\n let renders: string[] = []\n let built = false\n let lastErr = ''\n\n trace.push({ spanId: 's-brief', runId, kind: 'llm', name: 'brief', model: cfg.model, messages: [{ role: 'user', content: task.prompt }], startedAt: tick(), endedAt: tick(), status: 'ok' } as Span)\n\n // Migrated onto runRefineLoop: the mkdtemp dir (with the runner.py written once\n // in setup) is the Ctx; built (RENDER_DONE + ≥1 collected view) is the early-stop,\n // modeled as a judge so default-decide stops the loop. The round-2+ steer carries\n // lastErr + the prior script verbatim.\n const res = await runRefineLoop<string, string>({\n rounds,\n setup: async () => {\n const dir = await mkdtemp(join(tmpdir(), 'blender-'))\n await writeFile(join(dir, 'runner.py'), RUNNER_PY)\n return dir\n },\n prompt: (round, history) =>\n round === 1\n ? task.prompt\n : `Your previous bpy script failed:\\n${lastErr}\\n\\nPrevious script:\\n${history[history.length - 1]?.artifact ?? ''}\\n\\nFix it so it runs under \\`blender --background --python\\` and builds the object as mesh(es). Brief:\\n${task.prompt}`,\n runShot: async (user, round, dir) => {\n const runnerPath = join(dir, 'runner.py')\n const scriptPath = join(dir, 'model.py')\n const { content, usage: u } = await routerChatWithUsage(\n cfg,\n [\n { role: 'system', content: directive },\n { role: 'user', content: user },\n ],\n { temperature: 0.3 },\n )\n if (u) {\n usage.input += u.input\n usage.output += u.output\n }\n const script = extractPy(content)\n trace.push({ spanId: `s-author-${round}`, runId, kind: 'llm', name: `author r${round}`, model: cfg.model, messages: [{ role: 'user', content: round === 1 ? task.prompt : 'refine' }], output: content.slice(0, 600), startedAt: tick(), endedAt: tick(), status: 'ok' } as Span)\n trace.push({ spanId: `s-write-${round}`, runId, kind: 'tool', name: 'write_file', toolName: 'create_file', args: { path: 'model.py', content: script }, startedAt: tick(), endedAt: tick(), status: 'ok' } as Span)\n\n await writeFile(scriptPath, script)\n const run = await runLocal('xvfb-run', ['-a', 'blender', '--background', '--python', runnerPath, '--', scriptPath, dir, String(views)], dir)\n const out = `${run.stdout}\\n${run.stderr}`\n built = /RENDER_DONE/.test(out)\n lastErr = built ? '' : (/(AGENT_SCRIPT_ERROR:.*|NO_MESH)/.exec(out)?.[0] ?? out.trim().slice(-800))\n trace.push({ spanId: `s-blender-${round}`, runId, kind: 'tool', name: `blender r${round}`, toolName: 'shell.exec', args: 'blender --background --python runner.py model.py', result: (built ? 'RENDER_DONE' : lastErr).slice(0, 1500), startedAt: tick(), endedAt: tick(), status: built ? 'ok' : 'error', error: built ? undefined : `exit ${run.code}` } as Span)\n\n if (built) {\n const collected: string[] = []\n for (let v = 0; v < views; v++) {\n const buf = await readFile(join(dir, `view_${v}.png`)).catch(() => undefined)\n if (buf) collected.push(`data:image/png;base64,${buf.toString('base64')}`)\n }\n renders = collected\n // first view carries the screen span (run-capsule reveal)\n trace.push({ spanId: `s-render-${round}`, runId, kind: 'tool', name: 'render', toolName: 'render.screenshot', args: { action: 'rendered model', url: 'view_0.png' }, attributes: collected[0] ? { screenshot: collected[0] } : {}, startedAt: tick(), endedAt: tick(), status: collected.length ? 'ok' : 'error', error: collected.length ? undefined : 'render produced no image' } as Span)\n built = collected.length > 0\n }\n return { artifact: script }\n },\n judge: async () => ({ valid: built }),\n teardown: (dir) => rm(dir, { recursive: true, force: true }).then(() => {}, () => {}),\n })\n\n const script = res.final.artifact\n return {\n artifact: script,\n renders,\n trace,\n usage,\n ok: script.trim().length > 0,\n built,\n detail: built ? `built + rendered ${renders.length} views` : `did not build in ${rounds} rounds${lastErr ? `; last: ${lastErr.slice(0, 140)}` : ''}`,\n }\n}\n"],"mappings":"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","names":["readFile","readFile","execFile","promisify"]}
|
package/dist/chunk-QZZEAHWJ.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
benchRoot,
|
|
3
|
-
preflightVenvImports,
|
|
4
|
-
runVenvScriptStdin
|
|
5
|
-
} from "./chunk-EIETHPD5.js";
|
|
6
|
-
|
|
7
|
-
// src/benchmarks/programbench.ts
|
|
8
|
-
import { join } from "path";
|
|
9
|
-
import { readFile } from "fs/promises";
|
|
10
|
-
var FIXTURES = join(benchRoot, "fixtures", "programbench.json");
|
|
11
|
-
var TESTS_DATASET = "programbench/ProgramBench-Tests";
|
|
12
|
-
var TREE_API = `https://huggingface.co/api/datasets/${TESTS_DATASET}/tree/main`;
|
|
13
|
-
var PROMPT = [
|
|
14
|
-
"This is a cleanroom reverse-engineering task. You are given a compiled black-box `./executable` (run-only) and stripped documentation in the workspace.",
|
|
15
|
-
"Write an ORIGINAL codebase from scratch that, when built, produces an `./executable` with byte-for-byte identical observable behavior (stdout/stderr/exit codes/filesystem effects) to the original. You may run `./executable` to probe its behavior.",
|
|
16
|
-
"You MUST include a `./compile.sh` at the workspace root that builds your source into `./executable` at the workspace root.",
|
|
17
|
-
"Emit your COMPLETE submission as the LAST thing in your reply: one fenced block per file, each opening fence line being exactly ```path:<relative/path>``` followed by the file contents, then a closing fence. Include compile.sh and every source file. Nothing after the final closing fence."
|
|
18
|
-
].join("\n");
|
|
19
|
-
function fixtureToTask(f) {
|
|
20
|
-
const meta = { instanceId: f.instance_id, imageName: f.image_name };
|
|
21
|
-
return {
|
|
22
|
-
id: f.instance_id,
|
|
23
|
-
prompt: PROMPT,
|
|
24
|
-
metadata: meta
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function readMeta(task) {
|
|
28
|
-
const md = task.metadata;
|
|
29
|
-
if (!md || typeof md.instanceId !== "string") {
|
|
30
|
-
throw new Error(`programbench task ${task.id} missing metadata.instanceId \u2014 loadTasks did not populate it`);
|
|
31
|
-
}
|
|
32
|
-
return md;
|
|
33
|
-
}
|
|
34
|
-
function selectTasks(fixtures, opts) {
|
|
35
|
-
let tasks = fixtures.map(fixtureToTask);
|
|
36
|
-
if (opts.ids) {
|
|
37
|
-
const want = new Set(opts.ids);
|
|
38
|
-
tasks = tasks.filter((t) => want.has(t.id));
|
|
39
|
-
} else if (opts.limit !== void 0) {
|
|
40
|
-
tasks = tasks.slice(0, opts.limit);
|
|
41
|
-
}
|
|
42
|
-
return tasks;
|
|
43
|
-
}
|
|
44
|
-
function imageName(instanceId) {
|
|
45
|
-
return instanceId.split(".")[0].replace("__", "/");
|
|
46
|
-
}
|
|
47
|
-
async function loadFixtures(opts) {
|
|
48
|
-
const fixtures = JSON.parse(await readFile(FIXTURES, "utf8"));
|
|
49
|
-
console.warn(
|
|
50
|
-
`[programbench] PROGRAMBENCH_FIXTURES=1 \u2014 loading ${fixtures.length} committed instance ids from ${FIXTURES} (no HF tree fetch)`
|
|
51
|
-
);
|
|
52
|
-
return selectTasks(fixtures, opts);
|
|
53
|
-
}
|
|
54
|
-
async function fetchInstanceIds() {
|
|
55
|
-
const res = await fetch(TREE_API);
|
|
56
|
-
if (!res.ok) throw new Error(`programbench tree HTTP ${res.status}: ${TREE_API}`);
|
|
57
|
-
const tree = await res.json();
|
|
58
|
-
const ids = tree.filter((e) => e.type === "directory" && e.path.includes("__")).map((e) => e.path);
|
|
59
|
-
if (ids.length === 0) throw new Error(`programbench: no instance dirs in ${TESTS_DATASET} tree`);
|
|
60
|
-
return ids.map((id) => ({ instance_id: id, image_name: imageName(id) }));
|
|
61
|
-
}
|
|
62
|
-
async function runHarness(meta, submission) {
|
|
63
|
-
const judge = join(benchRoot, "scripts", "programbench_judge.py");
|
|
64
|
-
let stdout;
|
|
65
|
-
try {
|
|
66
|
-
stdout = await runVenvScriptStdin(judge, ["--instance", meta.instanceId], submission, { cwd: benchRoot });
|
|
67
|
-
} catch (err) {
|
|
68
|
-
const e = err;
|
|
69
|
-
throw new Error(`programbench harness failed for ${meta.instanceId}: ${(e.message || String(err)).slice(0, 1500)}`);
|
|
70
|
-
}
|
|
71
|
-
const report = JSON.parse(stdout.trim().split("\n").at(-1) ?? "{}");
|
|
72
|
-
if (report.error) throw new Error(`programbench harness error for ${meta.instanceId}: ${report.error}`);
|
|
73
|
-
if (typeof report.passed !== "number" || typeof report.total !== "number") {
|
|
74
|
-
throw new Error(`programbench judge returned no {passed,total}: ${stdout.slice(0, 400)}`);
|
|
75
|
-
}
|
|
76
|
-
const score = report.total > 0 ? report.passed / report.total : 0;
|
|
77
|
-
return {
|
|
78
|
-
resolved: report.resolved ?? (report.total > 0 && report.passed === report.total),
|
|
79
|
-
score,
|
|
80
|
-
detail: JSON.stringify({ instanceId: meta.instanceId, passed: report.passed, total: report.total })
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
var programbenchSubmissionOutput = {
|
|
84
|
-
parse(events) {
|
|
85
|
-
let text = "";
|
|
86
|
-
for (const ev of events) {
|
|
87
|
-
const d = ev?.data;
|
|
88
|
-
const t = d?.finalText ?? d?.text ?? d?.result;
|
|
89
|
-
if (typeof t === "string" && t.length > 0) text = t;
|
|
90
|
-
}
|
|
91
|
-
const blocks = [...text.matchAll(/```path:([^\n`]+)\n([\s\S]*?)```/g)];
|
|
92
|
-
if (blocks.length === 0) return "";
|
|
93
|
-
return blocks.map((m) => `===FILE:${m[1].trim()}===
|
|
94
|
-
${m[2]}`).join("\n");
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
function createProgrambenchAdapter() {
|
|
98
|
-
const fixturesMode = process.env.PROGRAMBENCH_FIXTURES === "1";
|
|
99
|
-
return {
|
|
100
|
-
name: "programbench",
|
|
101
|
-
output: programbenchSubmissionOutput,
|
|
102
|
-
async preflight() {
|
|
103
|
-
await preflightVenvImports({
|
|
104
|
-
modules: ["programbench"],
|
|
105
|
-
requireDocker: true,
|
|
106
|
-
fix: `Fix: (1) python3 -m venv bench/.venv && bench/.venv/bin/pip install programbench ; (2) Docker on linux/amd64 (per-task <image>:task_cleanroom images; will NOT run on ARM) ; (3) HF access for the hidden test blobs (the driver runs \`programbench blob sync\`). Set PROGRAMBENCH_FIXTURES=1 to list the committed instance ids offline.`
|
|
107
|
-
});
|
|
108
|
-
},
|
|
109
|
-
async loadTasks(opts = {}) {
|
|
110
|
-
if (fixturesMode) return loadFixtures(opts);
|
|
111
|
-
let fixtures;
|
|
112
|
-
try {
|
|
113
|
-
fixtures = await fetchInstanceIds();
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.warn(
|
|
116
|
-
`[programbench] live tree fetch failed (${err instanceof Error ? err.message : err}); falling back to committed fixtures at ${FIXTURES}`
|
|
117
|
-
);
|
|
118
|
-
return loadFixtures(opts);
|
|
119
|
-
}
|
|
120
|
-
return selectTasks(fixtures, opts);
|
|
121
|
-
},
|
|
122
|
-
async goldArtifact() {
|
|
123
|
-
return void 0;
|
|
124
|
-
},
|
|
125
|
-
async judge(task, artifact) {
|
|
126
|
-
const meta = readMeta(task);
|
|
127
|
-
return runHarness(meta, artifact);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export {
|
|
133
|
-
programbenchSubmissionOutput,
|
|
134
|
-
createProgrambenchAdapter
|
|
135
|
-
};
|
|
136
|
-
//# sourceMappingURL=chunk-QZZEAHWJ.js.map
|