@tangle-network/agent-bench 0.1.0 → 0.3.6
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 +23 -0
- package/HARNESS.md +302 -0
- package/README.md +39 -2
- 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 +53 -0
- package/dist/benchmarks/humaneval.js +15 -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-5SBJCB6W.js +144 -0
- package/dist/chunk-5SBJCB6W.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-PPYSEKFM.js +182 -0
- package/dist/chunk-PPYSEKFM.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.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/fixtures/aec-bench.json +18 -0
- package/fixtures/agentbench-dbbench.json +22 -0
- package/fixtures/bfcl.json +45 -0
- package/fixtures/commit0.json +72 -0
- package/fixtures/crag.json +10 -0
- package/fixtures/dabstep.json +22 -0
- package/fixtures/enterpriseops-gym.json +103 -0
- package/fixtures/finresearchbench.json +21 -0
- package/fixtures/finsearchcomp.json +66 -0
- package/fixtures/frames.json +26 -0
- package/fixtures/hotpotqa.json +182 -0
- package/fixtures/nomiracl.json +26 -0
- package/fixtures/open-rag-bench.json +16 -0
- package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
- package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
- package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
- package/fixtures/pier-agent/no-model-task/task.toml +35 -0
- package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
- package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
- package/fixtures/programbench.json +17 -0
- package/fixtures/ragbench.json +21 -0
- package/fixtures/simpleqa.json +121 -0
- package/fixtures/t2-ragbench.json +13 -0
- package/fixtures/tau2-bench.json +16 -0
- package/fixtures/tau3-banking.json +16 -0
- package/fixtures/toollm.json +28 -0
- package/fixtures/webarena-verified.json +20 -0
- package/package.json +60 -20
- package/pier_agents/__init__.py +18 -0
- package/pier_agents/candidate_contract.py +755 -0
- package/pier_agents/process_boundary.py +321 -0
- package/pier_agents/tangle_candidate.py +907 -0
- package/pier_agents/workspace_boundary.py +368 -0
- package/scripts/appworld_driver.py +359 -0
- package/scripts/cadbench_prepare.py +22 -0
- package/scripts/cadgenbench_hard_parts.py +48 -0
- package/scripts/clbench_codebase_judge.py +73 -0
- package/scripts/commit0_judge.py +170 -0
- package/scripts/dabstep_judge.py +42 -0
- package/scripts/enterpriseops_gym_judge.py +281 -0
- package/scripts/programbench_judge.py +120 -0
- package/scripts/render-gate-chart.mjs +176 -0
- package/scripts/run-package-tests.mjs +56 -0
- package/scripts/terminate-pier-trial.mts +66 -0
- package/scripts/trata-hedge/README.md +56 -0
- package/scripts/trata-hedge/run.sh +60 -0
- package/scripts/trata-hedge/solve.py +83 -0
- package/scripts/verify-packed-consumer.mjs +235 -0
- package/scripts/verify-pier-agent.mts +715 -0
- package/scripts/verify-pier-pair.mts +74 -0
- package/scripts/verify-pier-recovery.mts +139 -0
- package/src/adapters.ts +26 -0
- package/src/benchmarks/_harness.test.mts +178 -0
- package/src/benchmarks/_harness.ts +239 -16
- package/src/benchmarks/agentbench.ts +163 -0
- package/src/benchmarks/appworld.test.mts +15 -9
- package/src/benchmarks/bfcl.ts +346 -0
- package/src/benchmarks/crag.ts +137 -0
- package/src/benchmarks/dabstep.test.mts +70 -0
- package/src/benchmarks/dabstep.ts +212 -0
- package/src/benchmarks/external-adapters.test.mts +150 -0
- package/src/benchmarks/finresearchbench.ts +269 -0
- package/src/benchmarks/humaneval.ts +20 -8
- package/src/benchmarks/nomiracl.ts +180 -0
- package/src/benchmarks/open-rag-bench.ts +153 -0
- package/src/benchmarks/rag-benchmarks.test.mts +138 -0
- package/src/benchmarks/rag-shared.ts +327 -0
- package/src/benchmarks/ragbench.ts +171 -0
- package/src/benchmarks/swe-bench.test.mts +61 -0
- package/src/benchmarks/swe-bench.ts +201 -19
- package/src/benchmarks/t2-ragbench.ts +166 -0
- package/src/benchmarks/tau-bench-shared.ts +214 -0
- package/src/benchmarks/tau2-bench.ts +30 -0
- package/src/benchmarks/tau3-banking.ts +29 -0
- package/src/benchmarks/terminal-bench.test.mts +33 -0
- package/src/benchmarks/terminal-bench.ts +23 -8
- package/src/benchmarks/toollm.ts +254 -0
- package/src/benchmarks/types.ts +42 -0
- package/src/benchmarks/webarena-verified.ts +200 -0
- package/src/commit0-prereqs.sh +0 -0
- package/src/coordination-mcp-container-reach.mts +181 -0
- package/src/decoder-live.mts +1 -1
- package/src/examples/README.md +103 -39
- package/src/examples/benchmark-matrix.mts +101 -0
- package/src/examples/lean-proof-gate.README.md +77 -0
- package/src/examples/lean-proof-gate.mts +162 -0
- package/src/examples/lean-verify.ts +95 -0
- package/src/examples/lean.Dockerfile +12 -0
- package/src/examples/math-demo.mts +9 -7
- package/src/examples/strategy-demo.mts +10 -12
- package/src/gate.ts +3 -2
- package/src/hev-eval.mts +69 -0
- package/src/hev-improve.mts +169 -0
- package/src/hev-structural.mts +688 -0
- package/src/index.ts +73 -0
- package/src/mbpp-structural.mts +662 -0
- package/src/pier-agent.test-fixtures.mts +19 -0
- package/src/pier-agent.test.mts +363 -0
- package/src/pier-agent.ts +657 -0
- package/src/pier-result-grader.mjs +30 -0
- package/src/pier-result-grader.test.mts +62 -0
- package/src/pier-result-grader.ts +108 -0
- package/src/pier-task-outcome.test.mts +117 -0
- package/src/pier-task-outcome.ts +240 -0
- package/src/pier-trial-controller.test.mts +412 -0
- package/src/pier-trial-controller.ts +858 -0
- package/src/pier-trial-supervisor.mjs +352 -0
- package/src/resolve-client.ts +25 -2
- package/src/run-benchmarks-cli.mts +72 -0
- package/src/run-benchmarks-report.ts +66 -0
- package/src/run-benchmarks.test.mts +231 -0
- package/src/run-benchmarks.ts +589 -0
- package/src/smoke-structural-rollout.mts +393 -0
- package/src/swe-bench-env.test.ts +207 -0
- package/src/swe-bench-env.ts +554 -0
- package/src/swe-jail.ts +293 -0
- package/src/swe-self-improve.mts +84 -0
- package/src/swe-structural-judge-policy.test.ts +117 -0
- package/src/swe-structural-judge-policy.ts +133 -0
- package/src/swe-structural-policy.test.ts +124 -0
- package/src/swe-structural-policy.ts +132 -0
- package/src/swe-structural-provenance.test.ts +93 -0
- package/src/swe-structural-provenance.ts +138 -0
- package/src/swe-structural.mts +1260 -0
- package/src/swe-temp.ts +14 -0
- package/src/tb-container-executor.mts +234 -0
- package/src/tb-container-executor.test.mts +99 -0
- package/src/tb-supervisor-sidecar.mts +222 -0
- package/src/trata-gepa.mts +1 -1
- package/steerers/eops-itsm-population.json +1 -0
- package/tb_agents/opencode_refine_agent.py +117 -0
- package/tb_agents/opencode_router_agent.py +406 -0
- package/tb_agents/opencode_supervisor_agent.py +239 -0
- package/tb_agents/script_agent.py +66 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanEval STRUCTURAL lever — best-of-k selection + self-repair grounded ONLY on the
|
|
3
|
+
* VISIBLE docstring `>>>` examples (the honest oracle), graded on the HIDDEN check()
|
|
4
|
+
* suite. This is the experiment the self-improvement push identified but never ran:
|
|
5
|
+
* the existing gates (`humaneval-gate.mts`, `humaneval-repair-gate.mts`) select/steer
|
|
6
|
+
* on the task's own grading test — defensible in a deployable-verifier framing, but
|
|
7
|
+
* NOT a benchmark-lift claim. Here the harness sees nothing the model can't already
|
|
8
|
+
* read in its prompt.
|
|
9
|
+
*
|
|
10
|
+
* Honesty by construction — two physically separated phases:
|
|
11
|
+
* Phase A (harness): k samples/task at one temperature → honest doctest score per
|
|
12
|
+
* sample (docker, --network=none) → argmax select → ≤R repair rounds steered by
|
|
13
|
+
* the doctest FAILURE OUTPUT → final artifact locked. No access to task.test.
|
|
14
|
+
* Phase B (grading): the hidden check() suite grades every sample and every locked
|
|
15
|
+
* final. Nothing from this phase flows back.
|
|
16
|
+
*
|
|
17
|
+
* Judge integrity (adversarially reviewed; both spoof channels closed):
|
|
18
|
+
* - both judges print a per-call random NONCE sentinel and the verdict is parsed
|
|
19
|
+
* from that exact nonce — a candidate printing a forged summary line cannot win;
|
|
20
|
+
* - the hidden judge requires the sentinel, not exit-0 — `sys.exit(0)` before
|
|
21
|
+
* check() is a FAIL, not a pass;
|
|
22
|
+
* - containers run under an in-container `timeout -s KILL` so a hung candidate
|
|
23
|
+
* cannot outlive a crashed harness; a process-exit reaper force-removes strays.
|
|
24
|
+
*
|
|
25
|
+
* Estimators (paired across the same tasks, same sample batch):
|
|
26
|
+
* blind1_mean — mean hidden-pass over ALL k samples = expected pass@1 at this
|
|
27
|
+
* temperature (a built-in k-rep baseline; the primary control)
|
|
28
|
+
* blind1_first — hidden-pass of sample 0 (single-rep reference only)
|
|
29
|
+
* selected@1 — hidden-pass of the honest-oracle argmax sample (selection value)
|
|
30
|
+
* repaired@1 — hidden-pass of the final after honest-grounded repair (full harness)
|
|
31
|
+
* oracle@k — any sample passes hidden (the pass@k ceiling)
|
|
32
|
+
* Every lift carries a 95% paired-bootstrap CI (B=10000, seeded) AND an exact
|
|
33
|
+
* two-sided sign test — the bootstrap alone is anticonservative when few tasks move.
|
|
34
|
+
*
|
|
35
|
+
* CALIBRATE=1 skips the model entirely: canonical solutions vs both judges →
|
|
36
|
+
* hidden-judge self-check (must be ~100%) + honest-oracle coverage & false-fail rate.
|
|
37
|
+
*
|
|
38
|
+
* TANGLE_API_KEY=… WORKER_MODEL=meta-llama/Meta-Llama-3-8B-Instruct-Lite \
|
|
39
|
+
* ROUTER_BASE=https://api.together.xyz/v1 HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz \
|
|
40
|
+
* N=164 K=5 REPAIRS=2 TEMPERATURE=0.8 OUT=/abs/rows.jsonl tsx src/hev-structural.mts
|
|
41
|
+
*/
|
|
42
|
+
import { execFile, execFileSync } from 'node:child_process'
|
|
43
|
+
import { randomBytes } from 'node:crypto'
|
|
44
|
+
import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
45
|
+
import { tmpdir } from 'node:os'
|
|
46
|
+
import { join } from 'node:path'
|
|
47
|
+
import { type HumanEvalTask, extractCode, loadHumanEval } from './benchmarks/humaneval'
|
|
48
|
+
import { composeStrategies } from './directives'
|
|
49
|
+
import { type PairedLift, pairedLift, pool } from './stats.mts'
|
|
50
|
+
|
|
51
|
+
const dockerImage = 'python:3.12-slim'
|
|
52
|
+
const dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)
|
|
53
|
+
|
|
54
|
+
const solveInstruction =
|
|
55
|
+
'Complete the following Python function. Output the COMPLETE function definition (signature, docstring optional, body) inside a single ```python code block. Include any imports the function needs. Do not write tests or example calls.'
|
|
56
|
+
|
|
57
|
+
function must(name: string): string {
|
|
58
|
+
const v = process.env[name]
|
|
59
|
+
if (!v) throw new Error(`env ${name} is required`)
|
|
60
|
+
return v
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------- docker semaphore + jailed runner (shared by BOTH judges) ----------
|
|
64
|
+
// Phase A workers each run docker calls too, so the container count must be bounded
|
|
65
|
+
// by ONE global semaphore, not by whichever pool happens to wrap the caller.
|
|
66
|
+
|
|
67
|
+
let dockerSlots = 6
|
|
68
|
+
let dockerInFlight = 0
|
|
69
|
+
const dockerWaiters: Array<() => void> = []
|
|
70
|
+
async function withDockerSlot<T>(fn: () => Promise<T>): Promise<T> {
|
|
71
|
+
if (dockerInFlight >= dockerSlots) await new Promise<void>((r) => dockerWaiters.push(r))
|
|
72
|
+
dockerInFlight += 1
|
|
73
|
+
try {
|
|
74
|
+
return await fn()
|
|
75
|
+
} finally {
|
|
76
|
+
dockerInFlight -= 1
|
|
77
|
+
dockerWaiters.shift()?.()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const containerPrefix = `hevs-${process.pid}`
|
|
82
|
+
let containerSeq = 0
|
|
83
|
+
|
|
84
|
+
// Best-effort stray-container reap on any exit path (crash, SIGINT, clean end).
|
|
85
|
+
function reapContainers(): void {
|
|
86
|
+
try {
|
|
87
|
+
const ids = execFileSync('docker', ['ps', '-aq', '--filter', `name=${containerPrefix}`], { timeout: 10000 }).toString().trim()
|
|
88
|
+
if (ids) execFileSync('docker', ['rm', '-f', ...ids.split('\n')], { timeout: 15000 })
|
|
89
|
+
} catch {
|
|
90
|
+
/* reaper is best-effort by design */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
process.on('SIGINT', () => {
|
|
94
|
+
reapContainers()
|
|
95
|
+
process.exit(130)
|
|
96
|
+
})
|
|
97
|
+
process.on('SIGTERM', () => {
|
|
98
|
+
reapContainers()
|
|
99
|
+
process.exit(143)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
interface JailResult {
|
|
103
|
+
exitCode: number
|
|
104
|
+
stdout: string
|
|
105
|
+
stderr: string
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Run one python program in the jail: --network=none, cpu/mem caps, an IN-CONTAINER
|
|
109
|
+
* `timeout -s KILL` (so a hung candidate's container self-terminates even if this
|
|
110
|
+
* process dies), a client timeout, and a backstop. Docker INFRA faults (daemon,
|
|
111
|
+
* image, permission) throw — a broken checker must fail loud, not score zeros. */
|
|
112
|
+
function runJailed(program: string): Promise<JailResult> {
|
|
113
|
+
return withDockerSlot(
|
|
114
|
+
() =>
|
|
115
|
+
new Promise<JailResult>((resolvePromise, reject) => {
|
|
116
|
+
const dir = mkdtempSync(join(tmpdir(), 'hevs-'))
|
|
117
|
+
writeFileSync(join(dir, 'p.py'), program)
|
|
118
|
+
const name = `${containerPrefix}-${containerSeq++}`
|
|
119
|
+
let settled = false
|
|
120
|
+
const cleanup = () => {
|
|
121
|
+
rmSync(dir, { recursive: true, force: true })
|
|
122
|
+
execFile('docker', ['rm', '-f', name], () => {})
|
|
123
|
+
}
|
|
124
|
+
const finish = (res: JailResult) => {
|
|
125
|
+
if (settled) return
|
|
126
|
+
settled = true
|
|
127
|
+
clearTimeout(backstop)
|
|
128
|
+
cleanup()
|
|
129
|
+
resolvePromise(res)
|
|
130
|
+
}
|
|
131
|
+
const fail = (e: Error) => {
|
|
132
|
+
if (settled) return
|
|
133
|
+
settled = true
|
|
134
|
+
clearTimeout(backstop)
|
|
135
|
+
cleanup()
|
|
136
|
+
reject(e)
|
|
137
|
+
}
|
|
138
|
+
const backstop = setTimeout(() => finish({ exitCode: 124, stdout: '', stderr: 'backstop timeout (no output)' }), dockerTimeoutMs + 5000)
|
|
139
|
+
const inContainerSecs = Math.ceil(dockerTimeoutMs / 1000) + 2
|
|
140
|
+
execFile(
|
|
141
|
+
'docker',
|
|
142
|
+
[
|
|
143
|
+
'run', '--rm', '--name', name, '--network=none', '--cpus=1', '--memory=512m',
|
|
144
|
+
'-v', `${dir}:/w:ro`, '-w', '/w', dockerImage,
|
|
145
|
+
'timeout', '-s', 'KILL', String(inContainerSecs), 'python', '/w/p.py',
|
|
146
|
+
],
|
|
147
|
+
{ timeout: dockerTimeoutMs + 3000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
|
|
148
|
+
(err, stdout, stderr) => {
|
|
149
|
+
if (err) {
|
|
150
|
+
const e = err as NodeJS.ErrnoException & { code?: number | string }
|
|
151
|
+
if (e.code === 'ENOENT') return fail(new Error('docker binary not found on PATH'))
|
|
152
|
+
const se = stderr ?? ''
|
|
153
|
+
if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(se)) {
|
|
154
|
+
return fail(new Error(`docker daemon unreachable: ${se.slice(0, 200)}`))
|
|
155
|
+
}
|
|
156
|
+
if (/(unable to find image|pull access denied|manifest unknown|error response from daemon).*(pull|repository|registry)/i.test(se)) {
|
|
157
|
+
return fail(new Error(`docker image ${dockerImage} unavailable: ${se.slice(0, 200)}`))
|
|
158
|
+
}
|
|
159
|
+
const code = typeof e.code === 'number' ? e.code : 1
|
|
160
|
+
return finish({ exitCode: code, stdout: stdout ?? '', stderr: se })
|
|
161
|
+
}
|
|
162
|
+
finish({ exitCode: 0, stdout: stdout ?? '', stderr: stderr ?? '' })
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------- the honest oracle: doctest over the VISIBLE docstring examples ----------
|
|
170
|
+
|
|
171
|
+
export interface HonestResult {
|
|
172
|
+
/** total visible checks: doctest examples + model-generated asserts (0 = no
|
|
173
|
+
* signal, -1 = the candidate crashed before the oracle could run) */
|
|
174
|
+
attempted: number
|
|
175
|
+
failed: number
|
|
176
|
+
/** doctest's failure report — the ONLY feedback the repair loop may see */
|
|
177
|
+
failureOutput: string
|
|
178
|
+
/** attempted > 0 && failed === 0 */
|
|
179
|
+
pass: boolean
|
|
180
|
+
/** split for post-hoc audit: doctest vs generated-assert counts */
|
|
181
|
+
dAttempted?: number
|
|
182
|
+
dFailed?: number
|
|
183
|
+
gAttempted?: number
|
|
184
|
+
gFailed?: number
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The honest program: candidate executes (prompt header first, for its imports;
|
|
188
|
+
* candidate def shadows the stub), then doctest runs the examples taken from the
|
|
189
|
+
* STUB's `__doc__` (parsing the raw prompt text instead swallows the closing `"""`
|
|
190
|
+
* into the last example — caught by CALIBRATE=1). Verdict line carries a per-call
|
|
191
|
+
* NONCE so candidate-printed forgeries can't be parsed as the summary. task.test
|
|
192
|
+
* never appears here. */
|
|
193
|
+
function buildHonestProgram(task: HumanEvalTask, candidate: string, nonce: string, genTests: string[] = []): string {
|
|
194
|
+
const promptB64 = Buffer.from(task.prompt, 'utf8').toString('base64')
|
|
195
|
+
const entryB64 = Buffer.from(task.entryPoint, 'utf8').toString('base64')
|
|
196
|
+
const genB64 = Buffer.from(JSON.stringify(genTests), 'utf8').toString('base64')
|
|
197
|
+
return `${task.prompt}\n${candidate}\n
|
|
198
|
+
import ast as _ast, base64 as _b64, doctest as _doctest, io as _io, json as _json, sys as _sys
|
|
199
|
+
_prompt_text = _b64.b64decode("${promptB64}").decode("utf8")
|
|
200
|
+
_entry = _b64.b64decode("${entryB64}").decode("utf8")
|
|
201
|
+
_gen_tests = _json.loads(_b64.b64decode("${genB64}").decode("utf8"))
|
|
202
|
+
_stub_ns = {}
|
|
203
|
+
exec(_prompt_text, _stub_ns)
|
|
204
|
+
_doc = getattr(_stub_ns.get(_entry), "__doc__", None) or ""
|
|
205
|
+
try:
|
|
206
|
+
_examples = _doctest.DocTestParser().get_examples(_doc)
|
|
207
|
+
except ValueError:
|
|
208
|
+
_examples = [] # malformed docstring indentation -> no usable signal, not a crash
|
|
209
|
+
|
|
210
|
+
# Dataset-quirk normalizations, all decidable from VISIBLE output alone:
|
|
211
|
+
# assertion-style examples ("f(x) == 0" with no expected output) pass iff they print True;
|
|
212
|
+
# quote-style repr mismatches ("21" vs '21') compare by literal value.
|
|
213
|
+
class _Checker(_doctest.OutputChecker):
|
|
214
|
+
def check_output(self, want, got, optionflags):
|
|
215
|
+
if super().check_output(want, got, optionflags):
|
|
216
|
+
return True
|
|
217
|
+
if want.strip() == "" and got.strip() == "True":
|
|
218
|
+
return True
|
|
219
|
+
try:
|
|
220
|
+
return _ast.literal_eval(want.strip()) == _ast.literal_eval(got.strip())
|
|
221
|
+
except Exception:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
_test = _doctest.DocTest(_examples, globs=dict(globals()), name="visible", filename="p", lineno=0, docstring=_doc)
|
|
225
|
+
_runner = _doctest.DocTestRunner(checker=_Checker(), verbose=False, optionflags=_doctest.NORMALIZE_WHITESPACE | _doctest.IGNORE_EXCEPTION_DETAIL)
|
|
226
|
+
_buf = _io.StringIO()
|
|
227
|
+
_res = _runner.run(_test, out=_buf.write)
|
|
228
|
+
|
|
229
|
+
# Model-generated asserts (CodeT-style; written from the prompt BEFORE any candidate
|
|
230
|
+
# existed). Each runs individually so one malformed assert doesn't zero the rest.
|
|
231
|
+
_g_att, _g_fail = 0, 0
|
|
232
|
+
for _t in _gen_tests:
|
|
233
|
+
_g_att += 1
|
|
234
|
+
try:
|
|
235
|
+
exec(_t, dict(globals()))
|
|
236
|
+
except Exception as _e:
|
|
237
|
+
_g_fail += 1
|
|
238
|
+
_buf.write("GENTEST FAILED: %s -> %s: %s\\n" % (_t.strip()[:200], type(_e).__name__, str(_e)[:200]))
|
|
239
|
+
|
|
240
|
+
_att = _res.attempted + _g_att
|
|
241
|
+
_fail = _res.failed + _g_fail
|
|
242
|
+
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))
|
|
243
|
+
_sys.stdout.write(_buf.getvalue()[-1500:])
|
|
244
|
+
_sys.exit(0 if _att > 0 and _fail == 0 else 1)
|
|
245
|
+
`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function runHonestOracle(task: HumanEvalTask, candidate: string, genTests: string[] = []): Promise<HonestResult> {
|
|
249
|
+
const nonce = randomBytes(8).toString('hex')
|
|
250
|
+
const r = await runJailed(buildHonestProgram(task, candidate, nonce, genTests))
|
|
251
|
+
const summary = new RegExp(`HONEST-${nonce} attempted=(\\d+) failed=(\\d+) datt=(\\d+) dfail=(\\d+) gatt=(\\d+) gfail=(\\d+)`).exec(r.stdout)
|
|
252
|
+
if (!summary) {
|
|
253
|
+
// candidate crashed / hung before the oracle scaffold could report
|
|
254
|
+
const detail = (r.stderr || r.stdout).slice(-1500) || 'timed out (no output)'
|
|
255
|
+
return { attempted: -1, failed: -1, failureOutput: detail, pass: false }
|
|
256
|
+
}
|
|
257
|
+
const attempted = Number(summary[1])
|
|
258
|
+
const failed = Number(summary[2])
|
|
259
|
+
// Strip the sentinel line from the feedback shown to the repair loop — the model
|
|
260
|
+
// must never learn the summary format it could try to forge.
|
|
261
|
+
const failureOutput = r.stdout.replace(summary[0], '').slice(-1500)
|
|
262
|
+
return {
|
|
263
|
+
attempted,
|
|
264
|
+
failed,
|
|
265
|
+
failureOutput,
|
|
266
|
+
pass: attempted > 0 && failed === 0,
|
|
267
|
+
dAttempted: Number(summary[3]),
|
|
268
|
+
dFailed: Number(summary[4]),
|
|
269
|
+
gAttempted: Number(summary[5]),
|
|
270
|
+
gFailed: Number(summary[6]),
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------- CodeT-style test generation (visible info only, BEFORE any candidate) ----------
|
|
275
|
+
|
|
276
|
+
const testGenInstruction = (count: number, entry: string) =>
|
|
277
|
+
`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.`
|
|
278
|
+
|
|
279
|
+
/** One LLM call per task, before sampling. Keeps only single-line, paren-balanced
|
|
280
|
+
* asserts that reference the entry point — malformed lines are dropped here rather
|
|
281
|
+
* than poisoning every candidate's score identically. */
|
|
282
|
+
async function generateTests(cfg: ClientCfg, task: HumanEvalTask, count: number): Promise<{ tests: string[]; completion: Completion }> {
|
|
283
|
+
const c = await complete(cfg, [
|
|
284
|
+
{ role: 'user', content: `${testGenInstruction(count, task.entryPoint)}\n\n\`\`\`python\n${task.prompt}\`\`\`` },
|
|
285
|
+
])
|
|
286
|
+
const block = extractCode(c.content)
|
|
287
|
+
const balanced = (s: string) => {
|
|
288
|
+
let d = 0
|
|
289
|
+
for (const ch of s) {
|
|
290
|
+
if (ch === '(' || ch === '[' || ch === '{') d += 1
|
|
291
|
+
else if (ch === ')' || ch === ']' || ch === '}') d -= 1
|
|
292
|
+
if (d < 0) return false
|
|
293
|
+
}
|
|
294
|
+
return d === 0
|
|
295
|
+
}
|
|
296
|
+
const tests = block
|
|
297
|
+
.split('\n')
|
|
298
|
+
.map((l) => l.trim())
|
|
299
|
+
.filter((l) => l.startsWith('assert ') && l.includes(task.entryPoint) && balanced(l))
|
|
300
|
+
.slice(0, count)
|
|
301
|
+
return { tests, completion: c }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Honest score for ranking: fraction of visible examples passed; a candidate that
|
|
305
|
+
* crashed before doctest ran ranks below one that ran and failed everything. */
|
|
306
|
+
function honestScore(h: HonestResult): number {
|
|
307
|
+
if (h.attempted <= 0) return h.attempted === 0 ? 0 : -1
|
|
308
|
+
return (h.attempted - h.failed) / h.attempted
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ---------- the hidden judge (Phase B / calibration ONLY) ----------
|
|
312
|
+
// Rig-local rather than the shared runChecker: pass requires the nonce sentinel that
|
|
313
|
+
// check() prints AFTER succeeding — exit-0-before-check (sys.exit(0) in a candidate)
|
|
314
|
+
// is a fail here, where trusting the exit code alone would score it a pass.
|
|
315
|
+
|
|
316
|
+
function buildHiddenProgram(task: HumanEvalTask, candidate: string, nonce: string): string {
|
|
317
|
+
return `${task.prompt}\n${candidate}\n\n${task.test}\n\ncheck(${task.entryPoint})\nprint("HIDDEN-${nonce} PASS")\n`
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function runHiddenJudge(task: HumanEvalTask, candidate: string): Promise<{ pass: number; detail?: string }> {
|
|
321
|
+
const nonce = randomBytes(8).toString('hex')
|
|
322
|
+
const r = await runJailed(buildHiddenProgram(task, candidate, nonce))
|
|
323
|
+
if (r.exitCode === 0 && r.stdout.includes(`HIDDEN-${nonce} PASS`)) return { pass: 1 }
|
|
324
|
+
return { pass: 0, detail: (r.stderr || r.stdout).slice(-600) || 'timed out (no output)' }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ---------- model client (plain fetch; retries on transient HTTP + empty content) ----------
|
|
328
|
+
|
|
329
|
+
interface ClientCfg {
|
|
330
|
+
base: string
|
|
331
|
+
key: string
|
|
332
|
+
model: string
|
|
333
|
+
maxTokens: number
|
|
334
|
+
temperature: number
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
interface Completion {
|
|
338
|
+
content: string
|
|
339
|
+
attempts: number
|
|
340
|
+
tokensIn: number
|
|
341
|
+
tokensOut: number
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function complete(cfg: ClientCfg, messages: Array<{ role: string; content: string }>): Promise<Completion> {
|
|
345
|
+
let lastErr = ''
|
|
346
|
+
for (let attempt = 1; attempt <= 4; attempt += 1) {
|
|
347
|
+
if (attempt > 1) await new Promise((r) => setTimeout(r, 2000 * 2 ** attempt))
|
|
348
|
+
const ctl = new AbortController()
|
|
349
|
+
const timer = setTimeout(() => ctl.abort(), 240_000)
|
|
350
|
+
try {
|
|
351
|
+
const res = await fetch(`${cfg.base}/chat/completions`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
354
|
+
body: JSON.stringify({ model: cfg.model, max_tokens: cfg.maxTokens, temperature: cfg.temperature, messages }),
|
|
355
|
+
signal: ctl.signal,
|
|
356
|
+
})
|
|
357
|
+
if (!res.ok) {
|
|
358
|
+
lastErr = `HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
const d = (await res.json()) as {
|
|
362
|
+
choices?: Array<{ message?: { content?: string } }>
|
|
363
|
+
usage?: { prompt_tokens?: number; completion_tokens?: number }
|
|
364
|
+
}
|
|
365
|
+
const content = d.choices?.[0]?.message?.content ?? ''
|
|
366
|
+
// Reasoning models starve `content` when reasoning exhausts max_tokens — an
|
|
367
|
+
// empty reply is a transient fault to retry, not a candidate to score.
|
|
368
|
+
if (content.trim() === '') {
|
|
369
|
+
lastErr = 'empty content'
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
return { content, attempts: attempt, tokensIn: d.usage?.prompt_tokens ?? 0, tokensOut: d.usage?.completion_tokens ?? 0 }
|
|
373
|
+
} catch (e) {
|
|
374
|
+
lastErr = e instanceof Error ? e.message : String(e)
|
|
375
|
+
} finally {
|
|
376
|
+
clearTimeout(timer)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
throw new Error(`completion failed after retries: ${lastErr}`)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Repair replies often echo the failure report in a bare ``` block before the fixed
|
|
383
|
+
* code — first-fence extraction would grab the echo. Prefer the LAST fenced block
|
|
384
|
+
* that contains a `def`, else fall back to the shared extractor. Purely mechanical
|
|
385
|
+
* parsing of the model's own reply; no task information involved. */
|
|
386
|
+
function extractRepairCode(reply: string): string {
|
|
387
|
+
const fences = [...reply.matchAll(/```(?:python|py)?\s*\n([\s\S]*?)```/gi)].map((m) => (m[1] ?? '').trim())
|
|
388
|
+
for (let i = fences.length - 1; i >= 0; i -= 1) {
|
|
389
|
+
if (/(^|\n)\s*def\s+\w+/.test(fences[i] as string)) return fences[i] as string
|
|
390
|
+
}
|
|
391
|
+
return extractCode(reply)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ---------- Phase A: the harness (sees ONLY visible information) ----------
|
|
395
|
+
|
|
396
|
+
interface HarnessOutcome {
|
|
397
|
+
taskId: string
|
|
398
|
+
samples: string[]
|
|
399
|
+
honest: HonestResult[]
|
|
400
|
+
selectedIdx: number
|
|
401
|
+
repairs: Array<{ code: string; honest: HonestResult }>
|
|
402
|
+
finalCode: string
|
|
403
|
+
/** 'already-passing' | 'no-signal' | 'repaired-pass' | 'rounds-exhausted' */
|
|
404
|
+
repairStop: string
|
|
405
|
+
/** model-generated asserts used as extra oracle signal ([] when TESTGEN off) */
|
|
406
|
+
genTests: string[]
|
|
407
|
+
llmCalls: number
|
|
408
|
+
llmAttempts: number
|
|
409
|
+
tokensIn: number
|
|
410
|
+
tokensOut: number
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function runHarnessForTask(cfg: ClientCfg, task: HumanEvalTask, k: number, maxRepairs: number, testGen: number, diverse: boolean): Promise<HarnessOutcome> {
|
|
414
|
+
const basePrompt = `${solveInstruction}\n\n\`\`\`python\n${task.prompt}\`\`\``
|
|
415
|
+
// DIVERSE mode: each sample slot gets a distinct strategy prefix — targets the
|
|
416
|
+
// all-k-samples-fail bucket, where iid resampling keeps drawing the same bug.
|
|
417
|
+
const slotPrompts = diverse ? composeStrategies(basePrompt, k) : Array.from({ length: k }, () => basePrompt)
|
|
418
|
+
let llmCalls = 0
|
|
419
|
+
let llmAttempts = 0
|
|
420
|
+
let tokensIn = 0
|
|
421
|
+
let tokensOut = 0
|
|
422
|
+
const track = (c: Completion) => {
|
|
423
|
+
llmCalls += 1
|
|
424
|
+
llmAttempts += c.attempts
|
|
425
|
+
tokensIn += c.tokensIn
|
|
426
|
+
tokensOut += c.tokensOut
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Generated tests come from the prompt alone, BEFORE any candidate exists, and
|
|
430
|
+
// are frozen for every sample and repair round of this task.
|
|
431
|
+
let genTests: string[] = []
|
|
432
|
+
if (testGen > 0) {
|
|
433
|
+
const g = await generateTests(cfg, task, testGen)
|
|
434
|
+
track(g.completion)
|
|
435
|
+
genTests = g.tests
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const samples: string[] = []
|
|
439
|
+
for (let i = 0; i < k; i += 1) {
|
|
440
|
+
const c = await complete(cfg, [{ role: 'user', content: slotPrompts[i] as string }])
|
|
441
|
+
track(c)
|
|
442
|
+
samples.push(extractCode(c.content))
|
|
443
|
+
}
|
|
444
|
+
const honest: HonestResult[] = []
|
|
445
|
+
for (const s of samples) honest.push(await runHonestOracle(task, s, genTests))
|
|
446
|
+
|
|
447
|
+
// argmax by honest score, first index wins ties (deterministic; with zero
|
|
448
|
+
// coverage every sample ties at 0 → sample 0 = the blind pick)
|
|
449
|
+
let selectedIdx = 0
|
|
450
|
+
for (let i = 1; i < k; i += 1) {
|
|
451
|
+
if (honestScore(honest[i] as HonestResult) > honestScore(honest[selectedIdx] as HonestResult)) selectedIdx = i
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const selHonest = honest[selectedIdx] as HonestResult
|
|
455
|
+
let best = { code: samples[selectedIdx] as string, honest: selHonest }
|
|
456
|
+
const repairs: HarnessOutcome['repairs'] = []
|
|
457
|
+
let repairStop = 'already-passing'
|
|
458
|
+
if (!selHonest.pass) {
|
|
459
|
+
if (selHonest.attempted === 0) {
|
|
460
|
+
repairStop = 'no-signal' // no visible examples → nothing honest to steer on
|
|
461
|
+
} else {
|
|
462
|
+
repairStop = 'rounds-exhausted'
|
|
463
|
+
let current = best
|
|
464
|
+
for (let r = 0; r < maxRepairs; r += 1) {
|
|
465
|
+
const repairPrompt = [
|
|
466
|
+
'Your Python function failed some of the example checks shown in its own docstring.',
|
|
467
|
+
'Here is the task again:',
|
|
468
|
+
'```python',
|
|
469
|
+
task.prompt.trimEnd(),
|
|
470
|
+
'```',
|
|
471
|
+
'Your current attempt:',
|
|
472
|
+
'```python',
|
|
473
|
+
current.code,
|
|
474
|
+
'```',
|
|
475
|
+
'Result of running the docstring examples against your attempt:',
|
|
476
|
+
'```',
|
|
477
|
+
current.honest.failureOutput.trim() || '(the code crashed before the examples could run)',
|
|
478
|
+
'```',
|
|
479
|
+
'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.',
|
|
480
|
+
].join('\n')
|
|
481
|
+
const c = await complete(cfg, [{ role: 'user', content: repairPrompt }])
|
|
482
|
+
track(c)
|
|
483
|
+
const code = extractRepairCode(c.content)
|
|
484
|
+
const h = await runHonestOracle(task, code, genTests)
|
|
485
|
+
repairs.push({ code, honest: h })
|
|
486
|
+
if (honestScore(h) > honestScore(current.honest)) current = { code, honest: h }
|
|
487
|
+
if (honestScore(current.honest) > honestScore(best.honest)) best = current
|
|
488
|
+
if (h.pass) {
|
|
489
|
+
repairStop = 'repaired-pass'
|
|
490
|
+
break
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return { taskId: task.taskId, samples, honest, selectedIdx, repairs, finalCode: best.code, repairStop, genTests, llmCalls, llmAttempts, tokensIn, tokensOut }
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ---------- statistics: exact sign test to pair with the bootstrap CI ----------
|
|
500
|
+
// The percentile bootstrap is anticonservative when few tasks move (4 improved / 0
|
|
501
|
+
// regressed at n=164 prints CI [+0.6, +4.9]pp while the exact test says p=0.125).
|
|
502
|
+
// The verdict requires BOTH.
|
|
503
|
+
|
|
504
|
+
function signTestP(deltas: number[]): { pos: number; neg: number; p: number } {
|
|
505
|
+
const pos = deltas.filter((d) => d > 1e-9).length
|
|
506
|
+
const neg = deltas.filter((d) => d < -1e-9).length
|
|
507
|
+
const m = pos + neg
|
|
508
|
+
if (m === 0) return { pos, neg, p: 1 }
|
|
509
|
+
// two-sided exact binomial(m, 0.5) tail from the observed extreme
|
|
510
|
+
const logC: number[] = [0]
|
|
511
|
+
for (let i = 1; i <= m; i += 1) logC.push((logC[i - 1] as number) + Math.log(m - i + 1) - Math.log(i))
|
|
512
|
+
const pmf = (x: number) => Math.exp((logC[x] as number) - m * Math.LN2)
|
|
513
|
+
const extreme = Math.max(pos, neg)
|
|
514
|
+
let p = 0
|
|
515
|
+
for (let x = extreme; x <= m; x += 1) p += pmf(x)
|
|
516
|
+
p *= 2
|
|
517
|
+
if (pos === neg) p = 1
|
|
518
|
+
return { pos, neg, p: Math.min(1, p) }
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ---------- calibration mode ----------
|
|
522
|
+
|
|
523
|
+
async function calibrate(tasks: HumanEvalTask[]): Promise<void> {
|
|
524
|
+
console.log(`=== CALIBRATION · canonical solutions vs both judges · n=${tasks.length} ===`)
|
|
525
|
+
const usable = tasks.filter((t) => t.canonicalSolution)
|
|
526
|
+
if (usable.length !== tasks.length) console.log(` WARNING: ${tasks.length - usable.length} task(s) missing canonical_solution`)
|
|
527
|
+
const rows = await pool(usable, 16, async (t) => {
|
|
528
|
+
const full = `${t.prompt}${t.canonicalSolution}`
|
|
529
|
+
const hidden = await runHiddenJudge(t, full)
|
|
530
|
+
const honest = await runHonestOracle(t, full)
|
|
531
|
+
return { id: t.taskId, hidden: hidden.pass, attempted: honest.attempted, failed: honest.failed, honestPass: honest.pass }
|
|
532
|
+
})
|
|
533
|
+
const hiddenPass = rows.filter((r) => r.hidden === 1)
|
|
534
|
+
const covered = rows.filter((r) => r.attempted > 0)
|
|
535
|
+
const falseFail = covered.filter((r) => !r.honestPass)
|
|
536
|
+
console.log(` hidden judge self-check: ${hiddenPass.length}/${rows.length} canonical solutions pass (must be ~100%)`)
|
|
537
|
+
if (hiddenPass.length < rows.length) console.log(` hidden FAILS: ${rows.filter((r) => r.hidden !== 1).map((r) => r.id).join(', ')}`)
|
|
538
|
+
console.log(` honest-oracle coverage: ${covered.length}/${rows.length} tasks have >=1 parseable docstring example`)
|
|
539
|
+
console.log(` zero-coverage tasks: ${rows.filter((r) => r.attempted === 0).map((r) => r.id).join(', ') || '(none)'}`)
|
|
540
|
+
console.log(` crashed-oracle tasks (attempted=-1): ${rows.filter((r) => r.attempted < 0).map((r) => r.id).join(', ') || '(none)'}`)
|
|
541
|
+
console.log(` honest-oracle false-fail on canonical: ${falseFail.length}/${covered.length} covered tasks`)
|
|
542
|
+
if (falseFail.length > 0) console.log(` false-fail ids: ${falseFail.map((r) => `${r.id}(${r.failed}/${r.attempted})`).join(', ')}`)
|
|
543
|
+
const examplesTotal = covered.reduce((s, r) => s + r.attempted, 0)
|
|
544
|
+
console.log(` examples per covered task: mean ${(examplesTotal / Math.max(1, covered.length)).toFixed(1)}`)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ---------- main ----------
|
|
548
|
+
|
|
549
|
+
const pct = (x: number) => `${(x * 100).toFixed(1)}%`
|
|
550
|
+
const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
|
|
551
|
+
|
|
552
|
+
interface GradedRow extends HarnessOutcome {
|
|
553
|
+
hiddenSamples: number[]
|
|
554
|
+
hiddenFinal: number
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async function main(): Promise<void> {
|
|
558
|
+
const n = Number(process.env.N ?? 164)
|
|
559
|
+
const k = Number(process.env.K ?? 5)
|
|
560
|
+
const maxRepairs = Number(process.env.REPAIRS ?? 2)
|
|
561
|
+
const offset = Number(process.env.OFFSET ?? 0)
|
|
562
|
+
const temperature = Number(process.env.TEMPERATURE ?? 0.8)
|
|
563
|
+
const model = process.env.WORKER_MODEL ?? 'meta-llama/Meta-Llama-3-8B-Instruct-Lite'
|
|
564
|
+
const base = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
|
|
565
|
+
const solveConc = Number(process.env.CONCURRENCY ?? 6)
|
|
566
|
+
dockerSlots = Number(process.env.DOCKER_CONCURRENCY ?? 6)
|
|
567
|
+
const testGen = Number(process.env.TESTGEN ?? 0)
|
|
568
|
+
const diverse = process.env.DIVERSE === '1'
|
|
569
|
+
const out = process.env.OUT
|
|
570
|
+
|
|
571
|
+
const tasks = await loadHumanEval(n, offset)
|
|
572
|
+
|
|
573
|
+
if (process.env.CALIBRATE === '1') {
|
|
574
|
+
await calibrate(tasks)
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const cfg: ClientCfg = { base, key: must('TANGLE_API_KEY'), model, maxTokens: Number(process.env.MAX_TOKENS ?? 2500), temperature }
|
|
579
|
+
|
|
580
|
+
console.log(`=== HumanEval STRUCTURAL lever · honest docstring oracle · n=${tasks.length} k=${k} repairs<=${maxRepairs} temp=${temperature} testgen=${testGen} diverse=${diverse ? 1 : 0} ===`)
|
|
581
|
+
console.log(` model=${model} base=${base} llm-conc=${solveConc} docker-conc=${dockerSlots} (global semaphore)`)
|
|
582
|
+
console.log(` Phase A (harness: sample->honest-select->honest-repair) then Phase B (hidden grading)`)
|
|
583
|
+
|
|
584
|
+
// Phase A — all harness decisions locked before any hidden grading. A per-task
|
|
585
|
+
// fault becomes an error row (persisted, excluded from stats), not a lost run;
|
|
586
|
+
// >15% error rate aborts loud since that means the harness itself is broken.
|
|
587
|
+
let done = 0
|
|
588
|
+
let errCount = 0
|
|
589
|
+
const outcomes = await pool(tasks, solveConc, async (task): Promise<HarnessOutcome | { taskId: string; error: string }> => {
|
|
590
|
+
try {
|
|
591
|
+
const o = await runHarnessForTask(cfg, task, k, maxRepairs, testGen, diverse)
|
|
592
|
+
done += 1
|
|
593
|
+
if (out) appendFileSync(`${out}.phaseA`, `${JSON.stringify({ model, temperature, k, maxRepairs, ...o })}\n`)
|
|
594
|
+
process.stderr.write(
|
|
595
|
+
` [A ${done}/${tasks.length}] ${o.taskId}: sel=${o.selectedIdx} honest=${o.honest.map((h) => honestScore(h).toFixed(2)).join('/')} repairs=${o.repairs.length} stop=${o.repairStop}\n`,
|
|
596
|
+
)
|
|
597
|
+
return o
|
|
598
|
+
} catch (e) {
|
|
599
|
+
errCount += 1
|
|
600
|
+
const error = e instanceof Error ? e.message : String(e)
|
|
601
|
+
if (out) appendFileSync(`${out}.phaseA`, `${JSON.stringify({ model, taskId: task.taskId, error })}\n`)
|
|
602
|
+
process.stderr.write(` [A ERROR] ${task.taskId}: ${error.slice(0, 160)}\n`)
|
|
603
|
+
if (errCount > Math.max(3, 0.15 * tasks.length)) throw new Error(`aborting: ${errCount} task errors — harness-level fault, not task noise (last: ${error})`)
|
|
604
|
+
return { taskId: task.taskId, error }
|
|
605
|
+
}
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
const okOutcomes = outcomes.filter((o): o is HarnessOutcome => !('error' in o))
|
|
609
|
+
const okTasks = okOutcomes.map((o) => tasks.find((t) => t.taskId === o.taskId) as HumanEvalTask)
|
|
610
|
+
if (errCount > 0) console.log(` WARNING: ${errCount}/${tasks.length} task(s) errored in Phase A — excluded from stats, recorded in ${out ?? '(no OUT set)'}.phaseA`)
|
|
611
|
+
|
|
612
|
+
// Phase B — hidden grading of the locked artifacts.
|
|
613
|
+
console.log(`\n▶ Phase B: hidden grading (${okOutcomes.length} tasks × ${k} samples + finals)`)
|
|
614
|
+
const graded: GradedRow[] = await pool(okOutcomes, 16, async (o, ti) => {
|
|
615
|
+
const task = okTasks[ti] as HumanEvalTask
|
|
616
|
+
const hiddenSamples: number[] = []
|
|
617
|
+
for (const s of o.samples) hiddenSamples.push((await runHiddenJudge(task, s)).pass)
|
|
618
|
+
const finalIsSelected = o.finalCode === o.samples[o.selectedIdx]
|
|
619
|
+
const hiddenFinal = finalIsSelected ? (hiddenSamples[o.selectedIdx] as number) : (await runHiddenJudge(task, o.finalCode)).pass
|
|
620
|
+
const g: GradedRow = { ...o, hiddenSamples, hiddenFinal }
|
|
621
|
+
if (out) appendFileSync(out, `${JSON.stringify({ model, temperature, k, maxRepairs, ...g })}\n`)
|
|
622
|
+
return g
|
|
623
|
+
})
|
|
624
|
+
if (out) console.log(` raw rows appended to ${out} (phase-A rows incl. errors: ${out}.phaseA)`)
|
|
625
|
+
|
|
626
|
+
// Estimators (all paired over the same graded tasks).
|
|
627
|
+
const blind1First = graded.map((g) => g.hiddenSamples[0] as number)
|
|
628
|
+
const blind1Mean = graded.map((g) => g.hiddenSamples.reduce((s, x) => s + x, 0) / g.hiddenSamples.length)
|
|
629
|
+
const selected = graded.map((g) => g.hiddenSamples[g.selectedIdx] as number)
|
|
630
|
+
const repaired = graded.map((g) => g.hiddenFinal)
|
|
631
|
+
const oracleK = graded.map((g) => (g.hiddenSamples.some((x) => x === 1) ? 1 : 0))
|
|
632
|
+
// coverage is a task-level property; any non-crashed sample's oracle run proves it
|
|
633
|
+
const covered = graded.map((g) => (g.honest.some((h) => h.attempted > 0) ? 1 : 0))
|
|
634
|
+
const rate = (xs: number[]) => xs.reduce((s, x) => s + x, 0) / xs.length
|
|
635
|
+
const llmCallsTotal = graded.reduce((s, g) => s + g.llmCalls, 0)
|
|
636
|
+
const llmAttemptsTotal = graded.reduce((s, g) => s + g.llmAttempts, 0)
|
|
637
|
+
const tokensInTotal = graded.reduce((s, g) => s + g.tokensIn, 0)
|
|
638
|
+
const tokensOutTotal = graded.reduce((s, g) => s + g.tokensOut, 0)
|
|
639
|
+
const repairFired = graded.filter((g) => g.repairs.length > 0)
|
|
640
|
+
|
|
641
|
+
console.log(`\n${'='.repeat(78)}`)
|
|
642
|
+
console.log(`RESULTS · HumanEval structural lever · n=${graded.length} · k=${k} · repairs<=${maxRepairs} · temp=${temperature} · ${model}`)
|
|
643
|
+
console.log('='.repeat(78))
|
|
644
|
+
console.log(` honest-oracle coverage ${pct(rate(covered))} of tasks (>=1 docstring example)`)
|
|
645
|
+
console.log(` blind pass@1 (mean of k) ${pct(rate(blind1Mean))} [PRIMARY baseline — ${k}-rep estimator]`)
|
|
646
|
+
console.log(` blind pass@1 (first sample) ${pct(rate(blind1First))} [single-rep reference]`)
|
|
647
|
+
console.log(` selected@1 (honest argmax) ${pct(rate(selected))}`)
|
|
648
|
+
console.log(` repaired@1 (full harness) ${pct(rate(repaired))}`)
|
|
649
|
+
console.log(` oracle pass@${k} (ceiling) ${pct(rate(oracleK))}`)
|
|
650
|
+
console.log(
|
|
651
|
+
` compute: ${llmCallsTotal} llm calls (${llmAttemptsTotal} incl. retries) = ${(llmCallsTotal / graded.length).toFixed(2)}/task; tokens in/out ${tokensInTotal}/${tokensOutTotal} (blind@1 spends 1 call/task)`,
|
|
652
|
+
)
|
|
653
|
+
console.log(` repair fired on ${repairFired.length}/${graded.length} tasks (stop: ${['already-passing', 'no-signal', 'repaired-pass', 'rounds-exhausted'].map((s) => `${s}=${graded.filter((g) => g.repairStop === s).length}`).join(', ')})`)
|
|
654
|
+
|
|
655
|
+
const row = (label: string, baseline: number[], treatment: number[]) => {
|
|
656
|
+
const l = pairedLift(baseline, treatment)
|
|
657
|
+
const st = signTestP(baseline.map((b, i) => (treatment[i] as number) - b))
|
|
658
|
+
console.log(
|
|
659
|
+
` ${label.padEnd(36)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] sign-test p=${st.p < 0.001 ? st.p.toExponential(1) : st.p.toFixed(3)} (+${st.pos}/−${st.neg}) (pairs ${l.pairs})`,
|
|
660
|
+
)
|
|
661
|
+
return { l, st }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
console.log(`\n PAIRED LIFTS vs blind pass@1 (mean-of-${k}) · 95% bootstrap CI (B=10000) + exact sign test:`)
|
|
665
|
+
const sel = row('selected@1 − blind@1 (selection)', blind1Mean, selected)
|
|
666
|
+
const rep = row('repaired@1 − blind@1 (full harness)', blind1Mean, repaired)
|
|
667
|
+
row('repaired@1 − selected@1 (repair)', selected, repaired)
|
|
668
|
+
row(`oracle@${k} − repaired@1 (unrealized)`, repaired, oracleK)
|
|
669
|
+
|
|
670
|
+
// Subgroup views (report-only; the primary claim is the unconditional lift):
|
|
671
|
+
const coveredIdx = graded.map((_, i) => i).filter((i) => covered[i] === 1)
|
|
672
|
+
if (coveredIdx.length > 0 && coveredIdx.length < graded.length) {
|
|
673
|
+
const pick = (xs: number[]) => coveredIdx.map((i) => xs[i] as number)
|
|
674
|
+
console.log(`\n COVERED-ONLY subgroup (n=${coveredIdx.length} tasks with visible examples; oracle can only act here):`)
|
|
675
|
+
row(' selected@1 − blind@1', pick(blind1Mean), pick(selected))
|
|
676
|
+
row(' repaired@1 − blind@1', pick(blind1Mean), pick(repaired))
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const verdict = (name: string, r: { l: PairedLift; st: { p: number } }) =>
|
|
680
|
+
`${name}: ${pp(r.l.point)} — ${r.l.low > 0 && r.st.p < 0.05 ? 'POSITIVE (CI excludes 0 AND sign-test p<0.05)' : r.l.high < 0 && r.st.p < 0.05 ? 'NEGATIVE' : 'n.s.'}`
|
|
681
|
+
console.log(`\n VERDICT: ${verdict('full harness', rep)}; ${verdict('selection alone', sel)}`)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
main().catch((e) => {
|
|
685
|
+
reapContainers()
|
|
686
|
+
console.error(`hev-structural: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
|
|
687
|
+
process.exit(1)
|
|
688
|
+
})
|