@tangle-network/agent-bench 0.1.0

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.
Files changed (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * FinSearchComp adapter (randomtutu/FinSearchComp). Agentic financial search +
3
+ * reasoning. Worker artifact = the agent's free-form final answer text (zh/en).
4
+ *
5
+ * Each record is SELF-CONTAINED and carries its OWN judge: a per-record
6
+ * `judge_system_prompt` + a `judge_prompt_template` with {prompt},
7
+ * {response_reference} (gold answer WITH embedded tolerance/scoring-criteria),
8
+ * and {response} (the student answer) placeholders. We faithfully REPLICATE the
9
+ * benchmark's judge — fill the template, run it under the record's system prompt
10
+ * via the pinned router (temperature 0), and parse the JSON the judge emits. The
11
+ * judge returns an `answer_score` field; 1/positive = resolved, else not.
12
+ * This matches the FinSearchComp leaderboard (Grok-4-web 68.9%).
13
+ *
14
+ * There is NO deterministic Tier-1 path here: the gold answer is prose with
15
+ * region-specific units, embedded tolerances ("允许1%的误差"), and multi-point
16
+ * scoring criteria. The benchmark intends its own LLM judge to be the arbiter,
17
+ * so the judge IS the score — fail loud on unparseable judge output (never
18
+ * default to resolved).
19
+ *
20
+ * SCOPE: T2 (Simple_Historical_Lookup) + T3 (Complex_Historical_Investigation)
21
+ * are fully self-contained and supported. T1 (Time_Sensitive_Data_Fetching)
22
+ * needs a live market snapshot / akshare ground truth to fill the judge's
23
+ * {ground_truth} slot — it is SKIPPED in loadTasks and flagged in preflight.
24
+ * See {@link T1Seam}.
25
+ *
26
+ * Requires for a live run: network access to the GitHub-hosted dataset JSON and
27
+ * a TANGLE_API_KEY for the judge. For offline/CI verification, loadTasks falls back
28
+ * to the committed fixtures (bench/fixtures/finsearchcomp.json) with an explicit
29
+ * console.warn — never a silent fallback.
30
+ */
31
+
32
+ import { readFile } from 'node:fs/promises'
33
+ import { join } from 'node:path'
34
+ import { fileURLToPath } from 'node:url'
35
+ import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
36
+
37
+ const BENCH_ROOT = fileURLToPath(new URL('../..', import.meta.url))
38
+ const FIXTURES = join(BENCH_ROOT, 'fixtures', 'finsearchcomp.json')
39
+
40
+ const DATASET_URL =
41
+ 'https://raw.githubusercontent.com/randomtutu/FinSearchComp/main/data/finsearchcomp_data.json'
42
+
43
+ /** Task-type prefix on prompt_id, e.g. "(T2)Simple_Historical_Lookup_001". */
44
+ type TaskType = 'T1' | 'T2' | 'T3'
45
+ /** Only these are self-contained (gold + own judge, no external ground truth). */
46
+ const SUPPORTED_TYPES: ReadonlySet<TaskType> = new Set<TaskType>(['T2', 'T3'])
47
+
48
+ /**
49
+ * Worker contract appended to every task prompt. The judge is paraphrase- and
50
+ * keyword-aware ("答案为", "the answer is"), so we do not impose a sentinel; we
51
+ * pass the whole artifact through as {response}. We DO ask the worker to lead
52
+ * with its final value so a truncated artifact still carries the answer.
53
+ */
54
+ const WORKER_CONTRACT = [
55
+ '',
56
+ 'Research this financial question using live web/market sources and answer it.',
57
+ 'State your final answer explicitly, with the exact units and precision the question requests.',
58
+ 'Respect any tolerance the question implies; show the value you settled on, not just intermediate work.',
59
+ ].join('\n')
60
+
61
+ /**
62
+ * Typed seam for T1 (Time_Sensitive_Data_Fetching). T1 records score against a
63
+ * live market snapshot the judge consumes via a {ground_truth} template slot;
64
+ * the adapter does NOT fabricate that snapshot. Wiring T1 means supplying a
65
+ * resolver that fetches the akshare/market value for the record at judge time
66
+ * and filling {ground_truth}. Until then T1 is excluded from loadTasks.
67
+ */
68
+ export interface T1Seam {
69
+ promptId: string
70
+ /** Resolver that returns the real-time ground-truth block for the {ground_truth} slot. */
71
+ resolveGroundTruth(promptId: string): Promise<string>
72
+ }
73
+
74
+ interface FinSearchRecord {
75
+ prompt_id: string
76
+ prompt: string
77
+ response_reference: string
78
+ judge_prompt_template: string
79
+ judge_system_prompt: string
80
+ label: string
81
+ }
82
+
83
+ interface FinSearchMeta {
84
+ promptId: string
85
+ label: string
86
+ taskType: TaskType
87
+ region: string
88
+ responseReference: string
89
+ judgePromptTemplate: string
90
+ judgeSystemPrompt: string
91
+ rawPrompt: string
92
+ }
93
+
94
+ /** "(T2)Simple_Historical_Lookup_001" → "T2"; throws on an unknown/missing prefix. */
95
+ function parseTaskType(promptId: string): TaskType {
96
+ const m = promptId.match(/^\((T[123])\)/)
97
+ if (!m) throw new Error(`FinSearchComp prompt_id has no (T1|T2|T3) prefix: ${JSON.stringify(promptId)}`)
98
+ return m[1] as TaskType
99
+ }
100
+
101
+ /** "Simple_Historical_Lookup(Greater China)" → "Greater China"; '' when absent. */
102
+ function parseRegion(label: string): string {
103
+ const m = label.match(/\(([^()]+)\)\s*$/)
104
+ return m ? m[1].trim() : ''
105
+ }
106
+
107
+ function recordToTask(rec: FinSearchRecord): BenchTask {
108
+ const taskType = parseTaskType(rec.prompt_id)
109
+ const meta: FinSearchMeta = {
110
+ promptId: rec.prompt_id,
111
+ label: rec.label,
112
+ taskType,
113
+ region: parseRegion(rec.label),
114
+ responseReference: rec.response_reference,
115
+ judgePromptTemplate: rec.judge_prompt_template,
116
+ judgeSystemPrompt: rec.judge_system_prompt,
117
+ rawPrompt: rec.prompt,
118
+ }
119
+ return {
120
+ id: rec.prompt_id,
121
+ split: taskType,
122
+ prompt: rec.prompt + WORKER_CONTRACT,
123
+ metadata: meta as unknown as Record<string, unknown>,
124
+ }
125
+ }
126
+
127
+ function readMeta(task: BenchTask): FinSearchMeta {
128
+ const md = task.metadata
129
+ if (
130
+ !md ||
131
+ typeof md.responseReference !== 'string' ||
132
+ typeof md.judgePromptTemplate !== 'string' ||
133
+ typeof md.judgeSystemPrompt !== 'string' ||
134
+ typeof md.rawPrompt !== 'string'
135
+ ) {
136
+ throw new Error(`FinSearchComp task ${task.id} missing judge metadata — loadTasks did not populate it`)
137
+ }
138
+ return md as unknown as FinSearchMeta
139
+ }
140
+
141
+ /** Fill the per-record judge_prompt_template. {ground_truth} is T1-only and unused for T2/T3. */
142
+ function fillJudgePrompt(meta: FinSearchMeta, response: string): string {
143
+ return meta.judgePromptTemplate
144
+ .replaceAll('{prompt}', meta.rawPrompt)
145
+ .replaceAll('{response_reference}', meta.responseReference)
146
+ .replaceAll('{response}', response)
147
+ }
148
+
149
+ interface JudgeRouter {
150
+ baseUrl: string
151
+ key: string
152
+ model: string
153
+ }
154
+
155
+ function judgeRouter(): JudgeRouter {
156
+ const key = process.env.TANGLE_API_KEY
157
+ if (!key) throw new Error('TANGLE_API_KEY is required for the FinSearchComp per-record LLM judge')
158
+ const model = process.env.JUDGE_MODEL ?? 'deepseek-v4-flash'
159
+ const baseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
160
+ return { baseUrl, key, model }
161
+ }
162
+
163
+ /**
164
+ * The judge emits a JSON object with an `answer_score`. In this dataset it is a
165
+ * flat integer ({"answer_score": 1}); we also accept the nested-array form
166
+ * ([[1]] → read [0][0]) the upstream scorer can produce. 1/positive = resolved.
167
+ * A "null" sentinel (judge could not obtain ground truth) is NOT a pass.
168
+ */
169
+ function readAnswerScore(value: unknown): { resolved: boolean; score: number; raw: unknown } {
170
+ let v: unknown = value
171
+ // unwrap nested-array form: [[s]] / [s]
172
+ while (Array.isArray(v)) v = v[0]
173
+ if (v === null || v === undefined || v === 'null') {
174
+ return { resolved: false, score: 0, raw: value }
175
+ }
176
+ const n = typeof v === 'number' ? v : typeof v === 'string' ? Number(v) : NaN
177
+ if (!Number.isFinite(n)) {
178
+ throw new Error(`FinSearchComp judge answer_score not numeric: ${JSON.stringify(value)}`)
179
+ }
180
+ // Their scale: 1 = correct, 0 = wrong. Treat any positive as resolved; clamp to 0..1.
181
+ const resolved = n >= 1
182
+ const score = n <= 0 ? 0 : n >= 1 ? 1 : n
183
+ return { resolved, score, raw: value }
184
+ }
185
+
186
+ /** Extract the first JSON object carrying `answer_score` from judge output; throw if none. */
187
+ function parseJudgeOutput(content: string): { resolved: boolean; score: number; raw: unknown } {
188
+ // Prefer a fenced block, then fall back to scanning for a {...} with answer_score.
189
+ const candidates: string[] = []
190
+ for (const m of content.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)) candidates.push(m[1].trim())
191
+ for (const m of content.matchAll(/\{[^{}]*"answer_score"[\s\S]*?\}/g)) candidates.push(m[0])
192
+ candidates.push(content.trim())
193
+ for (const c of candidates) {
194
+ let parsed: { answer_score?: unknown }
195
+ try {
196
+ parsed = JSON.parse(c) as { answer_score?: unknown }
197
+ } catch {
198
+ continue
199
+ }
200
+ if ('answer_score' in parsed) return readAnswerScore(parsed.answer_score)
201
+ }
202
+ throw new Error(
203
+ `FinSearchComp judge produced no parseable {"answer_score": …}: ${content.slice(0, 400)}`,
204
+ )
205
+ }
206
+
207
+ /** Run the record's own judge via the router. Fail loud on transport/parse errors. */
208
+ async function runRecordJudge(meta: FinSearchMeta, response: string, router: JudgeRouter): Promise<BenchScore> {
209
+ const res = await fetch(`${router.baseUrl}/chat/completions`, {
210
+ method: 'POST',
211
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
212
+ body: JSON.stringify({
213
+ model: router.model,
214
+ temperature: 0,
215
+ messages: [
216
+ { role: 'system', content: meta.judgeSystemPrompt },
217
+ { role: 'user', content: fillJudgePrompt(meta, response) },
218
+ ],
219
+ }),
220
+ })
221
+ if (!res.ok) {
222
+ throw new Error(`FinSearchComp judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
223
+ }
224
+ const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
225
+ const content = body.choices?.[0]?.message?.content
226
+ if (typeof content !== 'string') {
227
+ throw new Error(`FinSearchComp judge returned no message content: ${JSON.stringify(body).slice(0, 300)}`)
228
+ }
229
+ const { resolved, score, raw } = parseJudgeOutput(content)
230
+ return {
231
+ resolved,
232
+ score,
233
+ detail: JSON.stringify({
234
+ taskType: meta.taskType,
235
+ region: meta.region,
236
+ answerScore: raw,
237
+ judgeModel: router.model,
238
+ }),
239
+ }
240
+ }
241
+
242
+ function selectRecords(records: FinSearchRecord[], opts: LoadOptions): BenchTask[] {
243
+ let tasks = records
244
+ .filter((r) => SUPPORTED_TYPES.has(parseTaskType(r.prompt_id)))
245
+ .map(recordToTask)
246
+ if (opts.split) tasks = tasks.filter((t) => t.split === opts.split)
247
+ if (opts.ids) {
248
+ const want = new Set(opts.ids)
249
+ tasks = tasks.filter((t) => want.has(t.id))
250
+ } else if (opts.limit !== undefined) {
251
+ tasks = balanceByType(tasks, opts.limit)
252
+ }
253
+ return tasks
254
+ }
255
+
256
+ /** Round-robin across task types (T2/T3) so a `limit` pulls a balanced mix
257
+ * rather than slicing the type-ordered dataset's first N (which yields all
258
+ * T2). Deterministic — preserves dataset order within each type. */
259
+ function balanceByType(tasks: BenchTask[], limit: number): BenchTask[] {
260
+ const queues = new Map<TaskType, BenchTask[]>()
261
+ for (const t of tasks) {
262
+ const k = parseTaskType(t.id)
263
+ const q = queues.get(k)
264
+ if (q) q.push(t)
265
+ else queues.set(k, [t])
266
+ }
267
+ const lanes = [...queues.values()]
268
+ const out: BenchTask[] = []
269
+ for (let i = 0; out.length < limit; i++) {
270
+ const before = out.length
271
+ for (const lane of lanes) {
272
+ if (i < lane.length) out.push(lane[i])
273
+ if (out.length >= limit) break
274
+ }
275
+ if (out.length === before) break // all lanes exhausted
276
+ }
277
+ return out
278
+ }
279
+
280
+ async function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {
281
+ const records = JSON.parse(await readFile(FIXTURES, 'utf8')) as FinSearchRecord[]
282
+ return selectRecords(records, opts)
283
+ }
284
+
285
+ async function fetchDataset(): Promise<FinSearchRecord[]> {
286
+ const res = await fetch(DATASET_URL)
287
+ if (!res.ok) throw new Error(`FinSearchComp dataset HTTP ${res.status} fetching ${DATASET_URL}`)
288
+ const data = (await res.json()) as FinSearchRecord[]
289
+ if (!Array.isArray(data) || data.length === 0) {
290
+ throw new Error(`FinSearchComp dataset empty or not an array: ${DATASET_URL}`)
291
+ }
292
+ return data
293
+ }
294
+
295
+ export function createFinsearchcompAdapter(): BenchmarkAdapter {
296
+ const fixturesMode = process.env.FINSEARCHCOMP_FIXTURES === '1'
297
+
298
+ return {
299
+ name: 'finsearchcomp',
300
+
301
+ async preflight() {
302
+ // The per-record LLM judge is the arbiter in both modes — its router
303
+ // config must exist or no score can be produced.
304
+ judgeRouter()
305
+ console.warn(
306
+ '[finsearchcomp] T1 (Time_Sensitive_Data_Fetching) is NOT scored: it needs a live ' +
307
+ 'market snapshot / akshare ground truth for the judge {ground_truth} slot. ' +
308
+ 'loadTasks supports T2 + T3 only; wire T1Seam to add T1.',
309
+ )
310
+ if (fixturesMode) {
311
+ await readFile(FIXTURES, 'utf8').catch((err) => {
312
+ throw new Error(
313
+ `FINSEARCHCOMP_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`,
314
+ )
315
+ })
316
+ return
317
+ }
318
+ const res = await fetch(DATASET_URL, { method: 'HEAD' }).catch((err) => {
319
+ throw new Error(
320
+ `finsearchcomp preflight failed reaching ${DATASET_URL}: ${err instanceof Error ? err.message : err}\n` +
321
+ `Fix: ensure network access to raw.githubusercontent.com, or set FINSEARCHCOMP_FIXTURES=1 ` +
322
+ `to run against the committed fixtures offline.`,
323
+ )
324
+ })
325
+ if (!res.ok) {
326
+ throw new Error(
327
+ `finsearchcomp preflight: dataset HEAD ${res.status} for ${DATASET_URL}. ` +
328
+ `Set FINSEARCHCOMP_FIXTURES=1 to run against the committed fixtures offline.`,
329
+ )
330
+ }
331
+ },
332
+
333
+ async loadTasks(opts: LoadOptions = {}) {
334
+ if (fixturesMode) return loadFixtures(opts)
335
+ let records: FinSearchRecord[]
336
+ try {
337
+ records = await fetchDataset()
338
+ } catch (err) {
339
+ console.warn(
340
+ `[finsearchcomp] live dataset fetch failed (${err instanceof Error ? err.message : err}); ` +
341
+ `falling back to committed fixtures at ${FIXTURES}`,
342
+ )
343
+ return loadFixtures(opts)
344
+ }
345
+ return selectRecords(records, opts)
346
+ },
347
+
348
+ async goldArtifact(task: BenchTask) {
349
+ // Gold artifact = the record's response_reference fed back as the student
350
+ // answer, so verify-judge proves gold→resolved through the SAME per-record
351
+ // judge the real artifact takes. The reference may carry tolerance/criteria
352
+ // prose; the judge is built to read the value out of it.
353
+ const meta = readMeta(task)
354
+ return meta.responseReference
355
+ },
356
+
357
+ async judge(task: BenchTask, artifact: string): Promise<BenchScore> {
358
+ const meta = readMeta(task)
359
+ const response = artifact.trim()
360
+ if (response.length === 0) {
361
+ // Fail-closed — the benchmark's own judges score an empty answer 0.
362
+ return {
363
+ resolved: false,
364
+ score: 0,
365
+ detail: JSON.stringify({ taskType: meta.taskType, region: meta.region, reason: 'empty answer' }),
366
+ }
367
+ }
368
+ return runRecordJudge(meta, response, judgeRouter())
369
+ },
370
+ }
371
+ }