agentinputlab 0.1.0a1__py3-none-any.whl

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 (469) hide show
  1. agentinputlab/__init__.py +28 -0
  2. agentinputlab/algorithms/__init__.py +6 -0
  3. agentinputlab/algorithms/acquire/__init__.py +145 -0
  4. agentinputlab/algorithms/acquire/adaptive_rag/__init__.py +43 -0
  5. agentinputlab/algorithms/acquire/adaptive_rag/replay.py +367 -0
  6. agentinputlab/algorithms/acquire/adaptive_rag/runtime.py +1202 -0
  7. agentinputlab/algorithms/acquire/adaptive_rag_common_support.py +542 -0
  8. agentinputlab/algorithms/acquire/hyde.py +296 -0
  9. agentinputlab/algorithms/acquire/hyde_controlled.py +200 -0
  10. agentinputlab/algorithms/acquire/hyde_external.py +148 -0
  11. agentinputlab/algorithms/acquire/ircot.py +28 -0
  12. agentinputlab/algorithms/acquire/ircot_common_support.py +478 -0
  13. agentinputlab/algorithms/acquire/ircot_native.py +355 -0
  14. agentinputlab/algorithms/acquire/ircot_replay.py +327 -0
  15. agentinputlab/algorithms/acquire/qrag/__init__.py +79 -0
  16. agentinputlab/algorithms/acquire/qrag/bridge.py +828 -0
  17. agentinputlab/algorithms/acquire/qrag/common_support.py +891 -0
  18. agentinputlab/algorithms/acquire/qrag/native_axis.py +71 -0
  19. agentinputlab/algorithms/acquire/qrag/reader_adapter.py +139 -0
  20. agentinputlab/algorithms/acquire/r2ag/__init__.py +57 -0
  21. agentinputlab/algorithms/acquire/r2ag/bridge.py +1312 -0
  22. agentinputlab/algorithms/acquire/r2ag/common_support.py +1029 -0
  23. agentinputlab/algorithms/acquire/r2ag/socket_runtime.py +255 -0
  24. agentinputlab/algorithms/clarify/__init__.py +61 -0
  25. agentinputlab/algorithms/clarify/_structured.py +64 -0
  26. agentinputlab/algorithms/clarify/active_task_disambiguation.py +371 -0
  27. agentinputlab/algorithms/clarify/ask_or_assume/__init__.py +17 -0
  28. agentinputlab/algorithms/clarify/ask_or_assume/monitor.py +410 -0
  29. agentinputlab/algorithms/clarify/future_turns.py +379 -0
  30. agentinputlab/algorithms/clarify/udcs.py +255 -0
  31. agentinputlab/algorithms/clarify/udcs_deterministic.py +196 -0
  32. agentinputlab/algorithms/curate/__init__.py +65 -0
  33. agentinputlab/algorithms/curate/acon/__init__.py +27 -0
  34. agentinputlab/algorithms/curate/acon/common.py +284 -0
  35. agentinputlab/algorithms/curate/acon/common_support.py +136 -0
  36. agentinputlab/algorithms/curate/acon/history_bridge.py +309 -0
  37. agentinputlab/algorithms/curate/acon/observation_bridge.py +290 -0
  38. agentinputlab/algorithms/curate/llmlingua/__init__.py +30 -0
  39. agentinputlab/algorithms/curate/llmlingua/adapters.py +537 -0
  40. agentinputlab/algorithms/curate/llmlingua/budget.py +48 -0
  41. agentinputlab/algorithms/curate/llmlingua/controlled.py +175 -0
  42. agentinputlab/algorithms/curate/llmlingua/multilingual.py +105 -0
  43. agentinputlab/algorithms/curate/llmlingua/span_map.py +167 -0
  44. agentinputlab/algorithms/curate/recomp/__init__.py +47 -0
  45. agentinputlab/algorithms/curate/recomp/external.py +1129 -0
  46. agentinputlab/algorithms/curate/recomp/selective.py +304 -0
  47. agentinputlab/algorithms/decompose/__init__.py +107 -0
  48. agentinputlab/algorithms/decompose/decomp/__init__.py +53 -0
  49. agentinputlab/algorithms/decompose/decomp/common_support.py +478 -0
  50. agentinputlab/algorithms/decompose/decomp/planner_component.py +199 -0
  51. agentinputlab/algorithms/decompose/decomp/worker_bridge.py +416 -0
  52. agentinputlab/algorithms/decompose/llm_compiler/__init__.py +35 -0
  53. agentinputlab/algorithms/decompose/llm_compiler/bridge.py +520 -0
  54. agentinputlab/algorithms/decompose/llm_compiler/common_support.py +504 -0
  55. agentinputlab/algorithms/decompose/plan_and_budget/__init__.py +43 -0
  56. agentinputlab/algorithms/decompose/plan_and_budget/cached_soft_budget.py +362 -0
  57. agentinputlab/algorithms/decompose/plan_and_budget/decomposer_component.py +107 -0
  58. agentinputlab/algorithms/decompose/plan_and_budget/full.py +205 -0
  59. agentinputlab/algorithms/decompose/plan_and_budget/hard_cap.py +246 -0
  60. agentinputlab/algorithms/decompose/plan_and_budget/regenerated_soft_budget.py +448 -0
  61. agentinputlab/algorithms/decompose/plan_and_solve/__init__.py +33 -0
  62. agentinputlab/algorithms/decompose/plan_and_solve/plan_component.py +192 -0
  63. agentinputlab/algorithms/decompose/plan_and_solve/ps_prompt.py +127 -0
  64. agentinputlab/algorithms/decompose/self_ask/__init__.py +27 -0
  65. agentinputlab/algorithms/decompose/self_ask/loop.py +362 -0
  66. agentinputlab/algorithms/decompose/self_ask/planner_component.py +117 -0
  67. agentinputlab/algorithms/reformulate/__init__.py +85 -0
  68. agentinputlab/algorithms/reformulate/infocqr/__init__.py +40 -0
  69. agentinputlab/algorithms/reformulate/infocqr/cache_replay.py +362 -0
  70. agentinputlab/algorithms/reformulate/infocqr/external.py +675 -0
  71. agentinputlab/algorithms/reformulate/infocqr/student.py +189 -0
  72. agentinputlab/algorithms/reformulate/query2doc/__init__.py +42 -0
  73. agentinputlab/algorithms/reformulate/query2doc/controlled_live.py +190 -0
  74. agentinputlab/algorithms/reformulate/query2doc/models.py +95 -0
  75. agentinputlab/algorithms/reformulate/query2doc/official_cached.py +335 -0
  76. agentinputlab/algorithms/reformulate/query2doc/querygym_bridge.py +223 -0
  77. agentinputlab/algorithms/reformulate/query2doc/querygym_runtime.py +326 -0
  78. agentinputlab/algorithms/reformulate/reformer/__init__.py +23 -0
  79. agentinputlab/algorithms/reformulate/reformer/external.py +417 -0
  80. agentinputlab/algorithms/reformulate/rrr.py +696 -0
  81. agentinputlab/annotation/__init__.py +5 -0
  82. agentinputlab/annotation/independent.py +428 -0
  83. agentinputlab/annotation/server.py +233 -0
  84. agentinputlab/annotation/static/app.js +121 -0
  85. agentinputlab/annotation/static/index.html +60 -0
  86. agentinputlab/annotation/static/review.css +2 -0
  87. agentinputlab/annotation/static/review.html +10 -0
  88. agentinputlab/annotation/static/review.js +11 -0
  89. agentinputlab/annotation/static/styles.css +55 -0
  90. agentinputlab/annotation/store.py +444 -0
  91. agentinputlab/api.py +368 -0
  92. agentinputlab/artifacts/__init__.py +15 -0
  93. agentinputlab/artifacts/json_file.py +104 -0
  94. agentinputlab/artifacts/manifest.py +98 -0
  95. agentinputlab/artifacts/store.py +157 -0
  96. agentinputlab/backends/__init__.py +98 -0
  97. agentinputlab/backends/embedding/__init__.py +25 -0
  98. agentinputlab/backends/embedding/base.py +145 -0
  99. agentinputlab/backends/embedding/callable.py +75 -0
  100. agentinputlab/backends/embedding/replay.py +129 -0
  101. agentinputlab/backends/llm/__init__.py +31 -0
  102. agentinputlab/backends/llm/base.py +149 -0
  103. agentinputlab/backends/llm/callable.py +108 -0
  104. agentinputlab/backends/llm/decomp_completion.py +88 -0
  105. agentinputlab/backends/llm/final_content_stop.py +72 -0
  106. agentinputlab/backends/llm/gpt_oss_final_channel.py +84 -0
  107. agentinputlab/backends/llm/openai_compatible.py +332 -0
  108. agentinputlab/backends/llm/transformers.py +171 -0
  109. agentinputlab/backends/retrieval/__init__.py +35 -0
  110. agentinputlab/backends/retrieval/base.py +50 -0
  111. agentinputlab/backends/retrieval/dense.py +777 -0
  112. agentinputlab/backends/retrieval/memory.py +119 -0
  113. agentinputlab/backends/retrieval/sqlite_fts.py +155 -0
  114. agentinputlab/backends/user/__init__.py +15 -0
  115. agentinputlab/backends/user/base.py +66 -0
  116. agentinputlab/backends/user/callable.py +54 -0
  117. agentinputlab/backends/user/replay.py +89 -0
  118. agentinputlab/backends/vector.py +38 -0
  119. agentinputlab/benchmarks/__init__.py +246 -0
  120. agentinputlab/benchmarks/acquisition.py +106 -0
  121. agentinputlab/benchmarks/ale.py +540 -0
  122. agentinputlab/benchmarks/appworld_ul.py +669 -0
  123. agentinputlab/benchmarks/assets.py +226 -0
  124. agentinputlab/benchmarks/base.py +68 -0
  125. agentinputlab/benchmarks/beir_archive.py +235 -0
  126. agentinputlab/benchmarks/beir_sample.py +269 -0
  127. agentinputlab/benchmarks/bfcl_native.py +245 -0
  128. agentinputlab/benchmarks/bfcl_v3.py +607 -0
  129. agentinputlab/benchmarks/cache.py +218 -0
  130. agentinputlab/benchmarks/clariq.py +203 -0
  131. agentinputlab/benchmarks/data/registry.json +321 -0
  132. agentinputlab/benchmarks/data/smoke_assets.json +511 -0
  133. agentinputlab/benchmarks/data/statebench_v0_8_1_task_ids.json +475 -0
  134. agentinputlab/benchmarks/data/toolsandbox_165848b_scenario_names.json +1 -0
  135. agentinputlab/benchmarks/execution.py +1111 -0
  136. agentinputlab/benchmarks/interactive.py +257 -0
  137. agentinputlab/benchmarks/ir_query.py +204 -0
  138. agentinputlab/benchmarks/longbench.py +203 -0
  139. agentinputlab/benchmarks/paired_replay.py +479 -0
  140. agentinputlab/benchmarks/preprocess.py +2313 -0
  141. agentinputlab/benchmarks/production.py +236 -0
  142. agentinputlab/benchmarks/qa.py +963 -0
  143. agentinputlab/benchmarks/registry.py +110 -0
  144. agentinputlab/benchmarks/retrieval.py +459 -0
  145. agentinputlab/benchmarks/runtime.py +509 -0
  146. agentinputlab/benchmarks/statebench.py +938 -0
  147. agentinputlab/benchmarks/tau3.py +725 -0
  148. agentinputlab/benchmarks/toolsandbox.py +626 -0
  149. agentinputlab/benchmarks/trec_dl_sample.py +263 -0
  150. agentinputlab/campaign.py +654 -0
  151. agentinputlab/campaign_runner.py +953 -0
  152. agentinputlab/cli/__init__.py +6 -0
  153. agentinputlab/cli/main.py +933 -0
  154. agentinputlab/connectors/__init__.py +46 -0
  155. agentinputlab/connectors/knowledge/__init__.py +4 -0
  156. agentinputlab/connectors/tools/__init__.py +30 -0
  157. agentinputlab/connectors/tools/base.py +57 -0
  158. agentinputlab/connectors/tools/mcp.py +222 -0
  159. agentinputlab/connectors/tools/memory.py +41 -0
  160. agentinputlab/connectors/tools/openapi.py +695 -0
  161. agentinputlab/connectors/tools/schema.py +102 -0
  162. agentinputlab/contracts/__init__.py +91 -0
  163. agentinputlab/contracts/availability.py +24 -0
  164. agentinputlab/contracts/dependency.py +58 -0
  165. agentinputlab/contracts/enums.py +82 -0
  166. agentinputlab/contracts/errors.py +63 -0
  167. agentinputlab/contracts/evidence.py +43 -0
  168. agentinputlab/contracts/intent.py +110 -0
  169. agentinputlab/contracts/lifecycle.py +164 -0
  170. agentinputlab/contracts/patch.py +167 -0
  171. agentinputlab/contracts/serialization.py +93 -0
  172. agentinputlab/contracts/state.py +127 -0
  173. agentinputlab/contracts/trace.py +59 -0
  174. agentinputlab/contracts/worker.py +42 -0
  175. agentinputlab/core/__init__.py +7 -0
  176. agentinputlab/data/__init__.py +32 -0
  177. agentinputlab/data/checksums.py +9 -0
  178. agentinputlab/data/downloader.py +235 -0
  179. agentinputlab/data/http.py +171 -0
  180. agentinputlab/data/index.py +21 -0
  181. agentinputlab/data/overlap.py +28 -0
  182. agentinputlab/data/preprocess.py +32 -0
  183. agentinputlab/data/storage.py +57 -0
  184. agentinputlab/data/trec_sample.py +427 -0
  185. agentinputlab/evaluation/__init__.py +5 -0
  186. agentinputlab/evaluation/outcomes.py +32 -0
  187. agentinputlab/experiments/__init__.py +278 -0
  188. agentinputlab/experiments/analysis.py +981 -0
  189. agentinputlab/experiments/artifacts.py +687 -0
  190. agentinputlab/experiments/automatic_measurement.py +678 -0
  191. agentinputlab/experiments/auxiliary_matrix.py +203 -0
  192. agentinputlab/experiments/bindings.py +430 -0
  193. agentinputlab/experiments/blueprints.py +147 -0
  194. agentinputlab/experiments/builders/__init__.py +45 -0
  195. agentinputlab/experiments/builders/executable_external_payload.py +423 -0
  196. agentinputlab/experiments/builders/external_process_arena.py +1018 -0
  197. agentinputlab/experiments/builders/registry_intervention.py +463 -0
  198. agentinputlab/experiments/cli.py +158 -0
  199. agentinputlab/experiments/controls.py +340 -0
  200. agentinputlab/experiments/driver_guard.py +205 -0
  201. agentinputlab/experiments/drivers/__init__.py +89 -0
  202. agentinputlab/experiments/drivers/external_arena.py +2908 -0
  203. agentinputlab/experiments/drivers/method_process.py +662 -0
  204. agentinputlab/experiments/drivers/parity_process.py +1484 -0
  205. agentinputlab/experiments/drivers/static_production.py +1574 -0
  206. agentinputlab/experiments/executor.py +448 -0
  207. agentinputlab/experiments/loader.py +605 -0
  208. agentinputlab/experiments/measurement.py +675 -0
  209. agentinputlab/experiments/paper_analysis.py +1818 -0
  210. agentinputlab/experiments/parity_identity.py +605 -0
  211. agentinputlab/experiments/plan.py +277 -0
  212. agentinputlab/experiments/production_builders.py +3739 -0
  213. agentinputlab/experiments/readiness.py +432 -0
  214. agentinputlab/experiments/schema.py +514 -0
  215. agentinputlab/experiments/secrets.py +177 -0
  216. agentinputlab/experiments/stateful_analysis.py +1738 -0
  217. agentinputlab/experiments/transfer_analysis.py +1316 -0
  218. agentinputlab/experiments/unseen_analysis.py +1025 -0
  219. agentinputlab/integrations/__init__.py +1 -0
  220. agentinputlab/integrations/action_space/__init__.py +1 -0
  221. agentinputlab/integrations/external_workers/__init__.py +76 -0
  222. agentinputlab/integrations/external_workers/ace.py +431 -0
  223. agentinputlab/integrations/external_workers/base.py +181 -0
  224. agentinputlab/integrations/external_workers/decomp_live.py +249 -0
  225. agentinputlab/integrations/external_workers/decomp_planner.py +30 -0
  226. agentinputlab/integrations/external_workers/infocqr_student.py +280 -0
  227. agentinputlab/integrations/external_workers/ircot.py +576 -0
  228. agentinputlab/integrations/external_workers/llmcompiler_live.py +360 -0
  229. agentinputlab/integrations/external_workers/longllmlingua.py +162 -0
  230. agentinputlab/integrations/external_workers/method.py +314 -0
  231. agentinputlab/integrations/external_workers/process.py +678 -0
  232. agentinputlab/integrations/external_workers/supervisor.py +1071 -0
  233. agentinputlab/integrations/full_architecture/__init__.py +1 -0
  234. agentinputlab/integrations/full_architecture/hyde.py +212 -0
  235. agentinputlab/integrations/full_architecture/plan_and_budget.py +188 -0
  236. agentinputlab/integrations/live_history.py +222 -0
  237. agentinputlab/integrations/prompt_optimization/__init__.py +1 -0
  238. agentinputlab/integrations/training/__init__.py +1 -0
  239. agentinputlab/metrics/__init__.py +50 -0
  240. agentinputlab/metrics/catalog.py +313 -0
  241. agentinputlab/metrics/harm.py +95 -0
  242. agentinputlab/metrics/paired.py +40 -0
  243. agentinputlab/metrics/qa.py +76 -0
  244. agentinputlab/metrics/text.py +33 -0
  245. agentinputlab/metrics/third_party.py +161 -0
  246. agentinputlab/models/__init__.py +4 -0
  247. agentinputlab/observability/__init__.py +56 -0
  248. agentinputlab/observability/budget.py +316 -0
  249. agentinputlab/observability/instrumentation.py +754 -0
  250. agentinputlab/observability/models.py +403 -0
  251. agentinputlab/observability/monitor.py +380 -0
  252. agentinputlab/optimizers/__init__.py +1 -0
  253. agentinputlab/optimizers/clarify/__init__.py +12 -0
  254. agentinputlab/optimizers/context/__init__.py +22 -0
  255. agentinputlab/optimizers/decompose/__init__.py +64 -0
  256. agentinputlab/optimizers/evidence/__init__.py +22 -0
  257. agentinputlab/optimizers/rewrite/__init__.py +30 -0
  258. agentinputlab/parity/__init__.py +5 -0
  259. agentinputlab/parity/base.py +206 -0
  260. agentinputlab/production_campaign.py +763 -0
  261. agentinputlab/py.typed +1 -0
  262. agentinputlab/recipes/__init__.py +333 -0
  263. agentinputlab/recipes/ace.py +418 -0
  264. agentinputlab/recipes/ace_bfcl.py +273 -0
  265. agentinputlab/recipes/ace_common_support.py +169 -0
  266. agentinputlab/recipes/ace_native_plan.py +322 -0
  267. agentinputlab/recipes/ace_online.py +711 -0
  268. agentinputlab/recipes/ace_retrieval.py +330 -0
  269. agentinputlab/recipes/ace_statebench.py +382 -0
  270. agentinputlab/recipes/ace_tau.py +463 -0
  271. agentinputlab/recipes/ace_toolsandbox.py +432 -0
  272. agentinputlab/recipes/acon_guideline.py +22 -0
  273. agentinputlab/recipes/adaptive_rag.py +822 -0
  274. agentinputlab/recipes/llmlingua2_inference.py +146 -0
  275. agentinputlab/recipes/llmlingua2_training.py +796 -0
  276. agentinputlab/recipes/mce.py +1486 -0
  277. agentinputlab/recipes/mce_final.py +335 -0
  278. agentinputlab/recipes/mce_text_fit.py +499 -0
  279. agentinputlab/recipes/mce_text_only.py +298 -0
  280. agentinputlab/recipes/qrag.py +729 -0
  281. agentinputlab/recipes/qrag_inference.py +364 -0
  282. agentinputlab/recipes/r2ag_features.py +236 -0
  283. agentinputlab/recipes/r2ag_inference.py +503 -0
  284. agentinputlab/recipes/r2ag_training.py +948 -0
  285. agentinputlab/recipes/recomp_abstractive.py +592 -0
  286. agentinputlab/recipes/recomp_extractive.py +525 -0
  287. agentinputlab/recipes/reformer.py +17 -0
  288. agentinputlab/recipes/rqrag.py +1516 -0
  289. agentinputlab/recipes/rqrag_common_support.py +193 -0
  290. agentinputlab/recipes/rqrag_completion.py +147 -0
  291. agentinputlab/recipes/skillopt.py +462 -0
  292. agentinputlab/recipes/skillopt_common_support.py +154 -0
  293. agentinputlab/recipes/skillopt_environment.py +165 -0
  294. agentinputlab/recipes/skillopt_environment_cli.py +5 -0
  295. agentinputlab/recipes/skillopt_retrieval.py +188 -0
  296. agentinputlab/recipes/smartsearch.py +897 -0
  297. agentinputlab/recipes/smartsearch_curriculum.py +283 -0
  298. agentinputlab/recipes/smartsearch_grpo_contracts.py +58 -0
  299. agentinputlab/recipes/smartsearch_grpo_data.py +314 -0
  300. agentinputlab/recipes/smartsearch_grpo_meter.py +324 -0
  301. agentinputlab/recipes/smartsearch_grpo_prefix.py +144 -0
  302. agentinputlab/recipes/smartsearch_grpo_rollout.py +119 -0
  303. agentinputlab/recipes/smartsearch_grpo_tools.py +364 -0
  304. agentinputlab/recipes/smartsearch_grpo_training.py +305 -0
  305. agentinputlab/recipes/smartsearch_grpo_transport.py +248 -0
  306. agentinputlab/recipes/smartsearch_grpo_worker.py +364 -0
  307. agentinputlab/recipes/smartsearch_inference.py +524 -0
  308. agentinputlab/recipes/smartsearch_native_data.py +172 -0
  309. agentinputlab/recipes/smartsearch_policy.py +616 -0
  310. agentinputlab/recipes/smartsearch_policy_worker.py +9 -0
  311. agentinputlab/recipes/smartsearch_sampling.py +232 -0
  312. agentinputlab/recipes/smartsearch_teacher.py +203 -0
  313. agentinputlab/recipes/smartsearch_training.py +265 -0
  314. agentinputlab/registry/__init__.py +46 -0
  315. agentinputlab/registry/beta.py +727 -0
  316. agentinputlab/registry/data/algorithms.json +1959 -0
  317. agentinputlab/registry/data/auxiliary_method_profiles.json +34 -0
  318. agentinputlab/registry/loader.py +100 -0
  319. agentinputlab/registry/result_manifest.py +184 -0
  320. agentinputlab/registry/runtime.py +195 -0
  321. agentinputlab/registry/schema.py +449 -0
  322. agentinputlab/runner/__init__.py +30 -0
  323. agentinputlab/runner/balance.py +248 -0
  324. agentinputlab/runner/cache.py +135 -0
  325. agentinputlab/runner/composite.py +130 -0
  326. agentinputlab/runner/engine.py +285 -0
  327. agentinputlab/runner/runtime_guard.py +268 -0
  328. agentinputlab/runner/worker.py +80 -0
  329. agentinputlab/runtime_bootstrap.py +288 -0
  330. agentinputlab/selectors/__init__.py +196 -0
  331. agentinputlab/selectors/advanced.py +1514 -0
  332. agentinputlab/selectors/artifacts.py +1087 -0
  333. agentinputlab/selectors/base.py +112 -0
  334. agentinputlab/selectors/campaigns.py +1893 -0
  335. agentinputlab/selectors/catalog.py +86 -0
  336. agentinputlab/selectors/discovery.py +1860 -0
  337. agentinputlab/selectors/evaluation.py +1348 -0
  338. agentinputlab/selectors/features.py +383 -0
  339. agentinputlab/selectors/learned.py +502 -0
  340. agentinputlab/selectors/no_op.py +28 -0
  341. agentinputlab/selectors/outcome_table.py +475 -0
  342. agentinputlab/selectors/producer.py +853 -0
  343. agentinputlab/selectors/rules.py +109 -0
  344. agentinputlab/selectors/runtime.py +420 -0
  345. agentinputlab/studies/__init__.py +16 -0
  346. agentinputlab/studies/design.py +165 -0
  347. agentinputlab/studies/paired.py +322 -0
  348. agentinputlab/studio/__init__.py +6 -0
  349. agentinputlab/studio/campaigns.py +819 -0
  350. agentinputlab/studio/engine.py +1408 -0
  351. agentinputlab/studio/model_backends.py +425 -0
  352. agentinputlab/studio/prepared.py +410 -0
  353. agentinputlab/studio/server.py +613 -0
  354. agentinputlab/studio/static/app.js +397 -0
  355. agentinputlab/studio/static/campaigns.html +175 -0
  356. agentinputlab/studio/static/campaigns.js +426 -0
  357. agentinputlab/studio/static/index.html +192 -0
  358. agentinputlab/studio/static/result.html +103 -0
  359. agentinputlab/studio/static/result.js +273 -0
  360. agentinputlab/studio/static/styles.css +467 -0
  361. agentinputlab-0.1.0a1.data/data/share/agentinputlab/CITATION.cff +22 -0
  362. agentinputlab-0.1.0a1.dist-info/METADATA +392 -0
  363. agentinputlab-0.1.0a1.dist-info/RECORD +469 -0
  364. agentinputlab-0.1.0a1.dist-info/WHEEL +5 -0
  365. agentinputlab-0.1.0a1.dist-info/entry_points.txt +6 -0
  366. agentinputlab-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  367. agentinputlab-0.1.0a1.dist-info/licenses/NOTICE +31 -0
  368. agentinputlab-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +94 -0
  369. agentinputlab-0.1.0a1.dist-info/top_level.txt +2 -0
  370. workers/README.md +94 -0
  371. workers/ace/README.md +261 -0
  372. workers/ace/__init__.py +1 -0
  373. workers/ace/compatibility_manifest.json +39 -0
  374. workers/ace/driver.py +1682 -0
  375. workers/acon/README.md +215 -0
  376. workers/acon/__init__.py +5 -0
  377. workers/acon/compatibility_manifest.json +93 -0
  378. workers/acon/driver.py +1859 -0
  379. workers/acon/fitted.py +395 -0
  380. workers/acon/guideline.py +470 -0
  381. workers/acon/guideline_source_manifest.json +20 -0
  382. workers/adaptive_rag/README.md +96 -0
  383. workers/adaptive_rag/__init__.py +15 -0
  384. workers/adaptive_rag/compatibility_manifest.json +61 -0
  385. workers/adaptive_rag/driver.py +1470 -0
  386. workers/adaptive_rag/fixture_runtime.py +163 -0
  387. workers/adaptive_rag/runtime.py +252 -0
  388. workers/ask_or_assume/README.md +124 -0
  389. workers/ask_or_assume/__init__.py +1 -0
  390. workers/ask_or_assume/compatibility_manifest.json +36 -0
  391. workers/ask_or_assume/driver.py +1227 -0
  392. workers/decomp/README.md +133 -0
  393. workers/decomp/UPSTREAM_LICENSE.Apache-2.0.txt +201 -0
  394. workers/decomp/__init__.py +5 -0
  395. workers/decomp/compatibility_manifest.json +205 -0
  396. workers/decomp/driver.py +1015 -0
  397. workers/decomp/live.py +137 -0
  398. workers/decomp/runtime.py +231 -0
  399. workers/infocqr/README.md +106 -0
  400. workers/infocqr/__init__.py +1 -0
  401. workers/infocqr/compatibility_manifest.json +84 -0
  402. workers/infocqr/driver.py +1766 -0
  403. workers/ircot/README.md +167 -0
  404. workers/ircot/__init__.py +7 -0
  405. workers/ircot/compatibility_manifest.json +121 -0
  406. workers/ircot/controlled.py +457 -0
  407. workers/ircot/driver.py +1373 -0
  408. workers/ircot/fixture_bindings.json +39 -0
  409. workers/ircot/fixture_config.json +48 -0
  410. workers/ircot/fixture_prompt.txt +2 -0
  411. workers/ircot/native_qa.py +398 -0
  412. workers/ircot/qa_compatibility_manifest.json +57 -0
  413. workers/ircot/runtime.py +97 -0
  414. workers/llmcompiler/README.md +160 -0
  415. workers/llmcompiler/__init__.py +1 -0
  416. workers/llmcompiler/compatibility_manifest.json +45 -0
  417. workers/llmcompiler/driver.py +1514 -0
  418. workers/llmcompiler/export_prompts.py +74 -0
  419. workers/llmcompiler/live.py +174 -0
  420. workers/llmcompiler/patch_manifest.json +147 -0
  421. workers/mce/README.md +198 -0
  422. workers/mce/__init__.py +29 -0
  423. workers/mce/compatibility_manifest.json +115 -0
  424. workers/mce/driver.py +2585 -0
  425. workers/mce/final_eval.py +173 -0
  426. workers/official_arenas/README.md +25 -0
  427. workers/official_arenas/__init__.py +1 -0
  428. workers/official_arenas/capture_records.py +110 -0
  429. workers/official_arenas/driver.py +619 -0
  430. workers/official_arenas/protocol.py +334 -0
  431. workers/official_arenas/providers/__init__.py +1 -0
  432. workers/official_arenas/providers/_live_history.py +228 -0
  433. workers/official_arenas/providers/_message_compat.py +77 -0
  434. workers/official_arenas/providers/appworld_ul.py +196 -0
  435. workers/official_arenas/providers/fixture.py +112 -0
  436. workers/official_arenas/providers/statebench.py +960 -0
  437. workers/official_arenas/providers/tau3.py +1672 -0
  438. workers/official_arenas/providers/tau_retail.py +586 -0
  439. workers/official_arenas/providers/toolsandbox.py +1321 -0
  440. workers/official_arenas/providers/tooltalk.py +699 -0
  441. workers/official_arenas/retail_capture.py +188 -0
  442. workers/official_arenas/retail_driver.py +275 -0
  443. workers/qrag/README.md +172 -0
  444. workers/qrag/__init__.py +1 -0
  445. workers/qrag/compatibility_manifest.json +64 -0
  446. workers/qrag/driver.py +673 -0
  447. workers/r2ag/README.md +154 -0
  448. workers/r2ag/__init__.py +5 -0
  449. workers/r2ag/compatibility_manifest.json +55 -0
  450. workers/r2ag/driver.py +1287 -0
  451. workers/r2ag/feature_schema.json +8 -0
  452. workers/r2ag/runtime.py +589 -0
  453. workers/recomp/README.md +120 -0
  454. workers/recomp/__init__.py +1 -0
  455. workers/recomp/compatibility_manifest.json +43 -0
  456. workers/recomp/driver.py +1653 -0
  457. workers/reformer/README.md +125 -0
  458. workers/reformer/__init__.py +1 -0
  459. workers/reformer/compatibility_manifest.json +68 -0
  460. workers/reformer/driver.py +1459 -0
  461. workers/reformer/induction.py +457 -0
  462. workers/rrr/README.md +126 -0
  463. workers/rrr/__init__.py +1 -0
  464. workers/rrr/compatibility_manifest.json +57 -0
  465. workers/rrr/driver.py +1133 -0
  466. workers/smartsearch/README.md +331 -0
  467. workers/smartsearch/__init__.py +1 -0
  468. workers/smartsearch/compatibility_manifest.json +41 -0
  469. workers/smartsearch/driver.py +1643 -0
@@ -0,0 +1,963 @@
1
+ """Prepared question-answering adapter with evaluator-only references.
2
+
3
+ The normalized input schema is intentionally small and dataset-neutral. A
4
+ visible row contains the user question plus optional context blocks, while its
5
+ matching gold row contains either one reference group or a set of annotation-
6
+ aligned reference groups. Keeping those files separate prevents an
7
+ intervention or prediction callback from observing answers during execution.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import math
14
+ import string
15
+ from collections.abc import Mapping, Sequence
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from agentinputlab.contracts.errors import ArtifactError, ValidationError
21
+ from agentinputlab.contracts.serialization import canonical_json, freeze_mapping
22
+ from agentinputlab.contracts.state import ContextBlock
23
+ from agentinputlab.metrics import (
24
+ best_reference_score,
25
+ english_qa_exact_match,
26
+ english_qa_f1,
27
+ normalize_english_answer,
28
+ set_f1,
29
+ )
30
+
31
+ from .base import BenchmarkExample
32
+ from .preprocess import PreparedBenchmark
33
+
34
+ _INPUT_FIELDS = {"id", "query", "instruction", "visible_context", "visible_metadata"}
35
+ _CONTEXT_FIELDS = {"block_id", "text", "source", "score", "metadata"}
36
+ _REFERENCE_FIELDS = {
37
+ "answer_type",
38
+ "answers",
39
+ "support_ids",
40
+ "evidence_ids",
41
+ "evidence_texts",
42
+ }
43
+ _BENCHMARK_PROFILES = {
44
+ "musique.answerable": "musique",
45
+ "hotpotqa.distractor": "hotpotqa",
46
+ "2wiki.multihop": "2wiki",
47
+ "qasper.v0_3_dev": "qasper",
48
+ }
49
+ _NAMED_PROFILE_IDENTITIES = {
50
+ "musique.answerable": ("musique.answerable.dev", "musique_zip_v1", "dev"),
51
+ "hotpotqa.distractor": (
52
+ "hotpotqa.distractor.validation",
53
+ "hotpotqa_parquet_v1",
54
+ "validation",
55
+ ),
56
+ "2wiki.multihop": ("2wiki.multihop.dev", "two_wiki_zip_v1", "dev"),
57
+ "qasper.v0_3_dev": ("qasper.dev.official", "qasper_tgz_v1", "dev"),
58
+ }
59
+ _CATEGORICAL_ANSWERS = frozenset({"yes", "no", "noanswer"})
60
+ _QASPER_ANSWER_TYPES = frozenset({"extractive", "abstractive", "boolean", "none"})
61
+ _PROFILE_METRIC_KEYS = {
62
+ "musique": ("answer_em", "answer_f1", "support_f1"),
63
+ "hotpotqa": (
64
+ "answer_em",
65
+ "answer_f1",
66
+ "answer_precision",
67
+ "answer_recall",
68
+ "support_em",
69
+ "support_f1",
70
+ "support_precision",
71
+ "support_recall",
72
+ "joint_em",
73
+ "joint_f1",
74
+ "joint_precision",
75
+ "joint_recall",
76
+ ),
77
+ "2wiki": (
78
+ "answer_em",
79
+ "answer_f1",
80
+ "answer_precision",
81
+ "answer_recall",
82
+ "support_em",
83
+ "support_f1",
84
+ "support_precision",
85
+ "support_recall",
86
+ "evidence_em",
87
+ "evidence_f1",
88
+ "evidence_precision",
89
+ "evidence_recall",
90
+ "joint_em",
91
+ "joint_f1",
92
+ "joint_precision",
93
+ "joint_recall",
94
+ ),
95
+ "qasper": ("answer_em", "answer_f1", "evidence_f1"),
96
+ }
97
+
98
+
99
+ @dataclass(frozen=True, slots=True)
100
+ class QAPrediction:
101
+ """One answer with optional support IDs and ID- or text-based evidence."""
102
+
103
+ answer: str
104
+ support_ids: tuple[str, ...] = ()
105
+ evidence_ids: tuple[str, ...] = ()
106
+ evidence_texts: tuple[str, ...] = ()
107
+
108
+ def __post_init__(self) -> None:
109
+ if not isinstance(self.answer, str):
110
+ raise ValidationError("QA prediction answer must be a string")
111
+ object.__setattr__(
112
+ self,
113
+ "support_ids",
114
+ _prediction_identifiers(self.support_ids, label="QA prediction support_ids"),
115
+ )
116
+ object.__setattr__(
117
+ self,
118
+ "evidence_ids",
119
+ _prediction_identifiers(self.evidence_ids, label="QA prediction evidence_ids"),
120
+ )
121
+ object.__setattr__(
122
+ self,
123
+ "evidence_texts",
124
+ _prediction_identifiers(self.evidence_texts, label="QA prediction evidence_texts"),
125
+ )
126
+
127
+
128
+ class PreparedQABenchmarkAdapter:
129
+ """Production adapter for a verified, normalized QA benchmark.
130
+
131
+ Input rows must use this exact schema::
132
+
133
+ {
134
+ "id": "...",
135
+ "query": "...",
136
+ "instruction": "...",
137
+ "visible_context": [
138
+ {"block_id": "...", "text": "...", "source": "...",
139
+ "score": null, "metadata": {}}
140
+ ],
141
+ "visible_metadata": {
142
+ "benchmark_cell": "...",
143
+ "source_split": "..."
144
+ }
145
+ }
146
+
147
+ A gold row uses either ``answers`` with optional ``support_ids`` and one of
148
+ ``evidence_ids``/``evidence_texts``, or ``reference_groups`` containing
149
+ those same fields. The latter preserves annotation alignment for datasets
150
+ such as QASPER; its official evidence score uses the text-based variant.
151
+ """
152
+
153
+ pairing_mode = "stateless_score_twice"
154
+
155
+ def __init__(
156
+ self,
157
+ prepared: PreparedBenchmark,
158
+ *,
159
+ benchmark_id: str,
160
+ split: str,
161
+ ) -> None:
162
+ if not isinstance(prepared, PreparedBenchmark):
163
+ raise TypeError("prepared must be a PreparedBenchmark")
164
+ _validated_identity(benchmark_id, label="benchmark_id")
165
+ _validated_identity(split, label="split")
166
+ _validate_prepared_identity(
167
+ prepared,
168
+ benchmark_id=benchmark_id,
169
+ split=split,
170
+ )
171
+
172
+ input_rows = _records_by_id(prepared.inputs_path, label="prepared QA inputs")
173
+ gold_rows = _records_by_id(prepared.gold_path, label="prepared QA gold")
174
+ if set(input_rows) != set(gold_rows):
175
+ raise ArtifactError("prepared QA input and gold IDs do not match")
176
+
177
+ examples: list[BenchmarkExample] = []
178
+ benchmark_profile = _BENCHMARK_PROFILES.get(benchmark_id, "generic")
179
+ metric_profile: tuple[bool, str | None] | None = None
180
+ for example_id in sorted(input_rows):
181
+ visible = input_rows[example_id]
182
+ evaluator = gold_rows[example_id]
183
+ _require_exact_fields(visible, _INPUT_FIELDS, label="prepared QA input")
184
+ query = _required_text(visible, "query", label="prepared QA input")
185
+ instruction = _required_text(visible, "instruction", label="prepared QA input")
186
+ context = _visible_context(visible["visible_context"], example_id=example_id)
187
+ metadata = _visible_metadata(
188
+ visible["visible_metadata"],
189
+ benchmark_id=benchmark_id,
190
+ split=split,
191
+ )
192
+ references, evaluator_metadata, profile = _reference_groups(evaluator)
193
+ _validate_benchmark_profile(
194
+ benchmark_profile,
195
+ references=references,
196
+ evaluator_metadata=evaluator_metadata,
197
+ )
198
+ if metric_profile is None:
199
+ metric_profile = profile
200
+ elif metric_profile != profile:
201
+ raise ArtifactError(
202
+ "prepared QA gold rows must expose one consistent support/evidence schema"
203
+ )
204
+ examples.append(
205
+ BenchmarkExample(
206
+ example_id=example_id,
207
+ query=query,
208
+ instruction=instruction,
209
+ visible_context=context,
210
+ visible_metadata=metadata,
211
+ evaluator_gold=_frozen_canonical_mapping(
212
+ {
213
+ "reference_groups": references,
214
+ "evaluator_metadata": evaluator_metadata,
215
+ },
216
+ label="prepared QA evaluator gold",
217
+ ),
218
+ )
219
+ )
220
+
221
+ self.benchmark_id = benchmark_id
222
+ self.split = split
223
+ self.prepared = prepared
224
+ self.metric_profile = benchmark_profile
225
+ self._examples = tuple(examples)
226
+ self._examples_by_id = {example.example_id: example for example in examples}
227
+ self._has_support, self._evidence_field = metric_profile or (False, None)
228
+ self.metric_keys = _PROFILE_METRIC_KEYS.get(
229
+ benchmark_profile,
230
+ (
231
+ "exact_match",
232
+ "answer_f1",
233
+ *(("support_f1",) if self._has_support else ()),
234
+ *(("evidence_f1",) if self._evidence_field is not None else ()),
235
+ ),
236
+ )
237
+
238
+ def examples(self, split: str) -> tuple[BenchmarkExample, ...]:
239
+ if split != self.split:
240
+ raise ValidationError(
241
+ f"prepared QA adapter exposes split {self.split!r}, not {split!r}"
242
+ )
243
+ return self._examples
244
+
245
+ def example(self, split: str, example_id: str) -> BenchmarkExample:
246
+ """Resolve one verified unit without rescanning the full split."""
247
+
248
+ if split != self.split:
249
+ raise ValidationError(
250
+ f"prepared QA adapter exposes split {self.split!r}, not {split!r}"
251
+ )
252
+ if not isinstance(example_id, str) or not example_id.strip():
253
+ raise ValidationError("prepared QA example_id must be non-empty")
254
+ try:
255
+ return self._examples_by_id[example_id]
256
+ except KeyError as exc:
257
+ raise ValidationError(
258
+ f"prepared QA split does not contain example {example_id!r}"
259
+ ) from exc
260
+
261
+ def score(
262
+ self,
263
+ example: BenchmarkExample,
264
+ prediction: QAPrediction | Mapping[str, Any],
265
+ ) -> Mapping[str, float]:
266
+ if not isinstance(example, BenchmarkExample):
267
+ raise TypeError("example must be a BenchmarkExample")
268
+ if self._examples_by_id.get(example.example_id) is not example:
269
+ raise ValidationError("benchmark example does not belong to this prepared QA adapter")
270
+ normalized = (
271
+ prediction
272
+ if isinstance(prediction, QAPrediction)
273
+ else _prediction_from_mapping(prediction)
274
+ )
275
+ raw_groups = example.evaluator_gold.get("reference_groups")
276
+ if not isinstance(raw_groups, tuple) or not raw_groups:
277
+ raise ArtifactError("benchmark example does not contain QA reference groups")
278
+
279
+ groups = _validated_runtime_groups(raw_groups)
280
+ evaluator_metadata = example.evaluator_gold.get("evaluator_metadata")
281
+ if not isinstance(evaluator_metadata, Mapping): # pragma: no cover - invariant
282
+ raise ArtifactError("benchmark example does not contain QA evaluator metadata")
283
+
284
+ if self.metric_profile == "musique":
285
+ return _score_musique(normalized, groups)
286
+ if self.metric_profile == "hotpotqa":
287
+ return _score_hotpotqa(normalized, groups)
288
+ if self.metric_profile == "2wiki":
289
+ return _score_two_wiki(normalized, groups, evaluator_metadata)
290
+ if self.metric_profile == "qasper":
291
+ return _score_qasper(normalized, groups)
292
+ return _score_generic(
293
+ normalized,
294
+ groups,
295
+ has_support=self._has_support,
296
+ evidence_field=self._evidence_field,
297
+ )
298
+
299
+
300
+ def _score_generic(
301
+ prediction: QAPrediction,
302
+ groups: tuple[Mapping[str, Any], ...],
303
+ *,
304
+ has_support: bool,
305
+ evidence_field: str | None,
306
+ ) -> Mapping[str, float]:
307
+ answer_exact: list[float] = []
308
+ answer_f1: list[float] = []
309
+ support_f1: list[float] = []
310
+ evidence_f1: list[float] = []
311
+ for group in groups:
312
+ answers = group["answers"]
313
+ answer_exact.append(
314
+ best_reference_score(
315
+ prediction.answer,
316
+ answers,
317
+ scorer=english_qa_exact_match,
318
+ )
319
+ )
320
+ answer_f1.append(best_reference_score(prediction.answer, answers, scorer=english_qa_f1))
321
+ if has_support:
322
+ support_f1.append(set_f1(prediction.support_ids, group["support_ids"]))
323
+ if evidence_field == "evidence_ids":
324
+ evidence_f1.append(set_f1(prediction.evidence_ids, group["evidence_ids"]))
325
+ elif evidence_field == "evidence_texts":
326
+ evidence_f1.append(set_f1(prediction.evidence_texts, group["evidence_texts"]))
327
+
328
+ metrics = {
329
+ "exact_match": max(answer_exact),
330
+ "answer_f1": max(answer_f1),
331
+ }
332
+ if has_support:
333
+ metrics["support_f1"] = max(support_f1)
334
+ if evidence_field is not None:
335
+ metrics["evidence_f1"] = max(evidence_f1)
336
+ return metrics
337
+
338
+
339
+ def _score_musique(
340
+ prediction: QAPrediction,
341
+ groups: tuple[Mapping[str, Any], ...],
342
+ ) -> Mapping[str, float]:
343
+ """Match the official answerable-split answer and paragraph metrics."""
344
+
345
+ answer_em = max(
346
+ best_reference_score(
347
+ prediction.answer,
348
+ group["answers"],
349
+ scorer=english_qa_exact_match,
350
+ )
351
+ for group in groups
352
+ )
353
+ answer_f1 = max(
354
+ best_reference_score(prediction.answer, group["answers"], scorer=english_qa_f1)
355
+ for group in groups
356
+ )
357
+ support_f1 = max(set_f1(prediction.support_ids, group["support_ids"]) for group in groups)
358
+ return {
359
+ "answer_em": answer_em,
360
+ "answer_f1": answer_f1,
361
+ "support_f1": support_f1,
362
+ }
363
+
364
+
365
+ def _score_hotpotqa(
366
+ prediction: QAPrediction,
367
+ groups: tuple[Mapping[str, Any], ...],
368
+ ) -> Mapping[str, float]:
369
+ answer = _best_answer_scores(prediction.answer, groups, categorical_guard=True)
370
+ support = _best_support_scores(prediction.support_ids, groups, lowercase_titles=False)
371
+ return _joint_metrics(answer, support)
372
+
373
+
374
+ def _score_two_wiki(
375
+ prediction: QAPrediction,
376
+ groups: tuple[Mapping[str, Any], ...],
377
+ evaluator_metadata: Mapping[str, Any],
378
+ ) -> Mapping[str, float]:
379
+ answer = _best_answer_scores(prediction.answer, groups, categorical_guard=True)
380
+ support = _best_support_scores(prediction.support_ids, groups, lowercase_titles=True)
381
+ evidence = _two_wiki_evidence_scores(
382
+ prediction.evidence_ids,
383
+ evaluator_metadata.get("evidence_options"),
384
+ )
385
+ return _joint_metrics(answer, support, evidence)
386
+
387
+
388
+ def _score_qasper(
389
+ prediction: QAPrediction,
390
+ groups: tuple[Mapping[str, Any], ...],
391
+ ) -> Mapping[str, float]:
392
+ """Match QASPER's independent max-over-annotations scoring rule."""
393
+
394
+ return {
395
+ "answer_em": max(
396
+ best_reference_score(
397
+ prediction.answer,
398
+ group["answers"],
399
+ scorer=english_qa_exact_match,
400
+ )
401
+ for group in groups
402
+ ),
403
+ "answer_f1": max(
404
+ best_reference_score(prediction.answer, group["answers"], scorer=english_qa_f1)
405
+ for group in groups
406
+ ),
407
+ "evidence_f1": max(
408
+ set_f1(prediction.evidence_texts, group["evidence_texts"]) for group in groups
409
+ ),
410
+ }
411
+
412
+
413
+ def _best_answer_scores(
414
+ prediction: str,
415
+ groups: tuple[Mapping[str, Any], ...],
416
+ *,
417
+ categorical_guard: bool,
418
+ ) -> Mapping[str, float]:
419
+ scores = [
420
+ _answer_scores(prediction, answer, categorical_guard=categorical_guard)
421
+ for group in groups
422
+ for answer in group["answers"]
423
+ ]
424
+ return {key: max(score[key] for score in scores) for key in ("em", "f1", "precision", "recall")}
425
+
426
+
427
+ def _answer_scores(
428
+ prediction: str,
429
+ reference: str,
430
+ *,
431
+ categorical_guard: bool,
432
+ ) -> Mapping[str, float]:
433
+ normalized_prediction = _normalized_answer(prediction)
434
+ normalized_reference = _normalized_answer(reference)
435
+ exact_match = float(normalized_prediction == normalized_reference)
436
+ if categorical_guard and (
437
+ normalized_prediction != normalized_reference
438
+ and (
439
+ normalized_prediction in _CATEGORICAL_ANSWERS
440
+ or normalized_reference in _CATEGORICAL_ANSWERS
441
+ )
442
+ ):
443
+ return {"em": exact_match, "f1": 0.0, "precision": 0.0, "recall": 0.0}
444
+
445
+ prediction_tokens = normalized_prediction.split()
446
+ reference_tokens = normalized_reference.split()
447
+ common = set(prediction_tokens) & set(reference_tokens)
448
+ # Token multiplicity matters in the official SQuAD/Hotpot scorer.
449
+ matched = sum(
450
+ min(prediction_tokens.count(token), reference_tokens.count(token)) for token in common
451
+ )
452
+ if matched == 0:
453
+ return {"em": exact_match, "f1": 0.0, "precision": 0.0, "recall": 0.0}
454
+ precision = matched / len(prediction_tokens)
455
+ recall = matched / len(reference_tokens)
456
+ return {
457
+ "em": exact_match,
458
+ "f1": _harmonic_mean(precision, recall),
459
+ "precision": precision,
460
+ "recall": recall,
461
+ }
462
+
463
+
464
+ def _best_support_scores(
465
+ prediction_ids: tuple[str, ...],
466
+ groups: tuple[Mapping[str, Any], ...],
467
+ *,
468
+ lowercase_titles: bool,
469
+ ) -> Mapping[str, float]:
470
+ predicted = _support_pairs(
471
+ prediction_ids,
472
+ lowercase_titles=lowercase_titles,
473
+ prediction=True,
474
+ )
475
+ scores = [
476
+ _set_scores(
477
+ predicted,
478
+ _support_pairs(
479
+ group["support_ids"],
480
+ lowercase_titles=lowercase_titles,
481
+ prediction=False,
482
+ ),
483
+ )
484
+ for group in groups
485
+ ]
486
+ return {key: max(score[key] for score in scores) for key in ("em", "f1", "precision", "recall")}
487
+
488
+
489
+ def _support_pairs(
490
+ identifiers: Sequence[str],
491
+ *,
492
+ lowercase_titles: bool,
493
+ prediction: bool,
494
+ ) -> set[tuple[str, int]]:
495
+ pairs: set[tuple[str, int]] = set()
496
+ for identifier in identifiers:
497
+ value = _canonical_identifier(
498
+ identifier,
499
+ expected_length=2,
500
+ label="support ID",
501
+ prediction=prediction,
502
+ )
503
+ title, sentence_index = value
504
+ if not isinstance(title, str) or not title.strip():
505
+ _identifier_error("support ID title must be a non-empty string", prediction)
506
+ if (
507
+ isinstance(sentence_index, bool)
508
+ or not isinstance(sentence_index, int)
509
+ or sentence_index < 0
510
+ ):
511
+ _identifier_error("support ID index must be a non-negative integer", prediction)
512
+ pairs.add((title.lower() if lowercase_titles else title, sentence_index))
513
+ return pairs
514
+
515
+
516
+ def _two_wiki_evidence_scores(
517
+ prediction_ids: tuple[str, ...],
518
+ raw_options: Any,
519
+ ) -> Mapping[str, float]:
520
+ if not isinstance(raw_options, tuple) or not raw_options:
521
+ raise ArtifactError("2Wiki evaluator metadata requires non-empty evidence_options")
522
+ accepted_groups: list[set[tuple[str, str, str]]] = []
523
+ for group_index, raw_group in enumerate(raw_options):
524
+ if not isinstance(raw_group, tuple) or not raw_group:
525
+ raise ArtifactError(f"2Wiki evidence_options group {group_index} must be non-empty")
526
+ accepted_groups.append(
527
+ {
528
+ _normalized_evidence_triple(
529
+ option,
530
+ prediction=False,
531
+ )
532
+ for option in raw_group
533
+ }
534
+ )
535
+
536
+ # The official v1.1 evaluator normalizes first and then converts predictions
537
+ # to a set. Without that second deduplication, case/punctuation variants of
538
+ # one triple can be counted more than once and push recall/F1 above 1.
539
+ predicted = {
540
+ _normalized_evidence_triple(identifier, prediction=True) for identifier in prediction_ids
541
+ }
542
+ matched = sum(
543
+ 1 for candidate in predicted if any(candidate in group for group in accepted_groups)
544
+ )
545
+ precision = matched / len(predicted) if predicted else 0.0
546
+ recall = matched / len(accepted_groups) if accepted_groups else 0.0
547
+ return {
548
+ "em": float(matched == len(predicted) == len(accepted_groups)),
549
+ "f1": _harmonic_mean(precision, recall),
550
+ "precision": precision,
551
+ "recall": recall,
552
+ }
553
+
554
+
555
+ def _normalized_evidence_triple(
556
+ identifier: str,
557
+ *,
558
+ prediction: bool,
559
+ ) -> tuple[str, str, str]:
560
+ value = _canonical_identifier(
561
+ identifier,
562
+ expected_length=3,
563
+ label="evidence ID",
564
+ prediction=prediction,
565
+ )
566
+ if not all(isinstance(item, str) and item.strip() for item in value):
567
+ _identifier_error("evidence ID must contain three non-empty strings", prediction)
568
+ subject, relation, object_ = value
569
+ return (
570
+ _normalize_evidence_component(subject),
571
+ _normalize_evidence_component(relation),
572
+ _normalize_evidence_component(object_),
573
+ )
574
+
575
+
576
+ def _canonical_identifier(
577
+ identifier: str,
578
+ *,
579
+ expected_length: int,
580
+ label: str,
581
+ prediction: bool,
582
+ ) -> list[Any]:
583
+ try:
584
+ value = json.loads(identifier)
585
+ except json.JSONDecodeError:
586
+ _identifier_error(f"{label} must be canonical JSON", prediction)
587
+ if (
588
+ not isinstance(value, list)
589
+ or len(value) != expected_length
590
+ or canonical_json(value) != identifier
591
+ ):
592
+ _identifier_error(
593
+ f"{label} must be a canonical JSON array of length {expected_length}",
594
+ prediction,
595
+ )
596
+ return value
597
+
598
+
599
+ def _identifier_error(message: str, prediction: bool) -> None:
600
+ if prediction:
601
+ raise ValidationError(f"QA prediction {message}")
602
+ raise ArtifactError(f"prepared QA gold {message}")
603
+
604
+
605
+ def _set_scores(prediction: set[Any], reference: set[Any]) -> Mapping[str, float]:
606
+ matched = len(prediction & reference)
607
+ precision = matched / len(prediction) if prediction else 0.0
608
+ recall = matched / len(reference) if reference else 0.0
609
+ return {
610
+ "em": float(prediction == reference),
611
+ "f1": _harmonic_mean(precision, recall),
612
+ "precision": precision,
613
+ "recall": recall,
614
+ }
615
+
616
+
617
+ def _joint_metrics(*components: Mapping[str, float]) -> Mapping[str, float]:
618
+ prefixes = ("answer", "support", "evidence")
619
+ metrics: dict[str, float] = {}
620
+ for prefix, component in zip(prefixes[: len(components)], components, strict=True):
621
+ for metric in ("em", "f1", "precision", "recall"):
622
+ metrics[f"{prefix}_{metric}"] = component[metric]
623
+ joint_em = math.prod(component["em"] for component in components)
624
+ joint_precision = math.prod(component["precision"] for component in components)
625
+ joint_recall = math.prod(component["recall"] for component in components)
626
+ metrics.update(
627
+ {
628
+ "joint_em": joint_em,
629
+ "joint_f1": _harmonic_mean(joint_precision, joint_recall),
630
+ "joint_precision": joint_precision,
631
+ "joint_recall": joint_recall,
632
+ }
633
+ )
634
+ return metrics
635
+
636
+
637
+ def _harmonic_mean(precision: float, recall: float) -> float:
638
+ return 2 * precision * recall / (precision + recall) if precision + recall else 0.0
639
+
640
+
641
+ def _normalized_answer(value: str) -> str:
642
+ # Keep one source of truth for SQuAD/Hotpot answer normalization.
643
+ return normalize_english_answer(value)
644
+
645
+
646
+ def _normalize_evidence_component(value: str) -> str:
647
+ without_punctuation = "".join(
648
+ character for character in value.lower() if character not in string.punctuation
649
+ )
650
+ return " ".join(without_punctuation.split())
651
+
652
+
653
+ def _validated_runtime_groups(value: tuple[Any, ...]) -> tuple[Mapping[str, Any], ...]:
654
+ groups: list[Mapping[str, Any]] = []
655
+ for group in value:
656
+ if not isinstance(group, Mapping): # pragma: no cover - construction invariant
657
+ raise ArtifactError("benchmark QA reference group is invalid")
658
+ groups.append(group)
659
+ return tuple(groups)
660
+
661
+
662
+ def _validate_prepared_identity(
663
+ prepared: PreparedBenchmark,
664
+ *,
665
+ benchmark_id: str,
666
+ split: str,
667
+ ) -> None:
668
+ expected = _NAMED_PROFILE_IDENTITIES.get(benchmark_id)
669
+ if expected is None:
670
+ # Direct construction remains available for custom datasets. Preserve
671
+ # the original compatibility contract: custom identities may be absent,
672
+ # but identities that are supplied must agree with the adapter request.
673
+ if prepared.cell_id and prepared.cell_id != benchmark_id:
674
+ raise ArtifactError("prepared QA cell_id does not match adapter benchmark_id")
675
+ if prepared.split and prepared.split != split:
676
+ raise ArtifactError("prepared QA split does not match adapter split")
677
+ return
678
+
679
+ expected_asset_id, expected_processor, expected_split = expected
680
+ if split != expected_split:
681
+ raise ArtifactError(
682
+ f"named QA profile {benchmark_id!r} requires adapter split {expected_split!r}"
683
+ )
684
+ expected_fields = {
685
+ "asset_id": expected_asset_id,
686
+ "processor": expected_processor,
687
+ "cell_id": benchmark_id,
688
+ "split": expected_split,
689
+ }
690
+ for field, expected_value in expected_fields.items():
691
+ if getattr(prepared, field) != expected_value:
692
+ raise ArtifactError(
693
+ f"prepared QA {field} does not match named profile {benchmark_id!r}"
694
+ )
695
+
696
+
697
+ def _validate_benchmark_profile(
698
+ profile: str,
699
+ *,
700
+ references: tuple[Mapping[str, Any], ...],
701
+ evaluator_metadata: Mapping[str, Any],
702
+ ) -> None:
703
+ if profile in {"musique", "hotpotqa"}:
704
+ if any(
705
+ "support_ids" not in group or "evidence_ids" in group or "evidence_texts" in group
706
+ for group in references
707
+ ):
708
+ raise ArtifactError(f"{profile} gold requires support_ids and no evidence")
709
+ elif profile == "2wiki":
710
+ if any("support_ids" not in group or "evidence_ids" not in group for group in references):
711
+ raise ArtifactError("2Wiki gold requires support_ids and evidence_ids")
712
+ options = evaluator_metadata.get("evidence_options")
713
+ if not isinstance(options, tuple) or not options:
714
+ raise ArtifactError("2Wiki evaluator metadata requires non-empty evidence_options")
715
+ if any(len(options) != len(group["evidence_ids"]) for group in references):
716
+ raise ArtifactError("2Wiki evidence_options must align with evidence_ids")
717
+ elif profile == "qasper" and any(
718
+ group.get("answer_type") not in _QASPER_ANSWER_TYPES
719
+ or "evidence_texts" not in group
720
+ or "support_ids" in group
721
+ for group in references
722
+ ):
723
+ raise ArtifactError(
724
+ "QASPER gold requires a valid answer_type and evidence_texts per annotation"
725
+ )
726
+
727
+
728
+ def _records_by_id(path: Path, *, label: str) -> dict[str, dict[str, Any]]:
729
+ records: dict[str, dict[str, Any]] = {}
730
+ try:
731
+ with path.open(encoding="utf-8") as handle:
732
+ for line_number, raw in enumerate(handle, start=1):
733
+ if not raw.strip():
734
+ raise ArtifactError(f"{label} contains a blank line at {line_number}")
735
+ value = json.loads(raw)
736
+ if not isinstance(value, dict):
737
+ raise ArtifactError(f"{label} row {line_number} must be an object")
738
+ row_id = _required_text(value, "id", label=label)
739
+ if row_id in records:
740
+ raise ArtifactError(f"{label} contains duplicate ID {row_id!r}")
741
+ records[row_id] = value
742
+ except (OSError, UnicodeError, json.JSONDecodeError) as exc:
743
+ raise ArtifactError(f"{label} is not valid UTF-8 JSONL") from exc
744
+ if not records:
745
+ raise ArtifactError(f"{label} contains no records")
746
+ return records
747
+
748
+
749
+ def _visible_context(value: Any, *, example_id: str) -> tuple[ContextBlock, ...]:
750
+ if not isinstance(value, list):
751
+ raise ArtifactError("prepared QA visible_context must be a JSON array")
752
+ blocks: list[ContextBlock] = []
753
+ block_ids: set[str] = set()
754
+ for index, item in enumerate(value):
755
+ label = f"prepared QA context {example_id!r} item {index}"
756
+ if not isinstance(item, dict):
757
+ raise ArtifactError(f"{label} must be an object")
758
+ unknown = set(item) - _CONTEXT_FIELDS
759
+ if unknown:
760
+ raise ArtifactError(f"{label} has unknown fields: {sorted(unknown)!r}")
761
+ if not {"block_id", "text"} <= set(item):
762
+ raise ArtifactError(f"{label} requires block_id and text")
763
+ block_id = _required_text(item, "block_id", label=label)
764
+ if block_id in block_ids:
765
+ raise ArtifactError(f"prepared QA context contains duplicate block ID {block_id!r}")
766
+ block_ids.add(block_id)
767
+ text = _required_text(item, "text", label=label)
768
+ source = item.get("source", "")
769
+ if not isinstance(source, str):
770
+ raise ArtifactError(f"{label} field 'source' must be a string")
771
+ score = item.get("score")
772
+ numeric_score: float | None = None
773
+ if score is not None:
774
+ if isinstance(score, bool) or not isinstance(score, (int, float)):
775
+ raise ArtifactError(f"{label} field 'score' must be finite numeric or null")
776
+ try:
777
+ numeric_score = float(score)
778
+ except OverflowError as exc:
779
+ raise ArtifactError(
780
+ f"{label} field 'score' must be finite numeric or null"
781
+ ) from exc
782
+ if not math.isfinite(numeric_score):
783
+ raise ArtifactError(f"{label} field 'score' must be finite numeric or null")
784
+ metadata = item.get("metadata", {})
785
+ if not isinstance(metadata, Mapping):
786
+ raise ArtifactError(f"{label} field 'metadata' must be an object")
787
+ try:
788
+ block = ContextBlock(
789
+ block_id=block_id,
790
+ text=text,
791
+ source=source,
792
+ score=numeric_score,
793
+ metadata=_frozen_canonical_mapping(metadata, label=f"{label} metadata"),
794
+ )
795
+ except (TypeError, ValidationError) as exc:
796
+ raise ArtifactError(f"{label} is invalid: {exc}") from exc
797
+ blocks.append(block)
798
+ return tuple(blocks)
799
+
800
+
801
+ def _visible_metadata(
802
+ value: Any,
803
+ *,
804
+ benchmark_id: str,
805
+ split: str,
806
+ ) -> Mapping[str, Any]:
807
+ if not isinstance(value, Mapping):
808
+ raise ArtifactError("prepared QA visible_metadata must be an object")
809
+ if value.get("benchmark_cell") != benchmark_id:
810
+ raise ArtifactError("prepared QA benchmark_cell does not match adapter benchmark_id")
811
+ if value.get("source_split") != split:
812
+ raise ArtifactError("prepared QA source_split does not match adapter split")
813
+ return _frozen_canonical_mapping(value, label="prepared QA visible_metadata")
814
+
815
+
816
+ def _reference_groups(
817
+ evaluator: Mapping[str, Any],
818
+ ) -> tuple[tuple[Mapping[str, Any], ...], Mapping[str, Any], tuple[bool, str | None]]:
819
+ raw_evaluator_metadata = evaluator.get("evaluator_metadata", {})
820
+ if not isinstance(raw_evaluator_metadata, Mapping):
821
+ raise ArtifactError("prepared QA evaluator_metadata must be an object")
822
+ evaluator_metadata = _frozen_canonical_mapping(
823
+ raw_evaluator_metadata,
824
+ label="prepared QA evaluator_metadata",
825
+ )
826
+ if "reference_groups" in evaluator:
827
+ if not set(evaluator) <= {"id", "reference_groups", "evaluator_metadata"}:
828
+ raise ArtifactError(
829
+ "prepared QA grouped gold permits only id, reference_groups, and evaluator_metadata"
830
+ )
831
+ raw_groups = evaluator["reference_groups"]
832
+ if not isinstance(raw_groups, list) or not raw_groups:
833
+ raise ArtifactError("prepared QA reference_groups must be a non-empty JSON array")
834
+ else:
835
+ fields = set(evaluator)
836
+ allowed = {"id", "evaluator_metadata"} | _REFERENCE_FIELDS
837
+ if not {"id", "answers"} <= fields or not fields <= allowed:
838
+ raise ArtifactError("prepared QA gold has an invalid flat reference schema")
839
+ raw_groups = [
840
+ {key: evaluator[key] for key in fields if key not in {"id", "evaluator_metadata"}}
841
+ ]
842
+
843
+ groups: list[Mapping[str, Any]] = []
844
+ profile: tuple[bool, str | None] | None = None
845
+ for index, raw_group in enumerate(raw_groups):
846
+ label = f"prepared QA reference group {index}"
847
+ if not isinstance(raw_group, dict):
848
+ raise ArtifactError(f"{label} must be an object")
849
+ fields = set(raw_group)
850
+ if "answers" not in fields or not fields <= _REFERENCE_FIELDS:
851
+ raise ArtifactError(f"{label} has an invalid schema")
852
+ answers = _answers(raw_group["answers"], label=label)
853
+ normalized: dict[str, Any] = {"answers": answers}
854
+ if "answer_type" in fields:
855
+ normalized["answer_type"] = _required_text(
856
+ raw_group,
857
+ "answer_type",
858
+ label=label,
859
+ )
860
+ has_support = "support_ids" in fields
861
+ evidence_fields = fields & {"evidence_ids", "evidence_texts"}
862
+ if len(evidence_fields) > 1:
863
+ raise ArtifactError(f"{label} cannot mix evidence_ids and evidence_texts")
864
+ evidence_field = next(iter(evidence_fields), None)
865
+ if has_support:
866
+ normalized["support_ids"] = _gold_identifiers(
867
+ raw_group["support_ids"], label=f"{label} support_ids"
868
+ )
869
+ if evidence_field is not None:
870
+ normalized[evidence_field] = _gold_identifiers(
871
+ raw_group[evidence_field], label=f"{label} {evidence_field}"
872
+ )
873
+ current_profile = (has_support, evidence_field)
874
+ if profile is None:
875
+ profile = current_profile
876
+ elif profile != current_profile:
877
+ raise ArtifactError("prepared QA reference groups must use one support/evidence schema")
878
+ groups.append(_frozen_canonical_mapping(normalized, label=label))
879
+ return tuple(groups), evaluator_metadata, profile or (False, None)
880
+
881
+
882
+ def _answers(value: Any, *, label: str) -> tuple[str, ...]:
883
+ if not isinstance(value, list) or not value:
884
+ raise ArtifactError(f"{label} answers must be a non-empty JSON array")
885
+ if not all(isinstance(item, str) and item.strip() for item in value):
886
+ raise ArtifactError(f"{label} answers must contain non-empty strings")
887
+ if len(value) != len(set(value)):
888
+ raise ArtifactError(f"{label} answers must not contain duplicates")
889
+ return tuple(value)
890
+
891
+
892
+ def _gold_identifiers(value: Any, *, label: str) -> tuple[str, ...]:
893
+ if not isinstance(value, list):
894
+ raise ArtifactError(f"{label} must be a JSON array")
895
+ try:
896
+ return _identifier_tuple(value, label=label)
897
+ except ValidationError as exc:
898
+ raise ArtifactError(str(exc)) from exc
899
+
900
+
901
+ def _prediction_identifiers(value: Any, *, label: str) -> tuple[str, ...]:
902
+ if isinstance(value, str) or not isinstance(value, Sequence):
903
+ raise ValidationError(f"{label} must be a sequence, not one string")
904
+ return _identifier_tuple(value, label=label)
905
+
906
+
907
+ def _identifier_tuple(value: Sequence[Any], *, label: str) -> tuple[str, ...]:
908
+ identifiers = tuple(value)
909
+ if not all(isinstance(item, str) and item.strip() for item in identifiers):
910
+ raise ValidationError(f"{label} must contain non-empty strings")
911
+ if len(identifiers) != len(set(identifiers)):
912
+ raise ValidationError(f"{label} must not contain duplicates")
913
+ return identifiers
914
+
915
+
916
+ def _prediction_from_mapping(value: Mapping[str, Any]) -> QAPrediction:
917
+ if not isinstance(value, Mapping):
918
+ raise TypeError("QA prediction must be QAPrediction or a mapping")
919
+ fields = set(value)
920
+ if "answer" not in fields or not fields <= {
921
+ "answer",
922
+ "support_ids",
923
+ "evidence_ids",
924
+ "evidence_texts",
925
+ }:
926
+ raise ValidationError("QA prediction mapping has an invalid schema")
927
+ return QAPrediction(
928
+ answer=value["answer"],
929
+ support_ids=value.get("support_ids", ()),
930
+ evidence_ids=value.get("evidence_ids", ()),
931
+ evidence_texts=value.get("evidence_texts", ()),
932
+ )
933
+
934
+
935
+ def _require_exact_fields(record: Mapping[str, Any], fields: set[str], *, label: str) -> None:
936
+ if set(record) != fields:
937
+ missing = sorted(fields - set(record))
938
+ unknown = sorted(set(record) - fields)
939
+ raise ArtifactError(f"{label} schema mismatch: missing={missing!r}, unknown={unknown!r}")
940
+
941
+
942
+ def _required_text(record: Mapping[str, Any], key: str, *, label: str) -> str:
943
+ value = record.get(key)
944
+ if not isinstance(value, str) or not value.strip():
945
+ raise ArtifactError(f"{label} field {key!r} must be a non-empty string")
946
+ return value
947
+
948
+
949
+ def _validated_identity(value: str, *, label: str) -> None:
950
+ if not isinstance(value, str) or not value.strip():
951
+ raise ValidationError(f"{label} must be non-empty")
952
+
953
+
954
+ def _frozen_canonical_mapping(value: Mapping[str, Any], *, label: str) -> Mapping[str, Any]:
955
+ try:
956
+ frozen = freeze_mapping(value)
957
+ canonical_json(frozen)
958
+ except (TypeError, ValueError) as exc:
959
+ raise ArtifactError(f"{label} is not canonical: {exc}") from exc
960
+ return frozen
961
+
962
+
963
+ __all__ = ["PreparedQABenchmarkAdapter", "QAPrediction"]