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,953 @@
1
+ """One-command orchestration over strict AgentInputLab experiment jobs.
2
+
3
+ The runner composes existing experiment manifests and drivers. It does not
4
+ weaken their prerequisite, binding, unit-inventory, guarded-driver, or result
5
+ verification contracts. A complete campaign is preflighted before the first
6
+ job executes so missing assets are reported together instead of after partial
7
+ model spend.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import re
16
+ from collections.abc import Iterator, Mapping, Sequence
17
+ from contextlib import contextmanager
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+ from types import MappingProxyType
21
+ from typing import Any
22
+
23
+ from agentinputlab.contracts.enums import ProfileKind, SemanticFamily
24
+ from agentinputlab.contracts.errors import AgentInputLabError, ArtifactError, ValidationError
25
+ from agentinputlab.contracts.serialization import content_hash, freeze_mapping, to_jsonable
26
+ from agentinputlab.experiments import (
27
+ ExperimentArmSpec,
28
+ ExperimentBenchmarkTarget,
29
+ ExperimentManifest,
30
+ ExperimentPrerequisite,
31
+ ExperimentStage,
32
+ PrerequisiteKind,
33
+ activate_guarded_driver,
34
+ build_experiment_plan,
35
+ execute_experiment_plan,
36
+ load_experiment_manifest,
37
+ preflight_experiment,
38
+ verify_experiment_run_directory,
39
+ )
40
+ from agentinputlab.experiments.plan import ExperimentPlan
41
+ from agentinputlab.experiments.secrets import redact_secret_text, require_secret_free
42
+ from agentinputlab.registry import AlgorithmRegistry
43
+
44
+ ONE_CLICK_CAMPAIGN_PROTOCOL = "agentinputlab.one-click-campaign.v1"
45
+
46
+ _MAX_CONFIG_BYTES = 8 * 1024 * 1024
47
+ _SHA256 = re.compile(r"^[0-9a-f]{64}$")
48
+ _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
49
+ _ENTRYPOINT = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*:[A-Za-z_][A-Za-z0-9_.]*$")
50
+ _ENVIRONMENT_NAME = re.compile(r"^[A-Z_][A-Z0-9_]*$")
51
+ _SECRET_NAME = re.compile(
52
+ r"(?i)(?:^|_)(?:api_?key|authorization|bearer|credential|password|secret|token)(?:_|$)"
53
+ )
54
+ _REQUIRED_DATA_CELLS = frozenset(
55
+ {
56
+ "beir.arguana",
57
+ "bright.earth_science",
58
+ "bright.economics",
59
+ "musique.answerable",
60
+ "2wiki.multihop",
61
+ "hotpotqa.distractor",
62
+ "qasper.v0_3_dev",
63
+ "clariq.main",
64
+ "tau3.banking_knowledge",
65
+ "bfcl.v3.simple_python",
66
+ }
67
+ )
68
+ _AUXILIARY_STATIC_CELLS = frozenset(
69
+ {
70
+ "beir.arguana",
71
+ "bright.earth_science",
72
+ "bright.economics",
73
+ "musique.answerable",
74
+ "2wiki.multihop",
75
+ "hotpotqa.distractor",
76
+ "qasper.v0_3_dev",
77
+ }
78
+ )
79
+ _REPRESENTATIVE_COVERAGE = "representative_27x10"
80
+ _AUXILIARY_COVERAGE = "auxiliary_27x7_legal"
81
+ _TAU3_REVISION = "cf71a8070269883e38a365ffa85f78f46844c1f4"
82
+ _METHOD_TARGETS = MappingProxyType(
83
+ {
84
+ "rewrite.query2doc": ("bfcl.v3.simple_python", "test"),
85
+ "rewrite.rrr": ("beir.arguana", "test"),
86
+ "rewrite.reformer": ("clariq.main", "dev"),
87
+ "rewrite.infocqr": ("bright.economics", "test"),
88
+ "plan.plan_and_solve": ("musique.answerable", "dev"),
89
+ "plan.decomp": ("2wiki.multihop", "dev"),
90
+ "plan.self_ask": ("hotpotqa.distractor", "validation"),
91
+ "plan.llm_compiler": ("musique.answerable", "dev"),
92
+ "plan.plan_and_budget": ("2wiki.multihop", "dev"),
93
+ "acquire.hyde": ("bright.earth_science", "test"),
94
+ "acquire.ircot": ("musique.answerable", "dev"),
95
+ "acquire.adaptive_rag": ("hotpotqa.distractor", "validation"),
96
+ "acquire.r2ag": ("beir.arguana", "test"),
97
+ "acquire.qrag": ("2wiki.multihop", "dev"),
98
+ "clarify.ask_or_assume": ("tau3.banking_knowledge", "discovery_train"),
99
+ "clarify.udcs": ("tau3.banking_knowledge", "discovery_train"),
100
+ "clarify.future_turns": ("tau3.banking_knowledge", "discovery_train"),
101
+ "clarify.active_task_disambiguation": (
102
+ "tau3.banking_knowledge",
103
+ "discovery_train",
104
+ ),
105
+ "curate.longllmlingua": ("qasper.v0_3_dev", "dev"),
106
+ "curate.llmlingua2": ("qasper.v0_3_dev", "dev"),
107
+ "curate.recomp": ("qasper.v0_3_dev", "dev"),
108
+ "curate.acon": ("qasper.v0_3_dev", "dev"),
109
+ "recipe.smartsearch": ("tau3.banking_knowledge", "discovery_train"),
110
+ "recipe.rqrag": ("tau3.banking_knowledge", "discovery_train"),
111
+ "recipe.ace": ("qasper.v0_3_dev", "dev"),
112
+ "recipe.mce": ("qasper.v0_3_dev", "dev"),
113
+ "recipe.skillopt": ("qasper.v0_3_dev", "dev"),
114
+ }
115
+ )
116
+ _CONFIG_FIELDS = frozenset(
117
+ {
118
+ "schema_version",
119
+ "protocol",
120
+ "campaign_id",
121
+ "prepared_data_root",
122
+ "data_cells",
123
+ "output_root",
124
+ "resume",
125
+ "fail_fast",
126
+ "jobs",
127
+ "campaign_hash",
128
+ }
129
+ )
130
+ _CONFIG_FIELDS_WITH_COVERAGE = _CONFIG_FIELDS | {"coverage_design"}
131
+ _JOB_FIELDS = frozenset(
132
+ {
133
+ "job_id",
134
+ "method_ids",
135
+ "benchmark_cells",
136
+ "manifest_path",
137
+ "manifest_sha256",
138
+ "driver_entrypoint",
139
+ "environment",
140
+ }
141
+ )
142
+
143
+
144
+ @dataclass(frozen=True, slots=True)
145
+ class OneClickCampaignJob:
146
+ """One strict experiment manifest inside a larger campaign."""
147
+
148
+ job_id: str
149
+ method_ids: tuple[str, ...]
150
+ benchmark_cells: tuple[str, ...]
151
+ manifest_path: Path
152
+ manifest_sha256: str
153
+ driver_entrypoint: str
154
+ environment: Mapping[str, str]
155
+
156
+ def __post_init__(self) -> None:
157
+ _identifier(self.job_id, label="campaign job_id")
158
+ methods = _identifier_tuple(self.method_ids, label=f"{self.job_id}.method_ids")
159
+ cells = _identifier_tuple(
160
+ self.benchmark_cells,
161
+ label=f"{self.job_id}.benchmark_cells",
162
+ )
163
+ source = self.manifest_path
164
+ if not isinstance(source, Path) or not source.is_absolute():
165
+ raise ValidationError(f"{self.job_id}.manifest_path must be absolute")
166
+ _require_sha256(self.manifest_sha256, label=f"{self.job_id}.manifest_sha256")
167
+ if (
168
+ not isinstance(self.driver_entrypoint, str)
169
+ or _ENTRYPOINT.fullmatch(self.driver_entrypoint) is None
170
+ ):
171
+ raise ValidationError(f"{self.job_id}.driver_entrypoint must use module:factory syntax")
172
+ environment: dict[str, str] = {}
173
+ for name, value in self.environment.items():
174
+ if (
175
+ not isinstance(name, str)
176
+ or _ENVIRONMENT_NAME.fullmatch(name) is None
177
+ or _SECRET_NAME.search(name) is not None
178
+ ):
179
+ raise ValidationError(
180
+ f"{self.job_id}.environment contains an invalid or secret-bearing name"
181
+ )
182
+ if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
183
+ raise ValidationError(f"{self.job_id}.environment values must be non-empty strings")
184
+ environment[name] = value
185
+ require_secret_free(environment, label=f"{self.job_id}.environment")
186
+ object.__setattr__(self, "method_ids", methods)
187
+ object.__setattr__(self, "benchmark_cells", cells)
188
+ object.__setattr__(self, "environment", MappingProxyType(environment))
189
+
190
+
191
+ @dataclass(frozen=True, slots=True)
192
+ class OneClickCampaign:
193
+ """Checksum-bound, exact-scope collection of experiment jobs."""
194
+
195
+ campaign_id: str
196
+ prepared_data_root: Path
197
+ data_cells: tuple[str, ...]
198
+ output_root: Path
199
+ resume: bool
200
+ fail_fast: bool
201
+ jobs: tuple[OneClickCampaignJob, ...]
202
+ campaign_hash: str
203
+ source_path: Path
204
+ source_sha256: str
205
+ coverage_design: str = _REPRESENTATIVE_COVERAGE
206
+
207
+ def __post_init__(self) -> None:
208
+ _identifier(self.campaign_id, label="campaign_id")
209
+ for label, path in (
210
+ ("prepared_data_root", self.prepared_data_root),
211
+ ("output_root", self.output_root),
212
+ ("source_path", self.source_path),
213
+ ):
214
+ if not isinstance(path, Path) or not path.is_absolute():
215
+ raise ValidationError(f"campaign {label} must be absolute")
216
+ if not isinstance(self.resume, bool) or not isinstance(self.fail_fast, bool):
217
+ raise ValidationError("campaign resume and fail_fast must be boolean")
218
+ if self.coverage_design not in {
219
+ _REPRESENTATIVE_COVERAGE,
220
+ _AUXILIARY_COVERAGE,
221
+ }:
222
+ raise ValidationError("one-click campaign coverage_design is unsupported")
223
+ cells = _identifier_tuple(self.data_cells, label="campaign data_cells")
224
+ expected_cells = (
225
+ _REQUIRED_DATA_CELLS
226
+ if self.coverage_design == _REPRESENTATIVE_COVERAGE
227
+ else _AUXILIARY_STATIC_CELLS
228
+ )
229
+ if set(cells) != expected_cells:
230
+ raise ValidationError(
231
+ "one-click campaign data_cells differ from the declared coverage design"
232
+ )
233
+ jobs = tuple(self.jobs)
234
+ if not jobs or any(not isinstance(job, OneClickCampaignJob) for job in jobs):
235
+ raise ValidationError("one-click campaign requires typed jobs")
236
+ if len({job.job_id for job in jobs}) != len(jobs):
237
+ raise ValidationError("one-click campaign job IDs must be unique")
238
+ methods = {item.method_id for item in AlgorithmRegistry.default().list_methods()}
239
+ observed_methods = {method_id for job in jobs for method_id in job.method_ids}
240
+ observed_cells = {cell for job in jobs for cell in job.benchmark_cells}
241
+ if self.coverage_design == _REPRESENTATIVE_COVERAGE:
242
+ if observed_methods != methods:
243
+ raise ValidationError(
244
+ "representative one-click jobs must cover the exact 27-method registry"
245
+ )
246
+ if observed_cells != _REQUIRED_DATA_CELLS:
247
+ raise ValidationError(
248
+ "representative one-click jobs must cover the exact ten cells"
249
+ )
250
+ else:
251
+ from agentinputlab.experiments.auxiliary_matrix import (
252
+ build_auxiliary_method_cell_matrix,
253
+ )
254
+
255
+ expected_pairs = {
256
+ (row["method_id"], row["benchmark_cell_id"])
257
+ for row in build_auxiliary_method_cell_matrix()["rows"]
258
+ if row["eligible"]
259
+ }
260
+ observed_pairs: set[tuple[str, str]] = set()
261
+ for job in jobs:
262
+ if len(job.method_ids) != 1 or len(job.benchmark_cells) != 1:
263
+ raise ValidationError(
264
+ "auxiliary matrix jobs must bind exactly one method and one cell"
265
+ )
266
+ observed_pairs.add((job.method_ids[0], job.benchmark_cells[0]))
267
+ if observed_pairs != expected_pairs or len(jobs) != 117:
268
+ raise ValidationError(
269
+ "auxiliary one-click jobs must cover the exact 117 legal pairs"
270
+ )
271
+ if observed_cells != _AUXILIARY_STATIC_CELLS:
272
+ raise ValidationError(
273
+ "auxiliary one-click jobs must cover the exact seven static cells"
274
+ )
275
+ _require_sha256(self.campaign_hash, label="campaign_hash")
276
+ _require_sha256(self.source_sha256, label="campaign source_sha256")
277
+ object.__setattr__(self, "data_cells", cells)
278
+ object.__setattr__(self, "jobs", jobs)
279
+
280
+
281
+ @dataclass(frozen=True, slots=True)
282
+ class _PreparedJob:
283
+ job: OneClickCampaignJob
284
+ manifest: ExperimentManifest
285
+ plan: ExperimentPlan | None
286
+ preflight: Any
287
+ environment: Mapping[str, str]
288
+
289
+
290
+ def campaign_bundle_hash(value: Mapping[str, Any]) -> str:
291
+ """Return the semantic hash for a strict bundle payload."""
292
+
293
+ if not isinstance(value, Mapping):
294
+ raise TypeError("campaign bundle must be a mapping")
295
+ return content_hash({key: item for key, item in value.items() if key != "campaign_hash"})
296
+
297
+
298
+ def one_click_method_targets() -> Mapping[str, tuple[str, str]]:
299
+ """Return the exact 27-method coverage map over ten accepted-source cells."""
300
+
301
+ methods = {item.method_id for item in AlgorithmRegistry.default().list_methods()}
302
+ if set(_METHOD_TARGETS) != methods:
303
+ raise ValidationError("one-click target map differs from the exact algorithm registry")
304
+ if {cell for cell, _split in _METHOD_TARGETS.values()} != _REQUIRED_DATA_CELLS:
305
+ raise ValidationError("one-click target map does not cover the exact ten data cells")
306
+ return _METHOD_TARGETS
307
+
308
+
309
+ def build_one_click_method_manifest(
310
+ *,
311
+ method_id: str,
312
+ release_id: str,
313
+ family: str,
314
+ profile_kind: str,
315
+ upstream_commit: str,
316
+ driver_entrypoint: str,
317
+ seeds: Sequence[int],
318
+ model_slots: Sequence[str],
319
+ target_cell: str | None = None,
320
+ target_split: str | None = None,
321
+ study_id: str | None = None,
322
+ ) -> ExperimentManifest:
323
+ """Build one candidate-versus-NO_OP discovery manifest for the campaign."""
324
+
325
+ targets = one_click_method_targets()
326
+ try:
327
+ default_cell, default_split = targets[method_id]
328
+ method, release = AlgorithmRegistry.default().get_release(release_id)
329
+ semantic_family = SemanticFamily(family)
330
+ profile = ProfileKind(profile_kind)
331
+ except (KeyError, ValueError) as exc:
332
+ raise ValidationError("invalid one-click method manifest identity") from exc
333
+ if (
334
+ method.method_id != method_id
335
+ or method.semantic_family is not semantic_family
336
+ or method.upstream.commit != upstream_commit
337
+ or profile not in release.profile_modes
338
+ ):
339
+ raise ValidationError("one-click method manifest differs from the algorithm registry")
340
+ selected_cell = default_cell if target_cell is None else target_cell
341
+ selected_split = default_split if target_split is None else target_split
342
+ if not isinstance(selected_cell, str) or not selected_cell.strip():
343
+ raise ValidationError("one-click target_cell must be non-empty")
344
+ if not isinstance(selected_split, str) or not selected_split.strip():
345
+ raise ValidationError("one-click target_split must be non-empty")
346
+ external = semantic_family in {SemanticFamily.CLARIFY, SemanticFamily.COMPOSITE}
347
+ prerequisites = [
348
+ ExperimentPrerequisite(
349
+ PrerequisiteKind.DIRECTORY_ENV,
350
+ "AILAB_PREPARED_DATA_ROOT",
351
+ "Checksum-verified prepared benchmark root",
352
+ ),
353
+ ExperimentPrerequisite(
354
+ PrerequisiteKind.DIRECTORY_ENV,
355
+ "AILAB_METHOD_CHECKOUT",
356
+ "Clean checkout of the method source pinned by the catalog",
357
+ ),
358
+ ExperimentPrerequisite(
359
+ PrerequisiteKind.DIRECTORY_ENV,
360
+ "AILAB_CANDIDATE_CHECKOUT",
361
+ "Clean AgentInputLab checkout used for the candidate runtime",
362
+ ),
363
+ ExperimentPrerequisite(
364
+ PrerequisiteKind.FILE_ENV,
365
+ "AILAB_METHOD_ASSET_MANIFEST",
366
+ "Checksum-locked weights, prompts, worker, and auxiliary assets",
367
+ sha256_env="AILAB_METHOD_ASSET_MANIFEST_SHA256",
368
+ ),
369
+ ExperimentPrerequisite(
370
+ PrerequisiteKind.FILE_ENV,
371
+ "AILAB_MODEL_CONFIG",
372
+ "Frozen backend/model configuration",
373
+ sha256_env="AILAB_MODEL_CONFIG_SHA256",
374
+ ),
375
+ ExperimentPrerequisite(
376
+ PrerequisiteKind.FILE_ENV,
377
+ "AILAB_RETRIEVER_CONFIG",
378
+ "Frozen corpus/index/embedder/retriever/reranker configuration",
379
+ sha256_env="AILAB_RETRIEVER_CONFIG_SHA256",
380
+ ),
381
+ ]
382
+ if driver_entrypoint == "agentinputlab.experiments.drivers.static_production:factory":
383
+ prerequisites.extend(
384
+ (
385
+ ExperimentPrerequisite(
386
+ PrerequisiteKind.FILE_ENV,
387
+ "AILAB_PREDICTOR_CONFIG",
388
+ "Frozen downstream predictor configuration",
389
+ sha256_env="AILAB_PREDICTOR_CONFIG_SHA256",
390
+ ),
391
+ ExperimentPrerequisite(
392
+ PrerequisiteKind.FILE_ENV,
393
+ "AILAB_STATIC_PRODUCTION_DRIVER_CONFIG",
394
+ "Generated checksum-locked static method-dataset driver",
395
+ sha256_env="AILAB_STATIC_PRODUCTION_DRIVER_CONFIG_SHA256",
396
+ ),
397
+ )
398
+ )
399
+ checkout_revisions = {"AILAB_METHOD_CHECKOUT": upstream_commit}
400
+ if external:
401
+ prerequisites.append(
402
+ ExperimentPrerequisite(
403
+ PrerequisiteKind.DIRECTORY_ENV,
404
+ "AILAB_BENCHMARK_CHECKOUT",
405
+ "Clean tau3 checkout used by the atomic discovery runtime",
406
+ )
407
+ )
408
+ checkout_revisions["AILAB_BENCHMARK_CHECKOUT"] = _TAU3_REVISION
409
+ return ExperimentManifest(
410
+ schema_version=1,
411
+ study_id=study_id or f"one_click.{method_id}",
412
+ stage=ExperimentStage.DISCOVERY,
413
+ estimand_profile_kind=profile,
414
+ benchmarks=(
415
+ ExperimentBenchmarkTarget(
416
+ cell_id=selected_cell,
417
+ split=selected_split,
418
+ unit_inventory_env="AILAB_METHOD_DISCOVERY_UNITS",
419
+ unit_inventory_sha256_env="AILAB_METHOD_DISCOVERY_UNITS_SHA256",
420
+ external=external,
421
+ source_revision=_TAU3_REVISION if external else None,
422
+ arm_ids=("no_op", "candidate"),
423
+ ),
424
+ ),
425
+ arms=(
426
+ ExperimentArmSpec(
427
+ arm_id="no_op",
428
+ release_id="baseline.no_op",
429
+ family=SemanticFamily.NO_OP,
430
+ profile_kind=profile,
431
+ ),
432
+ ExperimentArmSpec(
433
+ arm_id="candidate",
434
+ release_id=release_id,
435
+ family=semantic_family,
436
+ profile_kind=profile,
437
+ parameters={"method_id": method_id},
438
+ ),
439
+ ),
440
+ seeds=tuple(seeds),
441
+ model_slots=tuple(model_slots),
442
+ prerequisites=tuple(prerequisites),
443
+ driver_entrypoint=driver_entrypoint,
444
+ metadata={
445
+ "method_id": method_id,
446
+ "candidate_release_id": release_id,
447
+ "checkout_expected_revisions": checkout_revisions,
448
+ "comparison_scope": "one_click_capability_specific_method_vs_no_op",
449
+ "cross_method_raw_ranking_allowed": False,
450
+ "confirmatory_claimed": False,
451
+ "execution_default": "preflight_only",
452
+ "require_driver_unit_preflight": True,
453
+ "requires_atomic_external_replay": external,
454
+ },
455
+ )
456
+
457
+
458
+ def load_one_click_campaign(path: str | Path) -> OneClickCampaign:
459
+ """Load and strictly validate one immutable one-click campaign bundle."""
460
+
461
+ unresolved = Path(path).expanduser()
462
+ if unresolved.is_symlink():
463
+ raise ArtifactError("one-click campaign config must not be a symlink")
464
+ source = unresolved.resolve()
465
+ try:
466
+ if source.stat().st_size > _MAX_CONFIG_BYTES:
467
+ raise ArtifactError("one-click campaign config exceeds the size limit")
468
+ payload = source.read_bytes()
469
+ raw = json.loads(
470
+ payload.decode("utf-8"),
471
+ object_pairs_hook=_reject_duplicate_keys,
472
+ parse_constant=_reject_constant,
473
+ )
474
+ except (OSError, UnicodeError, json.JSONDecodeError) as exc:
475
+ raise ArtifactError("one-click campaign config is not strict UTF-8 JSON") from exc
476
+ if not isinstance(raw, Mapping):
477
+ raise ValidationError("one-click campaign must be an object")
478
+ fields = frozenset(raw)
479
+ if fields == _CONFIG_FIELDS:
480
+ coverage_design = _REPRESENTATIVE_COVERAGE
481
+ elif fields == _CONFIG_FIELDS_WITH_COVERAGE:
482
+ coverage_design = raw["coverage_design"]
483
+ else:
484
+ raise ValidationError(
485
+ "one-click campaign fields must match the representative or auxiliary schema"
486
+ )
487
+ data = raw
488
+ if data["schema_version"] != 1 or data["protocol"] != ONE_CLICK_CAMPAIGN_PROTOCOL:
489
+ raise ValidationError("unsupported one-click campaign protocol")
490
+ expected_hash = campaign_bundle_hash(data)
491
+ if data["campaign_hash"] != expected_hash:
492
+ raise ValidationError("one-click campaign hash differs from its content")
493
+ jobs_raw = data["jobs"]
494
+ if isinstance(jobs_raw, (str, bytes)) or not isinstance(jobs_raw, Sequence):
495
+ raise ValidationError("one-click campaign jobs must be an array")
496
+ jobs = tuple(_parse_job(item, index=index) for index, item in enumerate(jobs_raw))
497
+ return OneClickCampaign(
498
+ campaign_id=_identifier(data["campaign_id"], label="campaign_id"),
499
+ prepared_data_root=_absolute_path(
500
+ data["prepared_data_root"],
501
+ label="prepared_data_root",
502
+ ),
503
+ data_cells=tuple(data["data_cells"]),
504
+ output_root=_absolute_path(data["output_root"], label="output_root"),
505
+ resume=_boolean(data["resume"], label="resume"),
506
+ fail_fast=_boolean(data["fail_fast"], label="fail_fast"),
507
+ jobs=jobs,
508
+ campaign_hash=expected_hash,
509
+ source_path=source,
510
+ source_sha256=hashlib.sha256(payload).hexdigest(),
511
+ coverage_design=coverage_design,
512
+ )
513
+
514
+
515
+ def preflight_one_click_campaign(
516
+ campaign: OneClickCampaign,
517
+ ) -> tuple[Mapping[str, Any], tuple[_PreparedJob, ...]]:
518
+ """Preflight data and every job before any model or external runtime call."""
519
+
520
+ if not isinstance(campaign, OneClickCampaign):
521
+ raise TypeError("preflight requires a OneClickCampaign")
522
+ data_report: Mapping[str, Any] | None = None
523
+ data_blockers: list[str] = []
524
+ try:
525
+ if campaign.coverage_design == _REPRESENTATIVE_COVERAGE:
526
+ from agentinputlab.campaign import campaign_data_smoke
527
+
528
+ data_report = campaign_data_smoke(campaign.prepared_data_root)
529
+ observed = set(data_report["accepted_source_cells"])
530
+ if observed != _REQUIRED_DATA_CELLS:
531
+ data_blockers.append(
532
+ "prepared data smoke did not cover the exact ten accepted-source cells"
533
+ )
534
+ else:
535
+ from agentinputlab.benchmarks import load_production_benchmark_adapter
536
+
537
+ rows = []
538
+ for cell_id in sorted(_AUXILIARY_STATIC_CELLS):
539
+ adapter = load_production_benchmark_adapter(
540
+ cell_id,
541
+ campaign.prepared_data_root,
542
+ )
543
+ rows.append(
544
+ {
545
+ "cell_id": cell_id,
546
+ "split": adapter.split,
547
+ "unit_count": len(tuple(adapter.examples(adapter.split))),
548
+ }
549
+ )
550
+ data_report = freeze_mapping(
551
+ {
552
+ "schema_version": 1,
553
+ "protocol": "agentinputlab.auxiliary-static-data-smoke.v1",
554
+ "accepted_source_cells": tuple(sorted(_AUXILIARY_STATIC_CELLS)),
555
+ "cells": tuple(rows),
556
+ }
557
+ )
558
+ except (AgentInputLabError, OSError, TypeError, ValueError) as exc:
559
+ data_blockers.append(f"data gate: {type(exc).__name__}: {redact_secret_text(str(exc))}")
560
+
561
+ prepared_jobs: list[_PreparedJob] = []
562
+ job_rows: list[dict[str, Any]] = []
563
+ for job in campaign.jobs:
564
+ blockers: list[str] = []
565
+ manifest: ExperimentManifest | None = None
566
+ plan: ExperimentPlan | None = None
567
+ report: Any = None
568
+ environment = _job_environment(campaign, job)
569
+ try:
570
+ _verify_file(job.manifest_path, expected_sha256=job.manifest_sha256)
571
+ manifest = load_experiment_manifest(job.manifest_path)
572
+ _validate_job_manifest(job, manifest)
573
+ with _temporary_environment(environment):
574
+ report, units = preflight_experiment(
575
+ manifest,
576
+ driver_entrypoint=job.driver_entrypoint,
577
+ environment=environment,
578
+ resolve_units=True,
579
+ )
580
+ blockers.extend(report.blockers)
581
+ if report.ready:
582
+ plan = build_experiment_plan(manifest, units)
583
+ except (AgentInputLabError, OSError, TypeError, ValueError) as exc:
584
+ blockers.append(
585
+ f"{type(exc).__name__}: "
586
+ f"{redact_secret_text(str(exc), secret_values=_secret_values(environment))}"
587
+ )
588
+ secrets = _secret_values(environment)
589
+ blockers = sorted(
590
+ {redact_secret_text(blocker, secret_values=secrets) for blocker in blockers}
591
+ )
592
+ if manifest is not None and report is not None:
593
+ prepared_jobs.append(
594
+ _PreparedJob(
595
+ job=job,
596
+ manifest=manifest,
597
+ plan=plan,
598
+ preflight=report,
599
+ environment=freeze_mapping(environment),
600
+ )
601
+ )
602
+ job_rows.append(
603
+ {
604
+ "job_id": job.job_id,
605
+ "method_ids": job.method_ids,
606
+ "benchmark_cells": job.benchmark_cells,
607
+ "manifest_sha256": job.manifest_sha256,
608
+ "driver_entrypoint": job.driver_entrypoint,
609
+ "ready": not blockers and plan is not None,
610
+ "blockers": tuple(blockers),
611
+ "plan_hash": plan.plan_hash if plan is not None else None,
612
+ "planned_cells": len(plan.cells) if plan is not None else 0,
613
+ }
614
+ )
615
+ ready_jobs = sum(row["ready"] for row in job_rows)
616
+ ready = (
617
+ not data_blockers
618
+ and data_report is not None
619
+ and ready_jobs == len(campaign.jobs)
620
+ and len(prepared_jobs) == len(campaign.jobs)
621
+ )
622
+ result = {
623
+ "schema_version": 1,
624
+ "protocol": "agentinputlab.one-click-campaign-preflight.v1",
625
+ "campaign_id": campaign.campaign_id,
626
+ "campaign_hash": campaign.campaign_hash,
627
+ "source_sha256": campaign.source_sha256,
628
+ "method_count": len({method_id for job in campaign.jobs for method_id in job.method_ids}),
629
+ "method_dataset_pair_count": sum(
630
+ len(job.method_ids) * len(job.benchmark_cells) for job in campaign.jobs
631
+ ),
632
+ "coverage_design": campaign.coverage_design,
633
+ "full_cartesian": False,
634
+ "full_legal_matrix": campaign.coverage_design == _AUXILIARY_COVERAGE,
635
+ "declared_data_cell_count": len(campaign.data_cells),
636
+ "data_cells": campaign.data_cells,
637
+ "data_ready": not data_blockers,
638
+ "data_blockers": tuple(data_blockers),
639
+ "data_smoke": data_report,
640
+ "job_count": len(campaign.jobs),
641
+ "ready_jobs": ready_jobs,
642
+ "blocked_jobs": len(campaign.jobs) - ready_jobs,
643
+ "planned_cells": sum(row["planned_cells"] for row in job_rows),
644
+ "jobs": tuple(job_rows),
645
+ "ready": ready,
646
+ "experiment_executed": False,
647
+ }
648
+ return freeze_mapping(result), tuple(prepared_jobs)
649
+
650
+
651
+ def run_one_click_campaign(
652
+ campaign: OneClickCampaign,
653
+ *,
654
+ execute: bool,
655
+ ) -> Mapping[str, Any]:
656
+ """Preflight the whole bundle, then optionally execute and verify every job."""
657
+
658
+ if not isinstance(execute, bool):
659
+ raise TypeError("execute must be boolean")
660
+ preflight, prepared_jobs = preflight_one_click_campaign(campaign)
661
+ if not execute:
662
+ return preflight
663
+ if not preflight["ready"]:
664
+ return freeze_mapping(
665
+ {
666
+ **dict(preflight),
667
+ "experiment_executed": False,
668
+ "execution_blocked": True,
669
+ }
670
+ )
671
+
672
+ campaign_root = one_click_campaign_run_root(campaign)
673
+ campaign_root.mkdir(parents=True, exist_ok=True)
674
+ results: list[dict[str, Any]] = []
675
+ for prepared in prepared_jobs:
676
+ if prepared.plan is None or prepared.preflight.binding_receipt is None:
677
+ raise ArtifactError("ready one-click job lacks a frozen plan or binding receipt")
678
+ job_output = campaign_root / prepared.job.job_id
679
+ resume_job = campaign.resume and job_output.exists()
680
+ try:
681
+ with (
682
+ _temporary_environment(prepared.environment),
683
+ activate_guarded_driver(
684
+ manifest=prepared.manifest,
685
+ operator_entrypoint=prepared.job.driver_entrypoint,
686
+ operator_fingerprint=(prepared.preflight.operator_driver_fingerprint),
687
+ binding_receipt=prepared.preflight.binding_receipt,
688
+ parity_contract=prepared.preflight.parity_contract,
689
+ ) as guarded_entrypoint,
690
+ ):
691
+ summary = execute_experiment_plan(
692
+ prepared.manifest,
693
+ prepared.plan,
694
+ driver_entrypoint=guarded_entrypoint,
695
+ output_root=str(job_output),
696
+ resume=resume_job,
697
+ fail_fast=campaign.fail_fast,
698
+ )
699
+ verified = verify_experiment_run_directory(summary.run_dir)
700
+ if not verified["complete"] or not verified["all_succeeded"]:
701
+ raise ArtifactError("one-click job result verification did not pass")
702
+ results.append(
703
+ {
704
+ "job_id": prepared.job.job_id,
705
+ "status": "succeeded",
706
+ "run_dir": summary.run_dir,
707
+ "plan_hash": summary.plan_hash,
708
+ "planned_cells": summary.planned_cells,
709
+ "attempted_cells": summary.attempted_cells,
710
+ "skipped_cells": summary.skipped_cells,
711
+ "succeeded_cells": summary.succeeded_cells,
712
+ "failed_cells": summary.failed_cells,
713
+ "verified": True,
714
+ }
715
+ )
716
+ except (AgentInputLabError, OSError, TypeError, ValueError) as exc:
717
+ results.append(
718
+ {
719
+ "job_id": prepared.job.job_id,
720
+ "status": "aborted",
721
+ "failure": {
722
+ "category": type(exc).__name__,
723
+ "message": redact_secret_text(
724
+ str(exc),
725
+ secret_values=_secret_values(prepared.environment),
726
+ ),
727
+ },
728
+ "verified": False,
729
+ }
730
+ )
731
+ if campaign.fail_fast:
732
+ break
733
+ succeeded = sum(row["status"] == "succeeded" for row in results)
734
+ all_succeeded = succeeded == len(campaign.jobs)
735
+ summary_payload = {
736
+ "schema_version": 1,
737
+ "protocol": "agentinputlab.one-click-campaign-result.v1",
738
+ "campaign_id": campaign.campaign_id,
739
+ "campaign_hash": campaign.campaign_hash,
740
+ "source_sha256": campaign.source_sha256,
741
+ "method_count": preflight["method_count"],
742
+ "method_dataset_pair_count": preflight["method_dataset_pair_count"],
743
+ "coverage_design": preflight["coverage_design"],
744
+ "full_cartesian": preflight["full_cartesian"],
745
+ "data_cell_count": preflight["declared_data_cell_count"],
746
+ "job_count": len(campaign.jobs),
747
+ "attempted_jobs": len(results),
748
+ "succeeded_jobs": succeeded,
749
+ "failed_jobs": len(results) - succeeded,
750
+ "all_succeeded": all_succeeded,
751
+ "jobs": tuple(results),
752
+ "experiment_executed": True,
753
+ }
754
+ summary_payload["result_hash"] = content_hash(summary_payload)
755
+ _write_summary(campaign_root / "campaign-summary.json", summary_payload)
756
+ return freeze_mapping(summary_payload)
757
+
758
+
759
+ def one_click_campaign_run_root(campaign: OneClickCampaign) -> Path:
760
+ """Return the immutable output namespace for one frozen campaign.
761
+
762
+ A human-readable campaign ID may be reused after code, manifests, or locked
763
+ assets change. The content hash therefore participates in the path so a
764
+ new campaign can never accidentally resume an incompatible prior run.
765
+ """
766
+
767
+ if not isinstance(campaign, OneClickCampaign):
768
+ raise TypeError("campaign run root requires a OneClickCampaign")
769
+ return campaign.output_root / campaign.campaign_id / campaign.campaign_hash
770
+
771
+
772
+ def _parse_job(value: Any, *, index: int) -> OneClickCampaignJob:
773
+ data = _strict_mapping(value, _JOB_FIELDS, label=f"jobs[{index}]")
774
+ environment = data["environment"]
775
+ if not isinstance(environment, Mapping):
776
+ raise ValidationError(f"jobs[{index}].environment must be an object")
777
+ return OneClickCampaignJob(
778
+ job_id=data["job_id"],
779
+ method_ids=tuple(data["method_ids"]),
780
+ benchmark_cells=tuple(data["benchmark_cells"]),
781
+ manifest_path=_absolute_path(
782
+ data["manifest_path"],
783
+ label=f"jobs[{index}].manifest_path",
784
+ ),
785
+ manifest_sha256=data["manifest_sha256"],
786
+ driver_entrypoint=data["driver_entrypoint"],
787
+ environment=environment,
788
+ )
789
+
790
+
791
+ def _validate_job_manifest(
792
+ job: OneClickCampaignJob,
793
+ manifest: ExperimentManifest,
794
+ ) -> None:
795
+ manifest_cells = {target.cell_id for target in manifest.benchmarks}
796
+ if manifest_cells != set(job.benchmark_cells):
797
+ raise ValidationError(f"{job.job_id}: declared benchmark_cells differ from the manifest")
798
+ if not manifest_cells.issubset(_REQUIRED_DATA_CELLS):
799
+ raise ValidationError(f"{job.job_id}: manifest touches a frozen or undeclared data cell")
800
+ registry = AlgorithmRegistry.default()
801
+ manifest_methods: set[str] = set()
802
+ for arm in manifest.arms:
803
+ if arm.release_id == "baseline.no_op":
804
+ continue
805
+ try:
806
+ method, _release = registry.get_release(arm.release_id)
807
+ except Exception as exc:
808
+ raise ValidationError(
809
+ f"{job.job_id}: cannot resolve release {arm.release_id!r}"
810
+ ) from exc
811
+ manifest_methods.add(method.method_id)
812
+ if manifest_methods != set(job.method_ids):
813
+ raise ValidationError(f"{job.job_id}: method_ids differ from manifest release arms")
814
+
815
+
816
+ def _job_environment(
817
+ campaign: OneClickCampaign,
818
+ job: OneClickCampaignJob,
819
+ ) -> dict[str, str]:
820
+ environment = dict(os.environ)
821
+ environment.update(job.environment)
822
+ prepared = str(campaign.prepared_data_root)
823
+ current = environment.get("AILAB_PREPARED_DATA_ROOT")
824
+ if current is not None and Path(current).expanduser().resolve() != campaign.prepared_data_root:
825
+ raise ValidationError(
826
+ f"{job.job_id}: AILAB_PREPARED_DATA_ROOT differs from campaign prepared_data_root"
827
+ )
828
+ environment["AILAB_PREPARED_DATA_ROOT"] = prepared
829
+ return environment
830
+
831
+
832
+ @contextmanager
833
+ def _temporary_environment(environment: Mapping[str, str]) -> Iterator[None]:
834
+ previous = dict(os.environ)
835
+ os.environ.clear()
836
+ os.environ.update(environment)
837
+ try:
838
+ yield
839
+ finally:
840
+ os.environ.clear()
841
+ os.environ.update(previous)
842
+
843
+
844
+ def _secret_values(environment: Mapping[str, str]) -> tuple[str, ...]:
845
+ return tuple(
846
+ value
847
+ for name, value in environment.items()
848
+ if _SECRET_NAME.search(name) is not None and isinstance(value, str) and value
849
+ )
850
+
851
+
852
+ def _write_summary(path: Path, value: Mapping[str, Any]) -> None:
853
+ encoded = (
854
+ json.dumps(
855
+ to_jsonable(value),
856
+ ensure_ascii=False,
857
+ indent=2,
858
+ sort_keys=True,
859
+ )
860
+ + "\n"
861
+ ).encode("utf-8")
862
+ temporary = path.with_name(f".{path.name}.tmp")
863
+ try:
864
+ temporary.write_bytes(encoded)
865
+ temporary.replace(path)
866
+ except OSError as exc:
867
+ raise ArtifactError("cannot persist one-click campaign summary") from exc
868
+
869
+
870
+ def _verify_file(path: Path, *, expected_sha256: str) -> None:
871
+ if path.is_symlink() or not path.is_file():
872
+ raise ArtifactError("campaign manifest must be an existing non-symlink file")
873
+ if _sha256_file(path) != expected_sha256:
874
+ raise ArtifactError("campaign manifest checksum differs")
875
+
876
+
877
+ def _sha256_file(path: Path) -> str:
878
+ digest = hashlib.sha256()
879
+ try:
880
+ with path.open("rb") as handle:
881
+ for block in iter(lambda: handle.read(1024 * 1024), b""):
882
+ digest.update(block)
883
+ except OSError as exc:
884
+ raise ArtifactError(f"cannot hash campaign file: {path}") from exc
885
+ return digest.hexdigest()
886
+
887
+
888
+ def _strict_mapping(value: Any, fields: frozenset[str], *, label: str) -> Mapping[str, Any]:
889
+ if not isinstance(value, Mapping) or set(value) != fields:
890
+ raise ValidationError(f"{label} fields must be exactly: {', '.join(sorted(fields))}")
891
+ return value
892
+
893
+
894
+ def _identifier(value: Any, *, label: str) -> str:
895
+ if not isinstance(value, str) or value != value.strip() or _IDENTIFIER.fullmatch(value) is None:
896
+ raise ValidationError(f"{label} must be a stable identifier")
897
+ return value
898
+
899
+
900
+ def _identifier_tuple(value: Any, *, label: str) -> tuple[str, ...]:
901
+ if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
902
+ raise ValidationError(f"{label} must be an array")
903
+ result = tuple(_identifier(item, label=label) for item in value)
904
+ if not result or len(result) != len(set(result)):
905
+ raise ValidationError(f"{label} requires unique identifiers")
906
+ return result
907
+
908
+
909
+ def _absolute_path(value: Any, *, label: str) -> Path:
910
+ if not isinstance(value, str) or not value.strip() or value != value.strip():
911
+ raise ValidationError(f"{label} must be a non-empty path")
912
+ path = Path(value).expanduser()
913
+ if not path.is_absolute():
914
+ raise ValidationError(f"{label} must be absolute")
915
+ return path.resolve()
916
+
917
+
918
+ def _boolean(value: Any, *, label: str) -> bool:
919
+ if not isinstance(value, bool):
920
+ raise ValidationError(f"{label} must be boolean")
921
+ return value
922
+
923
+
924
+ def _require_sha256(value: Any, *, label: str) -> str:
925
+ if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
926
+ raise ValidationError(f"{label} must be a lowercase SHA-256")
927
+ return value
928
+
929
+
930
+ def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
931
+ result: dict[str, Any] = {}
932
+ for key, value in pairs:
933
+ if key in result:
934
+ raise ValidationError(f"duplicate JSON key: {key}")
935
+ result[key] = value
936
+ return result
937
+
938
+
939
+ def _reject_constant(value: str) -> None:
940
+ raise ValidationError(f"non-finite JSON value is not allowed: {value}")
941
+
942
+
943
+ __all__ = [
944
+ "ONE_CLICK_CAMPAIGN_PROTOCOL",
945
+ "OneClickCampaign",
946
+ "OneClickCampaignJob",
947
+ "build_one_click_method_manifest",
948
+ "campaign_bundle_hash",
949
+ "load_one_click_campaign",
950
+ "one_click_method_targets",
951
+ "preflight_one_click_campaign",
952
+ "run_one_click_campaign",
953
+ ]