@tangle-network/agent-bench 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +13 -1
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +43 -0
- package/dist/adapters.js.map +1 -0
- package/dist/benchmarks/_harness.d.ts +125 -0
- package/dist/benchmarks/_harness.js +33 -0
- package/dist/benchmarks/_harness.js.map +1 -0
- package/dist/benchmarks/aec-bench.d.ts +27 -0
- package/dist/benchmarks/aec-bench.js +8 -0
- package/dist/benchmarks/aec-bench.js.map +1 -0
- package/dist/benchmarks/agentbench.d.ts +16 -0
- package/dist/benchmarks/agentbench.js +10 -0
- package/dist/benchmarks/agentbench.js.map +1 -0
- package/dist/benchmarks/appworld.d.ts +37 -0
- package/dist/benchmarks/appworld.js +14 -0
- package/dist/benchmarks/appworld.js.map +1 -0
- package/dist/benchmarks/bfcl.d.ts +18 -0
- package/dist/benchmarks/bfcl.js +10 -0
- package/dist/benchmarks/bfcl.js.map +1 -0
- package/dist/benchmarks/cad-design.d.ts +45 -0
- package/dist/benchmarks/cad-design.js +7 -0
- package/dist/benchmarks/cad-design.js.map +1 -0
- package/dist/benchmarks/cadbench.d.ts +19 -0
- package/dist/benchmarks/cadbench.js +8 -0
- package/dist/benchmarks/cadbench.js.map +1 -0
- package/dist/benchmarks/cadgenbench.d.ts +22 -0
- package/dist/benchmarks/cadgenbench.js +8 -0
- package/dist/benchmarks/cadgenbench.js.map +1 -0
- package/dist/benchmarks/commit0.d.ts +31 -0
- package/dist/benchmarks/commit0.js +10 -0
- package/dist/benchmarks/commit0.js.map +1 -0
- package/dist/benchmarks/crag.d.ts +14 -0
- package/dist/benchmarks/crag.js +9 -0
- package/dist/benchmarks/crag.js.map +1 -0
- package/dist/benchmarks/dabstep.d.ts +18 -0
- package/dist/benchmarks/dabstep.js +10 -0
- package/dist/benchmarks/dabstep.js.map +1 -0
- package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
- package/dist/benchmarks/enterpriseops-gym.js +10 -0
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
- package/dist/benchmarks/finresearchbench.d.ts +15 -0
- package/dist/benchmarks/finresearchbench.js +8 -0
- package/dist/benchmarks/finresearchbench.js.map +1 -0
- package/dist/benchmarks/finsearchcomp.d.ts +49 -0
- package/dist/benchmarks/finsearchcomp.js +7 -0
- package/dist/benchmarks/finsearchcomp.js.map +1 -0
- package/dist/benchmarks/frames.d.ts +59 -0
- package/dist/benchmarks/frames.js +13 -0
- package/dist/benchmarks/frames.js.map +1 -0
- package/dist/benchmarks/hotpotqa.d.ts +48 -0
- package/dist/benchmarks/hotpotqa.js +15 -0
- package/dist/benchmarks/hotpotqa.js.map +1 -0
- package/dist/benchmarks/humaneval.d.ts +62 -0
- package/dist/benchmarks/humaneval.js +17 -0
- package/dist/benchmarks/humaneval.js.map +1 -0
- package/dist/benchmarks/mind2web.d.ts +41 -0
- package/dist/benchmarks/mind2web.js +9 -0
- package/dist/benchmarks/mind2web.js.map +1 -0
- package/dist/benchmarks/nomiracl.d.ts +15 -0
- package/dist/benchmarks/nomiracl.js +9 -0
- package/dist/benchmarks/nomiracl.js.map +1 -0
- package/dist/benchmarks/open-rag-bench.d.ts +14 -0
- package/dist/benchmarks/open-rag-bench.js +9 -0
- package/dist/benchmarks/open-rag-bench.js.map +1 -0
- package/dist/benchmarks/programbench.d.ts +38 -0
- package/dist/benchmarks/programbench.js +10 -0
- package/dist/benchmarks/programbench.js.map +1 -0
- package/dist/benchmarks/rag-shared.d.ts +42 -0
- package/dist/benchmarks/rag-shared.js +39 -0
- package/dist/benchmarks/rag-shared.js.map +1 -0
- package/dist/benchmarks/ragbench.d.ts +16 -0
- package/dist/benchmarks/ragbench.js +9 -0
- package/dist/benchmarks/ragbench.js.map +1 -0
- package/dist/benchmarks/simpleqa.d.ts +64 -0
- package/dist/benchmarks/simpleqa.js +11 -0
- package/dist/benchmarks/simpleqa.js.map +1 -0
- package/dist/benchmarks/swe-bench.d.ts +56 -0
- package/dist/benchmarks/swe-bench.js +14 -0
- package/dist/benchmarks/swe-bench.js.map +1 -0
- package/dist/benchmarks/t2-ragbench.d.ts +14 -0
- package/dist/benchmarks/t2-ragbench.js +9 -0
- package/dist/benchmarks/t2-ragbench.js.map +1 -0
- package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
- package/dist/benchmarks/tau-bench-shared.js +10 -0
- package/dist/benchmarks/tau-bench-shared.js.map +1 -0
- package/dist/benchmarks/tau2-bench.d.ts +7 -0
- package/dist/benchmarks/tau2-bench.js +11 -0
- package/dist/benchmarks/tau2-bench.js.map +1 -0
- package/dist/benchmarks/tau3-banking.d.ts +15 -0
- package/dist/benchmarks/tau3-banking.js +9 -0
- package/dist/benchmarks/tau3-banking.js.map +1 -0
- package/dist/benchmarks/terminal-bench.d.ts +24 -0
- package/dist/benchmarks/terminal-bench.js +8 -0
- package/dist/benchmarks/terminal-bench.js.map +1 -0
- package/dist/benchmarks/toollm.d.ts +16 -0
- package/dist/benchmarks/toollm.js +10 -0
- package/dist/benchmarks/toollm.js.map +1 -0
- package/dist/benchmarks/trata-hedge.d.ts +32 -0
- package/dist/benchmarks/trata-hedge.js +7 -0
- package/dist/benchmarks/trata-hedge.js.map +1 -0
- package/dist/benchmarks/types.d.ts +107 -0
- package/dist/benchmarks/types.js +1 -0
- package/dist/benchmarks/types.js.map +1 -0
- package/dist/benchmarks/webarena-verified.d.ts +16 -0
- package/dist/benchmarks/webarena-verified.js +10 -0
- package/dist/benchmarks/webarena-verified.js.map +1 -0
- package/dist/chunk-2PVVP7GN.js +197 -0
- package/dist/chunk-2PVVP7GN.js.map +1 -0
- package/dist/chunk-2XU6OGEN.js +170 -0
- package/dist/chunk-2XU6OGEN.js.map +1 -0
- package/dist/chunk-53UPUNBZ.js +325 -0
- package/dist/chunk-53UPUNBZ.js.map +1 -0
- package/dist/chunk-5H5XV76F.js +240 -0
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/chunk-7WSD27QQ.js +118 -0
- package/dist/chunk-7WSD27QQ.js.map +1 -0
- package/dist/chunk-C7T7WEK2.js +103 -0
- package/dist/chunk-C7T7WEK2.js.map +1 -0
- package/dist/chunk-CKUVRZ2T.js +251 -0
- package/dist/chunk-CKUVRZ2T.js.map +1 -0
- package/dist/chunk-HBSWHQNJ.js +30 -0
- package/dist/chunk-HBSWHQNJ.js.map +1 -0
- package/dist/chunk-HHXFIHXC.js +116 -0
- package/dist/chunk-HHXFIHXC.js.map +1 -0
- package/dist/chunk-IFAV6KEM.js +276 -0
- package/dist/chunk-IFAV6KEM.js.map +1 -0
- package/dist/chunk-INNOYXCP.js +387 -0
- package/dist/chunk-INNOYXCP.js.map +1 -0
- package/dist/chunk-J3KDJNX2.js +182 -0
- package/dist/chunk-J3KDJNX2.js.map +1 -0
- package/dist/chunk-JRWWGMK7.js +148 -0
- package/dist/chunk-JRWWGMK7.js.map +1 -0
- package/dist/chunk-JTHWEDEW.js +32 -0
- package/dist/chunk-JTHWEDEW.js.map +1 -0
- package/dist/chunk-KDIKRJGB.js +120 -0
- package/dist/chunk-KDIKRJGB.js.map +1 -0
- package/dist/chunk-LRRD7NAG.js +301 -0
- package/dist/chunk-LRRD7NAG.js.map +1 -0
- package/dist/chunk-ODT47UAY.js +221 -0
- package/dist/chunk-ODT47UAY.js.map +1 -0
- package/dist/chunk-PA2ZKHJC.js +230 -0
- package/dist/chunk-PA2ZKHJC.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-PWQVGAJB.js +144 -0
- package/dist/chunk-PWQVGAJB.js.map +1 -0
- package/dist/chunk-R36V2VP7.js +169 -0
- package/dist/chunk-R36V2VP7.js.map +1 -0
- package/dist/chunk-R67DFVLO.js +142 -0
- package/dist/chunk-R67DFVLO.js.map +1 -0
- package/dist/chunk-SEVJPLZC.js +260 -0
- package/dist/chunk-SEVJPLZC.js.map +1 -0
- package/dist/chunk-SYDW647C.js +318 -0
- package/dist/chunk-SYDW647C.js.map +1 -0
- package/dist/chunk-TBKU5XQI.js +228 -0
- package/dist/chunk-TBKU5XQI.js.map +1 -0
- package/dist/chunk-TSWPNOYM.js +147 -0
- package/dist/chunk-TSWPNOYM.js.map +1 -0
- package/dist/chunk-UAIOHCUK.js +27 -0
- package/dist/chunk-UAIOHCUK.js.map +1 -0
- package/dist/chunk-UPAMRDX4.js +233 -0
- package/dist/chunk-UPAMRDX4.js.map +1 -0
- package/dist/chunk-VQRS7VUC.js +342 -0
- package/dist/chunk-VQRS7VUC.js.map +1 -0
- package/dist/chunk-X3BTXCJ4.js +262 -0
- package/dist/chunk-X3BTXCJ4.js.map +1 -0
- package/dist/chunk-X5YKXC6V.js +211 -0
- package/dist/chunk-X5YKXC6V.js.map +1 -0
- package/dist/chunk-Y6O2OCUO.js +130 -0
- package/dist/chunk-Y6O2OCUO.js.map +1 -0
- package/dist/chunk-YCGY7UIZ.js +208 -0
- package/dist/chunk-YCGY7UIZ.js.map +1 -0
- package/dist/chunk-Z7ML6L77.js +162 -0
- package/dist/chunk-Z7ML6L77.js.map +1 -0
- package/dist/chunk-ZEWMTR5M.js +136 -0
- package/dist/chunk-ZEWMTR5M.js.map +1 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +1908 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -9
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +12 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +788 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/calibrate.ts +116 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +405 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +104 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2145 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +489 -0
- package/src/swe-arena/proposer-fanout.test.mts +372 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +361 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +498 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +166 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisor showdown arena — implements supervisor-lab
|
|
3
|
+
* docs/results/PREREG-supervisor-showdown.md (2026-07-09) EXACTLY. The prereg is
|
|
4
|
+
* the contract: task sets, arms, equal-compute rule, plan contract, and the
|
|
5
|
+
* Phase A/B separation are fixed there and must not be tuned after results.
|
|
6
|
+
*
|
|
7
|
+
* Arms (SET=hard: the 62 problems the committed structural run ended hiddenFinal=0;
|
|
8
|
+
* SET=control: 20 problems it solved — supervision must NOT move these):
|
|
9
|
+
* A — more-blind-compute control: one fresh structural round (k=5, testgen=6,
|
|
10
|
+
* <=2 repairs, Llama-3-8B temp 0.8), no evidence shown.
|
|
11
|
+
* B — self-supervisor: Llama reads the visible evidence package, writes a
|
|
12
|
+
* diagnosis+plan (no code), then the arm-A loop runs conditioned on it.
|
|
13
|
+
* C — smart supervisor: glm-4.5-air writes the plan (<=400 tokens by explicit
|
|
14
|
+
* instruction; fenced code / `def ` lines stripped + planLeaked recorded),
|
|
15
|
+
* same Llama loop conditioned on it.
|
|
16
|
+
* D — smart-solo anchor: one glm-4.5-air call, plain solve instruction.
|
|
17
|
+
*
|
|
18
|
+
* Evidence (arms B/C) comes ONLY from the committed run rows (EVIDENCE jsonl):
|
|
19
|
+
* samples, visible-check outputs, authored asserts, repair transcript. The rows'
|
|
20
|
+
* hiddenSamples/hiddenFinal fields are NEVER read into any prompt — hidden info
|
|
21
|
+
* defines the task sets (already hardcoded here) and nothing else.
|
|
22
|
+
*
|
|
23
|
+
* Phase separation (as hev-structural.mts, whose runJailed/nonce-judge/oracle
|
|
24
|
+
* this file replicates — that module runs main() on import, so its internals
|
|
25
|
+
* cannot be imported without side effects):
|
|
26
|
+
* Phase A: all arm decisions from visible info only; rows appended to OUT.phaseA.
|
|
27
|
+
* Phase B: rig-local nonce-sentinel hidden judge grades every sample and the
|
|
28
|
+
* locked final, script-side; graded rows appended to OUT.
|
|
29
|
+
*
|
|
30
|
+
* cd bench && ARM=A|B|C|D SET=hard|control N=… OUT=/abs/rows.jsonl \
|
|
31
|
+
* EVIDENCE=/abs/full-llama3-8b-testgen.jsonl HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz \
|
|
32
|
+
* TANGLE_API_KEY=$TOGETHER_API_KEY ZAI_API_KEY=… npx tsx src/supervisor-arena.mts
|
|
33
|
+
*/
|
|
34
|
+
import { execFile, execFileSync } from 'node:child_process'
|
|
35
|
+
import { randomBytes } from 'node:crypto'
|
|
36
|
+
import { appendFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
37
|
+
import { tmpdir } from 'node:os'
|
|
38
|
+
import { join } from 'node:path'
|
|
39
|
+
import { type HumanEvalTask, basePrompt, extractCode, loadHumanEval } from './benchmarks/humaneval'
|
|
40
|
+
import { pool } from './stats.mts'
|
|
41
|
+
|
|
42
|
+
// ---------- pre-registered task sets (verbatim from the prereg; DO NOT EDIT) ----------
|
|
43
|
+
|
|
44
|
+
const hardIds = [
|
|
45
|
+
2, 5, 10, 16, 18, 20, 21, 26, 27, 1, 33, 37, 39, 38, 41, 46, 54, 52, 62, 64, 65, 73, 75, 80, 81, 85, 84, 92, 91, 93,
|
|
46
|
+
100, 99, 101, 70, 32, 103, 102, 108, 110, 112, 109, 115, 118, 119, 120, 122, 125, 126, 127, 131, 129, 130, 134, 138,
|
|
47
|
+
135, 137, 140, 145, 148, 157, 160, 163,
|
|
48
|
+
].map((n) => `HumanEval/${n}`)
|
|
49
|
+
|
|
50
|
+
const controlIds = [0, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 19, 22, 23, 24, 25, 28, 29].map((n) => `HumanEval/${n}`)
|
|
51
|
+
|
|
52
|
+
// ---------- models + list prices (per 1M tokens; printed as list-price $, not billed truth) ----------
|
|
53
|
+
|
|
54
|
+
const workerModelDefault = 'meta-llama/Meta-Llama-3-8B-Instruct-Lite'
|
|
55
|
+
const glmModelDefault = 'glm-4.5-air'
|
|
56
|
+
const listPrices: Record<string, { in: number; out: number }> = {
|
|
57
|
+
[workerModelDefault]: { in: 0.1, out: 0.1 },
|
|
58
|
+
[glmModelDefault]: { in: 0.2, out: 1.1 },
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dockerImage = 'python:3.12-slim'
|
|
62
|
+
const dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)
|
|
63
|
+
|
|
64
|
+
function must(name: string): string {
|
|
65
|
+
const v = process.env[name]
|
|
66
|
+
if (!v) throw new Error(`env ${name} is required`)
|
|
67
|
+
return v
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------- docker semaphore + jailed runner (replicated from hev-structural) ----------
|
|
71
|
+
|
|
72
|
+
let dockerSlots = 6
|
|
73
|
+
let dockerInFlight = 0
|
|
74
|
+
const dockerWaiters: Array<() => void> = []
|
|
75
|
+
async function withDockerSlot<T>(fn: () => Promise<T>): Promise<T> {
|
|
76
|
+
if (dockerInFlight >= dockerSlots) await new Promise<void>((r) => dockerWaiters.push(r))
|
|
77
|
+
dockerInFlight += 1
|
|
78
|
+
try {
|
|
79
|
+
return await fn()
|
|
80
|
+
} finally {
|
|
81
|
+
dockerInFlight -= 1
|
|
82
|
+
dockerWaiters.shift()?.()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const containerPrefix = `supv-${process.pid}`
|
|
87
|
+
let containerSeq = 0
|
|
88
|
+
|
|
89
|
+
function reapContainers(): void {
|
|
90
|
+
try {
|
|
91
|
+
const ids = execFileSync('docker', ['ps', '-aq', '--filter', `name=${containerPrefix}`], { timeout: 10000 }).toString().trim()
|
|
92
|
+
if (ids) execFileSync('docker', ['rm', '-f', ...ids.split('\n')], { timeout: 15000 })
|
|
93
|
+
} catch {
|
|
94
|
+
/* reaper is best-effort by design */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
process.on('SIGINT', () => {
|
|
98
|
+
reapContainers()
|
|
99
|
+
process.exit(130)
|
|
100
|
+
})
|
|
101
|
+
process.on('SIGTERM', () => {
|
|
102
|
+
reapContainers()
|
|
103
|
+
process.exit(143)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
interface JailResult {
|
|
107
|
+
exitCode: number
|
|
108
|
+
stdout: string
|
|
109
|
+
stderr: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One python program in the jail: --network=none, cpu/mem caps, in-container
|
|
113
|
+
* `timeout -s KILL`, client timeout, backstop. Docker INFRA faults throw. */
|
|
114
|
+
function runJailed(program: string): Promise<JailResult> {
|
|
115
|
+
return withDockerSlot(
|
|
116
|
+
() =>
|
|
117
|
+
new Promise<JailResult>((resolvePromise, reject) => {
|
|
118
|
+
const dir = mkdtempSync(join(tmpdir(), 'supv-'))
|
|
119
|
+
writeFileSync(join(dir, 'p.py'), program)
|
|
120
|
+
const name = `${containerPrefix}-${containerSeq++}`
|
|
121
|
+
let settled = false
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
rmSync(dir, { recursive: true, force: true })
|
|
124
|
+
execFile('docker', ['rm', '-f', name], () => {})
|
|
125
|
+
}
|
|
126
|
+
const finish = (res: JailResult) => {
|
|
127
|
+
if (settled) return
|
|
128
|
+
settled = true
|
|
129
|
+
clearTimeout(backstop)
|
|
130
|
+
cleanup()
|
|
131
|
+
resolvePromise(res)
|
|
132
|
+
}
|
|
133
|
+
const fail = (e: Error) => {
|
|
134
|
+
if (settled) return
|
|
135
|
+
settled = true
|
|
136
|
+
clearTimeout(backstop)
|
|
137
|
+
cleanup()
|
|
138
|
+
reject(e)
|
|
139
|
+
}
|
|
140
|
+
const backstop = setTimeout(() => finish({ exitCode: 124, stdout: '', stderr: 'backstop timeout (no output)' }), dockerTimeoutMs + 5000)
|
|
141
|
+
const inContainerSecs = Math.ceil(dockerTimeoutMs / 1000) + 2
|
|
142
|
+
execFile(
|
|
143
|
+
'docker',
|
|
144
|
+
[
|
|
145
|
+
'run', '--rm', '--name', name, '--network=none', '--cpus=1', '--memory=512m',
|
|
146
|
+
'-v', `${dir}:/w:ro`, '-w', '/w', dockerImage,
|
|
147
|
+
'timeout', '-s', 'KILL', String(inContainerSecs), 'python', '/w/p.py',
|
|
148
|
+
],
|
|
149
|
+
{ timeout: dockerTimeoutMs + 3000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
|
|
150
|
+
(err, stdout, stderr) => {
|
|
151
|
+
if (err) {
|
|
152
|
+
const e = err as NodeJS.ErrnoException & { code?: number | string }
|
|
153
|
+
if (e.code === 'ENOENT') return fail(new Error('docker binary not found on PATH'))
|
|
154
|
+
const se = stderr ?? ''
|
|
155
|
+
if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(se)) {
|
|
156
|
+
return fail(new Error(`docker daemon unreachable: ${se.slice(0, 200)}`))
|
|
157
|
+
}
|
|
158
|
+
if (/(unable to find image|pull access denied|manifest unknown|error response from daemon).*(pull|repository|registry)/i.test(se)) {
|
|
159
|
+
return fail(new Error(`docker image ${dockerImage} unavailable: ${se.slice(0, 200)}`))
|
|
160
|
+
}
|
|
161
|
+
const code = typeof e.code === 'number' ? e.code : 1
|
|
162
|
+
return finish({ exitCode: code, stdout: stdout ?? '', stderr: se })
|
|
163
|
+
}
|
|
164
|
+
finish({ exitCode: 0, stdout: stdout ?? '', stderr: stderr ?? '' })
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------- honest oracle (visible docstring doctests + authored asserts; replicated) ----------
|
|
172
|
+
|
|
173
|
+
interface HonestResult {
|
|
174
|
+
attempted: number
|
|
175
|
+
failed: number
|
|
176
|
+
failureOutput: string
|
|
177
|
+
pass: boolean
|
|
178
|
+
dAttempted?: number
|
|
179
|
+
dFailed?: number
|
|
180
|
+
gAttempted?: number
|
|
181
|
+
gFailed?: number
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildHonestProgram(task: HumanEvalTask, candidate: string, nonce: string, genTests: string[] = []): string {
|
|
185
|
+
const promptB64 = Buffer.from(task.prompt, 'utf8').toString('base64')
|
|
186
|
+
const entryB64 = Buffer.from(task.entryPoint, 'utf8').toString('base64')
|
|
187
|
+
const genB64 = Buffer.from(JSON.stringify(genTests), 'utf8').toString('base64')
|
|
188
|
+
return `${task.prompt}\n${candidate}\n
|
|
189
|
+
import ast as _ast, base64 as _b64, doctest as _doctest, io as _io, json as _json, sys as _sys
|
|
190
|
+
_prompt_text = _b64.b64decode("${promptB64}").decode("utf8")
|
|
191
|
+
_entry = _b64.b64decode("${entryB64}").decode("utf8")
|
|
192
|
+
_gen_tests = _json.loads(_b64.b64decode("${genB64}").decode("utf8"))
|
|
193
|
+
_stub_ns = {}
|
|
194
|
+
exec(_prompt_text, _stub_ns)
|
|
195
|
+
_doc = getattr(_stub_ns.get(_entry), "__doc__", None) or ""
|
|
196
|
+
try:
|
|
197
|
+
_examples = _doctest.DocTestParser().get_examples(_doc)
|
|
198
|
+
except ValueError:
|
|
199
|
+
_examples = [] # malformed docstring indentation -> no usable signal, not a crash
|
|
200
|
+
|
|
201
|
+
class _Checker(_doctest.OutputChecker):
|
|
202
|
+
def check_output(self, want, got, optionflags):
|
|
203
|
+
if super().check_output(want, got, optionflags):
|
|
204
|
+
return True
|
|
205
|
+
if want.strip() == "" and got.strip() == "True":
|
|
206
|
+
return True
|
|
207
|
+
try:
|
|
208
|
+
return _ast.literal_eval(want.strip()) == _ast.literal_eval(got.strip())
|
|
209
|
+
except Exception:
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
_test = _doctest.DocTest(_examples, globs=dict(globals()), name="visible", filename="p", lineno=0, docstring=_doc)
|
|
213
|
+
_runner = _doctest.DocTestRunner(checker=_Checker(), verbose=False, optionflags=_doctest.NORMALIZE_WHITESPACE | _doctest.IGNORE_EXCEPTION_DETAIL)
|
|
214
|
+
_buf = _io.StringIO()
|
|
215
|
+
_res = _runner.run(_test, out=_buf.write)
|
|
216
|
+
|
|
217
|
+
_g_att, _g_fail = 0, 0
|
|
218
|
+
for _t in _gen_tests:
|
|
219
|
+
_g_att += 1
|
|
220
|
+
try:
|
|
221
|
+
exec(_t, dict(globals()))
|
|
222
|
+
except Exception as _e:
|
|
223
|
+
_g_fail += 1
|
|
224
|
+
_buf.write("GENTEST FAILED: %s -> %s: %s\\n" % (_t.strip()[:200], type(_e).__name__, str(_e)[:200]))
|
|
225
|
+
|
|
226
|
+
_att = _res.attempted + _g_att
|
|
227
|
+
_fail = _res.failed + _g_fail
|
|
228
|
+
print("HONEST-${nonce} attempted=%d failed=%d datt=%d dfail=%d gatt=%d gfail=%d" % (_att, _fail, _res.attempted, _res.failed, _g_att, _g_fail))
|
|
229
|
+
_sys.stdout.write(_buf.getvalue()[-1500:])
|
|
230
|
+
_sys.exit(0 if _att > 0 and _fail == 0 else 1)
|
|
231
|
+
`
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runHonestOracle(task: HumanEvalTask, candidate: string, genTests: string[] = []): Promise<HonestResult> {
|
|
235
|
+
const nonce = randomBytes(8).toString('hex')
|
|
236
|
+
const r = await runJailed(buildHonestProgram(task, candidate, nonce, genTests))
|
|
237
|
+
const summary = new RegExp(`HONEST-${nonce} attempted=(\\d+) failed=(\\d+) datt=(\\d+) dfail=(\\d+) gatt=(\\d+) gfail=(\\d+)`).exec(r.stdout)
|
|
238
|
+
if (!summary) {
|
|
239
|
+
const detail = (r.stderr || r.stdout).slice(-1500) || 'timed out (no output)'
|
|
240
|
+
return { attempted: -1, failed: -1, failureOutput: detail, pass: false }
|
|
241
|
+
}
|
|
242
|
+
const attempted = Number(summary[1])
|
|
243
|
+
const failed = Number(summary[2])
|
|
244
|
+
const failureOutput = r.stdout.replace(summary[0], '').slice(-1500)
|
|
245
|
+
return {
|
|
246
|
+
attempted,
|
|
247
|
+
failed,
|
|
248
|
+
failureOutput,
|
|
249
|
+
pass: attempted > 0 && failed === 0,
|
|
250
|
+
dAttempted: Number(summary[3]),
|
|
251
|
+
dFailed: Number(summary[4]),
|
|
252
|
+
gAttempted: Number(summary[5]),
|
|
253
|
+
gFailed: Number(summary[6]),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function honestScore(h: HonestResult): number {
|
|
258
|
+
if (h.attempted <= 0) return h.attempted === 0 ? 0 : -1
|
|
259
|
+
return (h.attempted - h.failed) / h.attempted
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------- hidden judge (Phase B ONLY; nonce sentinel, not exit-0; replicated) ----------
|
|
263
|
+
|
|
264
|
+
function buildHiddenProgram(task: HumanEvalTask, candidate: string, nonce: string): string {
|
|
265
|
+
return `${task.prompt}\n${candidate}\n\n${task.test}\n\ncheck(${task.entryPoint})\nprint("HIDDEN-${nonce} PASS")\n`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function runHiddenJudge(task: HumanEvalTask, candidate: string): Promise<{ pass: number; detail?: string }> {
|
|
269
|
+
const nonce = randomBytes(8).toString('hex')
|
|
270
|
+
const r = await runJailed(buildHiddenProgram(task, candidate, nonce))
|
|
271
|
+
if (r.exitCode === 0 && r.stdout.includes(`HIDDEN-${nonce} PASS`)) return { pass: 1 }
|
|
272
|
+
return { pass: 0, detail: (r.stderr || r.stdout).slice(-600) || 'timed out (no output)' }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ---------- model client (plain fetch; retries transient HTTP AND empty content — the
|
|
276
|
+
// glm-4.5-air reasoning path starves `content` when reasoning eats max_tokens) ----------
|
|
277
|
+
|
|
278
|
+
interface ClientCfg {
|
|
279
|
+
base: string
|
|
280
|
+
key: string
|
|
281
|
+
model: string
|
|
282
|
+
maxTokens: number
|
|
283
|
+
temperature: number
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
interface Completion {
|
|
287
|
+
content: string
|
|
288
|
+
attempts: number
|
|
289
|
+
tokensIn: number
|
|
290
|
+
tokensOut: number
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function complete(cfg: ClientCfg, messages: Array<{ role: string; content: string }>): Promise<Completion> {
|
|
294
|
+
let lastErr = ''
|
|
295
|
+
for (let attempt = 1; attempt <= 4; attempt += 1) {
|
|
296
|
+
if (attempt > 1) await new Promise((r) => setTimeout(r, 2000 * 2 ** attempt))
|
|
297
|
+
const ctl = new AbortController()
|
|
298
|
+
const timer = setTimeout(() => ctl.abort(), Number(process.env.LLM_TIMEOUT_MS ?? 240_000))
|
|
299
|
+
try {
|
|
300
|
+
const res = await fetch(`${cfg.base}/chat/completions`, {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
303
|
+
body: JSON.stringify({ model: cfg.model, max_tokens: cfg.maxTokens, temperature: cfg.temperature, messages }),
|
|
304
|
+
signal: ctl.signal,
|
|
305
|
+
})
|
|
306
|
+
if (!res.ok) {
|
|
307
|
+
lastErr = `HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
const d = (await res.json()) as {
|
|
311
|
+
choices?: Array<{ message?: { content?: string } }>
|
|
312
|
+
usage?: { prompt_tokens?: number; completion_tokens?: number }
|
|
313
|
+
}
|
|
314
|
+
const content = d.choices?.[0]?.message?.content ?? ''
|
|
315
|
+
if (content.trim() === '') {
|
|
316
|
+
lastErr = 'empty content'
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
return { content, attempts: attempt, tokensIn: d.usage?.prompt_tokens ?? 0, tokensOut: d.usage?.completion_tokens ?? 0 }
|
|
320
|
+
} catch (e) {
|
|
321
|
+
lastErr = e instanceof Error ? e.message : String(e)
|
|
322
|
+
} finally {
|
|
323
|
+
clearTimeout(timer)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`completion failed after retries: ${lastErr}`)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// zai 429s above ~4 concurrent requests — a dedicated semaphore, independent of the
|
|
330
|
+
// task pool width, caps every glm call (plans in C, solo solves in D).
|
|
331
|
+
let zaiSlots = 4
|
|
332
|
+
let zaiInFlight = 0
|
|
333
|
+
const zaiWaiters: Array<() => void> = []
|
|
334
|
+
async function withZaiSlot<T>(fn: () => Promise<T>): Promise<T> {
|
|
335
|
+
if (zaiInFlight >= zaiSlots) await new Promise<void>((r) => zaiWaiters.push(r))
|
|
336
|
+
zaiInFlight += 1
|
|
337
|
+
try {
|
|
338
|
+
return await fn()
|
|
339
|
+
} finally {
|
|
340
|
+
zaiInFlight -= 1
|
|
341
|
+
zaiWaiters.shift()?.()
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------- CodeT-style test generation (visible info only, BEFORE any candidate; replicated) ----------
|
|
346
|
+
|
|
347
|
+
const testGenInstruction = (count: number, entry: string) =>
|
|
348
|
+
`Read the following Python function signature and docstring. Write exactly ${count} single-line assert statements that test the function \`${entry}\`, based ONLY on the behavior the docstring describes. Each assert must be one physical line of the form \`assert ${entry}(...) == expected\` (or a True/False check). Do NOT implement the function. Do NOT copy examples verbatim if you can test other cases too. Output ONLY the assert lines inside a single \`\`\`python code block.`
|
|
349
|
+
|
|
350
|
+
async function generateTests(cfg: ClientCfg, task: HumanEvalTask, count: number): Promise<{ tests: string[]; completion: Completion }> {
|
|
351
|
+
const c = await complete(cfg, [
|
|
352
|
+
{ role: 'user', content: `${testGenInstruction(count, task.entryPoint)}\n\n\`\`\`python\n${task.prompt}\`\`\`` },
|
|
353
|
+
])
|
|
354
|
+
const block = extractCode(c.content)
|
|
355
|
+
const balanced = (s: string) => {
|
|
356
|
+
let d = 0
|
|
357
|
+
for (const ch of s) {
|
|
358
|
+
if (ch === '(' || ch === '[' || ch === '{') d += 1
|
|
359
|
+
else if (ch === ')' || ch === ']' || ch === '}') d -= 1
|
|
360
|
+
if (d < 0) return false
|
|
361
|
+
}
|
|
362
|
+
return d === 0
|
|
363
|
+
}
|
|
364
|
+
const tests = block
|
|
365
|
+
.split('\n')
|
|
366
|
+
.map((l) => l.trim())
|
|
367
|
+
.filter((l) => l.startsWith('assert ') && l.includes(task.entryPoint) && balanced(l))
|
|
368
|
+
.slice(0, count)
|
|
369
|
+
return { tests, completion: c }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Prefer the LAST fenced block containing a `def` (repair replies echo the failure
|
|
373
|
+
* report in a bare fence first), else the shared extractor. */
|
|
374
|
+
function extractRepairCode(reply: string): string {
|
|
375
|
+
const fences = [...reply.matchAll(/```(?:python|py)?\s*\n([\s\S]*?)```/gi)].map((m) => (m[1] ?? '').trim())
|
|
376
|
+
for (let i = fences.length - 1; i >= 0; i -= 1) {
|
|
377
|
+
if (/(^|\n)\s*def\s+\w+/.test(fences[i] as string)) return fences[i] as string
|
|
378
|
+
}
|
|
379
|
+
return extractCode(reply)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ---------- evidence package (arms B/C) — visible fields of the committed run rows ONLY ----------
|
|
383
|
+
|
|
384
|
+
/** The visible slice of a committed run row. hiddenSamples/hiddenFinal exist in the
|
|
385
|
+
* file but are deliberately absent here: parsing narrows to these fields so hidden
|
|
386
|
+
* info cannot reach a prompt by accident. */
|
|
387
|
+
interface EvidenceRow {
|
|
388
|
+
taskId: string
|
|
389
|
+
samples: string[]
|
|
390
|
+
honest: HonestResult[]
|
|
391
|
+
repairs: Array<{ code: string; honest: HonestResult }>
|
|
392
|
+
genTests: string[]
|
|
393
|
+
selectedIdx: number
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function loadEvidence(path: string): Map<string, EvidenceRow> {
|
|
397
|
+
const rows = new Map<string, EvidenceRow>()
|
|
398
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
399
|
+
if (line.trim() === '') continue
|
|
400
|
+
const d = JSON.parse(line) as EvidenceRow & Record<string, unknown>
|
|
401
|
+
if (!d.taskId || !Array.isArray(d.samples) || !Array.isArray(d.honest)) throw new Error(`malformed evidence row: ${line.slice(0, 120)}`)
|
|
402
|
+
rows.set(d.taskId, {
|
|
403
|
+
taskId: d.taskId,
|
|
404
|
+
samples: d.samples,
|
|
405
|
+
honest: d.honest,
|
|
406
|
+
repairs: Array.isArray(d.repairs) ? d.repairs : [],
|
|
407
|
+
genTests: Array.isArray(d.genTests) ? d.genTests : [],
|
|
408
|
+
selectedIdx: Number(d.selectedIdx ?? 0),
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
return rows
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const clip = (s: string, n: number): string => {
|
|
415
|
+
const t = s.trim()
|
|
416
|
+
return t.length <= n ? t : `${t.slice(0, n)} …[+${t.length - n} chars trimmed]`
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const firstLines = (code: string, n = 2): string =>
|
|
420
|
+
code
|
|
421
|
+
.split('\n')
|
|
422
|
+
.filter((l) => l.trim() !== '')
|
|
423
|
+
.slice(0, n)
|
|
424
|
+
.join(' | ')
|
|
425
|
+
|
|
426
|
+
const checkStatus = (h: HonestResult): string =>
|
|
427
|
+
h.attempted < 0
|
|
428
|
+
? 'crashed before the visible checks could run'
|
|
429
|
+
: h.attempted === 0
|
|
430
|
+
? 'no visible checks were runnable'
|
|
431
|
+
: h.pass
|
|
432
|
+
? `passed all ${h.attempted} visible checks`
|
|
433
|
+
: `failed ${h.failed}/${h.attempted} visible checks`
|
|
434
|
+
|
|
435
|
+
/** Problem prompt + per-attempt one-line summary (first 2 code lines + trimmed
|
|
436
|
+
* visible-check output) + authored asserts + repair outcomes, bounded to
|
|
437
|
+
* ~3000 tokens (maxChars chars). Visible information only. */
|
|
438
|
+
function renderEvidence(task: HumanEvalTask, ev: EvidenceRow, maxChars: number): string {
|
|
439
|
+
const parts: string[] = []
|
|
440
|
+
parts.push('The problem:', '```python', task.prompt.trimEnd(), '```', '')
|
|
441
|
+
parts.push(`Previous attempts by the programmer, each with the result of the VISIBLE checks (docstring examples + the authored asserts listed below):`)
|
|
442
|
+
ev.samples.forEach((code, i) => {
|
|
443
|
+
const h = ev.honest[i] ?? { attempted: -1, failed: -1, failureOutput: '', pass: false }
|
|
444
|
+
parts.push(`[attempt ${i + 1}]${i === ev.selectedIdx ? ' (selected as best)' : ''} starts: ${firstLines(code)}`)
|
|
445
|
+
parts.push(` result: ${checkStatus(h)}`)
|
|
446
|
+
const fo = clip(h.failureOutput ?? '', 350)
|
|
447
|
+
if (fo !== '') parts.push(` check output: ${fo}`)
|
|
448
|
+
})
|
|
449
|
+
parts.push('', 'Authored asserts used as visible checks:')
|
|
450
|
+
for (const t of ev.genTests) parts.push(` ${t}`)
|
|
451
|
+
if (ev.genTests.length === 0) parts.push(' (none survived filtering)')
|
|
452
|
+
if (ev.repairs.length > 0) {
|
|
453
|
+
parts.push('', 'Repair attempts (revisions of the selected attempt, steered by the check output):')
|
|
454
|
+
ev.repairs.forEach((r, i) => {
|
|
455
|
+
parts.push(`[repair ${i + 1}] starts: ${firstLines(r.code)}`)
|
|
456
|
+
parts.push(` result: ${checkStatus(r.honest)}`)
|
|
457
|
+
const fo = clip(r.honest.failureOutput ?? '', 350)
|
|
458
|
+
if (fo !== '') parts.push(` check output: ${fo}`)
|
|
459
|
+
})
|
|
460
|
+
} else {
|
|
461
|
+
parts.push('', 'No repair attempts were made on this problem.')
|
|
462
|
+
}
|
|
463
|
+
let text = parts.join('\n')
|
|
464
|
+
if (text.length > maxChars) text = `${text.slice(0, maxChars)}\n…[evidence truncated at ${maxChars} chars]`
|
|
465
|
+
return text
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---------- supervisor plan (arms B/C): diagnose + steer, never solve ----------
|
|
469
|
+
|
|
470
|
+
const planContract =
|
|
471
|
+
"Diagnose why every attempt failed and write a plan: the specific mistake, the correct approach, edge cases to handle. You may include at most 3 short illustrative expressions. Do NOT write the function; any fenced code block or line starting with 'def ' disqualifies the plan. Keep the plan under 400 tokens."
|
|
472
|
+
|
|
473
|
+
const supervisorPrompt = (evidence: string): string =>
|
|
474
|
+
["You are reviewing a programmer's previous attempts at a Python problem.", '', evidence, '', planContract].join('\n')
|
|
475
|
+
|
|
476
|
+
/** Enforce the no-code contract: strip fenced blocks and `def ` lines, record the
|
|
477
|
+
* leak (leak RATE is a pre-registered measurement, so the stripped plan is still
|
|
478
|
+
* used rather than the task being dropped). */
|
|
479
|
+
function stripPlanCode(raw: string): { plan: string; leaked: boolean } {
|
|
480
|
+
let leaked = false
|
|
481
|
+
let s = raw
|
|
482
|
+
if (s.includes('```')) {
|
|
483
|
+
// an unterminated fence leaks to end-of-text, so strip greedily to the closer or EOF
|
|
484
|
+
const stripped = s.replace(/```[a-zA-Z]*[^\n]*\n?[\s\S]*?(?:```|$)/g, '')
|
|
485
|
+
if (stripped !== s) leaked = true
|
|
486
|
+
s = stripped
|
|
487
|
+
}
|
|
488
|
+
const lines = s.split('\n')
|
|
489
|
+
const kept = lines.filter((l) => !/^\s*def\s/.test(l))
|
|
490
|
+
if (kept.length !== lines.length) leaked = true
|
|
491
|
+
return { plan: kept.join('\n').trim(), leaked }
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ---------- Phase A worker loop — the hev-structural recipe verbatim (k samples temp 0.8,
|
|
495
|
+
// fresh testgen, honest argmax select, <=R honest-grounded repairs); arms B/C prepend the plan ----------
|
|
496
|
+
|
|
497
|
+
interface WorkerOutcome {
|
|
498
|
+
samples: string[]
|
|
499
|
+
honest: HonestResult[]
|
|
500
|
+
selectedIdx: number
|
|
501
|
+
repairs: Array<{ code: string; honest: HonestResult }>
|
|
502
|
+
finalCode: string
|
|
503
|
+
repairStop: string
|
|
504
|
+
genTests: string[]
|
|
505
|
+
calls: number
|
|
506
|
+
attempts: number
|
|
507
|
+
tokensIn: number
|
|
508
|
+
tokensOut: number
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function runWorkerLoop(cfg: ClientCfg, task: HumanEvalTask, k: number, maxRepairs: number, testGen: number, planPreamble: string | null): Promise<WorkerOutcome> {
|
|
512
|
+
const samplePrompt = planPreamble === null ? basePrompt(task) : `${planPreamble}\n\n${basePrompt(task)}`
|
|
513
|
+
let calls = 0
|
|
514
|
+
let attempts = 0
|
|
515
|
+
let tokensIn = 0
|
|
516
|
+
let tokensOut = 0
|
|
517
|
+
const track = (c: Completion) => {
|
|
518
|
+
calls += 1
|
|
519
|
+
attempts += c.attempts
|
|
520
|
+
tokensIn += c.tokensIn
|
|
521
|
+
tokensOut += c.tokensOut
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// asserts regenerated FRESH for this arm's round, from the prompt alone (plan-free:
|
|
525
|
+
// testgen is oracle authorship, not solving — conditioning it would contaminate the oracle)
|
|
526
|
+
let genTests: string[] = []
|
|
527
|
+
if (testGen > 0) {
|
|
528
|
+
const g = await generateTests(cfg, task, testGen)
|
|
529
|
+
track(g.completion)
|
|
530
|
+
genTests = g.tests
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const samples: string[] = []
|
|
534
|
+
for (let i = 0; i < k; i += 1) {
|
|
535
|
+
const c = await complete(cfg, [{ role: 'user', content: samplePrompt }])
|
|
536
|
+
track(c)
|
|
537
|
+
samples.push(extractCode(c.content))
|
|
538
|
+
}
|
|
539
|
+
const honest: HonestResult[] = []
|
|
540
|
+
for (const s of samples) honest.push(await runHonestOracle(task, s, genTests))
|
|
541
|
+
|
|
542
|
+
let selectedIdx = 0
|
|
543
|
+
for (let i = 1; i < k; i += 1) {
|
|
544
|
+
if (honestScore(honest[i] as HonestResult) > honestScore(honest[selectedIdx] as HonestResult)) selectedIdx = i
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const selHonest = honest[selectedIdx] as HonestResult
|
|
548
|
+
let best = { code: samples[selectedIdx] as string, honest: selHonest }
|
|
549
|
+
const repairs: WorkerOutcome['repairs'] = []
|
|
550
|
+
let repairStop = 'already-passing'
|
|
551
|
+
if (!selHonest.pass) {
|
|
552
|
+
if (selHonest.attempted === 0) {
|
|
553
|
+
repairStop = 'no-signal'
|
|
554
|
+
} else {
|
|
555
|
+
repairStop = 'rounds-exhausted'
|
|
556
|
+
let current = best
|
|
557
|
+
for (let r = 0; r < maxRepairs; r += 1) {
|
|
558
|
+
const repairPrompt = [
|
|
559
|
+
'Your Python function failed some of the example checks shown in its own docstring.',
|
|
560
|
+
'Here is the task again:',
|
|
561
|
+
'```python',
|
|
562
|
+
task.prompt.trimEnd(),
|
|
563
|
+
'```',
|
|
564
|
+
'Your current attempt:',
|
|
565
|
+
'```python',
|
|
566
|
+
current.code,
|
|
567
|
+
'```',
|
|
568
|
+
'Result of running the docstring examples against your attempt:',
|
|
569
|
+
'```',
|
|
570
|
+
current.honest.failureOutput.trim() || '(the code crashed before the examples could run)',
|
|
571
|
+
'```',
|
|
572
|
+
'Fix the function so the docstring examples pass. Output the COMPLETE corrected function definition inside a single ```python code block. Do not write tests or example calls.',
|
|
573
|
+
].join('\n')
|
|
574
|
+
const c = await complete(cfg, [{ role: 'user', content: repairPrompt }])
|
|
575
|
+
track(c)
|
|
576
|
+
const code = extractRepairCode(c.content)
|
|
577
|
+
const h = await runHonestOracle(task, code, genTests)
|
|
578
|
+
repairs.push({ code, honest: h })
|
|
579
|
+
if (honestScore(h) > honestScore(current.honest)) current = { code, honest: h }
|
|
580
|
+
if (honestScore(current.honest) > honestScore(best.honest)) best = current
|
|
581
|
+
if (h.pass) {
|
|
582
|
+
repairStop = 'repaired-pass'
|
|
583
|
+
break
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return { samples, honest, selectedIdx, repairs, finalCode: best.code, repairStop, genTests, calls, attempts, tokensIn, tokensOut }
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ---------- rows ----------
|
|
593
|
+
|
|
594
|
+
interface ModelSpend {
|
|
595
|
+
model: string
|
|
596
|
+
calls: number
|
|
597
|
+
attempts: number
|
|
598
|
+
tokensIn: number
|
|
599
|
+
tokensOut: number
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
interface ArenaRow {
|
|
603
|
+
arm: string
|
|
604
|
+
set: string
|
|
605
|
+
taskId: string
|
|
606
|
+
k: number
|
|
607
|
+
maxRepairs: number
|
|
608
|
+
temperature: number
|
|
609
|
+
planRaw: string | null
|
|
610
|
+
plan: string | null
|
|
611
|
+
planLeaked: boolean | null
|
|
612
|
+
planEmptyAfterStrip: boolean | null
|
|
613
|
+
evidenceChars: number | null
|
|
614
|
+
genTests: string[]
|
|
615
|
+
samples: string[]
|
|
616
|
+
honest: HonestResult[]
|
|
617
|
+
selectedIdx: number
|
|
618
|
+
repairs: Array<{ code: string; honest: HonestResult }>
|
|
619
|
+
finalCode: string
|
|
620
|
+
repairStop: string
|
|
621
|
+
worker: ModelSpend
|
|
622
|
+
supervisor: ModelSpend | null
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
interface GradedRow extends ArenaRow {
|
|
626
|
+
hiddenSamples: number[]
|
|
627
|
+
hiddenFinal: number
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ---------- main ----------
|
|
631
|
+
|
|
632
|
+
const pct = (x: number) => `${(x * 100).toFixed(1)}%`
|
|
633
|
+
|
|
634
|
+
function costLine(s: ModelSpend): string {
|
|
635
|
+
const p = listPrices[s.model]
|
|
636
|
+
const dollars = p ? (s.tokensIn / 1e6) * p.in + (s.tokensOut / 1e6) * p.out : null
|
|
637
|
+
const d = dollars === null ? '$=null (no list price)' : `$${dollars.toFixed(4)} (list $${p?.in}/1M in, $${p?.out}/1M out)`
|
|
638
|
+
return `${s.model}: in=${s.tokensIn} out=${s.tokensOut} calls=${s.calls} (attempts incl. retries=${s.attempts}) → ${d}`
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function main(): Promise<void> {
|
|
642
|
+
const arm = must('ARM').toUpperCase()
|
|
643
|
+
if (!['A', 'B', 'C', 'D'].includes(arm)) throw new Error(`ARM must be A|B|C|D, got ${arm}`)
|
|
644
|
+
const setName = must('SET').toLowerCase()
|
|
645
|
+
if (!['hard', 'control'].includes(setName)) throw new Error(`SET must be hard|control, got ${setName}`)
|
|
646
|
+
const ids = (setName === 'hard' ? hardIds : controlIds).slice(0, Number(process.env.N ?? Number.POSITIVE_INFINITY))
|
|
647
|
+
const out = must('OUT')
|
|
648
|
+
|
|
649
|
+
const k = Number(process.env.K ?? 5)
|
|
650
|
+
const maxRepairs = Number(process.env.REPAIRS ?? 2)
|
|
651
|
+
const testGen = Number(process.env.TESTGEN ?? 6)
|
|
652
|
+
const temperature = Number(process.env.TEMPERATURE ?? 0.8)
|
|
653
|
+
const supTemperature = Number(process.env.SUPERVISOR_TEMPERATURE ?? 0.2)
|
|
654
|
+
const solveConc = Number(process.env.CONCURRENCY ?? 6)
|
|
655
|
+
dockerSlots = Number(process.env.DOCKER_CONCURRENCY ?? 6)
|
|
656
|
+
zaiSlots = Math.min(4, Number(process.env.ZAI_CONCURRENCY ?? 4))
|
|
657
|
+
const evidenceMaxChars = Number(process.env.EVIDENCE_MAX_CHARS ?? 12000)
|
|
658
|
+
|
|
659
|
+
const workerModel = process.env.WORKER_MODEL ?? workerModelDefault
|
|
660
|
+
const workerBase = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
|
|
661
|
+
const glmModel = process.env.GLM_MODEL ?? glmModelDefault
|
|
662
|
+
const glmBase = process.env.ZAI_BASE ?? 'https://api.z.ai/api/coding/paas/v4'
|
|
663
|
+
const glmMaxTokens = Math.max(8000, Number(process.env.ZAI_MAX_TOKENS ?? 8000))
|
|
664
|
+
|
|
665
|
+
const needsWorker = arm !== 'D'
|
|
666
|
+
const needsGlm = arm === 'C' || arm === 'D'
|
|
667
|
+
const workerCfg: ClientCfg | null = needsWorker
|
|
668
|
+
? { base: workerBase, key: process.env.TANGLE_API_KEY ?? must('TOGETHER_API_KEY'), model: workerModel, maxTokens: Number(process.env.MAX_TOKENS ?? 2500), temperature }
|
|
669
|
+
: null
|
|
670
|
+
const glmCfg: ClientCfg | null = needsGlm ? { base: glmBase, key: must('ZAI_API_KEY'), model: glmModel, maxTokens: glmMaxTokens, temperature: supTemperature } : null
|
|
671
|
+
|
|
672
|
+
const all = await loadHumanEval(164, 0)
|
|
673
|
+
const byId = new Map(all.map((t) => [t.taskId, t]))
|
|
674
|
+
const tasks = ids.map((id) => {
|
|
675
|
+
const t = byId.get(id)
|
|
676
|
+
if (!t) throw new Error(`task ${id} not found in HumanEval`)
|
|
677
|
+
return t
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
const needsEvidence = arm === 'B' || arm === 'C'
|
|
681
|
+
let evidence: Map<string, EvidenceRow> | null = null
|
|
682
|
+
if (needsEvidence) {
|
|
683
|
+
evidence = loadEvidence(must('EVIDENCE'))
|
|
684
|
+
for (const id of ids) if (!evidence.has(id)) throw new Error(`evidence row missing for ${id}`)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
console.log(`=== supervisor-arena · ARM=${arm} SET=${setName} n=${tasks.length} · k=${k} repairs<=${maxRepairs} testgen=${testGen} temp=${temperature} ===`)
|
|
688
|
+
console.log(` worker=${needsWorker ? `${workerModel} @ ${workerBase}` : '(none)'} glm=${needsGlm ? `${glmModel} @ ${glmBase} maxTokens=${glmMaxTokens} temp=${supTemperature} conc<=${zaiSlots}` : '(none)'}`)
|
|
689
|
+
console.log(` llm-conc=${solveConc} docker-conc=${dockerSlots} evidence=${needsEvidence ? `${process.env.EVIDENCE} (<=${evidenceMaxChars} chars rendered)` : '(none)'}`)
|
|
690
|
+
console.log(` Phase A (visible-only arm decisions) → Phase B (hidden nonce-judge grading, script-side)`)
|
|
691
|
+
|
|
692
|
+
// Phase A — per-task faults become error rows (persisted, excluded), >15% aborts loud.
|
|
693
|
+
let done = 0
|
|
694
|
+
let errCount = 0
|
|
695
|
+
const outcomes = await pool(tasks, solveConc, async (task): Promise<ArenaRow | { taskId: string; error: string }> => {
|
|
696
|
+
try {
|
|
697
|
+
let planRaw: string | null = null
|
|
698
|
+
let plan: string | null = null
|
|
699
|
+
let planLeaked: boolean | null = null
|
|
700
|
+
let planEmptyAfterStrip: boolean | null = null
|
|
701
|
+
let evidenceChars: number | null = null
|
|
702
|
+
let supervisor: ModelSpend | null = null
|
|
703
|
+
|
|
704
|
+
if (arm === 'B' || arm === 'C') {
|
|
705
|
+
const ev = (evidence as Map<string, EvidenceRow>).get(task.taskId) as EvidenceRow
|
|
706
|
+
const rendered = renderEvidence(task, ev, evidenceMaxChars)
|
|
707
|
+
evidenceChars = rendered.length
|
|
708
|
+
const supCfg: ClientCfg = arm === 'C' ? (glmCfg as ClientCfg) : { ...(workerCfg as ClientCfg), temperature: supTemperature }
|
|
709
|
+
const call = () => complete(supCfg, [{ role: 'user', content: supervisorPrompt(rendered) }])
|
|
710
|
+
const c = arm === 'C' ? await withZaiSlot(call) : await call()
|
|
711
|
+
supervisor = { model: supCfg.model, calls: 1, attempts: c.attempts, tokensIn: c.tokensIn, tokensOut: c.tokensOut }
|
|
712
|
+
planRaw = c.content
|
|
713
|
+
const stripped = stripPlanCode(c.content)
|
|
714
|
+
plan = stripped.plan
|
|
715
|
+
planLeaked = stripped.leaked
|
|
716
|
+
planEmptyAfterStrip = stripped.leaked && stripped.plan === ''
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
let row: ArenaRow
|
|
720
|
+
if (arm === 'D') {
|
|
721
|
+
const c = await withZaiSlot(() => complete(glmCfg as ClientCfg, [{ role: 'user', content: basePrompt(task) }]))
|
|
722
|
+
const code = extractCode(c.content)
|
|
723
|
+
row = {
|
|
724
|
+
arm, set: setName, taskId: task.taskId, k: 1, maxRepairs: 0, temperature: supTemperature,
|
|
725
|
+
planRaw: null, plan: null, planLeaked: null, planEmptyAfterStrip: null, evidenceChars: null,
|
|
726
|
+
genTests: [], samples: [code], honest: [], selectedIdx: 0, repairs: [], finalCode: code, repairStop: 'solo',
|
|
727
|
+
worker: { model: glmModel, calls: 1, attempts: c.attempts, tokensIn: c.tokensIn, tokensOut: c.tokensOut },
|
|
728
|
+
supervisor: null,
|
|
729
|
+
}
|
|
730
|
+
} else {
|
|
731
|
+
// A plan stripped to nothing carries no steering signal: the loop runs
|
|
732
|
+
// unconditioned (= arm A) and the row records the degenerate plan.
|
|
733
|
+
const preamble = plan !== null && plan !== '' ? `A reviewer analyzed previous failed attempts: ${plan}. Follow this guidance.` : null
|
|
734
|
+
const w = await runWorkerLoop(workerCfg as ClientCfg, task, k, maxRepairs, testGen, preamble)
|
|
735
|
+
row = {
|
|
736
|
+
arm, set: setName, taskId: task.taskId, k, maxRepairs, temperature,
|
|
737
|
+
planRaw, plan, planLeaked, planEmptyAfterStrip, evidenceChars,
|
|
738
|
+
genTests: w.genTests, samples: w.samples, honest: w.honest, selectedIdx: w.selectedIdx,
|
|
739
|
+
repairs: w.repairs, finalCode: w.finalCode, repairStop: w.repairStop,
|
|
740
|
+
worker: { model: workerModel, calls: w.calls, attempts: w.attempts, tokensIn: w.tokensIn, tokensOut: w.tokensOut },
|
|
741
|
+
supervisor,
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
done += 1
|
|
745
|
+
appendFileSync(`${out}.phaseA`, `${JSON.stringify(row)}\n`)
|
|
746
|
+
process.stderr.write(
|
|
747
|
+
` [A ${done}/${tasks.length}] ${task.taskId}: sel=${row.selectedIdx} honest=${row.honest.map((h) => honestScore(h).toFixed(2)).join('/')} repairs=${row.repairs.length} stop=${row.repairStop}${row.planLeaked === null ? '' : ` planLeaked=${row.planLeaked}`}\n`,
|
|
748
|
+
)
|
|
749
|
+
return row
|
|
750
|
+
} catch (e) {
|
|
751
|
+
errCount += 1
|
|
752
|
+
const error = e instanceof Error ? e.message : String(e)
|
|
753
|
+
appendFileSync(`${out}.phaseA`, `${JSON.stringify({ arm, set: setName, taskId: task.taskId, error })}\n`)
|
|
754
|
+
process.stderr.write(` [A ERROR] ${task.taskId}: ${error.slice(0, 160)}\n`)
|
|
755
|
+
if (errCount > Math.max(3, 0.15 * tasks.length)) throw new Error(`aborting: ${errCount} task errors — harness-level fault, not task noise (last: ${error})`)
|
|
756
|
+
return { taskId: task.taskId, error }
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
const okRows = outcomes.filter((o): o is ArenaRow => !('error' in o))
|
|
761
|
+
if (errCount > 0) console.log(` WARNING: ${errCount}/${tasks.length} task(s) errored in Phase A — excluded from stats, recorded in ${out}.phaseA`)
|
|
762
|
+
|
|
763
|
+
console.log(`\n▶ Phase B: hidden grading (${okRows.length} tasks × samples + finals)`)
|
|
764
|
+
const graded: GradedRow[] = await pool(okRows, 16, async (o) => {
|
|
765
|
+
const task = byId.get(o.taskId) as HumanEvalTask
|
|
766
|
+
const hiddenSamples: number[] = []
|
|
767
|
+
for (const s of o.samples) hiddenSamples.push((await runHiddenJudge(task, s)).pass)
|
|
768
|
+
const finalIsSelected = o.finalCode === o.samples[o.selectedIdx]
|
|
769
|
+
const hiddenFinal = finalIsSelected ? (hiddenSamples[o.selectedIdx] as number) : (await runHiddenJudge(task, o.finalCode)).pass
|
|
770
|
+
const g: GradedRow = { ...o, hiddenSamples, hiddenFinal }
|
|
771
|
+
appendFileSync(out, `${JSON.stringify(g)}\n`)
|
|
772
|
+
return g
|
|
773
|
+
})
|
|
774
|
+
console.log(` raw rows appended to ${out} (phase-A rows incl. errors: ${out}.phaseA)`)
|
|
775
|
+
|
|
776
|
+
// ---------- per-arm summary ----------
|
|
777
|
+
const passes = graded.filter((g) => g.hiddenFinal === 1)
|
|
778
|
+
const rateName = setName === 'hard' ? 'rescue rate' : 'retention rate'
|
|
779
|
+
console.log(`\n${'='.repeat(78)}`)
|
|
780
|
+
console.log(`RESULTS · supervisor-arena · ARM=${arm} SET=${setName} n=${graded.length}`)
|
|
781
|
+
console.log('='.repeat(78))
|
|
782
|
+
console.log(` per-problem hiddenFinal (paired analysis input):`)
|
|
783
|
+
console.log(` ${graded.map((g) => `${g.taskId.replace('HumanEval/', 'HE')}=${g.hiddenFinal}`).join(' ')}`)
|
|
784
|
+
console.log(` ${rateName}: ${passes.length}/${graded.length} = ${pct(graded.length > 0 ? passes.length / graded.length : 0)}${setName === 'hard' ? ' (every task in this set was hiddenFinal=0 in the committed run)' : ' (every task in this set was hiddenFinal=1 in the committed run)'}`)
|
|
785
|
+
if (arm === 'B' || arm === 'C') {
|
|
786
|
+
const leaked = graded.filter((g) => g.planLeaked === true)
|
|
787
|
+
const emptied = graded.filter((g) => g.planEmptyAfterStrip === true)
|
|
788
|
+
console.log(` plan leak rate: ${leaked.length}/${graded.length} plans contained code (stripped before use; ${emptied.length} stripped to empty → ran unconditioned)`)
|
|
789
|
+
const evChars = graded.map((g) => g.evidenceChars ?? 0)
|
|
790
|
+
if (evChars.length > 0) console.log(` evidence rendered chars: min=${Math.min(...evChars)} max=${Math.max(...evChars)} (cap ${evidenceMaxChars})`)
|
|
791
|
+
}
|
|
792
|
+
const spend = new Map<string, ModelSpend>()
|
|
793
|
+
const add = (s: ModelSpend | null) => {
|
|
794
|
+
if (!s) return
|
|
795
|
+
const cur = spend.get(s.model) ?? { model: s.model, calls: 0, attempts: 0, tokensIn: 0, tokensOut: 0 }
|
|
796
|
+
cur.calls += s.calls
|
|
797
|
+
cur.attempts += s.attempts
|
|
798
|
+
cur.tokensIn += s.tokensIn
|
|
799
|
+
cur.tokensOut += s.tokensOut
|
|
800
|
+
spend.set(s.model, cur)
|
|
801
|
+
}
|
|
802
|
+
for (const g of graded) {
|
|
803
|
+
add(g.worker)
|
|
804
|
+
add(g.supervisor)
|
|
805
|
+
}
|
|
806
|
+
console.log(` spend per model:`)
|
|
807
|
+
for (const s of spend.values()) console.log(` ${costLine(s)}`)
|
|
808
|
+
const stops = [...new Set(graded.map((g) => g.repairStop))]
|
|
809
|
+
console.log(` repair stops: ${stops.map((st) => `${st}=${graded.filter((g) => g.repairStop === st).length}`).join(', ')}`)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
main().catch((e) => {
|
|
813
|
+
reapContainers()
|
|
814
|
+
console.error(`supervisor-arena: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
|
|
815
|
+
process.exit(1)
|
|
816
|
+
})
|