@tangle-network/agent-bench 0.8.9 → 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 +14 -0
- package/package.json +6 -6
- package/src/aec-gate.mts +0 -238
- package/src/atom-humaneval.mts +0 -218
- package/src/david-attribution.mts +0 -97
- package/src/david-goliath.mts +0 -168
- package/src/decoder-live.mts +0 -133
- package/src/diverse-gate.mjs +0 -112
- package/src/hev-eval.mts +0 -101
- package/src/hev-improve.mts +0 -245
- package/src/humaneval-object-ablation.mts +0 -239
- package/src/trata-gate.mts +0 -243
package/src/david-goliath.mts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DAVID vs GOLIATH — the program's northstar, tested head-on: does a CHEAP model
|
|
3
|
-
* with a self-verification harness beat a FRONTIER model running solo, at EQUAL OR
|
|
4
|
-
* LOWER dollar cost, on held-out code?
|
|
5
|
-
*
|
|
6
|
-
* GOLIATH — a strong model, ONE shot. The "just use the big model" baseline.
|
|
7
|
-
* DAVID — a cheap/weak model + test-time compute: generate N candidate
|
|
8
|
-
* solutions AND M of its own unit tests, EXECUTE every candidate
|
|
9
|
-
* against the generated tests, and submit the candidate that passes
|
|
10
|
-
* the most (CodeT-style execution self-selection). No ground-truth
|
|
11
|
-
* test is ever used to select — only the model's own generated tests.
|
|
12
|
-
*
|
|
13
|
-
* Both are graded by the HIDDEN HumanEval test (never shown). Cost is the real
|
|
14
|
-
* token spend × the router-reported/priced rate per arm. The win condition is a
|
|
15
|
-
* Pareto beat: David's held-out pass rate >= Goliath's AND David's $ <= Goliath's.
|
|
16
|
-
* Mechanism under test: EXECUTION-BASED VERIFICATION is the lever that lets a weak
|
|
17
|
-
* generator punch above its solo weight — the standing "verification is live" claim
|
|
18
|
-
* at its most dramatic. Paired McNemar on per-task discordant pairs for significance.
|
|
19
|
-
*
|
|
20
|
-
* Run from cwd=bench: env DAVID=glm-5.2 GOLIATH=deepseek-v4-flash \
|
|
21
|
-
* N=8 T=5 NTASKS=164 REPS=2 node_modules/.bin/tsx src/david-goliath.mts
|
|
22
|
-
*/
|
|
23
|
-
import { execFile } from 'node:child_process'
|
|
24
|
-
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
25
|
-
import { tmpdir } from 'node:os'
|
|
26
|
-
import { join } from 'node:path'
|
|
27
|
-
import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
|
|
28
|
-
import { runBenchRouterTurn } from './router-turn'
|
|
29
|
-
|
|
30
|
-
const KEY = process.env.TANGLE_API_KEY
|
|
31
|
-
if (!KEY) throw new Error('TANGLE_API_KEY required')
|
|
32
|
-
const ROUTER_KEY = KEY
|
|
33
|
-
const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
34
|
-
const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
|
|
35
|
-
const GOLIATH = process.env.GOLIATH ?? 'deepseek-v4-flash'
|
|
36
|
-
const N = Number(process.env.N ?? 8) // David candidate solutions
|
|
37
|
-
const T = Number(process.env.T ?? 5) // David generated tests
|
|
38
|
-
const NTASKS = Number(process.env.NTASKS ?? 164)
|
|
39
|
-
const REPS = Number(process.env.REPS ?? 2)
|
|
40
|
-
const CONC = Number(process.env.CONCURRENCY ?? 6)
|
|
41
|
-
const EXEC_TIMEOUT = Number(process.env.EXEC_TIMEOUT_MS ?? 6000)
|
|
42
|
-
const LLM_TIMEOUT = Number(process.env.LLM_TIMEOUT_MS ?? 60_000)
|
|
43
|
-
const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 1000)
|
|
44
|
-
|
|
45
|
-
// Approx $/1M tokens (in,out) for cost accounting — the router does not price
|
|
46
|
-
// every model inline, so use public rates; a cheap/frontier gap of ~20-30x is the
|
|
47
|
-
// point, and the ratio is robust to small rate errors. Override via PRICES env.
|
|
48
|
-
const PRICES: Record<string, [number, number]> = {
|
|
49
|
-
'groq/llama-3.1-8b-instant': [0.05, 0.08],
|
|
50
|
-
'google/gemini-2.5-flash-lite': [0.10, 0.40],
|
|
51
|
-
'openai/gpt-4o-mini': [0.15, 0.60],
|
|
52
|
-
'anthropic/claude-haiku-4-5-20251001': [1.0, 5.0],
|
|
53
|
-
'glm-5.2': [0.60, 2.20],
|
|
54
|
-
}
|
|
55
|
-
const priceOf = (m: string) => PRICES[m] ?? [0.5, 1.5]
|
|
56
|
-
|
|
57
|
-
interface Usage { in: number; out: number }
|
|
58
|
-
const zero = (): Usage => ({ in: 0, out: 0 })
|
|
59
|
-
const addU = (a: Usage, b: Usage) => { a.in += b.in; a.out += b.out }
|
|
60
|
-
const usd = (m: string, u: Usage) => { const [pi, po] = priceOf(m); return (u.in * pi + u.out * po) / 1e6 }
|
|
61
|
-
|
|
62
|
-
async function chat(model: string, messages: { role: string; content: string }[], temperature: number, usage: Usage): Promise<string> {
|
|
63
|
-
try {
|
|
64
|
-
const system = messages.find((message) => message.role === 'system')?.content
|
|
65
|
-
const result = await runBenchRouterTurn(
|
|
66
|
-
{
|
|
67
|
-
routerBaseUrl: ROUTER,
|
|
68
|
-
routerKey: ROUTER_KEY,
|
|
69
|
-
profile: {
|
|
70
|
-
name: 'david-goliath-worker',
|
|
71
|
-
harness: 'cli-base',
|
|
72
|
-
model: {
|
|
73
|
-
provider: 'tangle-router',
|
|
74
|
-
default: model,
|
|
75
|
-
metadata: { temperature, maxTokens: MAX_TOKENS },
|
|
76
|
-
},
|
|
77
|
-
...(system ? { prompt: { systemPrompt: system } } : {}),
|
|
78
|
-
},
|
|
79
|
-
timeoutMs: LLM_TIMEOUT,
|
|
80
|
-
},
|
|
81
|
-
{ messages: messages.filter((message) => message.role !== 'system') },
|
|
82
|
-
)
|
|
83
|
-
if (result.usage.tokensKnown === false) throw new Error('provider omitted token usage')
|
|
84
|
-
addU(usage, { in: result.usage.input, out: result.usage.output })
|
|
85
|
-
return result.finalText
|
|
86
|
-
} catch {
|
|
87
|
-
return ''
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const exec = (file: string, args: string[], o: object) => new Promise<{ code: number; stdout: string }>((res) => execFile(file, args, { ...o, maxBuffer: 8 * 1024 * 1024 }, (e, stdout) => res({ code: (e as { code?: number } | null)?.code ?? (e ? 1 : 0), stdout: String(stdout) })))
|
|
91
|
-
async function runPy(program: string): Promise<{ ok: boolean }> {
|
|
92
|
-
const d = mkdtempSync(join(tmpdir(), 'dg-'))
|
|
93
|
-
try { writeFileSync(join(d, 'p.py'), program); const r = await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: EXEC_TIMEOUT }); return { ok: r.code === 0 } } finally { rmSync(d, { recursive: true, force: true }) }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const SOLVE = 'You are an expert Python programmer. Output the COMPLETE function (signature + body + imports) in a single ```python block. No prose, no tests.'
|
|
97
|
-
async function genSolution(model: string, t: HumanEvalTask, temp: number, u: Usage): Promise<string> {
|
|
98
|
-
return extractCode(await chat(model, [{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp, u))
|
|
99
|
-
}
|
|
100
|
-
// David writes its OWN tests (never sees the hidden test). Parse assert lines.
|
|
101
|
-
async function genTests(model: string, t: HumanEvalTask, u: Usage): Promise<string[]> {
|
|
102
|
-
const reply = await chat(model, [
|
|
103
|
-
{ role: 'system', content: 'Write Python assert-based unit tests for the described function. Output ONLY a ```python block of standalone `assert <entry>(...) == ...` lines (at least a few, covering normal + edge cases). No function definition, no prose.' },
|
|
104
|
-
{ role: 'user', content: `Function to test (entry point: ${t.entryPoint}):\n\n\`\`\`python\n${t.prompt}\`\`\`` },
|
|
105
|
-
], 0.4, u)
|
|
106
|
-
const block = extractCode(reply) || reply
|
|
107
|
-
return block.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
|
|
108
|
-
}
|
|
109
|
-
// Hidden held-out judge — the truth. Never used for selection.
|
|
110
|
-
async function judge(t: HumanEvalTask, code: string): Promise<boolean> {
|
|
111
|
-
if (!code.trim()) return false
|
|
112
|
-
return (await runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`)).ok
|
|
113
|
-
}
|
|
114
|
-
// David's self-selection: score each candidate by how many of ITS OWN tests it passes.
|
|
115
|
-
async function scoreOnTests(code: string, tests: string[]): Promise<number> {
|
|
116
|
-
if (!code.trim() || tests.length === 0) return 0
|
|
117
|
-
let pass = 0
|
|
118
|
-
// one program per test keeps a crash on one test from voiding the rest
|
|
119
|
-
for (const a of tests) if ((await runPy(`${code}\n\n${a}\n`)).ok) pass++
|
|
120
|
-
return pass
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function davidArm(t: HumanEvalTask, u: Usage): Promise<string> {
|
|
124
|
-
const cands = (await Promise.all(Array.from({ length: N }, () => genSolution(DAVID, t, 0.7, u)))).filter((c) => c.trim())
|
|
125
|
-
if (cands.length === 0) return ''
|
|
126
|
-
const tests = await genTests(DAVID, t, u)
|
|
127
|
-
if (tests.length === 0) return cands[0]! // no verifier signal → first sample
|
|
128
|
-
const scored = await Promise.all(cands.map(async (c) => ({ c, s: await scoreOnTests(c, tests) })))
|
|
129
|
-
scored.sort((a, b) => b.s - a.s || b.c.length - a.c.length)
|
|
130
|
-
return scored[0]!.c
|
|
131
|
-
}
|
|
132
|
-
const goliathArm = (t: HumanEvalTask, u: Usage) => genSolution(GOLIATH, t, 0.2, u)
|
|
133
|
-
|
|
134
|
-
async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2, i: number) => Promise<R>): Promise<R[]> {
|
|
135
|
-
const o = new Array<R>(xs.length); let i = 0
|
|
136
|
-
await Promise.all(Array.from({ length: Math.min(n, xs.length) }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!, k) } }))
|
|
137
|
-
return o
|
|
138
|
-
}
|
|
139
|
-
function mcnemar(b: number, c: number): number { const n = b + c; if (n === 0) return 1; const k = Math.min(b, c); const lf = (x: number) => { let s = 0; for (let i = 2; i <= x; i++) s += Math.log(i); return s }; let tl = 0; for (let i = 0; i <= k; i++) tl += Math.exp(lf(n) - lf(i) - lf(n - i) - n * Math.log(2)); return Math.min(1, 2 * tl) }
|
|
140
|
-
|
|
141
|
-
async function main(): Promise<void> {
|
|
142
|
-
if (['1', 'true'].includes((process.env.SMOKE ?? '').toLowerCase())) { console.error('SMOKE ok: david-goliath loaded'); return }
|
|
143
|
-
const tasks = await loadHumanEval(NTASKS, 0)
|
|
144
|
-
console.error(`=== DAVID(${DAVID}, N=${N} sols + ${T} self-tests) vs GOLIATH(${GOLIATH}, 1 shot) · HumanEval n=${tasks.length} · reps=${REPS} ===`)
|
|
145
|
-
const dU = zero(), gU = zero()
|
|
146
|
-
const units = tasks.flatMap((task) => Array.from({ length: REPS }, () => task))
|
|
147
|
-
let done = 0
|
|
148
|
-
const res = await pool(units, CONC, async (task) => {
|
|
149
|
-
const safe = async (fn: () => Promise<string>) => { try { return await fn() } catch { return '' } }
|
|
150
|
-
const [dCode, gCode] = await Promise.all([safe(() => davidArm(task, dU)), safe(() => goliathArm(task, gU))])
|
|
151
|
-
const [d, g] = await Promise.all([judge(task, dCode), judge(task, gCode)])
|
|
152
|
-
if (++done % 20 === 0) console.error(` ${done}/${units.length} units`)
|
|
153
|
-
return { d, g }
|
|
154
|
-
})
|
|
155
|
-
const n = res.length, dPass = res.filter((r) => r.d).length, gPass = res.filter((r) => r.g).length
|
|
156
|
-
const b = res.filter((r) => r.d && !r.g).length, c = res.filter((r) => !r.d && r.g).length
|
|
157
|
-
const p = mcnemar(b, c)
|
|
158
|
-
const dCost = usd(DAVID, dU), gCost = usd(GOLIATH, gU)
|
|
159
|
-
console.log('\n=== RESULT (held-out HumanEval) ===')
|
|
160
|
-
console.log(` GOLIATH ${GOLIATH} solo : ${gPass}/${n} = ${(gPass / n * 100).toFixed(1)}% $${gCost.toFixed(4)}`)
|
|
161
|
-
console.log(` DAVID ${DAVID} + verify: ${dPass}/${n} = ${(dPass / n * 100).toFixed(1)}% $${dCost.toFixed(4)}`)
|
|
162
|
-
console.log(` accuracy: David ${dPass >= gPass ? '>=' : '<'} Goliath (${(dPass / n * 100).toFixed(1)} vs ${(gPass / n * 100).toFixed(1)}); paired McNemar David-only=${b} Goliath-only=${c} p=${p.toFixed(4)}`)
|
|
163
|
-
console.log(` cost: David is ${(gCost / Math.max(dCost, 1e-9)).toFixed(1)}x CHEAPER ($${dCost.toFixed(4)} vs $${gCost.toFixed(4)})`)
|
|
164
|
-
const paretoBeat = dPass >= gPass && dCost <= gCost
|
|
165
|
-
const sigBeat = dPass > gPass && p < 0.05
|
|
166
|
-
console.log(` VERDICT: ${sigBeat ? 'DAVID SIGNIFICANTLY BEATS GOLIATH' : paretoBeat ? 'DAVID PARETO-DOMINATES (>= accuracy, <= cost)' : dPass >= gPass ? 'David matches accuracy (check cost)' : 'Goliath wins accuracy'}`)
|
|
167
|
-
}
|
|
168
|
-
main().catch((e) => { console.error('MAIN:', e instanceof Error ? (e.stack ?? e.message) : e); process.exit(1) })
|
package/src/decoder-live.mts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LIVE validation of the per-harness tool-part decoders against a REAL sandbox box.
|
|
3
|
-
*
|
|
4
|
-
* Spin a real box for HARNESS, make the harness call tools, capture the actual session part shapes,
|
|
5
|
-
* and prove the harness's decoder extracts the tool calls (with error status where the harness carries
|
|
6
|
-
* it). No mock. Exits non-zero on a decode miss so it can gate CI. The box is ALWAYS deleted.
|
|
7
|
-
*
|
|
8
|
-
* Run: HARNESS=opencode dotenvx run -f ~/company/devops/secrets/agent-state.env -- \
|
|
9
|
-
* pnpm exec tsx bench/src/decoder-live.mts (also: claude-code, codex, kimi-code, …)
|
|
10
|
-
*/
|
|
11
|
-
import { Sandbox } from '@tangle-network/sandbox'
|
|
12
|
-
import { decodeToolPart } from '../../src/runtime/supervise/trace-source'
|
|
13
|
-
|
|
14
|
-
function must(k: string): string {
|
|
15
|
-
const v = process.env[k]
|
|
16
|
-
if (!v) throw new Error(`missing env ${k}`)
|
|
17
|
-
return v
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function* candidateParts(node: unknown, depth = 0): Generator<unknown> {
|
|
21
|
-
if (!node || typeof node !== 'object' || depth > 6) return
|
|
22
|
-
const o = node as Record<string, unknown>
|
|
23
|
-
yield o
|
|
24
|
-
if (o.part) yield o.part
|
|
25
|
-
if (Array.isArray(o.parts)) for (const p of o.parts) yield p
|
|
26
|
-
if (o.data) yield* candidateParts(o.data, depth + 1)
|
|
27
|
-
if (o.message) yield* candidateParts(o.message, depth + 1)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const HARNESS = process.env.HARNESS ?? 'opencode'
|
|
31
|
-
|
|
32
|
-
async function main(): Promise<number> {
|
|
33
|
-
const client = new Sandbox({
|
|
34
|
-
baseUrl: process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools',
|
|
35
|
-
apiKey: must('TANGLE_API_KEY'),
|
|
36
|
-
timeoutMs: 600_000,
|
|
37
|
-
} as never)
|
|
38
|
-
|
|
39
|
-
console.error(`[live] creating ${HARNESS} box (${process.env.WORKER_MODEL ?? 'deepseek-v4-flash'})…`)
|
|
40
|
-
const box = (await client.create({
|
|
41
|
-
backend: {
|
|
42
|
-
type: HARNESS,
|
|
43
|
-
model: {
|
|
44
|
-
provider: process.env.WORKER_PROVIDER ?? 'openai',
|
|
45
|
-
model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
|
|
46
|
-
baseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
|
|
47
|
-
},
|
|
48
|
-
profile: { name: 'decoder-live' },
|
|
49
|
-
},
|
|
50
|
-
} as never)) as unknown as Record<string, (...a: never[]) => unknown> & { id?: string }
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
console.error('[live] box', box.id, '— waiting for running…')
|
|
54
|
-
await box.waitFor('running' as never, { timeoutMs: 180_000 } as never)
|
|
55
|
-
|
|
56
|
-
// Force several distinct tool calls AND at least one tool error (to exercise error-status decode).
|
|
57
|
-
const prompt =
|
|
58
|
-
'Use your tools, one tool call at a time: (1) list the files in the current directory; ' +
|
|
59
|
-
'(2) run `echo hello-from-tool`; (3) run `false` (this command fails on purpose); ' +
|
|
60
|
-
'(4) create notes.txt containing "hi". A separate tool call for each. Then reply "done".'
|
|
61
|
-
|
|
62
|
-
const rawEvents: unknown[] = []
|
|
63
|
-
const ac = new AbortController()
|
|
64
|
-
const timer = setTimeout(() => ac.abort(), 240_000)
|
|
65
|
-
try {
|
|
66
|
-
for await (const ev of box.streamPrompt(prompt as never, {
|
|
67
|
-
signal: ac.signal,
|
|
68
|
-
} as never) as AsyncGenerator<unknown>) {
|
|
69
|
-
rawEvents.push(ev)
|
|
70
|
-
}
|
|
71
|
-
} finally {
|
|
72
|
-
clearTimeout(timer)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Vocabulary: every distinct part `type` the harness emitted (so we learn its real schema).
|
|
76
|
-
const types = new Map<string, number>()
|
|
77
|
-
for (const ev of rawEvents)
|
|
78
|
-
for (const part of candidateParts(ev)) {
|
|
79
|
-
const t = (part as Record<string, unknown>)?.type
|
|
80
|
-
if (typeof t === 'string') types.set(t, (types.get(t) ?? 0) + 1)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Decode tool calls via the HARNESS adapter + de-dup by callId.
|
|
84
|
-
const decoded: Array<{ toolName: string; status?: string; callId?: string }> = []
|
|
85
|
-
const seen = new Set<string>()
|
|
86
|
-
for (const ev of rawEvents)
|
|
87
|
-
for (const part of candidateParts(ev)) {
|
|
88
|
-
const step = decodeToolPart(part, HARNESS)
|
|
89
|
-
if (!step) continue
|
|
90
|
-
if (step.callId && seen.has(step.callId)) continue
|
|
91
|
-
if (step.callId) seen.add(step.callId)
|
|
92
|
-
decoded.push({ toolName: step.toolName, ...(step.status ? { status: step.status } : {}), ...(step.callId ? { callId: step.callId } : {}) })
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Surface any harness/model error events (so a no-tools run isn't mistaken for a decoder miss).
|
|
96
|
-
const errs = rawEvents.filter((e) => JSON.stringify(e).match(/"error"/i)).slice(0, 2)
|
|
97
|
-
if (errs.length) console.error(`[live] harness error events:`, errs.map((e) => JSON.stringify(e).slice(0, 300)))
|
|
98
|
-
|
|
99
|
-
console.error(`\n========== LIVE DECODER RESULT — harness=${HARNESS} ==========`)
|
|
100
|
-
console.error(`raw stream events: ${rawEvents.length}`)
|
|
101
|
-
console.error(`part-type vocabulary:`, JSON.stringify(Object.fromEntries(types)))
|
|
102
|
-
console.error(`decoded ${decoded.length} tool calls:`, JSON.stringify(decoded))
|
|
103
|
-
console.error(`with error status: ${decoded.filter((d) => d.status === 'error').length}`)
|
|
104
|
-
// Sample raw shapes the decoder did NOT match but that mention a tool (the schema to learn from).
|
|
105
|
-
console.error(`\n--- raw toolish parts NOT decoded (first 4) ---`)
|
|
106
|
-
let shown = 0
|
|
107
|
-
for (const ev of rawEvents) {
|
|
108
|
-
if (shown >= 4) break
|
|
109
|
-
for (const part of candidateParts(ev)) {
|
|
110
|
-
if (shown >= 4) break
|
|
111
|
-
const s = JSON.stringify(part)
|
|
112
|
-
if (s.match(/tool/i) && !decodeToolPart(part, HARNESS)) {
|
|
113
|
-
console.error(s.slice(0, 380))
|
|
114
|
-
shown++
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return decoded.length > 0 ? 0 : 2
|
|
119
|
-
} finally {
|
|
120
|
-
await (box.delete as (...a: never[]) => Promise<void>)().catch(() => {})
|
|
121
|
-
console.error('[live] box deleted.')
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
main()
|
|
126
|
-
.then((code) => {
|
|
127
|
-
console.error(code === 0 ? `\n✅ ${HARNESS}: decoder extracts real tool calls.` : `\n❌ ${HARNESS}: decoder extracted ZERO — see raw shapes above.`)
|
|
128
|
-
process.exit(code)
|
|
129
|
-
})
|
|
130
|
-
.catch((e) => {
|
|
131
|
-
console.error('[live] FAILED:', e)
|
|
132
|
-
process.exit(1)
|
|
133
|
-
})
|
package/src/diverse-gate.mjs
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// The beat-blind gate, as ONE command — the experiment that decides whether the
|
|
2
|
-
// diversification/selection surface (the one PR #145 defers to as "tracked separately")
|
|
3
|
-
// actually beats compute-matched best-of-N. Composes only LANDED pieces:
|
|
4
|
-
// 1. random@k corpus — k identical-directive shots/instance (the compute control)
|
|
5
|
-
// 2. diverse@k corpus — k DIFFERENT strategy lenses/instance (DIVERSE=1; the bet)
|
|
6
|
-
// 3. selector replay — selfConsistencySelect@k over each (corpus-replay --selector)
|
|
7
|
-
// 4. paired report — bootstrap CI + Benjamini-Hochberg over both (corpus-report)
|
|
8
|
-
//
|
|
9
|
-
// The decomposition it yields:
|
|
10
|
-
// random@k = more-compute, no picking (control)
|
|
11
|
-
// selector@k (homog) = picking over IDENTICAL attempts (#143: −8.2pp on the committed corpus)
|
|
12
|
-
// diverse-selector@k = picking over DIVERSE attempts (THE bet: does approach-diversity
|
|
13
|
-
// give self-consistency the signal identical attempts don't?)
|
|
14
|
-
// Beat-blind iff diverse-selector@k > random@k at significant n.
|
|
15
|
-
//
|
|
16
|
-
// node diverse-gate.mjs run it (generates corpora — a real worker run)
|
|
17
|
-
// node diverse-gate.mjs --dry print the plan only (no run; safe while another
|
|
18
|
-
// sandbox run is live — zero router/sandbox contention)
|
|
19
|
-
//
|
|
20
|
-
// Knobs (env): BENCH (default hotpotqa) · N (default 30) · K (default 4) ·
|
|
21
|
-
// RESEARCH=1 (local opencode, default) | SANDBOX=1 (prod sandbox web worker) · MODELS ·
|
|
22
|
-
// DIVERSE_BASE (compose #145's GEPA-learned directive as the lens base — follow-on).
|
|
23
|
-
|
|
24
|
-
import { spawn } from 'node:child_process'
|
|
25
|
-
|
|
26
|
-
const DRY = process.argv.includes('--dry')
|
|
27
|
-
const BENCH = process.env.BENCH ?? 'hotpotqa'
|
|
28
|
-
const N = process.env.N ?? '30'
|
|
29
|
-
const K = process.env.K ?? '4'
|
|
30
|
-
const RANDOM_CORPUS = process.env.RANDOM_CORPUS ?? '/tmp/dg-random.jsonl'
|
|
31
|
-
const DIVERSE_CORPUS = process.env.DIVERSE_CORPUS ?? '/tmp/dg-diverse.jsonl'
|
|
32
|
-
// Worker-mode env passes through to batch-oracle unchanged (RESEARCH=1 / SANDBOX=1 / MODELS / TANGLE_API_KEY).
|
|
33
|
-
const passEnv = { ...process.env, BENCH, N, K }
|
|
34
|
-
|
|
35
|
-
const steps = [
|
|
36
|
-
{
|
|
37
|
-
label: 'random@k corpus (control — identical directive)',
|
|
38
|
-
cmd: 'npx',
|
|
39
|
-
args: ['tsx', 'src/run.ts', 'batch-oracle', N],
|
|
40
|
-
env: { ...passEnv, CORPUS: RANDOM_CORPUS },
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
label: 'diverse@k corpus (the bet — k distinct strategy lenses)',
|
|
44
|
-
cmd: 'npx',
|
|
45
|
-
args: ['tsx', 'src/run.ts', 'batch-oracle', N],
|
|
46
|
-
env: { ...passEnv, CORPUS: DIVERSE_CORPUS, DIVERSE: '1' },
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: 'selector@k over the CONTROL corpus (homogeneous)',
|
|
50
|
-
cmd: 'npx',
|
|
51
|
-
args: ['tsx', 'src/corpus-replay.mts', RANDOM_CORPUS, '--selector'],
|
|
52
|
-
env: passEnv,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
label: 'selector@k over the DIVERSE corpus (the beat-blind number)',
|
|
56
|
-
cmd: 'npx',
|
|
57
|
-
// The diverse corpus records carry condition="diverse@4"; corpus-replay's
|
|
58
|
-
// selector filter defaults to "random", so match the diverse condition here.
|
|
59
|
-
args: ['tsx', 'src/corpus-replay.mts', DIVERSE_CORPUS, '--selector', '--condition=diverse'],
|
|
60
|
-
env: passEnv,
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
label: 'paired bootstrap CI + Benjamini-Hochberg over both corpora',
|
|
64
|
-
cmd: 'npx',
|
|
65
|
-
args: ['tsx', 'src/corpus-report.mts', RANDOM_CORPUS, DIVERSE_CORPUS],
|
|
66
|
-
env: passEnv,
|
|
67
|
-
},
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
const shellPreview = (s) => {
|
|
71
|
-
const envStr = Object.entries(s.env)
|
|
72
|
-
.filter(([k]) => ['BENCH', 'N', 'K', 'CORPUS', 'DIVERSE', 'RESEARCH', 'SANDBOX', 'MODELS'].includes(k))
|
|
73
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
74
|
-
.join(' ')
|
|
75
|
-
return ` ${envStr} ${s.cmd} ${s.args.join(' ')}`.replace(/\s+/g, ' ')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function runStep(s) {
|
|
79
|
-
return new Promise((resolve, reject) => {
|
|
80
|
-
console.log(`\n▶ ${s.label}`)
|
|
81
|
-
const child = spawn(s.cmd, s.args, { cwd: process.cwd(), env: s.env, stdio: 'inherit' })
|
|
82
|
-
child.on('error', reject)
|
|
83
|
-
// Fail loud: a non-zero step aborts the gate (no silent partial result).
|
|
84
|
-
child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`${s.label} exited ${code}`))))
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function main() {
|
|
89
|
-
console.log(`=== beat-blind gate · BENCH=${BENCH} N=${N} K=${K} ${DRY ? '(DRY — plan only)' : ''} ===`)
|
|
90
|
-
if (DRY) {
|
|
91
|
-
console.log('plan (no run — zero sandbox/router contention):')
|
|
92
|
-
for (const s of steps) console.log(shellPreview(s))
|
|
93
|
-
console.log(
|
|
94
|
-
'\nbeat-blind iff diverse-selector@k > random@k at significant n.' +
|
|
95
|
-
'\nDIVERSE_BASE=<file> composes #145\'s GEPA-learned directive as the lens base (follow-on).',
|
|
96
|
-
)
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
for (const s of steps) await runStep(s)
|
|
100
|
-
console.log(
|
|
101
|
-
'\n=== read the gate ===\n' +
|
|
102
|
-
` random@k (control) — from ${RANDOM_CORPUS} replay\n` +
|
|
103
|
-
` selector@k (homogeneous) — same corpus, the pick (#143: −8.2pp on committed finsearch)\n` +
|
|
104
|
-
` diverse-selector@k — ${DIVERSE_CORPUS} replay: THE bet\n` +
|
|
105
|
-
' beat-blind iff diverse-selector@k > random@k, significant per the BH report above.',
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
main().catch((err) => {
|
|
110
|
-
console.error(`diverse-gate: ${err instanceof Error ? err.message : String(err)}`)
|
|
111
|
-
process.exit(1)
|
|
112
|
-
})
|
package/src/hev-eval.mts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal HumanEval evaluator: given an INSTRUCTION (env) + a fixed task set, run the
|
|
3
|
-
* worker model on each task and print the pass rate + the per-task result. Used to
|
|
4
|
-
* measure a baseline instruction vs a proposer-supplied instruction on the SAME
|
|
5
|
-
* held-out set (the proposer proposes; this grades — kept separate for honesty).
|
|
6
|
-
*
|
|
7
|
-
* INSTRUCTION="..." IDS=HumanEval/55,... WORKER_MODEL=... ROUTER_BASE=... TANGLE_API_KEY=... \
|
|
8
|
-
* HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz tsx src/hev-eval.mts
|
|
9
|
-
*/
|
|
10
|
-
import { readFileSync } from 'node:fs'
|
|
11
|
-
import { extractCode, loadHumanEval, runChecker, type HumanEvalTask } from './benchmarks/humaneval'
|
|
12
|
-
import { runBenchRouterTurn } from './router-turn'
|
|
13
|
-
|
|
14
|
-
const SEED_INSTRUCTION =
|
|
15
|
-
'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.'
|
|
16
|
-
|
|
17
|
-
async function complete(
|
|
18
|
-
base: string,
|
|
19
|
-
key: string,
|
|
20
|
-
model: string,
|
|
21
|
-
instruction: string,
|
|
22
|
-
prompt: string,
|
|
23
|
-
maxTokens: number,
|
|
24
|
-
): Promise<string> {
|
|
25
|
-
try {
|
|
26
|
-
const turn = await runBenchRouterTurn(
|
|
27
|
-
{
|
|
28
|
-
routerBaseUrl: base,
|
|
29
|
-
routerKey: key,
|
|
30
|
-
profile: {
|
|
31
|
-
name: 'humaneval-worker',
|
|
32
|
-
harness: 'cli-base',
|
|
33
|
-
model: {
|
|
34
|
-
provider: 'tangle-router',
|
|
35
|
-
default: model,
|
|
36
|
-
metadata: { temperature: 0.2, maxTokens },
|
|
37
|
-
},
|
|
38
|
-
prompt: { systemPrompt: instruction },
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
prompt,
|
|
42
|
-
)
|
|
43
|
-
return turn.finalText
|
|
44
|
-
} catch {
|
|
45
|
-
return ''
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function main(): Promise<void> {
|
|
50
|
-
const key = process.env.TANGLE_API_KEY
|
|
51
|
-
if (!key) throw new Error('TANGLE_API_KEY required')
|
|
52
|
-
const apiKey: string = key
|
|
53
|
-
const base = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
|
|
54
|
-
const model = process.env.WORKER_MODEL ?? 'meta-llama/Meta-Llama-3-8B-Instruct-Lite'
|
|
55
|
-
const instruction = process.env.INSTRUCTION_FILE
|
|
56
|
-
? readFileSync(process.env.INSTRUCTION_FILE, 'utf8')
|
|
57
|
-
: (process.env.INSTRUCTION ?? SEED_INSTRUCTION)
|
|
58
|
-
const maxTokens = Number(process.env.MAX_TOKENS ?? 2500)
|
|
59
|
-
const conc = Number(process.env.CONC ?? 6)
|
|
60
|
-
const offset = Number(process.env.OFFSET ?? 55)
|
|
61
|
-
const n = Number(process.env.N ?? 40)
|
|
62
|
-
const idsEnv = (process.env.IDS ?? '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
63
|
-
|
|
64
|
-
const all = await loadHumanEval(164, 0)
|
|
65
|
-
const byId = new Map(all.map((t) => [t.taskId, t]))
|
|
66
|
-
const tasks: HumanEvalTask[] = idsEnv.length
|
|
67
|
-
? idsEnv.map((id) => byId.get(id)).filter((t): t is HumanEvalTask => !!t)
|
|
68
|
-
: all.slice(offset, offset + n)
|
|
69
|
-
|
|
70
|
-
console.log(`eval model=${model} n=${tasks.length} instr_len=${instruction.length}`)
|
|
71
|
-
let pass = 0
|
|
72
|
-
const fails: string[] = []
|
|
73
|
-
// simple concurrency pool
|
|
74
|
-
let i = 0
|
|
75
|
-
async function worker(): Promise<void> {
|
|
76
|
-
while (i < tasks.length) {
|
|
77
|
-
const t = tasks[i]
|
|
78
|
-
i += 1
|
|
79
|
-
if (!t) continue
|
|
80
|
-
const reply = await complete(
|
|
81
|
-
base,
|
|
82
|
-
apiKey,
|
|
83
|
-
model,
|
|
84
|
-
instruction,
|
|
85
|
-
`\`\`\`python\n${t.prompt}\`\`\``,
|
|
86
|
-
maxTokens,
|
|
87
|
-
)
|
|
88
|
-
const { pass: p } = await runChecker(t, extractCode(reply))
|
|
89
|
-
if (p === 1) pass += 1
|
|
90
|
-
else fails.push(t.taskId)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
await Promise.all(Array.from({ length: conc }, () => worker()))
|
|
94
|
-
console.log(`PASS ${pass}/${tasks.length} = ${((100 * pass) / tasks.length).toFixed(1)}%`)
|
|
95
|
-
console.log(`FAILED: ${fails.sort().join(', ')}`)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
main().catch((e) => {
|
|
99
|
-
console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
|
|
100
|
-
process.exit(1)
|
|
101
|
-
})
|