@tangle-network/agent-bench 0.8.10 → 0.8.12

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.12
4
+
5
+ ### Changed
6
+
7
+ - The first-party cohort this benchmark resolves through the workspace catalog moves with the runtime: `agent-interface` 0.53.0 to 1.0.0, `agent-eval` 0.145.15 to 0.145.21, `agent-knowledge` 8.0.1 to 8.0.5, and `sandbox` 0.27.0 to 0.27.1. Interface 1.0.0 publishes the surface of 0.56.0 unchanged and states a compatibility promise, so one interface copy resolves for the whole tree.
8
+
9
+ ## 0.8.11
10
+
11
+ - Consume Runtime 0.135.3, Eval 0.145.15, Interface 0.53.0, Knowledge 8.0.1, and Sandbox 0.26.2 as one compatible set.
12
+
3
13
  ## 0.8.10
4
14
 
5
15
  - Consume Runtime 0.134.9 and Knowledge 8.0.0 as one compatible dependency set.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-bench",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "type": "module",
5
5
  "description": "Benchmark adapters and execution for agent-runtime across coding, tool-use, RAG, memory, browser, and terminal tasks.",
6
6
  "repository": {
@@ -25,11 +25,11 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@tangle-network/agent-eval": "0.145.11",
29
- "@tangle-network/agent-interface": "0.52.0",
30
- "@tangle-network/agent-knowledge": "8.0.0",
31
- "@tangle-network/sandbox": "0.26.1",
32
- "@tangle-network/agent-runtime": "0.134.9"
28
+ "@tangle-network/agent-eval": "0.145.21",
29
+ "@tangle-network/agent-interface": "1.0.0",
30
+ "@tangle-network/agent-knowledge": "8.0.5",
31
+ "@tangle-network/sandbox": "0.27.1",
32
+ "@tangle-network/agent-runtime": "0.137.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@arethetypeswrong/cli": "0.18.5",
package/src/aec-gate.mts DELETED
@@ -1,238 +0,0 @@
1
- /**
2
- * Router-based gate runner for aec-bench — the fix for the null-score sandbox path.
3
- *
4
- * aec-bench is closed-form reasoning + a deterministic local verify.py judge, so a
5
- * sandbox is unnecessary: solve each task with one direct router chat call, then
6
- * judge the raw response locally (verify.py extracts the last fenced ```json block
7
- * itself). The prior sandbox path emitted the JSON in-stream but never fed it to
8
- * the judge, so every verdict.score came out null — the bug this runner fixes by
9
- * passing the model's full response straight to adapter.judge().
10
- *
11
- * Two paired arms over the SAME task set (loadTasks once):
12
- * random@K — K identical-base-prompt shots/task (the compute control)
13
- * diverse@K — K shots, the i-th prefixed with composeStrategies(base, K)[i]
14
- *
15
- * Each attempt carries a REAL numeric verdict.score (from verify.py) + the output,
16
- * written as a corpus RunRecord (condition random@K / diverse@K) the existing
17
- * corpus-replay --selector + corpus-report consume unchanged. Fail loud on a router
18
- * error — never a fabricated score.
19
- */
20
-
21
- import { resolveAdapter } from './adapters'
22
- import type { BenchmarkAdapter, BenchTask } from './benchmarks/types'
23
- import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
24
- import { composeStrategies } from './directives'
25
- import {
26
- benchProfileModel,
27
- benchRouterProfile,
28
- type BenchRouterTarget,
29
- runBenchRouterTurn,
30
- withBenchProfile,
31
- } from './router-turn'
32
- import { pool } from './stats.mts'
33
-
34
- function must(name: string): string {
35
- const v = process.env[name]
36
- if (!v) throw new Error(`env ${name} is required`)
37
- return v
38
- }
39
-
40
- interface ArmSpec {
41
- /** Corpus condition label the selector/report filter on (e.g. random@4). */
42
- condition: string
43
- /** Per-attempt prompt builder: the i-th of K shots for a task. */
44
- promptFor(task: BenchTask, i: number, k: number): string
45
- }
46
-
47
- interface AttemptOutcome {
48
- prompt: string
49
- output: string
50
- score: number
51
- resolved: boolean
52
- costUsd?: number
53
- tokensIn?: number
54
- tokensOut?: number
55
- wallMs: number
56
- /** the router/judge call failed after retries — EXCLUDED from stats, never scored 0. */
57
- infraError?: boolean
58
- }
59
-
60
- async function runAttempt(
61
- cfg: BenchRouterTarget,
62
- adapter: BenchmarkAdapter,
63
- task: BenchTask,
64
- prompt: string,
65
- ): Promise<AttemptOutcome> {
66
- const startedAt = Date.now()
67
- // Retry transient router/judge failures (rate limits, stream drops, 5xx) with
68
- // backoff; a genuine empty completion still scores a real 0 (verify.py fail-closes).
69
- // Only after retries are exhausted do we record an EXCLUDED infraError — never a
70
- // fabricated score, and never a throw that aborts the whole multi-model run.
71
- let lastErr: unknown
72
- for (let attempt = 0; attempt < 3; attempt += 1) {
73
- try {
74
- const res = await runBenchRouterTurn(
75
- {
76
- routerBaseUrl: cfg.routerBaseUrl,
77
- routerKey: cfg.routerKey,
78
- profile: withBenchProfile(cfg.profile, { name: 'aec-worker' }),
79
- },
80
- prompt,
81
- )
82
- const content = res.finalText
83
- const verdict = await adapter.judge(task, content)
84
- return {
85
- prompt,
86
- output: content,
87
- score: verdict.score,
88
- resolved: verdict.resolved,
89
- wallMs: Date.now() - startedAt,
90
- ...(res.usage.costUsd !== undefined ? { costUsd: res.usage.costUsd } : {}),
91
- ...(res.usage.tokensKnown === false
92
- ? {}
93
- : { tokensIn: res.usage.input, tokensOut: res.usage.output }),
94
- }
95
- } catch (err) {
96
- lastErr = err
97
- if (attempt < 2) await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt))
98
- }
99
- }
100
- console.warn(`[aec-gate] ${task.id}: infra error after 3 tries — excluded: ${(lastErr instanceof Error ? lastErr.message : String(lastErr)).slice(0, 160)}`)
101
- return { prompt, output: '', score: 0, resolved: false, wallMs: Date.now() - startedAt, infraError: true }
102
- }
103
-
104
- function toAttemptRecord(o: AttemptOutcome, round: number): AttemptRecord {
105
- return {
106
- round,
107
- prompt: o.prompt,
108
- output: o.output,
109
- // infra-errored attempts carry NO score/valid → corpus-replay skips them.
110
- ...(o.infraError ? {} : { valid: o.resolved, score: o.score }),
111
- wallMs: o.wallMs,
112
- eventCount: 1,
113
- eventTypes: o.infraError ? { 'router.error': 1 } : { 'router.chat': 1 },
114
- traceTail: o.output.slice(-600),
115
- ...(o.costUsd !== undefined ? { costUsd: o.costUsd } : {}),
116
- ...(o.tokensIn !== undefined ? { tokensIn: o.tokensIn } : {}),
117
- ...(o.tokensOut !== undefined ? { tokensOut: o.tokensOut } : {}),
118
- }
119
- }
120
-
121
- interface ArmResult {
122
- /** mean graded score across SCORED (non-infra) attempts */
123
- meanScore: number
124
- /** fraction of scored attempts at full credit (score >= 1) */
125
- fullCreditRate: number
126
- attemptCount: number
127
- /** attempts excluded as infra errors (router/judge failed after retries) */
128
- infraCount: number
129
- }
130
-
131
- async function runArm(
132
- arm: ArmSpec,
133
- cfg: BenchRouterTarget,
134
- adapter: BenchmarkAdapter,
135
- tasks: BenchTask[],
136
- k: number,
137
- concurrency: number,
138
- corpusPath: string,
139
- ): Promise<ArmResult> {
140
- // Flatten (task, shot) into one unit of work so the pool bounds TOTAL in-flight
141
- // router calls across all tasks, not per-task.
142
- const units = tasks.flatMap((task) => Array.from({ length: k }, (_, i) => ({ task, i })))
143
- const outcomes = await pool(units, concurrency, (u) => runAttempt(cfg, adapter, u.task, arm.promptFor(u.task, u.i, k)))
144
-
145
- const scored = outcomes.filter((o) => !o.infraError)
146
- let scoreSum = 0
147
- let fullCredit = 0
148
- for (const o of scored) {
149
- scoreSum += o.score
150
- if (o.score >= 1) fullCredit += 1
151
- }
152
-
153
- // Group K outcomes back per task → one RunRecord/task (the controller-run shape).
154
- for (let t = 0; t < tasks.length; t += 1) {
155
- const task = tasks[t] as BenchTask
156
- const taskOutcomes = outcomes.slice(t * k, t * k + k)
157
- const attempts = taskOutcomes.map((o, i) => toAttemptRecord(o, i))
158
- const record = buildRunRecordFromAttempts(attempts, {
159
- benchmark: adapter.name,
160
- instanceId: task.id,
161
- condition: arm.condition,
162
- model: benchProfileModel(cfg.profile),
163
- // k-attempt outcome = any usable attempt resolved (the oracle@k ceiling for
164
- // this run; the deployable selector is scored separately by corpus-replay).
165
- resolved: taskOutcomes.some((o) => o.resolved),
166
- // a task whose every attempt infra-errored is itself infra-errored.
167
- infraError: taskOutcomes.length > 0 && taskOutcomes.every((o) => o.infraError),
168
- })
169
- await appendRunRecord(corpusPath, record)
170
- }
171
-
172
- return {
173
- meanScore: scored.length > 0 ? scoreSum / scored.length : 0,
174
- fullCreditRate: scored.length > 0 ? fullCredit / scored.length : 0,
175
- infraCount: outcomes.length - scored.length,
176
- attemptCount: outcomes.length,
177
- }
178
- }
179
-
180
- async function main(): Promise<void> {
181
- const n = Number(process.env.N ?? 5)
182
- const k = Number(process.env.K ?? 4)
183
- const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
184
- const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
185
- const routerKey = must('TANGLE_API_KEY')
186
- const concurrency = Number(process.env.CONCURRENCY ?? 6)
187
- const randomCorpus = process.env.RANDOM_CORPUS ?? '/tmp/aec-r.jsonl'
188
- const diverseCorpus = process.env.DIVERSE_CORPUS ?? '/tmp/aec-d.jsonl'
189
-
190
- if (!Number.isFinite(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
191
- if (!Number.isFinite(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
192
-
193
- const cfg: BenchRouterTarget = {
194
- routerBaseUrl,
195
- routerKey,
196
- profile: benchRouterProfile('aec-worker', model, {
197
- retry: { maxAttempts: Number(process.env.MAX_ATTEMPTS ?? 3) },
198
- }),
199
- }
200
- const bench = process.env.BENCH ?? 'aec-bench'
201
- const adapter = resolveAdapter(bench)
202
-
203
- console.log(`=== ${bench} router gate · N=${n} K=${k} model=${model} conc=${concurrency} ===`)
204
- await adapter.preflight()
205
- const tasks = await adapter.loadTasks({ limit: n })
206
- console.log(`loaded ${tasks.length} task(s): ${tasks.map((t) => t.id).join(', ')}`)
207
-
208
- // random arm: the task prompt verbatim, K times (the compute control).
209
- const randomArm: ArmSpec = {
210
- condition: `random@${k}`,
211
- promptFor: (task) => task.prompt,
212
- }
213
- // diverse arm: the i-th shot prefixed with the i-th distinct strategy lens.
214
- const diverseArm: ArmSpec = {
215
- condition: `diverse@${k}`,
216
- promptFor: (task, i, kk) => composeStrategies(task.prompt, kk)[i] as string,
217
- }
218
-
219
- console.log(`\n▶ random@${k} (control — identical base prompt) → ${randomCorpus}`)
220
- const r = await runArm(randomArm, cfg, adapter, tasks, k, concurrency, randomCorpus)
221
- console.log(` random@${k}: mean score ${(r.meanScore * 100).toFixed(1)}% full-credit ${(r.fullCreditRate * 100).toFixed(1)}% (n=${r.attemptCount} attempts${r.infraCount ? `, ${r.infraCount} infra-excluded` : ''})`)
222
-
223
- console.log(`\n▶ diverse@${k} (K distinct strategy lenses) → ${diverseCorpus}`)
224
- const d = await runArm(diverseArm, cfg, adapter, tasks, k, concurrency, diverseCorpus)
225
- console.log(` diverse@${k}: mean score ${(d.meanScore * 100).toFixed(1)}% full-credit ${(d.fullCreditRate * 100).toFixed(1)}% (n=${d.attemptCount} attempts${d.infraCount ? `, ${d.infraCount} infra-excluded` : ''})`)
226
-
227
- console.log(
228
- `\n=== next: read the gate ===\n` +
229
- ` npx tsx src/corpus-replay.mts ${randomCorpus} --selector\n` +
230
- ` npx tsx src/corpus-replay.mts ${diverseCorpus} --selector --condition=diverse\n` +
231
- ` npx tsx src/corpus-report.mts ${randomCorpus} ${diverseCorpus}`,
232
- )
233
- }
234
-
235
- main().catch((err) => {
236
- console.error(`aec-gate: ${err instanceof Error ? err.message : String(err)}`)
237
- process.exit(1)
238
- })
@@ -1,218 +0,0 @@
1
- /**
2
- * The "useful or BS" verdict: agents-driving-agents on a REAL deployable-checked domain.
3
- *
4
- * A `driverAgent` with a REAL router-LLM brain drives, per HumanEval task: it spawns
5
- * worker agents (each a router LLM that writes the function), every worker GATED by the
6
- * deterministic local Docker checker (the deliverable — a worker settles `valid` ⟺ its tests
7
- * pass), and the completion-oracle keeps-best a DELIVERED worker. The supervisor returns a winner
8
- * ONLY when a worker actually passed the tests (no self-declared done). We measure the driver's
9
- * delivered rate against a BLIND best-of-K baseline (K independent workers, no orchestration) at
10
- * the same K — the honest "does the recursion+oracle beat blind compute, or is it BS" question.
11
- *
12
- * Run (creds via dotenvx; Docker daemon must be up):
13
- * DOTENV_PRIVATE_KEY_FILE=~/company/devops/secrets/.env.keys \
14
- * dotenvx run -f ~/company/devops/secrets/agent-state.env -- \
15
- * N=5 K=3 WORKER_MODEL=deepseek-v4-flash DRIVER_MODEL=deepseek-v4-flash \
16
- * npx tsx bench/src/atom-humaneval.mts
17
- */
18
-
19
- import {
20
- type Agent,
21
- type AgentProfile,
22
- type AgentSpec,
23
- contentAddress,
24
- createExecutor,
25
- createExecutorRegistry,
26
- createSupervisor,
27
- gateOnDeliverable,
28
- InMemoryResultBlobStore,
29
- InMemorySpawnJournal,
30
- mapExecutorResult,
31
- supervisorAgent,
32
- } from '../../src/runtime/index'
33
- import { basePrompt, extractCode, type HumanEvalTask, loadHumanEval, runChecker } from './benchmarks/humaneval'
34
- import {
35
- benchProfileModel,
36
- benchRouterProfile,
37
- type BenchRouterTarget,
38
- runBenchRouterTurn,
39
- withBenchProfile,
40
- } from './router-turn'
41
-
42
- function must(k: string): string {
43
- const v = process.env[k]
44
- if (!v) throw new Error(`missing required env ${k}`)
45
- return v
46
- }
47
-
48
- const N = Number(process.env.N ?? 5)
49
- const K = Number(process.env.K ?? 3)
50
- const OFFSET = Number(process.env.OFFSET ?? 0)
51
- const WORKER_TEMP = Number(process.env.WORKER_TEMP ?? 0.7)
52
-
53
- const cfg: BenchRouterTarget = {
54
- routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
55
- routerKey: must('TANGLE_API_KEY'),
56
- profile: benchRouterProfile(
57
- 'humaneval-worker',
58
- process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
59
- { temperature: WORKER_TEMP },
60
- ),
61
- }
62
- const driverCfg: BenchRouterTarget = {
63
- ...cfg,
64
- profile: benchRouterProfile(
65
- 'humaneval-driver',
66
- process.env.DRIVER_MODEL ?? benchProfileModel(cfg.profile),
67
- { maxTurns: K + 4 },
68
- ),
69
- }
70
-
71
- // ── A gated router worker: one router call → candidate code, settled valid ⟺ the tests pass ──
72
- function humanEvalWorker(task: HumanEvalTask, label: string): Agent<unknown, unknown> {
73
- const profile: AgentProfile = withBenchProfile(cfg.profile, {
74
- name: label,
75
- systemPrompt: basePrompt(task),
76
- })
77
- const routerFactory = createExecutor({
78
- backend: 'router',
79
- routerBaseUrl: cfg.routerBaseUrl,
80
- routerKey: cfg.routerKey,
81
- })
82
- const executorFactory = (spec: AgentSpec, ctx: Parameters<typeof routerFactory>[1]) => {
83
- const inner = routerFactory(spec, ctx)
84
- const mapped = mapExecutorResult(inner, (result) => {
85
- const raw = result.out as { content?: unknown }
86
- const code = extractCode(typeof raw?.content === 'string' ? raw.content : '')
87
- return { outRef: contentAddress(code), out: code }
88
- })
89
- return gateOnDeliverable(mapped, {
90
- check: async (out) => (await runChecker(task, String(out))).pass === 1,
91
- describe: `${task.taskId}: the provided test suite passes`,
92
- })
93
- }
94
- const spec: AgentSpec = { profile, harness: null, executorFactory }
95
- return { name: label, act: async () => '', executorSpec: spec } as Agent<unknown, unknown> & {
96
- executorSpec: AgentSpec
97
- }
98
- }
99
-
100
- const driverSystem = `You are an orchestrator driving worker agents to solve a Python coding task. You do NOT write code yourself. Each worker independently attempts the task and is graded by a deterministic, hidden test suite. Tools: spawn_worker (dispatch one attempt; the "profile" argument may be {} and "task" a short note), await_event (collect the next settled worker — its result tells you valid:true if its tests PASSED, valid:false if they failed), and stopping (reply with NO tool call) once a worker has DELIVERED. Spawn one worker, await it; if it delivered, stop; if not, spawn another, up to ${K} workers total. You cannot declare success yourself — only a delivered (valid:true) worker counts.`
101
-
102
- interface TaskOutcome {
103
- taskId: string
104
- driverDelivered: boolean
105
- blindDelivered: boolean
106
- driverSpawns: number
107
- driverWorkerTokens: number
108
- }
109
-
110
- // ── Driver arm: the orchestrated atom ────────────────────────────────────────────────────────
111
- async function driveTask(
112
- task: HumanEvalTask,
113
- ): Promise<{ delivered: boolean; spawns: number; tokens: number }> {
114
- const blobs = new InMemoryResultBlobStore()
115
- const journal = new InMemorySpawnJournal()
116
- let spawns = 0
117
- const makeWorker = (): Agent<unknown, unknown> => {
118
- const w = humanEvalWorker(task, `w-${spawns}`)
119
- spawns += 1
120
- return w
121
- }
122
- const root = supervisorAgent(
123
- withBenchProfile(driverCfg.profile, {
124
- name: `drv-${task.taskId}`,
125
- systemPrompt: driverSystem,
126
- }),
127
- {
128
- router: {
129
- routerBaseUrl: driverCfg.routerBaseUrl,
130
- routerKey: driverCfg.routerKey,
131
- },
132
- blobs,
133
- makeWorkerAgent: makeWorker,
134
- perWorker: { maxIterations: 2, maxTokens: 4000 },
135
- },
136
- )
137
- const runId = `he-${task.taskId.replace('/', '-')}`
138
- const result = await createSupervisor<unknown, unknown>().run(root, basePrompt(task), {
139
- budget: { maxIterations: 100, maxTokens: 400_000 },
140
- runId,
141
- journal,
142
- blobs,
143
- executors: createExecutorRegistry(),
144
- maxDepth: 4,
145
- now: () => Date.now(),
146
- })
147
- const tree = await journal.loadTree(runId)
148
- const tokens = (tree ?? [])
149
- .filter((e): e is Extract<NonNullable<typeof tree>[number], { kind: 'settled' }> => e.kind === 'settled')
150
- .reduce((s, e) => s + e.spent.tokens.input + e.spent.tokens.output, 0)
151
- return { delivered: result.kind === 'winner', spawns, tokens }
152
- }
153
-
154
- // ── Blind arm: K independent workers, best-of-K by the checker (no orchestration) ─────────────
155
- async function blindTask(task: HumanEvalTask): Promise<boolean> {
156
- for (let i = 0; i < K; i += 1) {
157
- // A transient router error is a FAILED attempt, not a crash — the driver arm already types
158
- // an executor throw into a `down` settlement, so the blind arm must match (fair comparison).
159
- let content = ''
160
- try {
161
- const res = await runBenchRouterTurn(
162
- {
163
- routerBaseUrl: cfg.routerBaseUrl,
164
- routerKey: cfg.routerKey,
165
- profile: withBenchProfile(cfg.profile, {
166
- name: 'humaneval-blind-atom-worker',
167
- }),
168
- },
169
- basePrompt(task),
170
- )
171
- content = res.finalText
172
- } catch {
173
- continue
174
- }
175
- if ((await runChecker(task, extractCode(content))).pass === 1) return true
176
- }
177
- return false
178
- }
179
-
180
- async function main(): Promise<void> {
181
- console.log(
182
- `atom-humaneval: N=${N} K=${K} offset=${OFFSET} worker=${benchProfileModel(cfg.profile)} driver=${benchProfileModel(driverCfg.profile)}`,
183
- )
184
- const tasks = await loadHumanEval(N, OFFSET)
185
- const outcomes: TaskOutcome[] = []
186
- for (const task of tasks) {
187
- const drv = await driveTask(task)
188
- const blind = await blindTask(task)
189
- outcomes.push({
190
- taskId: task.taskId,
191
- driverDelivered: drv.delivered,
192
- blindDelivered: blind,
193
- driverSpawns: drv.spawns,
194
- driverWorkerTokens: drv.tokens,
195
- })
196
- console.log(
197
- ` ${task.taskId.padEnd(14)} driver=${drv.delivered ? 'PASS' : 'fail'} (spawns=${drv.spawns}, tok=${drv.tokens}) blind@${K}=${blind ? 'PASS' : 'fail'}`,
198
- )
199
- }
200
- const driverPass = outcomes.filter((o) => o.driverDelivered).length
201
- const blindPass = outcomes.filter((o) => o.blindDelivered).length
202
- const avgSpawns = outcomes.reduce((s, o) => s + o.driverSpawns, 0) / Math.max(1, outcomes.length)
203
- console.log('\n── verdict ──')
204
- console.log(`driver-orchestrated delivered: ${driverPass}/${outcomes.length} (avg spawns ${avgSpawns.toFixed(1)} of ${K} allowed)`)
205
- console.log(`blind best-of-${K} delivered: ${blindPass}/${outcomes.length}`)
206
- console.log(
207
- driverPass > blindPass
208
- ? `→ orchestration BEAT blind by +${driverPass - blindPass} tasks`
209
- : driverPass === blindPass
210
- ? `→ orchestration TIED blind (the atom delivers, but adds no lift here at this N)`
211
- : `→ orchestration LOST to blind by ${blindPass - driverPass} tasks`,
212
- )
213
- }
214
-
215
- main().catch((e) => {
216
- console.error(e)
217
- process.exit(1)
218
- })
@@ -1,97 +0,0 @@
1
- /**
2
- * DAVID mechanism attribution — decompose the cheap-model harness's held-out
3
- * accuracy into what SAMPLING buys vs what VERIFICATION-SELECTION buys, so a
4
- * David-Goliath win is credited to the right lever (not just best-of-N luck).
5
- *
6
- * For each task, generate N candidate solutions + the model's own tests, then
7
- * report four numbers on the HIDDEN test:
8
- * pass@1 — first candidate (no harness).
9
- * mean-cand — expected accuracy of a RANDOM candidate (sampling floor).
10
- * oracle@N — a correct candidate exists among the N (ceiling of selection).
11
- * verify-select — the candidate the self-tests picked (the actual David).
12
- * verify-select − mean-cand = what VERIFICATION adds over blind sampling;
13
- * oracle@N − verify-select = the selection gap left on the table.
14
- *
15
- * Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant N=8 T=5 NTASKS=60 \
16
- * node_modules/.bin/tsx src/david-attribution.mts
17
- */
18
- import { execFile } from 'node:child_process'
19
- import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
20
- import { tmpdir } from 'node:os'
21
- import { join } from 'node:path'
22
- import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
23
- import { benchRouterProfile, runBenchRouterTurn } from './router-turn'
24
-
25
- function requiredEnv(name: string): string {
26
- const value = process.env[name]
27
- if (!value) throw new Error(`${name} required`)
28
- return value
29
- }
30
-
31
- const KEY = requiredEnv('TANGLE_API_KEY')
32
- const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
33
- const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
34
- const N = Number(process.env.N ?? 8)
35
- const T = Number(process.env.T ?? 5)
36
- const NTASKS = Number(process.env.NTASKS ?? 60)
37
- const CONC = Number(process.env.CONCURRENCY ?? 6)
38
- const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 1000)
39
- const LLM_TIMEOUT_MS = Number(process.env.LLM_TIMEOUT_MS ?? 60_000)
40
-
41
- async function chat(messages: { role: string; content: string }[], temp: number): Promise<string> {
42
- try {
43
- const system = messages.find((message) => message.role === 'system')?.content
44
- const turn = await runBenchRouterTurn(
45
- {
46
- routerBaseUrl: ROUTER,
47
- routerKey: KEY,
48
- profile: benchRouterProfile('david-attribution-worker', DAVID, {
49
- ...(system ? { systemPrompt: system } : {}),
50
- temperature: temp,
51
- maxTokens: MAX_TOKENS,
52
- }),
53
- timeoutMs: LLM_TIMEOUT_MS,
54
- },
55
- { messages: messages.filter((message) => message.role !== 'system') },
56
- )
57
- return turn.finalText
58
- } catch {
59
- return ''
60
- }
61
- }
62
- const exec = (f: string, a: string[], o: object) => new Promise<number>((res) => execFile(f, a, { ...o, maxBuffer: 8e6 }, (e) => res((e as { code?: number } | null)?.code ?? (e ? 1 : 0))))
63
- async function runPy(p: string): Promise<boolean> { const d = mkdtempSync(join(tmpdir(), 'da-')); try { writeFileSync(join(d, 'p.py'), p); return (await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: 6000 })) === 0 } finally { rmSync(d, { recursive: true, force: true }) } }
64
- const SOLVE = 'Expert Python. Output the COMPLETE function in one ```python block, no prose, no tests.'
65
- const genSol = async (t: HumanEvalTask, temp: number) => extractCode(await chat([{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp))
66
- async function genTests(t: HumanEvalTask): Promise<string[]> {
67
- const b = extractCode(await chat([{ role: 'system', content: 'Write Python assert unit tests. Output ONLY a ```python block of `assert <entry>(...) == ...` lines. No function, no prose.' }, { role: 'user', content: `entry: ${t.entryPoint}\n\n\`\`\`python\n${t.prompt}\`\`\`` }], 0.4))
68
- return b.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
69
- }
70
- const judge = async (t: HumanEvalTask, code: string) => code.trim() ? runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`) : false
71
- async function scoreTests(code: string, tests: string[]): Promise<number> { if (!code.trim() || !tests.length) return 0; let p = 0; for (const a of tests) if (await runPy(`${code}\n\n${a}\n`)) p++; return p }
72
- async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2) => Promise<R>): Promise<R[]> { const o = new Array<R>(xs.length); let i = 0; await Promise.all(Array.from({ length: n }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!) } })); return o }
73
-
74
- async function main(): Promise<void> {
75
- const tasks = await loadHumanEval(NTASKS, 0)
76
- console.error(`=== ATTRIBUTION · ${DAVID} · N=${N} sols + ${T} tests · n=${tasks.length} ===`)
77
- let done = 0
78
- const rows = await pool(tasks, CONC, async (t) => {
79
- const cands = (await Promise.all(Array.from({ length: N }, () => genSol(t, 0.7)))).filter((c) => c.trim())
80
- if (!cands.length) return { p1: 0, mean: 0, oracle: 0, sel: 0 }
81
- const tests = await genTests(t)
82
- const hidden = await Promise.all(cands.map((c) => judge(t, c))) // hidden-test pass per candidate (for attribution only)
83
- const selScores = tests.length ? await Promise.all(cands.map((c) => scoreTests(c, tests))) : cands.map(() => 0)
84
- let bi = 0; for (let i = 1; i < cands.length; i++) if (selScores[i]! > selScores[bi]! || (selScores[i]! === selScores[bi]! && cands[i]!.length > cands[bi]!.length)) bi = i
85
- if (++done % 15 === 0) console.error(` ${done}/${tasks.length}`)
86
- return { p1: hidden[0] ? 1 : 0, mean: hidden.filter(Boolean).length / cands.length, oracle: hidden.some(Boolean) ? 1 : 0, sel: hidden[bi] ? 1 : 0 }
87
- })
88
- const n = rows.length, avg = (f: (r: typeof rows[number]) => number) => (rows.reduce((s, r) => s + f(r), 0) / n) * 100
89
- console.log('\n=== ATTRIBUTION (held-out) ===')
90
- console.log(` pass@1 (no harness) : ${avg((r) => r.p1).toFixed(1)}%`)
91
- console.log(` mean random candidate : ${avg((r) => r.mean).toFixed(1)}% (sampling floor)`)
92
- console.log(` verify-select (DAVID) : ${avg((r) => r.sel).toFixed(1)}%`)
93
- console.log(` oracle@N (a correct exists): ${avg((r) => r.oracle).toFixed(1)}% (selection ceiling)`)
94
- console.log(` --> verification adds over random sampling: +${(avg((r) => r.sel) - avg((r) => r.mean)).toFixed(1)}pp`)
95
- console.log(` --> selection gap still on table (oracle-select): ${(avg((r) => r.oracle) - avg((r) => r.sel)).toFixed(1)}pp`)
96
- }
97
- main().catch((e) => { console.error('MAIN:', e instanceof Error ? e.stack : e); process.exit(1) })