@tangle-network/agent-bench 0.4.0 → 0.4.3

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 (200) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/adapters.d.ts +4 -11
  3. package/dist/adapters.js +78 -41
  4. package/dist/adapters.js.map +1 -1
  5. package/dist/benchmarks/_harness.d.ts +51 -66
  6. package/dist/benchmarks/_harness.js +329 -31
  7. package/dist/benchmarks/_harness.js.map +1 -1
  8. package/dist/benchmarks/aec-bench.d.ts +4 -25
  9. package/dist/benchmarks/aec-bench.js +242 -7
  10. package/dist/benchmarks/aec-bench.js.map +1 -1
  11. package/dist/benchmarks/agentbench.d.ts +5 -13
  12. package/dist/benchmarks/agentbench.js +114 -9
  13. package/dist/benchmarks/agentbench.js.map +1 -1
  14. package/dist/benchmarks/appworld.d.ts +9 -29
  15. package/dist/benchmarks/appworld.js +317 -12
  16. package/dist/benchmarks/appworld.js.map +1 -1
  17. package/dist/benchmarks/bfcl.d.ts +5 -15
  18. package/dist/benchmarks/bfcl.js +264 -9
  19. package/dist/benchmarks/bfcl.js.map +1 -1
  20. package/dist/benchmarks/cad-design.d.ts +16 -41
  21. package/dist/benchmarks/cad-design.js +512 -6
  22. package/dist/benchmarks/cad-design.js.map +1 -1
  23. package/dist/benchmarks/cadbench.d.ts +4 -17
  24. package/dist/benchmarks/cadbench.js +2 -8
  25. package/dist/benchmarks/cadgenbench.d.ts +4 -20
  26. package/dist/benchmarks/cadgenbench.js +2 -8
  27. package/dist/benchmarks/commit0.d.ts +5 -27
  28. package/dist/benchmarks/commit0.js +187 -9
  29. package/dist/benchmarks/commit0.js.map +1 -1
  30. package/dist/benchmarks/crag.d.ts +4 -12
  31. package/dist/benchmarks/crag.js +110 -8
  32. package/dist/benchmarks/crag.js.map +1 -1
  33. package/dist/benchmarks/dabstep.d.ts +5 -15
  34. package/dist/benchmarks/dabstep.js +177 -9
  35. package/dist/benchmarks/dabstep.js.map +1 -1
  36. package/dist/benchmarks/enterpriseops-gym.d.ts +5 -34
  37. package/dist/benchmarks/enterpriseops-gym.js +236 -9
  38. package/dist/benchmarks/enterpriseops-gym.js.map +1 -1
  39. package/dist/benchmarks/finresearchbench.d.ts +4 -13
  40. package/dist/benchmarks/finresearchbench.js +218 -7
  41. package/dist/benchmarks/finresearchbench.js.map +1 -1
  42. package/dist/benchmarks/finsearchcomp.d.ts +8 -39
  43. package/dist/benchmarks/finsearchcomp.js +267 -6
  44. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  45. package/dist/benchmarks/frames.d.ts +15 -37
  46. package/dist/benchmarks/frames.js +408 -11
  47. package/dist/benchmarks/frames.js.map +1 -1
  48. package/dist/benchmarks/hotpotqa.d.ts +4 -24
  49. package/dist/benchmarks/hotpotqa.js +250 -14
  50. package/dist/benchmarks/hotpotqa.js.map +1 -1
  51. package/dist/benchmarks/humaneval.d.ts +19 -37
  52. package/dist/benchmarks/humaneval.js +279 -16
  53. package/dist/benchmarks/humaneval.js.map +1 -1
  54. package/dist/benchmarks/mind2web.d.ts +7 -34
  55. package/dist/benchmarks/mind2web.js +257 -8
  56. package/dist/benchmarks/mind2web.js.map +1 -1
  57. package/dist/benchmarks/nomiracl.d.ts +4 -13
  58. package/dist/benchmarks/nomiracl.js +146 -8
  59. package/dist/benchmarks/nomiracl.js.map +1 -1
  60. package/dist/benchmarks/open-rag-bench.d.ts +4 -12
  61. package/dist/benchmarks/open-rag-bench.js +110 -8
  62. package/dist/benchmarks/open-rag-bench.js.map +1 -1
  63. package/dist/benchmarks/programbench.d.ts +5 -29
  64. package/dist/benchmarks/programbench.js +161 -9
  65. package/dist/benchmarks/programbench.js.map +1 -1
  66. package/dist/benchmarks/rag-shared.d.ts +21 -20
  67. package/dist/benchmarks/rag-shared.js +245 -38
  68. package/dist/benchmarks/rag-shared.js.map +1 -1
  69. package/dist/benchmarks/ragbench.d.ts +4 -14
  70. package/dist/benchmarks/ragbench.js +127 -8
  71. package/dist/benchmarks/ragbench.js.map +1 -1
  72. package/dist/benchmarks/simpleqa.d.ts +17 -44
  73. package/dist/benchmarks/simpleqa.js +293 -10
  74. package/dist/benchmarks/simpleqa.js.map +1 -1
  75. package/dist/benchmarks/swe-bench.d.ts +23 -36
  76. package/dist/benchmarks/swe-bench.js +234 -13
  77. package/dist/benchmarks/swe-bench.js.map +1 -1
  78. package/dist/benchmarks/t2-ragbench.d.ts +4 -12
  79. package/dist/benchmarks/t2-ragbench.js +118 -8
  80. package/dist/benchmarks/t2-ragbench.js.map +1 -1
  81. package/dist/benchmarks/tau-bench-shared.d.ts +14 -22
  82. package/dist/benchmarks/tau-bench-shared.js +169 -9
  83. package/dist/benchmarks/tau-bench-shared.js.map +1 -1
  84. package/dist/benchmarks/tau2-bench.d.ts +5 -5
  85. package/dist/benchmarks/tau2-bench.js +28 -10
  86. package/dist/benchmarks/tau2-bench.js.map +1 -1
  87. package/dist/benchmarks/tau3-banking.d.ts +4 -13
  88. package/dist/benchmarks/tau3-banking.js +28 -8
  89. package/dist/benchmarks/tau3-banking.js.map +1 -1
  90. package/dist/benchmarks/terminal-bench.d.ts +4 -22
  91. package/dist/benchmarks/terminal-bench.js +140 -7
  92. package/dist/benchmarks/terminal-bench.js.map +1 -1
  93. package/dist/benchmarks/toollm.d.ts +5 -13
  94. package/dist/benchmarks/toollm.js +184 -9
  95. package/dist/benchmarks/toollm.js.map +1 -1
  96. package/dist/benchmarks/trata-hedge.d.ts +4 -30
  97. package/dist/benchmarks/trata-hedge.js +336 -6
  98. package/dist/benchmarks/trata-hedge.js.map +1 -1
  99. package/dist/benchmarks/types.d.ts +85 -94
  100. package/dist/benchmarks/types.js +1 -1
  101. package/dist/benchmarks/webarena-verified.d.ts +5 -13
  102. package/dist/benchmarks/webarena-verified.js +152 -9
  103. package/dist/benchmarks/webarena-verified.js.map +1 -1
  104. package/dist/cadbench-DpQWZHp4.js +285 -0
  105. package/dist/cadbench-DpQWZHp4.js.map +1 -0
  106. package/dist/cadgenbench-DRhczfsG.js +151 -0
  107. package/dist/cadgenbench-DRhczfsG.js.map +1 -0
  108. package/dist/index.d.ts +245 -293
  109. package/dist/index.js +1669 -1791
  110. package/dist/index.js.map +1 -1
  111. package/package.json +17 -14
  112. package/pier_agents/candidate_contract.py +238 -24
  113. package/pier_agents/tangle_candidate.py +75 -5
  114. package/scripts/verify-packed-consumer.mjs +84 -17
  115. package/scripts/verify-pier-agent.mts +6 -4
  116. package/src/benchmarks/_harness.test.mts +16 -1
  117. package/src/benchmarks/_harness.ts +9 -2
  118. package/src/benchmarks/terminal-bench.ts +2 -1
  119. package/src/corpus.test.mts +13 -0
  120. package/src/corpus.ts +4 -0
  121. package/src/profile-coordinates.ts +2 -2
  122. package/src/rollout-ledger/backfill-swe-arena.mts +5 -1
  123. package/src/rollout-ledger/settle-capture.mts +7 -1
  124. package/src/search-bench/profiles.ts +1 -1
  125. package/src/skill-sandbox-smoke.mts +2 -1
  126. package/src/swe-arena/gepa-seat.mts +1 -1
  127. package/src/swe-arena/ledger-orphans.test.mts +36 -34
  128. package/dist/benchmarks/cadbench.js.map +0 -1
  129. package/dist/benchmarks/cadgenbench.js.map +0 -1
  130. package/dist/benchmarks/types.js.map +0 -1
  131. package/dist/chunk-3U5TXJZS.js +0 -251
  132. package/dist/chunk-3U5TXJZS.js.map +0 -1
  133. package/dist/chunk-53UPUNBZ.js +0 -325
  134. package/dist/chunk-53UPUNBZ.js.map +0 -1
  135. package/dist/chunk-5H5XV76F.js +0 -240
  136. package/dist/chunk-5H5XV76F.js.map +0 -1
  137. package/dist/chunk-7GRVHU22.js +0 -208
  138. package/dist/chunk-7GRVHU22.js.map +0 -1
  139. package/dist/chunk-C7T7WEK2.js +0 -103
  140. package/dist/chunk-C7T7WEK2.js.map +0 -1
  141. package/dist/chunk-HWST3SED.js +0 -162
  142. package/dist/chunk-HWST3SED.js.map +0 -1
  143. package/dist/chunk-IA2FBTWC.js +0 -318
  144. package/dist/chunk-IA2FBTWC.js.map +0 -1
  145. package/dist/chunk-IFVINJ4B.js +0 -142
  146. package/dist/chunk-IFVINJ4B.js.map +0 -1
  147. package/dist/chunk-INNOYXCP.js +0 -387
  148. package/dist/chunk-INNOYXCP.js.map +0 -1
  149. package/dist/chunk-IZ5M6OAC.js +0 -169
  150. package/dist/chunk-IZ5M6OAC.js.map +0 -1
  151. package/dist/chunk-JTHWEDEW.js +0 -32
  152. package/dist/chunk-JTHWEDEW.js.map +0 -1
  153. package/dist/chunk-K3BQGZCT.js +0 -221
  154. package/dist/chunk-K3BQGZCT.js.map +0 -1
  155. package/dist/chunk-KP5KD6EN.js +0 -276
  156. package/dist/chunk-KP5KD6EN.js.map +0 -1
  157. package/dist/chunk-MQMRLGOG.js +0 -136
  158. package/dist/chunk-MQMRLGOG.js.map +0 -1
  159. package/dist/chunk-NQG5XDSB.js +0 -147
  160. package/dist/chunk-NQG5XDSB.js.map +0 -1
  161. package/dist/chunk-PA2ZKHJC.js +0 -230
  162. package/dist/chunk-PA2ZKHJC.js.map +0 -1
  163. package/dist/chunk-PB64GYIG.js +0 -118
  164. package/dist/chunk-PB64GYIG.js.map +0 -1
  165. package/dist/chunk-PUIRNYI7.js +0 -189
  166. package/dist/chunk-PUIRNYI7.js.map +0 -1
  167. package/dist/chunk-RCYQEFNX.js +0 -30
  168. package/dist/chunk-RCYQEFNX.js.map +0 -1
  169. package/dist/chunk-RH5F53JT.js +0 -182
  170. package/dist/chunk-RH5F53JT.js.map +0 -1
  171. package/dist/chunk-SEVJPLZC.js +0 -260
  172. package/dist/chunk-SEVJPLZC.js.map +0 -1
  173. package/dist/chunk-SFLA7OH3.js +0 -27
  174. package/dist/chunk-SFLA7OH3.js.map +0 -1
  175. package/dist/chunk-SHM6MRRF.js +0 -130
  176. package/dist/chunk-SHM6MRRF.js.map +0 -1
  177. package/dist/chunk-SHYIRB7I.js +0 -120
  178. package/dist/chunk-SHYIRB7I.js.map +0 -1
  179. package/dist/chunk-SVR2LKYI.js +0 -116
  180. package/dist/chunk-SVR2LKYI.js.map +0 -1
  181. package/dist/chunk-TBKU5XQI.js +0 -228
  182. package/dist/chunk-TBKU5XQI.js.map +0 -1
  183. package/dist/chunk-UPAMRDX4.js +0 -233
  184. package/dist/chunk-UPAMRDX4.js.map +0 -1
  185. package/dist/chunk-V7AEBY6U.js +0 -144
  186. package/dist/chunk-V7AEBY6U.js.map +0 -1
  187. package/dist/chunk-VQRS7VUC.js +0 -342
  188. package/dist/chunk-VQRS7VUC.js.map +0 -1
  189. package/dist/chunk-WSKWVEQB.js +0 -317
  190. package/dist/chunk-WSKWVEQB.js.map +0 -1
  191. package/dist/chunk-X3BTXCJ4.js +0 -262
  192. package/dist/chunk-X3BTXCJ4.js.map +0 -1
  193. package/dist/chunk-XKEFIFIC.js +0 -197
  194. package/dist/chunk-XKEFIFIC.js.map +0 -1
  195. package/dist/chunk-XYA4XSNU.js +0 -148
  196. package/dist/chunk-XYA4XSNU.js.map +0 -1
  197. package/dist/chunk-YSMEKBTD.js +0 -211
  198. package/dist/chunk-YSMEKBTD.js.map +0 -1
  199. package/dist/chunk-Z4TZ76N7.js +0 -170
  200. package/dist/chunk-Z4TZ76N7.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"file":"dabstep.js","names":[],"sources":["../../src/benchmarks/dabstep.ts"],"sourcesContent":["/**\n * DABStep adapter (EnvCommons/DABStep) — data-analysis questions over synthetic\n * payment files. Worker artifact = final answer text. Judge = the official\n * DABStep `grade.py` normalization/matching function. No LLM judge.\n *\n * Live tasks require `DABSTEP_DIR` pointing at an official DABStep checkout that\n * includes `dataset.csv`, `splits/*.txt`, `files/*`, and `grade.py`. The adapter\n * exposes `metadata.resourceRoot` so runners can mount the benchmark files into\n * AgentProfile.resources.files; it does not paste the dataset into prompt text.\n */\n\nimport { join } from 'node:path'\nimport { access, readFile, stat } from 'node:fs/promises'\nimport type { OutputAdapter } from '@tangle-network/agent-runtime/loops'\nimport { benchRoot, runVenvPython, runVenvScriptStdin } from './_harness'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst FIXTURES = join(benchRoot, 'fixtures', 'dabstep.json')\nconst DEFAULT_SPLIT = 'easy'\n\ninterface DabstepFixtureRow {\n task_id: number\n instructions: string\n all_golds_by_task: Array<Record<string, unknown>>\n}\n\ninterface DabstepMeta {\n taskId: number\n split: string\n golds: Array<Record<string, unknown>>\n resourceRoot?: string\n}\n\nconst dabstepDir = (): string | undefined => process.env.DABSTEP_DIR\nconst gradeFile = (dir: string): string => join(dir, 'grade.py')\nconst resourceRoot = (dir: string): string => join(dir, 'files')\n\nasync function assertFile(path: string, label: string): Promise<void> {\n try {\n await access(path)\n } catch (err) {\n throw new Error(`DABStep: missing ${label} at ${path} (${err instanceof Error ? err.message : err})`)\n }\n}\n\nasync function assertOfficialFiles(dir: string, split: string): Promise<void> {\n await assertFile(join(dir, 'dataset.csv'), 'released dataset.csv')\n await assertFile(join(dir, 'splits', `${split}.txt`), `${split} split file`)\n await assertFile(gradeFile(dir), 'official grade.py')\n const files = resourceRoot(dir)\n try {\n const s = await stat(files)\n if (!s.isDirectory()) throw new Error('not a directory')\n } catch (err) {\n throw new Error(`DABStep: missing benchmark files directory at ${files} (${err instanceof Error ? err.message : err})`)\n }\n}\n\nexport const dabstepAnswerOutput: OutputAdapter<string> = {\n parse(events) {\n let text = ''\n for (const ev of events) {\n const d = (ev as { data?: Record<string, unknown> })?.data\n const t = d?.finalText ?? d?.text ?? d?.result\n if (typeof t === 'string' && t.length > 0) text = t\n }\n const fences = [...text.matchAll(/```(?:text|answer)?\\s*\\n([\\s\\S]*?)```/g)]\n return (fences.at(-1)?.[1] ?? text).trim()\n },\n}\n\nfunction rowToTask(row: DabstepFixtureRow, split: string, dir?: string): BenchTask {\n const meta: DabstepMeta = {\n taskId: row.task_id,\n split,\n golds: row.all_golds_by_task,\n ...(dir ? { resourceRoot: resourceRoot(dir) } : {}),\n }\n return {\n id: String(row.task_id),\n split,\n prompt: [\n 'Solve this DABStep data-analysis task using the mounted payment files.',\n 'Use code or shell commands as needed, then return only the final answer.',\n '',\n row.instructions,\n ].join('\\n'),\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask): DabstepMeta {\n const md = task.metadata\n if (!md || typeof md.taskId !== 'number' || !Array.isArray(md.golds)) {\n throw new Error(`dabstep task ${task.id} missing metadata — loadTasks did not populate it`)\n }\n return md as unknown as DabstepMeta\n}\n\nfunction selectRows(rows: DabstepFixtureRow[], opts: LoadOptions, split: string, dir?: string): BenchTask[] {\n let tasks = rows.map((row) => rowToTask(row, split, dir))\n if (opts.ids) {\n const want = new Set(opts.ids)\n tasks = tasks.filter((task) => want.has(task.id))\n } else if (opts.limit !== undefined) {\n tasks = tasks.slice(0, opts.limit)\n }\n if (tasks.length === 0) throw new Error(`DABStep: no tasks matched ${JSON.stringify(opts)} for split=${split}`)\n return tasks\n}\n\nasync function loadFixtures(opts: LoadOptions, split: string): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as DabstepFixtureRow[]\n console.warn(`[dabstep] DABSTEP_FIXTURES=1 — loading ${rows.length} adapter fixtures from ${FIXTURES}`)\n return selectRows(rows, opts, split)\n}\n\nasync function loadOfficialTasks(dir: string, opts: LoadOptions, split: string): Promise<BenchTask[]> {\n const script = `\nimport ast, csv, json, sys\nfrom pathlib import Path\n\nroot = Path(sys.argv[1])\nsplit = sys.argv[2]\nlimit = None if sys.argv[3] == \"\" else int(sys.argv[3])\nids = set(json.loads(sys.argv[4]))\ndataset = root / \"dataset.csv\"\nsplit_file = root / \"splits\" / f\"{split}.txt\"\nif not dataset.exists():\n raise SystemExit(f\"missing official DABStep dataset.csv at {dataset}\")\nif not split_file.exists():\n raise SystemExit(f\"missing official DABStep split file at {split_file}\")\nsplit_ids = {int(line.strip()) for line in split_file.read_text().splitlines() if line.strip()}\nout = []\nwith dataset.open(newline=\"\") as f:\n for row in csv.DictReader(f):\n task_id = int(row[\"task_id\"])\n if task_id not in split_ids:\n continue\n if ids and str(task_id) not in ids:\n continue\n out.append({\n \"task_id\": task_id,\n \"instructions\": f\"{row['question']}\\\\n{row['guidelines']}\",\n \"all_golds_by_task\": ast.literal_eval(str(row[\"all_golds_by_task\"])),\n })\n if limit is not None and len(out) >= limit:\n break\nif not out:\n raise SystemExit(f\"no DABStep rows matched split={split} ids={sorted(ids)} limit={limit}\")\nprint(json.dumps(out))\n`\n const stdout = await runVenvPython(script, [dir, split, opts.limit === undefined ? '' : String(opts.limit), JSON.stringify(opts.ids ?? [])])\n return selectRows(JSON.parse(stdout) as DabstepFixtureRow[], opts, split, dir)\n}\n\nexport function createDabstepAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.DABSTEP_FIXTURES === '1'\n\n return {\n name: 'dabstep',\n output: dabstepAnswerOutput,\n\n async preflight() {\n if (fixturesMode) return\n const dir = dabstepDir()\n if (!dir) {\n throw new Error(\n 'DABSTEP_DIR is required. Fix: clone https://github.com/EnvCommons/DABStep, add the released dataset.csv under that checkout, then set DABSTEP_DIR=/path/to/DABStep.',\n )\n }\n await assertOfficialFiles(dir, DEFAULT_SPLIT)\n await loadOfficialTasks(dir, { limit: 1 }, DEFAULT_SPLIT)\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n const split = opts.split ?? DEFAULT_SPLIT\n if (fixturesMode) return loadFixtures(opts, split)\n const dir = dabstepDir()\n if (!dir) throw new Error('DABSTEP_DIR is required to load official DABStep tasks')\n return loadOfficialTasks(dir, opts, split)\n },\n\n async goldArtifact(task: BenchTask) {\n const meta = readMeta(task)\n const first = meta.golds[0]\n if (!first) return undefined\n const value = first.value\n return value === undefined ? undefined : String(value)\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = readMeta(task)\n const dir = dabstepDir()\n if (!dir) throw new Error('DABSTEP_DIR is required to judge DABStep tasks with the official grade.py')\n const stdout = await runVenvScriptStdin(\n join(benchRoot, 'scripts', 'dabstep_judge.py'),\n ['--grade-file', gradeFile(dir)],\n JSON.stringify({ prediction: artifact, golds: meta.golds }),\n { cwd: benchRoot },\n )\n const report = JSON.parse(stdout.trim().split('\\n').at(-1) ?? '{}') as { correct?: boolean; score?: number; error?: string }\n if (report.error) throw new Error(`DABStep judge error for ${task.id}: ${report.error}`)\n const score = typeof report.score === 'number' ? report.score : 0\n return {\n resolved: report.correct === true,\n score,\n detail: JSON.stringify({ taskId: meta.taskId, split: meta.split, correct: report.correct }),\n }\n },\n }\n}\n"],"mappings":"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"}
@@ -1,38 +1,9 @@
1
- import { OutputAdapter } from '@tangle-network/agent-runtime/loops';
2
- import { BenchmarkAdapter } from './types.js';
3
-
4
- /**
5
- * EnterpriseOps-Gym adapter (ServiceNow-AI/EnterpriseOps-Gym, Apache-2.0) —
6
- * stateful agentic planning + tool use in enterprise settings. Each record is an
7
- * enterprise-ops task (Customer Service, HR, ITSM, Calendar, Email, Drive, Teams,
8
- * Hybrid) handed to the agent with a domain `system_prompt`, a `selected_tools`
9
- * allow-list, and one or more containerized gym MCP servers (`gym_servers_config`).
10
- * Worker artifact = the ordered tool-call transcript the agent would issue against
11
- * those servers, emitted as a single fenced ```json block of
12
- * `{ "calls": [ { "tool": ..., "arguments": {...}, "gym_name"?: ... } ] }`.
13
- *
14
- * Judge = the benchmark's OWN deterministic state-checker. The driver replays the
15
- * transcript against a freshly-seeded gym server (mutating its database), then runs
16
- * each task's `database_state` verifier — an SQL SELECT executed via the gym
17
- * server's /api/sql-runner endpoint, compared to `expected_value` under
18
- * `comparison_type` (equals/greater_than/less_than/contains). GRADED: score =
19
- * (verifiers passing) / (total verifiers) = the bench's verifier_level_pass_rate;
20
- * binary `resolved` = ALL verifiers pass = the bench's overall_success_rate. Fully
21
- * deterministic — no LLM judge.
22
- *
23
- * loadTasks enumerates the real suite from the HF rows server (config = tool-set
24
- * MODE oracle|plus_5_tools|plus_10_tools|plus_15_tools; split = DOMAIN); a committed
25
- * sample (bench/fixtures/enterpriseops-gym.json) loads offline.
26
- *
27
- * Requires for a LIVE judge run: a Docker daemon with the domain gym images
28
- * (`docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-<domain>:latest`) up
29
- * on the ports in `gym_servers_config`, seeded from gym_dbs.zip. preflight + judge
30
- * fail loud with the exact pull/run/unzip step when a server is unreachable — never
31
- * a fabricated score. No portable gold artifact ships, so goldArtifact is undefined.
32
- */
33
-
1
+ import { BenchmarkAdapter } from "./types.js";
2
+ import { OutputAdapter } from "@tangle-network/agent-runtime/loops";
3
+ //#region src/benchmarks/enterpriseops-gym.d.ts
34
4
  /** Worker transcript = the last fenced ```json block, else the raw text. */
35
5
  declare const enterpriseOpsTranscriptOutput: OutputAdapter<string>;
36
6
  declare function createEnterpriseOpsGymAdapter(): BenchmarkAdapter;
37
-
7
+ //#endregion
38
8
  export { createEnterpriseOpsGymAdapter, enterpriseOpsTranscriptOutput };
9
+ //# sourceMappingURL=enterpriseops-gym.d.ts.map
@@ -1,10 +1,237 @@
1
- import {
2
- createEnterpriseOpsGymAdapter,
3
- enterpriseOpsTranscriptOutput
4
- } from "../chunk-YSMEKBTD.js";
5
- import "../chunk-WSKWVEQB.js";
6
- export {
7
- createEnterpriseOpsGymAdapter,
8
- enterpriseOpsTranscriptOutput
9
- };
1
+ import { benchRoot, runVenvScriptStdin } from "./_harness.js";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ //#region src/benchmarks/enterpriseops-gym.ts
5
+ /**
6
+ * EnterpriseOps-Gym adapter (ServiceNow-AI/EnterpriseOps-Gym, Apache-2.0) —
7
+ * stateful agentic planning + tool use in enterprise settings. Each record is an
8
+ * enterprise-ops task (Customer Service, HR, ITSM, Calendar, Email, Drive, Teams,
9
+ * Hybrid) handed to the agent with a domain `system_prompt`, a `selected_tools`
10
+ * allow-list, and one or more containerized gym MCP servers (`gym_servers_config`).
11
+ * Worker artifact = the ordered tool-call transcript the agent would issue against
12
+ * those servers, emitted as a single fenced ```json block of
13
+ * `{ "calls": [ { "tool": ..., "arguments": {...}, "gym_name"?: ... } ] }`.
14
+ *
15
+ * Judge = the benchmark's OWN deterministic state-checker. The driver replays the
16
+ * transcript against a freshly-seeded gym server (mutating its database), then runs
17
+ * each task's `database_state` verifier — an SQL SELECT executed via the gym
18
+ * server's /api/sql-runner endpoint, compared to `expected_value` under
19
+ * `comparison_type` (equals/greater_than/less_than/contains). GRADED: score =
20
+ * (verifiers passing) / (total verifiers) = the bench's verifier_level_pass_rate;
21
+ * binary `resolved` = ALL verifiers pass = the bench's overall_success_rate. Fully
22
+ * deterministic — no LLM judge.
23
+ *
24
+ * loadTasks enumerates the real suite from the HF rows server (config = tool-set
25
+ * MODE oracle|plus_5_tools|plus_10_tools|plus_15_tools; split = DOMAIN); a committed
26
+ * sample (bench/fixtures/enterpriseops-gym.json) loads offline.
27
+ *
28
+ * Requires for a LIVE judge run: a Docker daemon with the domain gym images
29
+ * (`docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-<domain>:latest`) up
30
+ * on the ports in `gym_servers_config`, seeded from gym_dbs.zip. preflight + judge
31
+ * fail loud with the exact pull/run/unzip step when a server is unreachable — never
32
+ * a fabricated score. No portable gold artifact ships, so goldArtifact is undefined.
33
+ */
34
+ const FIXTURES = join(benchRoot, "fixtures", "enterpriseops-gym.json");
35
+ const JUDGE = join(benchRoot, "scripts", "enterpriseops_gym_judge.py");
36
+ /** Monotonic discriminator so concurrent judge calls stage distinct task-cache files. */
37
+ let judgeCallSeq = 0;
38
+ const DATASET = "ServiceNow-AI/EnterpriseOps-Gym";
39
+ /** Tool-set mode = HF config; oracle ships exact tools, plus_N adds N distractors. */
40
+ const DEFAULT_MODE = "oracle";
41
+ /** Domain = HF split. */
42
+ const DEFAULT_DOMAIN = "itsm";
43
+ const rowsApi = (mode, domain) => `https://datasets-server.huggingface.co/rows?dataset=${encodeURIComponent(DATASET)}&config=${encodeURIComponent(mode)}&split=${encodeURIComponent(domain)}`;
44
+ /** Worker transcript = the last fenced ```json block, else the raw text. */
45
+ const enterpriseOpsTranscriptOutput = { parse(events) {
46
+ let text = "";
47
+ for (const ev of events) {
48
+ const d = ev?.data;
49
+ const t = d?.finalText ?? d?.text ?? d?.result;
50
+ if (typeof t === "string" && t.length > 0) text = t;
51
+ }
52
+ return ([...text.matchAll(/```(?:json)?\s*\n([\s\S]*?)```/g)].at(-1)?.[1] ?? text).trim();
53
+ } };
54
+ function asArray(v) {
55
+ return typeof v === "string" ? JSON.parse(v) : v;
56
+ }
57
+ function workerContract(tools) {
58
+ return [
59
+ "",
60
+ `You have exactly these tools available (call NO others): ${tools.join(", ")}.`,
61
+ "Plan the full sequence of tool calls that brings the enterprise database to the required final state, honoring every policy in the role above.",
62
+ "Emit your COMPLETE plan as the LAST thing in your reply, in a single fenced ```json block, as an object:",
63
+ "{ \"calls\": [ { \"tool\": \"<tool_name>\", \"arguments\": { ... } } ] }",
64
+ "Include one entry per tool call in execution order. Nothing after the closing fence."
65
+ ].join("\n");
66
+ }
67
+ function rowToTask(row, mode) {
68
+ const servers = asArray(row.gym_servers_config);
69
+ const verifiers = asArray(row.verifiers);
70
+ const meta = {
71
+ taskId: row.task_id,
72
+ domain: row.domain,
73
+ mode,
74
+ selectedTools: row.selected_tools,
75
+ servers,
76
+ verifiers
77
+ };
78
+ return {
79
+ id: row.task_id,
80
+ split: row.domain,
81
+ prompt: [
82
+ row.system_prompt,
83
+ "",
84
+ row.user_prompt,
85
+ workerContract(row.selected_tools)
86
+ ].join("\n"),
87
+ metadata: meta
88
+ };
89
+ }
90
+ function readMeta(task) {
91
+ const md = task.metadata;
92
+ if (!md || typeof md.taskId !== "string" || !Array.isArray(md.servers) || !Array.isArray(md.verifiers) || md.verifiers.length === 0) throw new Error(`enterpriseops-gym task ${task.id} missing metadata — loadTasks did not populate it`);
93
+ return md;
94
+ }
95
+ function selectRows(rows, mode, opts) {
96
+ let tasks = rows.map((r) => rowToTask(r, mode));
97
+ if (opts.ids) {
98
+ const want = new Set(opts.ids);
99
+ tasks = tasks.filter((t) => want.has(t.id));
100
+ } else if (opts.limit !== void 0) tasks = tasks.slice(0, opts.limit);
101
+ return tasks;
102
+ }
103
+ async function loadFixtures(mode, opts) {
104
+ const rows = JSON.parse(await readFile(FIXTURES, "utf8"));
105
+ console.warn(`[enterpriseops-gym] EOPS_FIXTURES=1 — loading ${rows.length} committed sample rows from ${FIXTURES} (no HF fetch)`);
106
+ const domain = opts.split;
107
+ return selectRows(domain ? rows.filter((r) => r.domain === domain) : rows, mode, opts);
108
+ }
109
+ /** Pull real rows from the HF rows server (paged) for one mode/domain. Throws loud on a non-OK response. */
110
+ async function fetchRows(mode, domain, opts) {
111
+ const target = opts.ids ? opts.ids.length * 4 : opts.limit ?? 16;
112
+ const rows = [];
113
+ const want = opts.ids ? new Set(opts.ids) : null;
114
+ const page = 100;
115
+ const base = rowsApi(mode, domain);
116
+ for (let offset = 0; offset < 1024 && rows.length < target; offset += page) {
117
+ const res = await fetch(`${base}&offset=${offset}&length=${page}`);
118
+ if (!res.ok) throw new Error(`enterpriseops-gym rows HTTP ${res.status} (offset ${offset}): ${(await res.text()).slice(0, 200)}`);
119
+ const got = (await res.json()).rows ?? [];
120
+ if (got.length === 0) break;
121
+ for (const r of got) {
122
+ if (want && !want.has(r.row.task_id)) continue;
123
+ rows.push(r.row);
124
+ }
125
+ if (got.length < page) break;
126
+ }
127
+ if (rows.length === 0) throw new Error(`enterpriseops-gym: no rows matched ${JSON.stringify(opts)} for ${mode}/${domain}`);
128
+ return rows;
129
+ }
130
+ /**
131
+ * Run the benchmark's own state-checker for one task over the worker's transcript.
132
+ * The driver replays the tool calls against the live gym server, runs each
133
+ * database_state verifier's SQL via /api/sql-runner, and reports {passes,total}.
134
+ * Score = passes/total (verifier_level_pass_rate); resolved = all pass
135
+ * (overall_success_rate). This is the expensive Docker-backed boundary — delegated
136
+ * to the python driver, not reimplemented. The transcript is piped on stdin via the
137
+ * shared stdin-aware runner (execFile's `input` is not honored async and hangs the
138
+ * reader).
139
+ */
140
+ async function runJudge(meta, artifact) {
141
+ const taskJsonPath = join(benchRoot, ".eops-task-cache", `${meta.taskId}-${process.pid}-${judgeCallSeq++}.json`);
142
+ await writeTaskCache(taskJsonPath, meta);
143
+ let stdout;
144
+ try {
145
+ stdout = await runVenvScriptStdin(JUDGE, [
146
+ "judge",
147
+ "--task-json",
148
+ taskJsonPath
149
+ ], artifact, { cwd: benchRoot });
150
+ } catch (err) {
151
+ const e = err;
152
+ throw new Error(`enterpriseops-gym judge failed for ${meta.taskId}: ${(e.message || String(err)).slice(0, 1500)}\nFix: (1) run the ${meta.domain} gym server — docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; docker run -d -p <host>:8005 shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; (2) unzip gym_dbs.zip (ServiceNow/EnterpriseOps-Gym) and export EOPS_GYM_DBS_DIR to its dir (the judge seeds each task's database from seed_database_file).`);
153
+ } finally {
154
+ await rm(taskJsonPath, { force: true }).catch(() => {});
155
+ }
156
+ const report = JSON.parse(stdout.trim().split("\n").at(-1) ?? "{}");
157
+ if (report.error) throw new Error(`enterpriseops-gym judge error for ${meta.taskId}: ${report.error}`);
158
+ if (typeof report.passes !== "number" || typeof report.total !== "number") throw new Error(`enterpriseops-gym judge returned no {passes,total}: ${stdout.slice(0, 400)}`);
159
+ const score = report.total > 0 ? report.passes / report.total : 0;
160
+ return {
161
+ resolved: report.success === true && report.total > 0 && report.passes === report.total,
162
+ score,
163
+ detail: JSON.stringify({
164
+ taskId: meta.taskId,
165
+ domain: meta.domain,
166
+ passes: report.passes,
167
+ total: report.total
168
+ })
169
+ };
170
+ }
171
+ async function writeTaskCache(path, meta) {
172
+ await mkdir(join(path, ".."), { recursive: true });
173
+ await writeFile(path, JSON.stringify({
174
+ task_id: meta.taskId,
175
+ domain: meta.domain,
176
+ gym_servers_config: meta.servers,
177
+ verifiers: meta.verifiers
178
+ }));
179
+ }
180
+ /** Ping the configured gym servers' SQL endpoint; throw loud with the docker fix if any is unreachable. */
181
+ async function probeServers(servers, domain) {
182
+ for (const s of servers) {
183
+ const url = `${s.mcp_server_url.replace(/\/$/, "")}/api/sql-runner`;
184
+ try {
185
+ (await fetch(url, {
186
+ method: "POST",
187
+ headers: { "content-type": "application/json" },
188
+ body: "{}"
189
+ })).status;
190
+ } catch (err) {
191
+ throw new Error(`enterpriseops-gym preflight: ${s.mcp_server_name} unreachable at ${url}: ${err instanceof Error ? err.message : err}\nFix: docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; docker run -d -p <port>:<port> shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; seed from gym_dbs.zip. Set EOPS_FIXTURES=1 to load sample tasks offline (judge still needs a live server).`);
192
+ }
193
+ }
194
+ }
195
+ function createEnterpriseOpsGymAdapter() {
196
+ const fixturesMode = process.env.EOPS_FIXTURES === "1";
197
+ const mode = process.env.EOPS_MODE ?? DEFAULT_MODE;
198
+ return {
199
+ name: "enterpriseops-gym",
200
+ output: enterpriseOpsTranscriptOutput,
201
+ async preflight() {
202
+ if (fixturesMode) {
203
+ await readFile(FIXTURES, "utf8").catch((err) => {
204
+ throw new Error(`EOPS_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`);
205
+ });
206
+ return;
207
+ }
208
+ const sample = await loadFixtures(mode, {
209
+ split: DEFAULT_DOMAIN,
210
+ limit: 1
211
+ }).catch(() => []);
212
+ const servers = sample[0] ? readMeta(sample[0]).servers : [];
213
+ if (servers.length === 0) throw new Error("enterpriseops-gym preflight: no gym_servers_config to probe. Set EOPS_FIXTURES=1 to load offline, or ensure the dataset rows carry gym_servers_config.");
214
+ await probeServers(servers, DEFAULT_DOMAIN);
215
+ },
216
+ async loadTasks(opts = {}) {
217
+ const domain = opts.split ?? DEFAULT_DOMAIN;
218
+ if (fixturesMode) return loadFixtures(mode, opts);
219
+ let rows;
220
+ try {
221
+ rows = await fetchRows(mode, domain, opts);
222
+ } catch (err) {
223
+ console.warn(`[enterpriseops-gym] live rows fetch failed (${err instanceof Error ? err.message : err}); falling back to committed sample at ${FIXTURES}`);
224
+ return loadFixtures(mode, opts);
225
+ }
226
+ return selectRows(rows, mode, opts);
227
+ },
228
+ async goldArtifact() {},
229
+ async judge(task, artifact) {
230
+ return runJudge(readMeta(task), artifact);
231
+ }
232
+ };
233
+ }
234
+ //#endregion
235
+ export { createEnterpriseOpsGymAdapter, enterpriseOpsTranscriptOutput };
236
+
10
237
  //# sourceMappingURL=enterpriseops-gym.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"file":"enterpriseops-gym.js","names":[],"sources":["../../src/benchmarks/enterpriseops-gym.ts"],"sourcesContent":["/**\n * EnterpriseOps-Gym adapter (ServiceNow-AI/EnterpriseOps-Gym, Apache-2.0) —\n * stateful agentic planning + tool use in enterprise settings. Each record is an\n * enterprise-ops task (Customer Service, HR, ITSM, Calendar, Email, Drive, Teams,\n * Hybrid) handed to the agent with a domain `system_prompt`, a `selected_tools`\n * allow-list, and one or more containerized gym MCP servers (`gym_servers_config`).\n * Worker artifact = the ordered tool-call transcript the agent would issue against\n * those servers, emitted as a single fenced ```json block of\n * `{ \"calls\": [ { \"tool\": ..., \"arguments\": {...}, \"gym_name\"?: ... } ] }`.\n *\n * Judge = the benchmark's OWN deterministic state-checker. The driver replays the\n * transcript against a freshly-seeded gym server (mutating its database), then runs\n * each task's `database_state` verifier — an SQL SELECT executed via the gym\n * server's /api/sql-runner endpoint, compared to `expected_value` under\n * `comparison_type` (equals/greater_than/less_than/contains). GRADED: score =\n * (verifiers passing) / (total verifiers) = the bench's verifier_level_pass_rate;\n * binary `resolved` = ALL verifiers pass = the bench's overall_success_rate. Fully\n * deterministic — no LLM judge.\n *\n * loadTasks enumerates the real suite from the HF rows server (config = tool-set\n * MODE oracle|plus_5_tools|plus_10_tools|plus_15_tools; split = DOMAIN); a committed\n * sample (bench/fixtures/enterpriseops-gym.json) loads offline.\n *\n * Requires for a LIVE judge run: a Docker daemon with the domain gym images\n * (`docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-<domain>:latest`) up\n * on the ports in `gym_servers_config`, seeded from gym_dbs.zip. preflight + judge\n * fail loud with the exact pull/run/unzip step when a server is unreachable — never\n * a fabricated score. No portable gold artifact ships, so goldArtifact is undefined.\n */\n\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { OutputAdapter } from '@tangle-network/agent-runtime/loops'\nimport { benchRoot, runVenvScriptStdin } from './_harness'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst FIXTURES = join(benchRoot, 'fixtures', 'enterpriseops-gym.json')\nconst JUDGE = join(benchRoot, 'scripts', 'enterpriseops_gym_judge.py')\n\n/** Monotonic discriminator so concurrent judge calls stage distinct task-cache files. */\nlet judgeCallSeq = 0\n\nconst DATASET = 'ServiceNow-AI/EnterpriseOps-Gym'\n/** Tool-set mode = HF config; oracle ships exact tools, plus_N adds N distractors. */\nconst DEFAULT_MODE = 'oracle'\n/** Domain = HF split. */\nconst DEFAULT_DOMAIN = 'itsm'\n\nconst rowsApi = (mode: string, domain: string) =>\n `https://datasets-server.huggingface.co/rows?dataset=${encodeURIComponent(DATASET)}&config=${encodeURIComponent(mode)}&split=${encodeURIComponent(domain)}`\n\ninterface GymServerConfig {\n mcp_server_name: string\n mcp_server_url: string\n seed_database_file: string\n context?: Record<string, string>\n user_info?: Record<string, unknown>\n}\n\ninterface Verifier {\n verifier_type: string\n name: string\n description?: string\n gym_name: string\n validation_config: { query: string; expected_value: unknown; comparison_type: string }\n}\n\ninterface EopsRow {\n task_id: string\n domain: string\n system_prompt: string\n user_prompt: string\n selected_tools: string[]\n restricted_tools: string[]\n mcp_endpoint: string\n number_of_runs: number\n reset_database_between_runs: boolean\n /** HF parquet stores these as JSON strings; fixtures store them as parsed arrays. */\n gym_servers_config: string | GymServerConfig[]\n verifiers: string | Verifier[]\n}\n\ninterface EopsMeta {\n taskId: string\n domain: string\n mode: string\n selectedTools: string[]\n servers: GymServerConfig[]\n verifiers: Verifier[]\n}\n\n/** Worker transcript = the last fenced ```json block, else the raw text. */\nexport const enterpriseOpsTranscriptOutput: OutputAdapter<string> = {\n parse(events) {\n let text = ''\n for (const ev of events) {\n const d = (ev as { data?: Record<string, unknown> })?.data\n const t = d?.finalText ?? d?.text ?? d?.result\n if (typeof t === 'string' && t.length > 0) text = t\n }\n const fences = [...text.matchAll(/```(?:json)?\\s*\\n([\\s\\S]*?)```/g)]\n return (fences.at(-1)?.[1] ?? text).trim()\n },\n}\n\nfunction asArray<T>(v: string | T[]): T[] {\n return typeof v === 'string' ? (JSON.parse(v) as T[]) : v\n}\n\nfunction workerContract(tools: string[]): string {\n return [\n '',\n `You have exactly these tools available (call NO others): ${tools.join(', ')}.`,\n 'Plan the full sequence of tool calls that brings the enterprise database to the required final state, honoring every policy in the role above.',\n 'Emit your COMPLETE plan as the LAST thing in your reply, in a single fenced ```json block, as an object:',\n '{ \"calls\": [ { \"tool\": \"<tool_name>\", \"arguments\": { ... } } ] }',\n 'Include one entry per tool call in execution order. Nothing after the closing fence.',\n ].join('\\n')\n}\n\nfunction rowToTask(row: EopsRow, mode: string): BenchTask {\n const servers = asArray<GymServerConfig>(row.gym_servers_config)\n const verifiers = asArray<Verifier>(row.verifiers)\n const meta: EopsMeta = {\n taskId: row.task_id,\n domain: row.domain,\n mode,\n selectedTools: row.selected_tools,\n servers,\n verifiers,\n }\n return {\n id: row.task_id,\n split: row.domain,\n prompt: [row.system_prompt, '', row.user_prompt, workerContract(row.selected_tools)].join('\\n'),\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask): EopsMeta {\n const md = task.metadata\n if (\n !md ||\n typeof md.taskId !== 'string' ||\n !Array.isArray(md.servers) ||\n !Array.isArray(md.verifiers) ||\n (md.verifiers as unknown[]).length === 0\n ) {\n throw new Error(`enterpriseops-gym task ${task.id} missing metadata — loadTasks did not populate it`)\n }\n return md as unknown as EopsMeta\n}\n\nfunction selectRows(rows: EopsRow[], mode: string, opts: LoadOptions): BenchTask[] {\n let tasks = rows.map((r) => rowToTask(r, mode))\n if (opts.ids) {\n const want = new Set(opts.ids)\n tasks = tasks.filter((t) => want.has(t.id))\n } else if (opts.limit !== undefined) {\n tasks = tasks.slice(0, opts.limit)\n }\n return tasks\n}\n\nasync function loadFixtures(mode: string, opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as EopsRow[]\n console.warn(\n `[enterpriseops-gym] EOPS_FIXTURES=1 — loading ${rows.length} committed sample rows from ${FIXTURES} (no HF fetch)`,\n )\n const domain = opts.split\n const scoped = domain ? rows.filter((r) => r.domain === domain) : rows\n return selectRows(scoped, mode, opts)\n}\n\n/** Pull real rows from the HF rows server (paged) for one mode/domain. Throws loud on a non-OK response. */\nasync function fetchRows(mode: string, domain: string, opts: LoadOptions): Promise<EopsRow[]> {\n const target = opts.ids ? opts.ids.length * 4 : (opts.limit ?? 16)\n const rows: EopsRow[] = []\n const want = opts.ids ? new Set(opts.ids) : null\n const page = 100\n const base = rowsApi(mode, domain)\n for (let offset = 0; offset < 1024 && rows.length < target; offset += page) {\n const res = await fetch(`${base}&offset=${offset}&length=${page}`)\n if (!res.ok) {\n throw new Error(`enterpriseops-gym rows HTTP ${res.status} (offset ${offset}): ${(await res.text()).slice(0, 200)}`)\n }\n const body = (await res.json()) as { rows?: Array<{ row: EopsRow }> }\n const got = body.rows ?? []\n if (got.length === 0) break\n for (const r of got) {\n if (want && !want.has(r.row.task_id)) continue\n rows.push(r.row)\n }\n if (got.length < page) break\n }\n if (rows.length === 0) throw new Error(`enterpriseops-gym: no rows matched ${JSON.stringify(opts)} for ${mode}/${domain}`)\n return rows\n}\n\n/**\n * Run the benchmark's own state-checker for one task over the worker's transcript.\n * The driver replays the tool calls against the live gym server, runs each\n * database_state verifier's SQL via /api/sql-runner, and reports {passes,total}.\n * Score = passes/total (verifier_level_pass_rate); resolved = all pass\n * (overall_success_rate). This is the expensive Docker-backed boundary — delegated\n * to the python driver, not reimplemented. The transcript is piped on stdin via the\n * shared stdin-aware runner (execFile's `input` is not honored async and hangs the\n * reader).\n */\nasync function runJudge(meta: EopsMeta, artifact: string): Promise<BenchScore> {\n // Stage the full task record (servers + verifiers) so the driver has the live\n // server URLs/contexts and the SQL it must run. The path is UNIQUE per call: the gate\n // runs k judges for one task concurrently, so a `${taskId}.json` shared path would race.\n const taskJsonPath = join(\n benchRoot,\n '.eops-task-cache',\n `${meta.taskId}-${process.pid}-${judgeCallSeq++}.json`,\n )\n await writeTaskCache(taskJsonPath, meta)\n let stdout: string\n try {\n stdout = await runVenvScriptStdin(JUDGE, ['judge', '--task-json', taskJsonPath], artifact, { cwd: benchRoot })\n } catch (err) {\n const e = err as { message?: string }\n throw new Error(\n `enterpriseops-gym judge failed for ${meta.taskId}: ${(e.message || String(err)).slice(0, 1500)}\\n` +\n `Fix: (1) run the ${meta.domain} gym server — ` +\n `docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; ` +\n `docker run -d -p <host>:8005 shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; ` +\n `(2) unzip gym_dbs.zip (ServiceNow/EnterpriseOps-Gym) and export EOPS_GYM_DBS_DIR to its dir ` +\n `(the judge seeds each task's database from seed_database_file).`,\n )\n } finally {\n await rm(taskJsonPath, { force: true }).catch(() => {})\n }\n const report = JSON.parse(stdout.trim().split('\\n').at(-1) ?? '{}') as {\n success?: boolean\n passes?: number\n total?: number\n error?: string\n }\n if (report.error) throw new Error(`enterpriseops-gym judge error for ${meta.taskId}: ${report.error}`)\n if (typeof report.passes !== 'number' || typeof report.total !== 'number') {\n throw new Error(`enterpriseops-gym judge returned no {passes,total}: ${stdout.slice(0, 400)}`)\n }\n const score = report.total > 0 ? report.passes / report.total : 0\n return {\n resolved: report.success === true && report.total > 0 && report.passes === report.total,\n score,\n detail: JSON.stringify({ taskId: meta.taskId, domain: meta.domain, passes: report.passes, total: report.total }),\n }\n}\n\nasync function writeTaskCache(path: string, meta: EopsMeta): Promise<void> {\n await mkdir(join(path, '..'), { recursive: true })\n await writeFile(\n path,\n JSON.stringify({ task_id: meta.taskId, domain: meta.domain, gym_servers_config: meta.servers, verifiers: meta.verifiers }),\n )\n}\n\n/** Ping the configured gym servers' SQL endpoint; throw loud with the docker fix if any is unreachable. */\nasync function probeServers(servers: GymServerConfig[], domain: string): Promise<void> {\n for (const s of servers) {\n const url = `${s.mcp_server_url.replace(/\\/$/, '')}/api/sql-runner`\n try {\n // A HEAD/empty POST just proves reachability; a real query runs in judge.\n const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })\n // Any HTTP response (even an error status) proves the server is up. Only a\n // transport failure (refused/ENOTFOUND) means the container is not running.\n void res.status\n } catch (err) {\n throw new Error(\n `enterpriseops-gym preflight: ${s.mcp_server_name} unreachable at ${url}: ${err instanceof Error ? err.message : err}\\n` +\n `Fix: docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; ` +\n `docker run -d -p <port>:<port> shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; ` +\n `seed from gym_dbs.zip. Set EOPS_FIXTURES=1 to load sample tasks offline (judge still needs a live server).`,\n )\n }\n }\n}\n\nexport function createEnterpriseOpsGymAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.EOPS_FIXTURES === '1'\n const mode = process.env.EOPS_MODE ?? DEFAULT_MODE\n\n return {\n name: 'enterpriseops-gym',\n output: enterpriseOpsTranscriptOutput,\n\n async preflight() {\n // Fixtures mode proves only that the sample file is readable; a live judge\n // still requires running gym servers (and fails loud there if absent).\n if (fixturesMode) {\n await readFile(FIXTURES, 'utf8').catch((err) => {\n throw new Error(`EOPS_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`)\n })\n return\n }\n // Live mode: probe the gym servers for the default domain (the suite's tasks\n // each carry their own server config; preflight verifies reachability up\n // front so a batch fails fast with the docker fix rather than mid-run).\n const sample = await loadFixtures(mode, { split: DEFAULT_DOMAIN, limit: 1 }).catch(() => [])\n const servers = sample[0] ? readMeta(sample[0]).servers : []\n if (servers.length === 0) {\n throw new Error(\n `enterpriseops-gym preflight: no gym_servers_config to probe. ` +\n `Set EOPS_FIXTURES=1 to load offline, or ensure the dataset rows carry gym_servers_config.`,\n )\n }\n await probeServers(servers, DEFAULT_DOMAIN)\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n const domain = opts.split ?? DEFAULT_DOMAIN\n if (fixturesMode) return loadFixtures(mode, opts)\n let rows: EopsRow[]\n try {\n rows = await fetchRows(mode, domain, opts)\n } catch (err) {\n console.warn(\n `[enterpriseops-gym] live rows fetch failed (${err instanceof Error ? err.message : err}); falling back to committed sample at ${FIXTURES}`,\n )\n return loadFixtures(mode, opts)\n }\n return selectRows(rows, mode, opts)\n },\n\n async goldArtifact() {\n // The benchmark ships no portable per-task oracle transcript — the reference\n // is the seeded final DB state the verifiers check, not a tool-call script.\n // Judge correctness is proven by replaying a real solve against the live gym\n // server, not by a synthetic gold transcript. Returns undefined (documented,\n // not faked).\n return undefined\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = readMeta(task)\n return runJudge(meta, artifact)\n },\n }\n}\n"],"mappings":"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"}
@@ -1,15 +1,6 @@
1
- import { BenchmarkAdapter } from './types.js';
2
- import '@tangle-network/agent-runtime/loops';
3
-
4
- /**
5
- * FinResearchBench-compatible adapter.
6
- *
7
- * The paper defines a logic-tree Agent-as-a-Judge benchmark for financial
8
- * research reports, but there is no stable public scorer package wired here.
9
- * Live mode therefore requires a local data export whose rows carry the official
10
- * judge prompt/template/logic tree. The adapter refuses to invent a judge.
11
- */
12
-
1
+ import { BenchmarkAdapter } from "./types.js";
2
+ //#region src/benchmarks/finresearchbench.d.ts
13
3
  declare function createFinResearchBenchAdapter(): BenchmarkAdapter;
14
-
4
+ //#endregion
15
5
  export { createFinResearchBenchAdapter };
6
+ //# sourceMappingURL=finresearchbench.d.ts.map
@@ -1,8 +1,219 @@
1
- import {
2
- createFinResearchBenchAdapter
3
- } from "../chunk-7GRVHU22.js";
4
- import "../chunk-WSKWVEQB.js";
5
- export {
6
- createFinResearchBenchAdapter
7
- };
1
+ import { benchRoot } from "./_harness.js";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ //#region src/benchmarks/finresearchbench.ts
5
+ /**
6
+ * FinResearchBench-compatible adapter.
7
+ *
8
+ * The paper defines a logic-tree Agent-as-a-Judge benchmark for financial
9
+ * research reports, but there is no stable public scorer package wired here.
10
+ * Live mode therefore requires a local data export whose rows carry the official
11
+ * judge prompt/template/logic tree. The adapter refuses to invent a judge.
12
+ */
13
+ const FIXTURES = join(benchRoot, "fixtures", "finresearchbench.json");
14
+ const dataFile = () => process.env.FINRESEARCHBENCH_DATA_FILE;
15
+ function routerConfig() {
16
+ const key = process.env.TANGLE_API_KEY;
17
+ if (!key) throw new Error("TANGLE_API_KEY is required for FinResearchBench live LLM judging");
18
+ return {
19
+ baseUrl: process.env.ROUTER_BASE ?? "https://router.tangle.tools/v1",
20
+ key,
21
+ model: process.env.FINRESEARCHBENCH_JUDGE_MODEL ?? process.env.JUDGE_MODEL ?? "deepseek-v4-flash"
22
+ };
23
+ }
24
+ async function assertReadable(path, label) {
25
+ try {
26
+ await stat(path);
27
+ } catch (err) {
28
+ throw new Error(`FinResearchBench: missing ${label} at ${path} (${err instanceof Error ? err.message : err})`);
29
+ }
30
+ }
31
+ function readRecords(raw) {
32
+ const trimmed = raw.trim();
33
+ if (trimmed.startsWith("[")) return JSON.parse(trimmed);
34
+ return trimmed.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
35
+ }
36
+ function assertLiveJudgeFields(records, source) {
37
+ const missing = records.filter((row) => !row.judge_system_prompt || !row.judge_prompt_template).map((row) => row.id);
38
+ if (missing.length > 0) throw new Error(`FinResearchBench live rows from ${source} missing official judge prompts for ${missing.length}/${records.length} row(s): ${missing.slice(0, 5).join(", ")}. Use a benchmark export with judge_system_prompt and judge_prompt_template, or do not score this benchmark live.`);
39
+ }
40
+ function rowToTask(row, fixturesMode) {
41
+ const referenceAnswer = row.reference_answer ?? "";
42
+ const referenceReport = row.reference_report ?? referenceAnswer;
43
+ const meta = {
44
+ id: row.id,
45
+ category: row.category ?? "unknown",
46
+ question: row.question,
47
+ referenceAnswer,
48
+ referenceReport,
49
+ logicTree: row.logic_tree ?? null,
50
+ rubric: row.rubric ?? null,
51
+ judgeSystemPrompt: row.judge_system_prompt,
52
+ judgePromptTemplate: row.judge_prompt_template,
53
+ scoring: fixturesMode ? "fixture-exact-reference" : "official-logic-tree-judge"
54
+ };
55
+ return {
56
+ id: row.id,
57
+ split: row.category,
58
+ prompt: [
59
+ "Complete this FinResearchBench financial research task.",
60
+ "Produce a decision-grade research answer with explicit reasoning, evidence, and final conclusion.",
61
+ "",
62
+ row.question
63
+ ].join("\n"),
64
+ metadata: meta
65
+ };
66
+ }
67
+ function readMeta(task) {
68
+ const md = task.metadata;
69
+ if (!md || typeof md.question !== "string") throw new Error(`FinResearchBench task ${task.id} missing metadata — loadTasks did not populate it`);
70
+ return md;
71
+ }
72
+ function selectRows(rows, opts, fixturesMode) {
73
+ let tasks = rows.map((row) => rowToTask(row, fixturesMode));
74
+ if (opts.split) tasks = tasks.filter((task) => task.split === opts.split);
75
+ if (opts.ids) {
76
+ const want = new Set(opts.ids);
77
+ tasks = tasks.filter((task) => want.has(task.id));
78
+ } else if (opts.limit !== void 0) tasks = tasks.slice(0, opts.limit);
79
+ if (tasks.length === 0) throw new Error(`FinResearchBench: no tasks matched ${JSON.stringify(opts)}`);
80
+ return tasks;
81
+ }
82
+ async function loadFixtures(opts) {
83
+ const records = readRecords(await readFile(FIXTURES, "utf8"));
84
+ console.warn(`[finresearchbench] FINRESEARCHBENCH_FIXTURES=1 — loading ${records.length} adapter fixtures`);
85
+ return selectRows(records, opts, true);
86
+ }
87
+ async function loadOfficialTasks(path, opts) {
88
+ const records = readRecords(await readFile(path, "utf8"));
89
+ assertLiveJudgeFields(records, path);
90
+ return selectRows(records, opts, false);
91
+ }
92
+ function fillTemplate(meta, response) {
93
+ const template = meta.judgePromptTemplate;
94
+ if (!template) throw new Error(`FinResearchBench task ${meta.id} missing judge_prompt_template`);
95
+ return template.replaceAll("{question}", meta.question).replaceAll("{response}", response).replaceAll("{reference_answer}", meta.referenceAnswer).replaceAll("{reference_report}", meta.referenceReport).replaceAll("{logic_tree}", JSON.stringify(meta.logicTree, null, 2)).replaceAll("{rubric}", JSON.stringify(meta.rubric, null, 2));
96
+ }
97
+ function parseJudgeScore(content) {
98
+ const candidates = [];
99
+ for (const m of content.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)) candidates.push(m[1].trim());
100
+ for (const m of content.matchAll(/\{[\s\S]*?\}/g)) candidates.push(m[0]);
101
+ candidates.push(content.trim());
102
+ for (const candidate of candidates) {
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(candidate);
106
+ } catch {
107
+ continue;
108
+ }
109
+ const raw = parsed.score ?? parsed.overall_score ?? parsed.total_score ?? parsed.answer_score;
110
+ const n = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : NaN;
111
+ if (!Number.isFinite(n)) continue;
112
+ if (n < 0) throw new Error(`FinResearchBench judge score is negative: ${JSON.stringify(parsed)}`);
113
+ const score = n <= 1 ? n : n <= 10 ? n / 10 : n / 100;
114
+ if (score > 1) throw new Error(`FinResearchBench judge score outside supported range: ${JSON.stringify(parsed)}`);
115
+ return {
116
+ score,
117
+ raw: parsed
118
+ };
119
+ }
120
+ throw new Error(`FinResearchBench judge produced no parseable JSON score: ${content.slice(0, 400)}`);
121
+ }
122
+ async function runOfficialJudge(meta, response) {
123
+ if (!meta.judgeSystemPrompt) throw new Error(`FinResearchBench task ${meta.id} missing judge_system_prompt`);
124
+ const router = routerConfig();
125
+ const res = await fetch(`${router.baseUrl}/chat/completions`, {
126
+ method: "POST",
127
+ headers: {
128
+ "content-type": "application/json",
129
+ authorization: `Bearer ${router.key}`
130
+ },
131
+ body: JSON.stringify({
132
+ model: router.model,
133
+ temperature: 0,
134
+ messages: [{
135
+ role: "system",
136
+ content: meta.judgeSystemPrompt
137
+ }, {
138
+ role: "user",
139
+ content: fillTemplate(meta, response)
140
+ }]
141
+ })
142
+ });
143
+ if (!res.ok) throw new Error(`FinResearchBench judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
144
+ const body = await res.json();
145
+ const content = body.choices?.[0]?.message?.content;
146
+ if (typeof content !== "string") throw new Error(`FinResearchBench judge returned no message content: ${JSON.stringify(body).slice(0, 300)}`);
147
+ const { score, raw } = parseJudgeScore(content);
148
+ return {
149
+ resolved: score >= Number(process.env.FINRESEARCHBENCH_PASS_THRESHOLD ?? .8),
150
+ score,
151
+ detail: JSON.stringify({
152
+ scoring: meta.scoring,
153
+ category: meta.category,
154
+ judgeModel: router.model,
155
+ raw
156
+ })
157
+ };
158
+ }
159
+ function normalizeText(value) {
160
+ return value.toLowerCase().replace(/\s+/g, " ").trim();
161
+ }
162
+ function scoreFixture(meta, artifact) {
163
+ const answer = normalizeText(meta.referenceAnswer || meta.referenceReport);
164
+ const response = normalizeText(artifact);
165
+ const score = answer.length > 0 && response.includes(answer) ? 1 : 0;
166
+ return {
167
+ resolved: score === 1,
168
+ score,
169
+ detail: JSON.stringify({
170
+ scoring: meta.scoring,
171
+ category: meta.category
172
+ })
173
+ };
174
+ }
175
+ function createFinResearchBenchAdapter() {
176
+ const fixturesMode = process.env.FINRESEARCHBENCH_FIXTURES === "1";
177
+ return {
178
+ name: "finresearchbench",
179
+ async preflight() {
180
+ if (fixturesMode) {
181
+ await assertReadable(FIXTURES, "fixture file");
182
+ return;
183
+ }
184
+ const file = dataFile();
185
+ if (!file) throw new Error("FINRESEARCHBENCH_DATA_FILE is required. Fix: export the official FinResearchBench rows as JSON/JSONL with judge_system_prompt and judge_prompt_template fields, then set FINRESEARCHBENCH_DATA_FILE=/path/to/export.jsonl.");
186
+ routerConfig();
187
+ await assertReadable(file, "data file");
188
+ await loadOfficialTasks(file, { limit: 1 });
189
+ },
190
+ async loadTasks(opts = {}) {
191
+ if (fixturesMode) return loadFixtures(opts);
192
+ const file = dataFile();
193
+ if (!file) throw new Error("FINRESEARCHBENCH_DATA_FILE is required to load FinResearchBench rows");
194
+ return loadOfficialTasks(file, opts);
195
+ },
196
+ async goldArtifact(task) {
197
+ const meta = readMeta(task);
198
+ return meta.referenceReport || meta.referenceAnswer || void 0;
199
+ },
200
+ async judge(task, artifact) {
201
+ const meta = readMeta(task);
202
+ if (fixturesMode) return scoreFixture(meta, artifact);
203
+ if (artifact.trim().length === 0) return {
204
+ resolved: false,
205
+ score: 0,
206
+ detail: JSON.stringify({
207
+ scoring: meta.scoring,
208
+ category: meta.category,
209
+ reason: "empty answer"
210
+ })
211
+ };
212
+ return runOfficialJudge(meta, artifact);
213
+ }
214
+ };
215
+ }
216
+ //#endregion
217
+ export { createFinResearchBenchAdapter };
218
+
8
219
  //# sourceMappingURL=finresearchbench.js.map