@tangle-network/agent-bench 0.1.0 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +39 -2
  4. package/dist/adapters.d.ts +15 -0
  5. package/dist/adapters.js +43 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/benchmarks/_harness.d.ts +125 -0
  8. package/dist/benchmarks/_harness.js +33 -0
  9. package/dist/benchmarks/_harness.js.map +1 -0
  10. package/dist/benchmarks/aec-bench.d.ts +27 -0
  11. package/dist/benchmarks/aec-bench.js +8 -0
  12. package/dist/benchmarks/aec-bench.js.map +1 -0
  13. package/dist/benchmarks/agentbench.d.ts +16 -0
  14. package/dist/benchmarks/agentbench.js +10 -0
  15. package/dist/benchmarks/agentbench.js.map +1 -0
  16. package/dist/benchmarks/appworld.d.ts +37 -0
  17. package/dist/benchmarks/appworld.js +14 -0
  18. package/dist/benchmarks/appworld.js.map +1 -0
  19. package/dist/benchmarks/bfcl.d.ts +18 -0
  20. package/dist/benchmarks/bfcl.js +10 -0
  21. package/dist/benchmarks/bfcl.js.map +1 -0
  22. package/dist/benchmarks/cad-design.d.ts +45 -0
  23. package/dist/benchmarks/cad-design.js +7 -0
  24. package/dist/benchmarks/cad-design.js.map +1 -0
  25. package/dist/benchmarks/cadbench.d.ts +19 -0
  26. package/dist/benchmarks/cadbench.js +8 -0
  27. package/dist/benchmarks/cadbench.js.map +1 -0
  28. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  29. package/dist/benchmarks/cadgenbench.js +8 -0
  30. package/dist/benchmarks/cadgenbench.js.map +1 -0
  31. package/dist/benchmarks/commit0.d.ts +31 -0
  32. package/dist/benchmarks/commit0.js +10 -0
  33. package/dist/benchmarks/commit0.js.map +1 -0
  34. package/dist/benchmarks/crag.d.ts +14 -0
  35. package/dist/benchmarks/crag.js +9 -0
  36. package/dist/benchmarks/crag.js.map +1 -0
  37. package/dist/benchmarks/dabstep.d.ts +18 -0
  38. package/dist/benchmarks/dabstep.js +10 -0
  39. package/dist/benchmarks/dabstep.js.map +1 -0
  40. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  41. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  42. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  43. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  44. package/dist/benchmarks/finresearchbench.js +8 -0
  45. package/dist/benchmarks/finresearchbench.js.map +1 -0
  46. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  47. package/dist/benchmarks/finsearchcomp.js +7 -0
  48. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  49. package/dist/benchmarks/frames.d.ts +59 -0
  50. package/dist/benchmarks/frames.js +13 -0
  51. package/dist/benchmarks/frames.js.map +1 -0
  52. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  53. package/dist/benchmarks/hotpotqa.js +15 -0
  54. package/dist/benchmarks/hotpotqa.js.map +1 -0
  55. package/dist/benchmarks/humaneval.d.ts +53 -0
  56. package/dist/benchmarks/humaneval.js +15 -0
  57. package/dist/benchmarks/humaneval.js.map +1 -0
  58. package/dist/benchmarks/mind2web.d.ts +41 -0
  59. package/dist/benchmarks/mind2web.js +9 -0
  60. package/dist/benchmarks/mind2web.js.map +1 -0
  61. package/dist/benchmarks/nomiracl.d.ts +15 -0
  62. package/dist/benchmarks/nomiracl.js +9 -0
  63. package/dist/benchmarks/nomiracl.js.map +1 -0
  64. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  65. package/dist/benchmarks/open-rag-bench.js +9 -0
  66. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  67. package/dist/benchmarks/programbench.d.ts +38 -0
  68. package/dist/benchmarks/programbench.js +10 -0
  69. package/dist/benchmarks/programbench.js.map +1 -0
  70. package/dist/benchmarks/rag-shared.d.ts +42 -0
  71. package/dist/benchmarks/rag-shared.js +39 -0
  72. package/dist/benchmarks/rag-shared.js.map +1 -0
  73. package/dist/benchmarks/ragbench.d.ts +16 -0
  74. package/dist/benchmarks/ragbench.js +9 -0
  75. package/dist/benchmarks/ragbench.js.map +1 -0
  76. package/dist/benchmarks/simpleqa.d.ts +64 -0
  77. package/dist/benchmarks/simpleqa.js +11 -0
  78. package/dist/benchmarks/simpleqa.js.map +1 -0
  79. package/dist/benchmarks/swe-bench.d.ts +56 -0
  80. package/dist/benchmarks/swe-bench.js +14 -0
  81. package/dist/benchmarks/swe-bench.js.map +1 -0
  82. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  83. package/dist/benchmarks/t2-ragbench.js +9 -0
  84. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  85. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  86. package/dist/benchmarks/tau-bench-shared.js +10 -0
  87. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  88. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  89. package/dist/benchmarks/tau2-bench.js +11 -0
  90. package/dist/benchmarks/tau2-bench.js.map +1 -0
  91. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  92. package/dist/benchmarks/tau3-banking.js +9 -0
  93. package/dist/benchmarks/tau3-banking.js.map +1 -0
  94. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  95. package/dist/benchmarks/terminal-bench.js +8 -0
  96. package/dist/benchmarks/terminal-bench.js.map +1 -0
  97. package/dist/benchmarks/toollm.d.ts +16 -0
  98. package/dist/benchmarks/toollm.js +10 -0
  99. package/dist/benchmarks/toollm.js.map +1 -0
  100. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  101. package/dist/benchmarks/trata-hedge.js +7 -0
  102. package/dist/benchmarks/trata-hedge.js.map +1 -0
  103. package/dist/benchmarks/types.d.ts +107 -0
  104. package/dist/benchmarks/types.js +1 -0
  105. package/dist/benchmarks/types.js.map +1 -0
  106. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  107. package/dist/benchmarks/webarena-verified.js +10 -0
  108. package/dist/benchmarks/webarena-verified.js.map +1 -0
  109. package/dist/chunk-2PVVP7GN.js +197 -0
  110. package/dist/chunk-2PVVP7GN.js.map +1 -0
  111. package/dist/chunk-2XU6OGEN.js +170 -0
  112. package/dist/chunk-2XU6OGEN.js.map +1 -0
  113. package/dist/chunk-53UPUNBZ.js +325 -0
  114. package/dist/chunk-53UPUNBZ.js.map +1 -0
  115. package/dist/chunk-5SBJCB6W.js +144 -0
  116. package/dist/chunk-5SBJCB6W.js.map +1 -0
  117. package/dist/chunk-7WSD27QQ.js +118 -0
  118. package/dist/chunk-7WSD27QQ.js.map +1 -0
  119. package/dist/chunk-C7T7WEK2.js +103 -0
  120. package/dist/chunk-C7T7WEK2.js.map +1 -0
  121. package/dist/chunk-CKUVRZ2T.js +251 -0
  122. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  123. package/dist/chunk-HBSWHQNJ.js +30 -0
  124. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  125. package/dist/chunk-HHXFIHXC.js +116 -0
  126. package/dist/chunk-HHXFIHXC.js.map +1 -0
  127. package/dist/chunk-IFAV6KEM.js +276 -0
  128. package/dist/chunk-IFAV6KEM.js.map +1 -0
  129. package/dist/chunk-INNOYXCP.js +387 -0
  130. package/dist/chunk-INNOYXCP.js.map +1 -0
  131. package/dist/chunk-J3KDJNX2.js +182 -0
  132. package/dist/chunk-J3KDJNX2.js.map +1 -0
  133. package/dist/chunk-JRWWGMK7.js +148 -0
  134. package/dist/chunk-JRWWGMK7.js.map +1 -0
  135. package/dist/chunk-JTHWEDEW.js +32 -0
  136. package/dist/chunk-JTHWEDEW.js.map +1 -0
  137. package/dist/chunk-KDIKRJGB.js +120 -0
  138. package/dist/chunk-KDIKRJGB.js.map +1 -0
  139. package/dist/chunk-LRRD7NAG.js +301 -0
  140. package/dist/chunk-LRRD7NAG.js.map +1 -0
  141. package/dist/chunk-ODT47UAY.js +221 -0
  142. package/dist/chunk-ODT47UAY.js.map +1 -0
  143. package/dist/chunk-PA2ZKHJC.js +230 -0
  144. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  145. package/dist/chunk-PPYSEKFM.js +182 -0
  146. package/dist/chunk-PPYSEKFM.js.map +1 -0
  147. package/dist/chunk-PUIRNYI7.js +189 -0
  148. package/dist/chunk-PUIRNYI7.js.map +1 -0
  149. package/dist/chunk-R36V2VP7.js +169 -0
  150. package/dist/chunk-R36V2VP7.js.map +1 -0
  151. package/dist/chunk-R67DFVLO.js +142 -0
  152. package/dist/chunk-R67DFVLO.js.map +1 -0
  153. package/dist/chunk-SEVJPLZC.js +260 -0
  154. package/dist/chunk-SEVJPLZC.js.map +1 -0
  155. package/dist/chunk-SYDW647C.js +318 -0
  156. package/dist/chunk-SYDW647C.js.map +1 -0
  157. package/dist/chunk-TBKU5XQI.js +228 -0
  158. package/dist/chunk-TBKU5XQI.js.map +1 -0
  159. package/dist/chunk-TSWPNOYM.js +147 -0
  160. package/dist/chunk-TSWPNOYM.js.map +1 -0
  161. package/dist/chunk-UAIOHCUK.js +27 -0
  162. package/dist/chunk-UAIOHCUK.js.map +1 -0
  163. package/dist/chunk-UPAMRDX4.js +233 -0
  164. package/dist/chunk-UPAMRDX4.js.map +1 -0
  165. package/dist/chunk-VQRS7VUC.js +342 -0
  166. package/dist/chunk-VQRS7VUC.js.map +1 -0
  167. package/dist/chunk-X3BTXCJ4.js +262 -0
  168. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  169. package/dist/chunk-X5YKXC6V.js +211 -0
  170. package/dist/chunk-X5YKXC6V.js.map +1 -0
  171. package/dist/chunk-Y6O2OCUO.js +130 -0
  172. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  173. package/dist/chunk-YCGY7UIZ.js +208 -0
  174. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  175. package/dist/chunk-Z7ML6L77.js +162 -0
  176. package/dist/chunk-Z7ML6L77.js.map +1 -0
  177. package/dist/chunk-ZEWMTR5M.js +136 -0
  178. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  179. package/dist/index.d.ts +355 -0
  180. package/dist/index.js +1908 -0
  181. package/dist/index.js.map +1 -0
  182. package/fixtures/aec-bench.json +18 -0
  183. package/fixtures/agentbench-dbbench.json +22 -0
  184. package/fixtures/bfcl.json +45 -0
  185. package/fixtures/commit0.json +72 -0
  186. package/fixtures/crag.json +10 -0
  187. package/fixtures/dabstep.json +22 -0
  188. package/fixtures/enterpriseops-gym.json +103 -0
  189. package/fixtures/finresearchbench.json +21 -0
  190. package/fixtures/finsearchcomp.json +66 -0
  191. package/fixtures/frames.json +26 -0
  192. package/fixtures/hotpotqa.json +182 -0
  193. package/fixtures/nomiracl.json +26 -0
  194. package/fixtures/open-rag-bench.json +16 -0
  195. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  196. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  197. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  198. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  199. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  200. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  201. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  202. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  203. package/fixtures/programbench.json +17 -0
  204. package/fixtures/ragbench.json +21 -0
  205. package/fixtures/simpleqa.json +121 -0
  206. package/fixtures/t2-ragbench.json +13 -0
  207. package/fixtures/tau2-bench.json +16 -0
  208. package/fixtures/tau3-banking.json +16 -0
  209. package/fixtures/toollm.json +28 -0
  210. package/fixtures/webarena-verified.json +20 -0
  211. package/package.json +60 -20
  212. package/pier_agents/__init__.py +18 -0
  213. package/pier_agents/candidate_contract.py +755 -0
  214. package/pier_agents/process_boundary.py +321 -0
  215. package/pier_agents/tangle_candidate.py +907 -0
  216. package/pier_agents/workspace_boundary.py +368 -0
  217. package/scripts/appworld_driver.py +359 -0
  218. package/scripts/cadbench_prepare.py +22 -0
  219. package/scripts/cadgenbench_hard_parts.py +48 -0
  220. package/scripts/clbench_codebase_judge.py +73 -0
  221. package/scripts/commit0_judge.py +170 -0
  222. package/scripts/dabstep_judge.py +42 -0
  223. package/scripts/enterpriseops_gym_judge.py +281 -0
  224. package/scripts/programbench_judge.py +120 -0
  225. package/scripts/render-gate-chart.mjs +176 -0
  226. package/scripts/run-package-tests.mjs +56 -0
  227. package/scripts/terminate-pier-trial.mts +66 -0
  228. package/scripts/trata-hedge/README.md +56 -0
  229. package/scripts/trata-hedge/run.sh +60 -0
  230. package/scripts/trata-hedge/solve.py +83 -0
  231. package/scripts/verify-packed-consumer.mjs +235 -0
  232. package/scripts/verify-pier-agent.mts +715 -0
  233. package/scripts/verify-pier-pair.mts +74 -0
  234. package/scripts/verify-pier-recovery.mts +139 -0
  235. package/src/adapters.ts +26 -0
  236. package/src/benchmarks/_harness.test.mts +178 -0
  237. package/src/benchmarks/_harness.ts +239 -16
  238. package/src/benchmarks/agentbench.ts +163 -0
  239. package/src/benchmarks/appworld.test.mts +15 -9
  240. package/src/benchmarks/bfcl.ts +346 -0
  241. package/src/benchmarks/crag.ts +137 -0
  242. package/src/benchmarks/dabstep.test.mts +70 -0
  243. package/src/benchmarks/dabstep.ts +212 -0
  244. package/src/benchmarks/external-adapters.test.mts +150 -0
  245. package/src/benchmarks/finresearchbench.ts +269 -0
  246. package/src/benchmarks/humaneval.ts +20 -8
  247. package/src/benchmarks/nomiracl.ts +180 -0
  248. package/src/benchmarks/open-rag-bench.ts +153 -0
  249. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  250. package/src/benchmarks/rag-shared.ts +327 -0
  251. package/src/benchmarks/ragbench.ts +171 -0
  252. package/src/benchmarks/swe-bench.test.mts +61 -0
  253. package/src/benchmarks/swe-bench.ts +201 -19
  254. package/src/benchmarks/t2-ragbench.ts +166 -0
  255. package/src/benchmarks/tau-bench-shared.ts +214 -0
  256. package/src/benchmarks/tau2-bench.ts +30 -0
  257. package/src/benchmarks/tau3-banking.ts +29 -0
  258. package/src/benchmarks/terminal-bench.test.mts +33 -0
  259. package/src/benchmarks/terminal-bench.ts +23 -8
  260. package/src/benchmarks/toollm.ts +254 -0
  261. package/src/benchmarks/types.ts +42 -0
  262. package/src/benchmarks/webarena-verified.ts +200 -0
  263. package/src/commit0-prereqs.sh +0 -0
  264. package/src/coordination-mcp-container-reach.mts +181 -0
  265. package/src/decoder-live.mts +1 -1
  266. package/src/examples/README.md +103 -39
  267. package/src/examples/benchmark-matrix.mts +101 -0
  268. package/src/examples/lean-proof-gate.README.md +77 -0
  269. package/src/examples/lean-proof-gate.mts +162 -0
  270. package/src/examples/lean-verify.ts +95 -0
  271. package/src/examples/lean.Dockerfile +12 -0
  272. package/src/examples/math-demo.mts +9 -7
  273. package/src/examples/strategy-demo.mts +10 -12
  274. package/src/gate.ts +3 -2
  275. package/src/hev-eval.mts +69 -0
  276. package/src/hev-improve.mts +169 -0
  277. package/src/hev-structural.mts +688 -0
  278. package/src/index.ts +73 -0
  279. package/src/mbpp-structural.mts +662 -0
  280. package/src/pier-agent.test-fixtures.mts +19 -0
  281. package/src/pier-agent.test.mts +363 -0
  282. package/src/pier-agent.ts +657 -0
  283. package/src/pier-result-grader.mjs +30 -0
  284. package/src/pier-result-grader.test.mts +62 -0
  285. package/src/pier-result-grader.ts +108 -0
  286. package/src/pier-task-outcome.test.mts +117 -0
  287. package/src/pier-task-outcome.ts +240 -0
  288. package/src/pier-trial-controller.test.mts +412 -0
  289. package/src/pier-trial-controller.ts +858 -0
  290. package/src/pier-trial-supervisor.mjs +352 -0
  291. package/src/resolve-client.ts +25 -2
  292. package/src/run-benchmarks-cli.mts +72 -0
  293. package/src/run-benchmarks-report.ts +66 -0
  294. package/src/run-benchmarks.test.mts +231 -0
  295. package/src/run-benchmarks.ts +589 -0
  296. package/src/smoke-structural-rollout.mts +393 -0
  297. package/src/swe-bench-env.test.ts +207 -0
  298. package/src/swe-bench-env.ts +554 -0
  299. package/src/swe-jail.ts +293 -0
  300. package/src/swe-self-improve.mts +84 -0
  301. package/src/swe-structural-judge-policy.test.ts +117 -0
  302. package/src/swe-structural-judge-policy.ts +133 -0
  303. package/src/swe-structural-policy.test.ts +124 -0
  304. package/src/swe-structural-policy.ts +132 -0
  305. package/src/swe-structural-provenance.test.ts +93 -0
  306. package/src/swe-structural-provenance.ts +138 -0
  307. package/src/swe-structural.mts +1260 -0
  308. package/src/swe-temp.ts +14 -0
  309. package/src/tb-container-executor.mts +234 -0
  310. package/src/tb-container-executor.test.mts +99 -0
  311. package/src/tb-supervisor-sidecar.mts +222 -0
  312. package/src/trata-gepa.mts +1 -1
  313. package/steerers/eops-itsm-population.json +1 -0
  314. package/tb_agents/opencode_refine_agent.py +117 -0
  315. package/tb_agents/opencode_router_agent.py +406 -0
  316. package/tb_agents/opencode_supervisor_agent.py +239 -0
  317. package/tb_agents/script_agent.py +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/benchmarks/tau-bench-shared.ts"],"sourcesContent":["/**\n * Shared tau-bench adapter spine.\n *\n * tau2 and tau3 live in the same upstream repository/package namespace today:\n * `sierra-research/tau2-bench`, Python package `tau2`. Keep one implementation\n * for task loading and reward recomputation so the domain/version adapters only\n * choose env names, default domain, and fixture file.\n */\n\nimport { readFile, stat } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport type { OutputAdapter } from '@tangle-network/agent-runtime/loops'\nimport { runVenvPython } from './_harness'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nexport interface TauBenchConfig {\n name: string\n fixturePath: string\n fixturesEnv: string\n dirEnv: string\n domainEnv: string\n defaultDomain: string\n installHint: string\n taskIntro: string\n}\n\ninterface TauRow {\n id: string\n domain: string\n user_scenario?: unknown\n description?: unknown\n evaluation_criteria?: unknown\n}\n\ninterface TauMeta {\n taskId: string\n domain: string\n split?: string\n userScenario?: unknown\n description?: unknown\n evaluationCriteria?: unknown\n}\n\nexport const tauResultsOutput: 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|path|json)?\\s*\\n([\\s\\S]*?)```/g)]\n return (fences.at(-1)?.[1] ?? text).trim()\n },\n}\n\nasync function assertPath(path: string, label: string, benchName: string): Promise<void> {\n try {\n await stat(path)\n } catch (err) {\n throw new Error(`${benchName}: missing ${label} at ${path} (${err instanceof Error ? err.message : err})`)\n }\n}\n\nfunction benchDir(config: TauBenchConfig): string | undefined {\n return process.env[config.dirEnv]\n}\n\nfunction benchDomain(config: TauBenchConfig): string {\n return process.env[config.domainEnv] ?? config.defaultDomain\n}\n\nfunction rowToTask(row: TauRow, config: TauBenchConfig, split?: string): BenchTask {\n const meta: TauMeta = {\n taskId: row.id,\n domain: row.domain,\n split,\n userScenario: row.user_scenario,\n description: row.description,\n evaluationCriteria: row.evaluation_criteria,\n }\n return {\n id: row.id,\n split,\n prompt: [\n config.taskIntro,\n `Run this task in the official ${row.domain} domain.`,\n 'The benchmark is a simulated multi-turn user/tool conversation.',\n '',\n typeof row.user_scenario === 'string' ? row.user_scenario : JSON.stringify(row.user_scenario ?? {}, null, 2),\n '',\n 'Return the path to the official tau results.json or trajectory file containing this task run.',\n ].join('\\n'),\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask, benchName: string): TauMeta {\n const md = task.metadata\n if (!md || typeof md.taskId !== 'string' || typeof md.domain !== 'string') {\n throw new Error(`${benchName} task ${task.id} missing metadata — loadTasks did not populate it`)\n }\n return md as unknown as TauMeta\n}\n\nfunction selectRows(rows: TauRow[], opts: LoadOptions, config: TauBenchConfig, split?: string): BenchTask[] {\n let tasks = rows.map((row) => rowToTask(row, config, split))\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(`${config.name}: no tasks matched ${JSON.stringify(opts)}`)\n return tasks\n}\n\nasync function loadFixtures(config: TauBenchConfig, opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(config.fixturePath, 'utf8')) as TauRow[]\n console.warn(`[${config.name}] ${config.fixturesEnv}=1 — loading ${rows.length} adapter fixtures`)\n return selectRows(rows, opts, config, opts.split)\n}\n\nasync function loadOfficialTasks(config: TauBenchConfig, root: string, opts: LoadOptions): Promise<BenchTask[]> {\n const domain = benchDomain(config)\n const script = `\nimport json, sys\nfrom pathlib import Path\nroot = Path(sys.argv[1])\ndomain = sys.argv[2]\nsplit = sys.argv[3] or None\nsys.path.insert(0, str(root / \"src\"))\nfrom tau2.registry import registry\nloader = registry.get_tasks_loader(domain)\ntasks = loader(split)\nrows = []\nfor task in tasks:\n row = task.model_dump(mode=\"json\")\n row[\"domain\"] = domain\n rows.append(row)\nprint(json.dumps(rows))\n`\n const stdout = await runVenvPython(script, [root, domain, opts.split ?? ''])\n return selectRows(JSON.parse(stdout) as TauRow[], opts, config, opts.split)\n}\n\nasync function scoreOfficialTrajectory(root: string, meta: TauMeta, artifactPath: string): Promise<Record<string, unknown>> {\n const script = `\nimport json, sys\nfrom pathlib import Path\nroot = Path(sys.argv[1])\ntask_id = sys.argv[2]\nartifact = Path(sys.argv[3])\nsys.path.insert(0, str(root / \"src\"))\nfrom tau2.data_model.simulation import Results\nfrom tau2.scripts.evaluate_trajectories import compute_simulation_rewards\nresults = Results.load(artifact)\nupdated = compute_simulation_rewards(results)\nscores = []\nfor sim in updated.simulations:\n if sim.task_id == task_id and sim.reward_info is not None:\n scores.append(float(sim.reward_info.reward))\nif not scores:\n raise SystemExit(f\"no scored simulations for task_id={task_id} in {artifact}\")\nprint(json.dumps({\"count\": len(scores), \"score\": sum(scores) / len(scores), \"scores\": scores}))\n`\n const stdout = await runVenvPython(script, [root, meta.taskId, artifactPath], 0)\n return JSON.parse(stdout.trim().split('\\n').at(-1) ?? '{}') as Record<string, unknown>\n}\n\nexport function createTauBenchAdapter(config: TauBenchConfig): BenchmarkAdapter {\n const fixturesMode = process.env[config.fixturesEnv] === '1'\n\n return {\n name: config.name,\n output: tauResultsOutput,\n\n async preflight() {\n if (fixturesMode) return\n const dir = benchDir(config)\n if (!dir) {\n throw new Error(`${config.dirEnv} is required. Fix: ${config.installHint}`)\n }\n await assertPath(`${dir}/src/tau2/registry.py`, 'tau registry', config.name)\n await loadOfficialTasks(config, dir, { limit: 1 })\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n if (fixturesMode) return loadFixtures(config, opts)\n const dir = benchDir(config)\n if (!dir) throw new Error(`${config.dirEnv} is required to load official ${config.name} tasks`)\n return loadOfficialTasks(config, dir, opts)\n },\n\n async goldArtifact() {\n return undefined\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const dir = benchDir(config)\n if (!dir) throw new Error(`${config.dirEnv} is required to judge ${config.name} trajectory artifacts`)\n const meta = readMeta(task, config.name)\n const artifactPath = resolve(artifact.trim())\n await assertPath(artifactPath, 'tau results/trajectory artifact', config.name)\n const report = await scoreOfficialTrajectory(dir, meta, artifactPath)\n const score = typeof report.score === 'number' ? report.score : 0\n return {\n resolved: score === 1,\n score,\n detail: JSON.stringify({ taskId: meta.taskId, domain: meta.domain, count: report.count }),\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,142 @@
1
+ import {
2
+ FINAL_ANSWER_SENTINEL,
3
+ allStrings,
4
+ answerScoreToBenchScore,
5
+ contextBlock,
6
+ contextsFrom,
7
+ firstString,
8
+ isObject,
9
+ ragAnswerOutput,
10
+ readJsonRows,
11
+ scoreAnswerArtifact,
12
+ selectTasks,
13
+ stringFrom
14
+ } from "./chunk-X3BTXCJ4.js";
15
+ import {
16
+ benchRoot
17
+ } from "./chunk-LRRD7NAG.js";
18
+
19
+ // src/benchmarks/ragbench.ts
20
+ import { readFile } from "fs/promises";
21
+ import { join } from "path";
22
+ var FIXTURES = join(benchRoot, "fixtures", "ragbench.json");
23
+ var dataFile = () => process.env.RAGBENCH_DATA_FILE;
24
+ function rowToTask(raw, index) {
25
+ if (!isObject(raw)) throw new Error(`RAGBench row ${index} must be an object`);
26
+ const query = firstString(raw, ["question", "query", "user_input", "prompt"]);
27
+ const goldAnswers = allStrings(raw, [
28
+ "response",
29
+ "responses",
30
+ "model_response",
31
+ "generated_response",
32
+ "reference",
33
+ "references",
34
+ "reference_answer",
35
+ "reference_answers",
36
+ "answer",
37
+ "answers",
38
+ "gold",
39
+ "gold_answer",
40
+ "ground_truth",
41
+ "expected_answer"
42
+ ]);
43
+ if (!query) throw new Error(`RAGBench row ${index} missing question/query`);
44
+ if (goldAnswers.length === 0) throw new Error(`RAGBench row ${index} missing reference answer`);
45
+ const contexts = contextsFrom(raw.contexts).length > 0 ? contextsFrom(raw.contexts) : contextsFrom(raw.retrieved_contexts).length > 0 ? contextsFrom(raw.retrieved_contexts) : contextsFrom(raw.documents);
46
+ const traceLabels = {};
47
+ for (const key of [
48
+ "adherence",
49
+ "completeness",
50
+ "relevance",
51
+ "utilization",
52
+ "all_relevant_sentence_keys",
53
+ "all_utilized_sentence_keys"
54
+ ]) {
55
+ if (raw[key] !== void 0) traceLabels[key] = raw[key];
56
+ }
57
+ const dataset = stringFrom(raw.dataset) ?? stringFrom(raw.source_dataset);
58
+ const id = stringFrom(raw.id) ?? stringFrom(raw.example_id) ?? `ragbench-${index}`;
59
+ const meta = {
60
+ benchmark: "ragbench",
61
+ query,
62
+ goldAnswers,
63
+ contexts,
64
+ ...dataset ? { dataset } : {},
65
+ traceLabels
66
+ };
67
+ return {
68
+ id,
69
+ split: stringFrom(raw.split) ?? dataset ?? "ragbench",
70
+ prompt: [
71
+ "Answer this RAGBench question using the supplied retrieved context.",
72
+ "End with a single final line: `FINAL ANSWER: <answer>`.",
73
+ "",
74
+ `Question: ${query}`,
75
+ contexts.length > 0 ? `
76
+ Retrieved context:
77
+ ${contextBlock(contexts)}` : void 0
78
+ ].filter(Boolean).join("\n"),
79
+ metadata: meta
80
+ };
81
+ }
82
+ function readMeta(task) {
83
+ const md = task.metadata;
84
+ if (!md || !Array.isArray(md.goldAnswers)) {
85
+ throw new Error(`RAGBench task ${task.id} missing metadata \u2014 loadTasks did not populate it`);
86
+ }
87
+ return md;
88
+ }
89
+ async function loadRows(path) {
90
+ const rows = await readJsonRows(path);
91
+ if (rows.length === 0) throw new Error(`RAGBench: no rows in ${path}`);
92
+ return rows;
93
+ }
94
+ async function loadFixtures(opts) {
95
+ const rows = JSON.parse(await readFile(FIXTURES, "utf8"));
96
+ console.warn(`[ragbench] RAGBENCH_FIXTURES=1 \u2014 loading ${rows.length} adapter fixtures`);
97
+ return selectTasks(rows.map(rowToTask), opts, "RAGBench");
98
+ }
99
+ function createRagBenchAdapter() {
100
+ const fixturesMode = process.env.RAGBENCH_FIXTURES === "1";
101
+ return {
102
+ name: "ragbench",
103
+ output: ragAnswerOutput,
104
+ async preflight() {
105
+ if (fixturesMode) {
106
+ await readFile(FIXTURES, "utf8");
107
+ return;
108
+ }
109
+ const path = dataFile();
110
+ if (!path) {
111
+ throw new Error(
112
+ "RAGBENCH_DATA_FILE is required. Fix: export rungalileo/ragbench rows to JSONL and set RAGBENCH_DATA_FILE=/path/to/ragbench.jsonl, or set RAGBENCH_FIXTURES=1 for adapter plumbing."
113
+ );
114
+ }
115
+ await loadRows(path);
116
+ },
117
+ async loadTasks(opts = {}) {
118
+ if (fixturesMode) return loadFixtures(opts);
119
+ const path = dataFile();
120
+ if (!path) throw new Error("RAGBENCH_DATA_FILE is required to load RAGBench tasks");
121
+ return selectTasks((await loadRows(path)).map(rowToTask), opts, "RAGBench");
122
+ },
123
+ async goldArtifact(task) {
124
+ return `${FINAL_ANSWER_SENTINEL} ${readMeta(task).goldAnswers[0] ?? ""}`;
125
+ },
126
+ async judge(task, artifact) {
127
+ const meta = readMeta(task);
128
+ const score = scoreAnswerArtifact(artifact, meta.goldAnswers);
129
+ return answerScoreToBenchScore(score, {
130
+ benchmark: meta.benchmark,
131
+ dataset: meta.dataset ?? null,
132
+ traceLabels: meta.traceLabels,
133
+ contextCount: meta.contexts.length
134
+ });
135
+ }
136
+ };
137
+ }
138
+
139
+ export {
140
+ createRagBenchAdapter
141
+ };
142
+ //# sourceMappingURL=chunk-R67DFVLO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/benchmarks/ragbench.ts"],"sourcesContent":["/**\n * RAGBench-compatible adapter.\n *\n * Live mode expects a local JSON/JSONL export from rungalileo/ragbench or a\n * compatible table. Rows must carry a query and at least one reference answer.\n * Contexts, TRACe labels, and source metadata are preserved in task metadata\n * for diagnostics; the deterministic judge scores the worker's final answer\n * against the reference answer(s).\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { benchRoot } from './_harness'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\nimport {\n FINAL_ANSWER_SENTINEL,\n allStrings,\n answerScoreToBenchScore,\n contextBlock,\n contextsFrom,\n firstString,\n isObject,\n ragAnswerOutput,\n readJsonRows,\n scoreAnswerArtifact,\n selectTasks,\n stringFrom,\n type RagContext,\n} from './rag-shared'\n\nconst FIXTURES = join(benchRoot, 'fixtures', 'ragbench.json')\n\ninterface RagBenchMeta {\n benchmark: 'ragbench'\n query: string\n goldAnswers: string[]\n contexts: RagContext[]\n dataset?: string\n traceLabels: Record<string, unknown>\n}\n\nconst dataFile = (): string | undefined => process.env.RAGBENCH_DATA_FILE\n\nfunction rowToTask(raw: unknown, index: number): BenchTask {\n if (!isObject(raw)) throw new Error(`RAGBench row ${index} must be an object`)\n const query = firstString(raw, ['question', 'query', 'user_input', 'prompt'])\n const goldAnswers = allStrings(raw, [\n 'response',\n 'responses',\n 'model_response',\n 'generated_response',\n 'reference',\n 'references',\n 'reference_answer',\n 'reference_answers',\n 'answer',\n 'answers',\n 'gold',\n 'gold_answer',\n 'ground_truth',\n 'expected_answer',\n ])\n if (!query) throw new Error(`RAGBench row ${index} missing question/query`)\n if (goldAnswers.length === 0) throw new Error(`RAGBench row ${index} missing reference answer`)\n const contexts =\n contextsFrom(raw.contexts).length > 0\n ? contextsFrom(raw.contexts)\n : contextsFrom(raw.retrieved_contexts).length > 0\n ? contextsFrom(raw.retrieved_contexts)\n : contextsFrom(raw.documents)\n const traceLabels: Record<string, unknown> = {}\n for (const key of [\n 'adherence',\n 'completeness',\n 'relevance',\n 'utilization',\n 'all_relevant_sentence_keys',\n 'all_utilized_sentence_keys',\n ]) {\n if (raw[key] !== undefined) traceLabels[key] = raw[key]\n }\n const dataset = stringFrom(raw.dataset) ?? stringFrom(raw.source_dataset)\n const id = stringFrom(raw.id) ?? stringFrom(raw.example_id) ?? `ragbench-${index}`\n const meta: RagBenchMeta = {\n benchmark: 'ragbench',\n query,\n goldAnswers,\n contexts,\n ...(dataset ? { dataset } : {}),\n traceLabels,\n }\n return {\n id,\n split: stringFrom(raw.split) ?? dataset ?? 'ragbench',\n prompt: [\n 'Answer this RAGBench question using the supplied retrieved context.',\n 'End with a single final line: `FINAL ANSWER: <answer>`.',\n '',\n `Question: ${query}`,\n contexts.length > 0 ? `\\nRetrieved context:\\n${contextBlock(contexts)}` : undefined,\n ]\n .filter(Boolean)\n .join('\\n'),\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask): RagBenchMeta {\n const md = task.metadata\n if (!md || !Array.isArray(md.goldAnswers)) {\n throw new Error(`RAGBench task ${task.id} missing metadata — loadTasks did not populate it`)\n }\n return md as unknown as RagBenchMeta\n}\n\nasync function loadRows(path: string): Promise<unknown[]> {\n const rows = await readJsonRows(path)\n if (rows.length === 0) throw new Error(`RAGBench: no rows in ${path}`)\n return rows\n}\n\nasync function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as unknown[]\n console.warn(`[ragbench] RAGBENCH_FIXTURES=1 — loading ${rows.length} adapter fixtures`)\n return selectTasks(rows.map(rowToTask), opts, 'RAGBench')\n}\n\nexport function createRagBenchAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.RAGBENCH_FIXTURES === '1'\n\n return {\n name: 'ragbench',\n output: ragAnswerOutput,\n\n async preflight() {\n if (fixturesMode) {\n await readFile(FIXTURES, 'utf8')\n return\n }\n const path = dataFile()\n if (!path) {\n throw new Error(\n 'RAGBENCH_DATA_FILE is required. Fix: export rungalileo/ragbench rows to JSONL and set RAGBENCH_DATA_FILE=/path/to/ragbench.jsonl, or set RAGBENCH_FIXTURES=1 for adapter plumbing.',\n )\n }\n await loadRows(path)\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n if (fixturesMode) return loadFixtures(opts)\n const path = dataFile()\n if (!path) throw new Error('RAGBENCH_DATA_FILE is required to load RAGBench tasks')\n return selectTasks((await loadRows(path)).map(rowToTask), opts, 'RAGBench')\n },\n\n async goldArtifact(task: BenchTask) {\n return `${FINAL_ANSWER_SENTINEL} ${readMeta(task).goldAnswers[0] ?? ''}`\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = readMeta(task)\n const score = scoreAnswerArtifact(artifact, meta.goldAnswers)\n return answerScoreToBenchScore(score, {\n benchmark: meta.benchmark,\n dataset: meta.dataset ?? null,\n traceLabels: meta.traceLabels,\n contextCount: meta.contexts.length,\n })\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,260 @@
1
+ // src/benchmarks/simpleqa.ts
2
+ import { execFile } from "child_process";
3
+ import { readFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { promisify } from "util";
7
+ var execFileAsync = promisify(execFile);
8
+ var BENCH_ROOT = fileURLToPath(new URL("../..", import.meta.url));
9
+ var PY = join(BENCH_ROOT, ".venv", "bin", "python");
10
+ var FIXTURES = join(BENCH_ROOT, "fixtures", "simpleqa.json");
11
+ var DATASET_URL = "https://openaipublic.blob.core.windows.net/simple-evals/simple_qa_test_set.csv";
12
+ var FINAL_ANSWER_SENTINEL = "FINAL ANSWER:";
13
+ var WORKER_CONTRACT = [
14
+ "",
15
+ "Research the question using live web sources and answer it with a short, specific factual value.",
16
+ "Cite the sources you used as full URLs, one per line, in a `CITATIONS:` block.",
17
+ `End your response with a single final line: \`${FINAL_ANSWER_SENTINEL} <answer>\``,
18
+ "The answer after the sentinel must be the bare value only (no explanation on that line).",
19
+ "If you do not know the answer, state that you do not know rather than guessing."
20
+ ].join("\n");
21
+ async function py(script, args = []) {
22
+ const { stdout } = await execFileAsync(PY, ["-c", script, ...args], {
23
+ maxBuffer: 1024 * 1024 * 256
24
+ });
25
+ return stdout;
26
+ }
27
+ function parseFinalAnswer(artifact) {
28
+ const lines = artifact.split(/\r?\n/);
29
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
30
+ const line = lines[i] ?? "";
31
+ const idx = line.toUpperCase().indexOf(FINAL_ANSWER_SENTINEL);
32
+ if (idx !== -1) return line.slice(idx + FINAL_ANSWER_SENTINEL.length).trim();
33
+ }
34
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
35
+ const t = (lines[i] ?? "").trim();
36
+ if (t.length > 0) return t;
37
+ }
38
+ return "";
39
+ }
40
+ function parseCitations(artifact) {
41
+ const urls = /* @__PURE__ */ new Set();
42
+ for (const m of artifact.matchAll(/https?:\/\/[^\s)<>"']+/g)) {
43
+ urls.add(m[0].replace(/[.,;]+$/, ""));
44
+ }
45
+ return [...urls];
46
+ }
47
+ var GRADER_PROMPT = (question, gold, predicted) => [
48
+ "Your job is to grade a predicted answer to a factual question against the single gold target answer.",
49
+ "Grade the prediction into exactly one of three categories: CORRECT, INCORRECT, or NOT_ATTEMPTED.",
50
+ "First, follow these grading rules carefully.",
51
+ "",
52
+ "CORRECT:",
53
+ " The predicted answer fully contains the gold target without contradicting it.",
54
+ " Predictions with more detail than the gold are still CORRECT, as long as no part contradicts the gold.",
55
+ ' Hedging that still commits to the gold value (e.g. "I think it is X" where X is the gold) is CORRECT.',
56
+ " Minor formatting, capitalization, ordering, or obvious-typo differences do not matter.",
57
+ " For numeric/date targets, the prediction is CORRECT only if it matches the gold to the precision the gold specifies.",
58
+ "",
59
+ "INCORRECT:",
60
+ " The predicted answer contradicts the gold target in any way, even if the contradiction is hedged.",
61
+ " A prediction that states a different factual value than the gold is INCORRECT.",
62
+ "",
63
+ "NOT_ATTEMPTED:",
64
+ " The gold target is neither fully given nor contradicted: the prediction declines to answer, says it does not know,",
65
+ " asks for clarification, or gives a non-committal, vague, or empty response that commits to no specific value.",
66
+ "",
67
+ "Also note the following rules:",
68
+ " Do not punish a prediction for omitting information that would not change whether the gold target is contained.",
69
+ " Grade ONLY whether the gold value is present and uncontradicted \u2014 not the overall quality of the response.",
70
+ "",
71
+ `Question: ${question}`,
72
+ `Gold target: ${gold}`,
73
+ `Predicted answer: ${predicted}`,
74
+ "",
75
+ "Respond with ONLY a fenced JSON block and nothing else:",
76
+ "```json",
77
+ '{"grade": "CORRECT" | "INCORRECT" | "NOT_ATTEMPTED"}',
78
+ "```"
79
+ ].join("\n");
80
+ function graderRouter() {
81
+ const key = process.env.TANGLE_API_KEY;
82
+ if (!key) throw new Error("TANGLE_API_KEY is required for the SimpleQA grader (set the Tangle API key)");
83
+ const model = process.env.JUDGE_MODEL ?? "deepseek-v4-flash";
84
+ const baseUrl = process.env.ROUTER_BASE ?? "https://router.tangle.tools/v1";
85
+ return { baseUrl, key, model };
86
+ }
87
+ async function gradeAnswer(question, gold, predicted, router) {
88
+ const res = await fetch(`${router.baseUrl}/chat/completions`, {
89
+ method: "POST",
90
+ headers: { "content-type": "application/json", authorization: `Bearer ${router.key}` },
91
+ body: JSON.stringify({
92
+ model: router.model,
93
+ temperature: 0,
94
+ messages: [{ role: "user", content: GRADER_PROMPT(question, gold, predicted) }]
95
+ })
96
+ });
97
+ if (!res.ok) {
98
+ throw new Error(`SimpleQA grader HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
99
+ }
100
+ const body = await res.json();
101
+ const content = body.choices?.[0]?.message?.content;
102
+ if (typeof content !== "string") {
103
+ throw new Error(`SimpleQA grader returned no message content: ${JSON.stringify(body).slice(0, 300)}`);
104
+ }
105
+ const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/);
106
+ const raw = (fenced ? fenced[1] : content)?.trim() ?? "";
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse(raw);
110
+ } catch {
111
+ throw new Error(`SimpleQA grader produced unparseable output (no JSON grade): ${content.slice(0, 300)}`);
112
+ }
113
+ if (parsed.grade === "CORRECT" || parsed.grade === "INCORRECT" || parsed.grade === "NOT_ATTEMPTED") {
114
+ return parsed.grade;
115
+ }
116
+ throw new Error(
117
+ `SimpleQA grader grade not in {CORRECT,INCORRECT,NOT_ATTEMPTED}: ${JSON.stringify(parsed).slice(0, 200)}`
118
+ );
119
+ }
120
+ function rowToTask(row, index) {
121
+ const meta = {
122
+ gold: row.answer,
123
+ goldSources: row.urls,
124
+ topic: row.topic,
125
+ answerType: row.answer_type,
126
+ question: row.problem
127
+ };
128
+ return {
129
+ id: `simpleqa-${index}`,
130
+ split: "test",
131
+ prompt: row.problem + WORKER_CONTRACT,
132
+ metadata: meta
133
+ };
134
+ }
135
+ function readMeta(task) {
136
+ const md = task.metadata;
137
+ if (!md || typeof md.gold !== "string" || typeof md.question !== "string") {
138
+ throw new Error(`SimpleQA task ${task.id} missing metadata.gold/question \u2014 loadTasks did not populate it`);
139
+ }
140
+ return md;
141
+ }
142
+ function selectTasks(tasks, opts) {
143
+ if (opts.ids) {
144
+ const want = new Set(opts.ids);
145
+ return tasks.filter((t) => want.has(t.id));
146
+ }
147
+ if (opts.limit !== void 0) return tasks.slice(0, opts.limit);
148
+ return tasks;
149
+ }
150
+ async function loadFixtures(opts) {
151
+ const rows = JSON.parse(await readFile(FIXTURES, "utf8"));
152
+ console.warn(`[simpleqa] SIMPLEQA_FIXTURES=1 \u2014 loading ${rows.length} committed fixtures (no network)`);
153
+ return selectTasks(rows.map(rowToTask), opts);
154
+ }
155
+ async function loadLive(opts) {
156
+ const script = `
157
+ import csv, ast, json, io, sys
158
+ from urllib.request import urlopen
159
+ with urlopen(${JSON.stringify(DATASET_URL)}, timeout=120) as resp:
160
+ text = resp.read().decode('utf-8')
161
+ rows = list(csv.DictReader(io.StringIO(text)))
162
+ out = []
163
+ for r in rows:
164
+ meta = ast.literal_eval(r['metadata']) if r.get('metadata') else {}
165
+ out.append({
166
+ 'problem': r.get('problem', ''),
167
+ 'answer': r.get('answer', ''),
168
+ 'topic': str(meta.get('topic', '')),
169
+ 'answer_type': str(meta.get('answer_type', '')),
170
+ 'urls': list(meta.get('urls', [])),
171
+ })
172
+ print(json.dumps(out))
173
+ `;
174
+ const stdout = await py(script);
175
+ const rows = JSON.parse(stdout);
176
+ return selectTasks(rows.map(rowToTask), opts);
177
+ }
178
+ function createSimpleQaAdapter() {
179
+ const fixturesMode = process.env.SIMPLEQA_FIXTURES === "1";
180
+ return {
181
+ name: "simpleqa",
182
+ async preflight() {
183
+ graderRouter();
184
+ if (fixturesMode) {
185
+ await readFile(FIXTURES, "utf8").catch((err) => {
186
+ throw new Error(
187
+ `SIMPLEQA_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`
188
+ );
189
+ });
190
+ return;
191
+ }
192
+ try {
193
+ await py(
194
+ `from urllib.request import urlopen
195
+ with urlopen(${JSON.stringify(DATASET_URL)}, timeout=120) as resp:
196
+ head = resp.read(64).decode('utf-8', 'replace')
197
+ assert head.startswith('metadata,problem,answer'), 'unexpected CSV header: ' + head[:40]
198
+ print('ok')`
199
+ );
200
+ } catch (err) {
201
+ const msg = err instanceof Error ? err.message : String(err);
202
+ throw new Error(
203
+ `simpleqa preflight failed: ${msg}
204
+ Fix: (1) ensure the bench venv exists (python3 -m venv bench/.venv) ; (2) ensure network access to ${DATASET_URL} ; or set SIMPLEQA_FIXTURES=1 to run against the committed fixtures offline.`
205
+ );
206
+ }
207
+ },
208
+ async loadTasks(opts = {}) {
209
+ if (fixturesMode) return loadFixtures(opts);
210
+ return loadLive(opts);
211
+ },
212
+ async goldArtifact(task) {
213
+ const meta = readMeta(task);
214
+ return `${FINAL_ANSWER_SENTINEL} ${meta.gold}`;
215
+ },
216
+ async judge(task, artifact) {
217
+ const meta = readMeta(task);
218
+ const finalAnswer = parseFinalAnswer(artifact);
219
+ const citations = parseCitations(artifact);
220
+ if (finalAnswer.length === 0) {
221
+ return {
222
+ resolved: false,
223
+ score: 0,
224
+ detail: JSON.stringify({
225
+ grade: "NOT_ATTEMPTED",
226
+ gradeLetter: "C",
227
+ reason: "no parseable answer",
228
+ gold: meta.gold,
229
+ topic: meta.topic,
230
+ answerType: meta.answerType,
231
+ citationCount: citations.length
232
+ })
233
+ };
234
+ }
235
+ const grade = await gradeAnswer(meta.question, meta.gold, finalAnswer, graderRouter());
236
+ const gradeLetter = grade === "CORRECT" ? "A" : grade === "INCORRECT" ? "B" : "C";
237
+ const resolved = grade === "CORRECT";
238
+ return {
239
+ resolved,
240
+ score: resolved ? 1 : 0,
241
+ detail: JSON.stringify({
242
+ grade,
243
+ gradeLetter,
244
+ gold: meta.gold,
245
+ predicted: finalAnswer,
246
+ topic: meta.topic,
247
+ answerType: meta.answerType,
248
+ citationCount: citations.length
249
+ })
250
+ };
251
+ }
252
+ };
253
+ }
254
+
255
+ export {
256
+ parseFinalAnswer,
257
+ parseCitations,
258
+ createSimpleQaAdapter
259
+ };
260
+ //# sourceMappingURL=chunk-SEVJPLZC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/benchmarks/simpleqa.ts"],"sourcesContent":["/**\n * SimpleQA adapter (OpenAI simple-evals; short-form factual QA). Worker artifact\n * = a single free-text final answer string (optionally preceded by a CITATIONS:\n * block). Each item ships a short gold `answer` plus metadata (topic,\n * answer_type, source urls).\n *\n * Judge is the official SimpleQA grader — an LLM classifier that maps\n * (question, gold target, predicted answer) to exactly one of:\n * A = CORRECT — fully contains the gold, no contradiction\n * B = INCORRECT — contradicts / contains a different factual value\n * C = NOT_ATTEMPTED — hedged, non-committal, or no value given\n * resolved = (grade == CORRECT). score = 1 for CORRECT else 0. The grade letter\n * (with its label) rides in `detail`; NOT_ATTEMPTED is surfaced distinctly from\n * INCORRECT so the scorecard can separate abstention from error.\n *\n * There is no deterministic tier: SimpleQA's rubric (containment + abstention)\n * is the grader's job by design, so judge() always calls the pinned grader model\n * (temperature 0) and fails loud on unparseable grader output. The final-answer\n * extraction IS deterministic (FINAL ANSWER: sentinel, fail-closed to '').\n *\n * Requires for a live run: the bench `.venv` with `datasets`/`requests` not\n * needed — the test set is a single public CSV fetched over HTTP — plus a\n * grader key (TANGLE_API_KEY). For offline/CI verification set\n * SIMPLEQA_FIXTURES=1 to load the committed fixtures\n * (bench/fixtures/simpleqa.json) — no network.\n */\n\nimport { execFile } from 'node:child_process'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { promisify } from 'node:util'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst execFileAsync = promisify(execFile)\nconst BENCH_ROOT = fileURLToPath(new URL('../..', import.meta.url))\nconst PY = join(BENCH_ROOT, '.venv', 'bin', 'python')\nconst FIXTURES = join(BENCH_ROOT, 'fixtures', 'simpleqa.json')\n\nconst DATASET_URL =\n 'https://openaipublic.blob.core.windows.net/simple-evals/simple_qa_test_set.csv'\nconst FINAL_ANSWER_SENTINEL = 'FINAL ANSWER:'\n\n/** Worker contract appended to every task prompt. Answer extraction keys off the sentinel. */\nconst WORKER_CONTRACT = [\n '',\n 'Research the question using live web sources and answer it with a short, specific factual value.',\n 'Cite the sources you used as full URLs, one per line, in a `CITATIONS:` block.',\n `End your response with a single final line: \\`${FINAL_ANSWER_SENTINEL} <answer>\\``,\n 'The answer after the sentinel must be the bare value only (no explanation on that line).',\n 'If you do not know the answer, state that you do not know rather than guessing.',\n].join('\\n')\n\n/**\n * Typed seam for the research worker. The benchmark adapter scores a plain\n * `string` artifact (the BenchmarkAdapter contract); the loop worker decodes its\n * agent runs into a {@link ResearchAnswer} and serializes `finalAnswer` (+ an\n * optional `CITATIONS:` block) into that string before judging.\n */\nexport interface ResearchTask {\n id: string\n question: string\n /** Gold short answer — the grader's target. */\n gold: string\n /** Source URLs cited by the dataset for this item — SOFT provenance signal, never a hard gate. */\n goldSources: string[]\n /** SimpleQA topic, e.g. 'Politics' — slices the scorecard by domain. */\n topic: string\n /** SimpleQA answer_type, e.g. 'Person' | 'Date' | 'Number' | 'Place'. */\n answerType: string\n}\n\nexport interface ResearchAnswer {\n finalAnswer: string\n citations: string[]\n}\n\n/** Normalized fixture/loader row — the single shape both the CSV loader and fixtures emit. */\ninterface SimpleQaRow {\n problem: string\n answer: string\n topic: string\n answer_type: string\n urls: string[]\n}\n\ninterface SimpleQaMeta {\n gold: string\n goldSources: string[]\n topic: string\n answerType: string\n question: string\n}\n\n/** The three official SimpleQA grades. */\ntype Grade = 'CORRECT' | 'INCORRECT' | 'NOT_ATTEMPTED'\n\n/** Run the bench venv python with a script; return stdout (throws on nonzero). */\nasync function py(script: string, args: string[] = []): Promise<string> {\n const { stdout } = await execFileAsync(PY, ['-c', script, ...args], {\n maxBuffer: 1024 * 1024 * 256,\n })\n return stdout\n}\n\n/**\n * Parse the worker artifact into the final answer string.\n * Order: the `FINAL ANSWER:` sentinel first (deterministic-extraction discipline);\n * fall back to the last non-empty line. Returns '' when nothing is parseable\n * (fail-closed — never guess); judge() grades '' as NOT_ATTEMPTED.\n */\nexport function parseFinalAnswer(artifact: string): string {\n const lines = artifact.split(/\\r?\\n/)\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const line = lines[i] ?? ''\n const idx = line.toUpperCase().indexOf(FINAL_ANSWER_SENTINEL)\n if (idx !== -1) return line.slice(idx + FINAL_ANSWER_SENTINEL.length).trim()\n }\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const t = (lines[i] ?? '').trim()\n if (t.length > 0) return t\n }\n return ''\n}\n\n/** Extract every bare URL in the artifact (CITATIONS: block + inline). SOFT provenance signal. */\nexport function parseCitations(artifact: string): string[] {\n const urls = new Set<string>()\n for (const m of artifact.matchAll(/https?:\\/\\/[^\\s)<>\"']+/g)) {\n urls.add(m[0].replace(/[.,;]+$/, ''))\n }\n return [...urls]\n}\n\n/**\n * The official SimpleQA grader prompt (faithful paraphrase of OpenAI's\n * simple-evals GRADER_TEMPLATE). Classifies the predicted answer against the\n * gold target into A=CORRECT / B=INCORRECT / C=NOT_ATTEMPTED. The predicted\n * answer must fully contain the gold value with no contradicting statement;\n * hedged or non-committal answers are NOT_ATTEMPTED.\n */\nconst GRADER_PROMPT = (question: string, gold: string, predicted: string): string =>\n [\n 'Your job is to grade a predicted answer to a factual question against the single gold target answer.',\n 'Grade the prediction into exactly one of three categories: CORRECT, INCORRECT, or NOT_ATTEMPTED.',\n 'First, follow these grading rules carefully.',\n '',\n 'CORRECT:',\n ' The predicted answer fully contains the gold target without contradicting it.',\n ' Predictions with more detail than the gold are still CORRECT, as long as no part contradicts the gold.',\n ' Hedging that still commits to the gold value (e.g. \"I think it is X\" where X is the gold) is CORRECT.',\n ' Minor formatting, capitalization, ordering, or obvious-typo differences do not matter.',\n ' For numeric/date targets, the prediction is CORRECT only if it matches the gold to the precision the gold specifies.',\n '',\n 'INCORRECT:',\n ' The predicted answer contradicts the gold target in any way, even if the contradiction is hedged.',\n ' A prediction that states a different factual value than the gold is INCORRECT.',\n '',\n 'NOT_ATTEMPTED:',\n ' The gold target is neither fully given nor contradicted: the prediction declines to answer, says it does not know,',\n ' asks for clarification, or gives a non-committal, vague, or empty response that commits to no specific value.',\n '',\n 'Also note the following rules:',\n ' Do not punish a prediction for omitting information that would not change whether the gold target is contained.',\n ' Grade ONLY whether the gold value is present and uncontradicted — not the overall quality of the response.',\n '',\n `Question: ${question}`,\n `Gold target: ${gold}`,\n `Predicted answer: ${predicted}`,\n '',\n 'Respond with ONLY a fenced JSON block and nothing else:',\n '```json',\n '{\"grade\": \"CORRECT\" | \"INCORRECT\" | \"NOT_ATTEMPTED\"}',\n '```',\n ].join('\\n')\n\ninterface GraderRouter {\n baseUrl: string\n key: string\n model: string\n}\n\nfunction graderRouter(): GraderRouter {\n const key = process.env.TANGLE_API_KEY\n if (!key) throw new Error('TANGLE_API_KEY is required for the SimpleQA grader (set the Tangle API key)')\n const model = process.env.JUDGE_MODEL ?? 'deepseek-v4-flash'\n const baseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'\n return { baseUrl, key, model }\n}\n\n/** Call the official grader. Pinned model, temperature 0; fail loud on unparseable output. */\nasync function gradeAnswer(\n question: string,\n gold: string,\n predicted: string,\n router: GraderRouter,\n): Promise<Grade> {\n const res = await fetch(`${router.baseUrl}/chat/completions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },\n body: JSON.stringify({\n model: router.model,\n temperature: 0,\n messages: [{ role: 'user', content: GRADER_PROMPT(question, gold, predicted) }],\n }),\n })\n if (!res.ok) {\n throw new Error(`SimpleQA grader HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)\n }\n const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }\n const content = body.choices?.[0]?.message?.content\n if (typeof content !== 'string') {\n throw new Error(`SimpleQA grader returned no message content: ${JSON.stringify(body).slice(0, 300)}`)\n }\n const fenced = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/)\n const raw = (fenced ? fenced[1] : content)?.trim() ?? ''\n let parsed: { grade?: unknown }\n try {\n parsed = JSON.parse(raw) as { grade?: unknown }\n } catch {\n throw new Error(`SimpleQA grader produced unparseable output (no JSON grade): ${content.slice(0, 300)}`)\n }\n if (parsed.grade === 'CORRECT' || parsed.grade === 'INCORRECT' || parsed.grade === 'NOT_ATTEMPTED') {\n return parsed.grade\n }\n throw new Error(\n `SimpleQA grader grade not in {CORRECT,INCORRECT,NOT_ATTEMPTED}: ${JSON.stringify(parsed).slice(0, 200)}`,\n )\n}\n\nfunction rowToTask(row: SimpleQaRow, index: number): BenchTask {\n const meta: SimpleQaMeta = {\n gold: row.answer,\n goldSources: row.urls,\n topic: row.topic,\n answerType: row.answer_type,\n question: row.problem,\n }\n return {\n id: `simpleqa-${index}`,\n split: 'test',\n prompt: row.problem + WORKER_CONTRACT,\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction readMeta(task: BenchTask): SimpleQaMeta {\n const md = task.metadata\n if (!md || typeof md.gold !== 'string' || typeof md.question !== 'string') {\n throw new Error(`SimpleQA task ${task.id} missing metadata.gold/question — loadTasks did not populate it`)\n }\n return md as unknown as SimpleQaMeta\n}\n\nfunction selectTasks(tasks: BenchTask[], opts: LoadOptions): BenchTask[] {\n if (opts.ids) {\n const want = new Set(opts.ids)\n return tasks.filter((t) => want.has(t.id))\n }\n if (opts.limit !== undefined) return tasks.slice(0, opts.limit)\n return tasks\n}\n\nasync function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as SimpleQaRow[]\n console.warn(`[simpleqa] SIMPLEQA_FIXTURES=1 — loading ${rows.length} committed fixtures (no network)`)\n return selectTasks(rows.map(rowToTask), opts)\n}\n\n/**\n * Load the live test set: fetch the public CSV via the bench venv python, parse\n * with `csv` (handles quoted/embedded-comma fields) and `ast.literal_eval` for\n * the python-repr `metadata` dict. Emits the same normalized SimpleQaRow shape\n * the fixtures use, so loadTasks/judge share one parse path.\n */\nasync function loadLive(opts: LoadOptions): Promise<BenchTask[]> {\n const script = `\nimport csv, ast, json, io, sys\nfrom urllib.request import urlopen\nwith urlopen(${JSON.stringify(DATASET_URL)}, timeout=120) as resp:\n text = resp.read().decode('utf-8')\nrows = list(csv.DictReader(io.StringIO(text)))\nout = []\nfor r in rows:\n meta = ast.literal_eval(r['metadata']) if r.get('metadata') else {}\n out.append({\n 'problem': r.get('problem', ''),\n 'answer': r.get('answer', ''),\n 'topic': str(meta.get('topic', '')),\n 'answer_type': str(meta.get('answer_type', '')),\n 'urls': list(meta.get('urls', [])),\n })\nprint(json.dumps(out))\n`\n const stdout = await py(script)\n const rows = JSON.parse(stdout) as SimpleQaRow[]\n return selectTasks(rows.map(rowToTask), opts)\n}\n\nexport function createSimpleQaAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.SIMPLEQA_FIXTURES === '1'\n\n return {\n name: 'simpleqa',\n\n async preflight() {\n // The grader router must be configured in both modes — SimpleQA's score is\n // defined by the grader, so a run without it is meaningless.\n graderRouter()\n if (fixturesMode) {\n await readFile(FIXTURES, 'utf8').catch((err) => {\n throw new Error(\n `SIMPLEQA_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`,\n )\n })\n return\n }\n try {\n await py(\n `from urllib.request import urlopen\nwith urlopen(${JSON.stringify(DATASET_URL)}, timeout=120) as resp:\n head = resp.read(64).decode('utf-8', 'replace')\nassert head.startswith('metadata,problem,answer'), 'unexpected CSV header: ' + head[:40]\nprint('ok')`,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(\n `simpleqa preflight failed: ${msg}\\n` +\n `Fix: (1) ensure the bench venv exists (python3 -m venv bench/.venv) ; ` +\n `(2) ensure network access to ${DATASET_URL} ; ` +\n `or set SIMPLEQA_FIXTURES=1 to run against the committed fixtures offline.`,\n )\n }\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n if (fixturesMode) return loadFixtures(opts)\n return loadLive(opts)\n },\n\n async goldArtifact(task: BenchTask) {\n // Gold artifact = the worker-contract serialization of the gold answer, so\n // verify-judge proves gold→CORRECT through the SAME parse path the real\n // artifact takes.\n const meta = readMeta(task)\n return `${FINAL_ANSWER_SENTINEL} ${meta.gold}`\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const meta = readMeta(task)\n const finalAnswer = parseFinalAnswer(artifact)\n const citations = parseCitations(artifact)\n\n if (finalAnswer.length === 0) {\n // No parseable value committed — NOT_ATTEMPTED by construction, no grader call.\n return {\n resolved: false,\n score: 0,\n detail: JSON.stringify({\n grade: 'NOT_ATTEMPTED',\n gradeLetter: 'C',\n reason: 'no parseable answer',\n gold: meta.gold,\n topic: meta.topic,\n answerType: meta.answerType,\n citationCount: citations.length,\n }),\n }\n }\n\n const grade = await gradeAnswer(meta.question, meta.gold, finalAnswer, graderRouter())\n const gradeLetter = grade === 'CORRECT' ? 'A' : grade === 'INCORRECT' ? 'B' : 'C'\n const resolved = grade === 'CORRECT'\n return {\n resolved,\n score: resolved ? 1 : 0,\n detail: JSON.stringify({\n grade,\n gradeLetter,\n gold: meta.gold,\n predicted: finalAnswer,\n topic: meta.topic,\n answerType: meta.answerType,\n citationCount: citations.length,\n }),\n }\n },\n }\n}\n"],"mappings":"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","names":[]}