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,1408 @@
1
+ """Safe, deterministic experiment execution used by the packaged local Studio.
2
+
3
+ The Studio deliberately exposes only profiles that can be constructed without
4
+ arbitrary imports, shell commands, uploaded code, or network access. The full
5
+ registry remains visible so that unavailable profiles fail with an explicit
6
+ compatibility reason instead of silently falling back to another algorithm.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import copy
12
+ import logging
13
+ import math
14
+ import re
15
+ import threading
16
+ import time
17
+ import uuid
18
+ from collections.abc import Mapping
19
+ from concurrent.futures import ThreadPoolExecutor
20
+ from dataclasses import dataclass, field
21
+ from datetime import datetime, timezone
22
+ from pathlib import Path
23
+ from statistics import fmean, median
24
+ from types import MappingProxyType
25
+ from typing import Any
26
+
27
+ from agentinputlab.algorithms.clarify import UDCSPromptNativePolicy
28
+ from agentinputlab.algorithms.decompose.plan_and_budget import (
29
+ CachedPlanBudgetRecord,
30
+ PlanAndBudgetCachedSoftBudget,
31
+ )
32
+ from agentinputlab.algorithms.decompose.plan_and_solve import PlanAndSolvePSPrompt
33
+ from agentinputlab.algorithms.reformulate.query2doc import (
34
+ OfficialQuery2DocRow,
35
+ Query2DocCache,
36
+ Query2DocFixtureCached,
37
+ )
38
+ from agentinputlab.backends.llm import ReplayBackend
39
+ from agentinputlab.backends.retrieval import Document, InMemoryBM25Retriever
40
+ from agentinputlab.benchmarks import BenchmarkRegistry, BenchmarkSmokeAssetRegistry
41
+ from agentinputlab.contracts import (
42
+ FrozenAgentState,
43
+ InputPatch,
44
+ InterventionContext,
45
+ SemanticFamily,
46
+ )
47
+ from agentinputlab.contracts.errors import ArtifactError
48
+ from agentinputlab.contracts.serialization import to_jsonable
49
+ from agentinputlab.metrics import compression_ratio, intent_token_recall, token_jaccard
50
+ from agentinputlab.metrics.paired import summarize_pairs
51
+ from agentinputlab.observability import ResourceUsage
52
+ from agentinputlab.registry import AlgorithmRegistry
53
+ from agentinputlab.runner import InterventionRunner
54
+ from agentinputlab.selectors import list_selector_cards
55
+
56
+ from .model_backends import (
57
+ StudioModelBackendGate,
58
+ StudioModelBackendUnavailable,
59
+ StudioModelRuntime,
60
+ )
61
+ from .prepared import (
62
+ MAX_PREPARED_SAMPLE_LIMIT,
63
+ discover_prepared_benchmarks,
64
+ discover_prepared_production_cells,
65
+ load_verified_studio_dataset,
66
+ prepared_catalog_payload,
67
+ )
68
+
69
+ _TOKEN = re.compile(r"[\w]+", re.UNICODE)
70
+ _SECRET_KEY = re.compile(
71
+ r"(?:api[_-]?key|authorization|bearer|password|secret|access[_-]?token|private[_-]?key)",
72
+ re.IGNORECASE,
73
+ )
74
+
75
+ MAX_RUNS = 100
76
+ MAX_TEXT_LENGTH = 4_096
77
+ _LOGGER = logging.getLogger(__name__)
78
+
79
+
80
+ class StudioValidationError(ValueError):
81
+ """Raised when an execution request does not pass Studio validation."""
82
+
83
+
84
+ class StudioSecurityError(ValueError):
85
+ """Raised when a request attempts to submit credentials or executable input."""
86
+
87
+
88
+ class StudioQueueFullError(RuntimeError):
89
+ """Raised when the bounded local queue cannot accept more work."""
90
+
91
+
92
+ @dataclass(frozen=True, slots=True)
93
+ class StudioExample:
94
+ example_id: str
95
+ query: str
96
+ relevant_document_ids: tuple[str, ...]
97
+ pseudo_doc: str = ""
98
+ instruction: str = ""
99
+ visible_metadata: Mapping[str, Any] = field(default_factory=dict)
100
+
101
+ def __post_init__(self) -> None:
102
+ if not self.relevant_document_ids:
103
+ raise ValueError("Studio examples require at least one relevant document ID")
104
+ object.__setattr__(self, "visible_metadata", MappingProxyType(dict(self.visible_metadata)))
105
+
106
+ @property
107
+ def target_doc_id(self) -> str:
108
+ """Compatibility label for the first evaluator-only relevant ID."""
109
+
110
+ return self.relevant_document_ids[0]
111
+
112
+
113
+ TINY_EXAMPLES = (
114
+ StudioExample(
115
+ example_id="capital-france",
116
+ query="capital france",
117
+ relevant_document_ids=("france",),
118
+ pseudo_doc="Paris is France's capital city and the seat of its national government.",
119
+ ),
120
+ StudioExample(
121
+ example_id="python-creator",
122
+ query="creator python programming language",
123
+ relevant_document_ids=("python",),
124
+ pseudo_doc="Guido van Rossum created the Python programming language.",
125
+ ),
126
+ StudioExample(
127
+ example_id="water-freezing",
128
+ query="water freezing point celsius",
129
+ relevant_document_ids=("water",),
130
+ pseudo_doc=(
131
+ "At standard atmospheric pressure, pure water freezes at zero degrees Celsius."
132
+ ),
133
+ ),
134
+ StudioExample(
135
+ example_id="pride-author",
136
+ query="author pride prejudice novel",
137
+ relevant_document_ids=("austen",),
138
+ pseudo_doc="Jane Austen wrote the novel Pride and Prejudice.",
139
+ ),
140
+ )
141
+
142
+ TINY_DOCUMENTS = (
143
+ Document(
144
+ doc_id="france",
145
+ title="France",
146
+ text="Paris is the capital and most populous city of France.",
147
+ corpus_revision="studio-tiny-v1",
148
+ ),
149
+ Document(
150
+ doc_id="germany",
151
+ title="Germany",
152
+ text="Berlin is the capital city of Germany.",
153
+ corpus_revision="studio-tiny-v1",
154
+ ),
155
+ Document(
156
+ doc_id="python",
157
+ title="Python",
158
+ text="Python was created by Guido van Rossum and first released in 1991.",
159
+ corpus_revision="studio-tiny-v1",
160
+ ),
161
+ Document(
162
+ doc_id="java",
163
+ title="Java",
164
+ text="James Gosling designed Java at Sun Microsystems.",
165
+ corpus_revision="studio-tiny-v1",
166
+ ),
167
+ Document(
168
+ doc_id="water",
169
+ title="Freezing point",
170
+ text="Water freezes at 0 degrees Celsius under standard pressure.",
171
+ corpus_revision="studio-tiny-v1",
172
+ ),
173
+ Document(
174
+ doc_id="austen",
175
+ title="Pride and Prejudice",
176
+ text="Pride and Prejudice is an 1813 novel written by Jane Austen.",
177
+ corpus_revision="studio-tiny-v1",
178
+ ),
179
+ )
180
+
181
+
182
+ @dataclass(frozen=True, slots=True)
183
+ class RuntimeProfile:
184
+ algorithm_id: str
185
+ release_id: str
186
+ family: str
187
+ backend_ids: tuple[str, ...]
188
+ note: str
189
+
190
+
191
+ RUNTIME_PROFILES = {
192
+ "baseline.no_op": RuntimeProfile(
193
+ "baseline.no_op",
194
+ "baseline.no_op",
195
+ "baseline",
196
+ ("offline-replay",),
197
+ "Typed NO_OP paired baseline from the core patch contract.",
198
+ ),
199
+ "fixture.query2doc.cached": RuntimeProfile(
200
+ "rewrite.query2doc",
201
+ "fixture.query2doc.cached",
202
+ SemanticFamily.REFORMULATE.value,
203
+ ("offline-replay",),
204
+ "Local unverified Query2doc fixture; never presented as the official artifact.",
205
+ ),
206
+ "plan.plan_and_solve.ps_prompt": RuntimeProfile(
207
+ "plan.plan_and_solve",
208
+ "plan.plan_and_solve.ps_prompt",
209
+ SemanticFamily.DECOMPOSE.value,
210
+ ("offline-replay",),
211
+ "Clean-room paper prompt adapter; downstream answer generation is not simulated.",
212
+ ),
213
+ "plan.plan_and_budget.native_cached_soft_budget": RuntimeProfile(
214
+ "plan.plan_and_budget",
215
+ "plan.plan_and_budget.native_cached_soft_budget",
216
+ SemanticFamily.DECOMPOSE.value,
217
+ ("offline-replay",),
218
+ "Pinned cached-plan adapter with deterministic soft word allocation.",
219
+ ),
220
+ "clarify.udcs.repo_native_prompt_route": RuntimeProfile(
221
+ "clarify.udcs",
222
+ "clarify.udcs.repo_native_prompt_route",
223
+ SemanticFamily.CLARIFY.value,
224
+ ("offline-replay", "openai-compatible", "transformers-local"),
225
+ (
226
+ "Prompt-native routing evaluated against replay by default, or an "
227
+ "operator-attested ProductionBackendBuilder deployment."
228
+ ),
229
+ ),
230
+ }
231
+
232
+ _OFFLINE_BACKEND = {
233
+ "id": "offline-replay",
234
+ "name": "Offline Replay",
235
+ "ready": True,
236
+ "network": False,
237
+ "description": "Recorded deterministic responses; zero billable cost.",
238
+ "operator_attested": False,
239
+ }
240
+
241
+ DEFAULT_PARAMETERS: dict[str, int | float] = {
242
+ "seed": 7,
243
+ "sample_limit": 4,
244
+ "top_k": 3,
245
+ "temperature": 0.0,
246
+ "soft_word_budget": 180,
247
+ "clarification_threshold": 0.5,
248
+ }
249
+ DEFAULT_FAIRNESS = {
250
+ "paired_baseline": True,
251
+ "fixed_seed": True,
252
+ "shared_retriever": True,
253
+ "freeze_dependencies": True,
254
+ "fail_on_fingerprint_drift": True,
255
+ }
256
+
257
+
258
+ def _utc_now() -> str:
259
+ return datetime.now(timezone.utc).isoformat(timespec="milliseconds")
260
+
261
+
262
+ def _version_payload(release: Any) -> dict[str, Any]:
263
+ payload = release.to_dict()
264
+ profile = RUNTIME_PROFILES.get(release.id)
265
+ payload.update(
266
+ {
267
+ "studio_ready": profile is not None,
268
+ "studio_reason": (
269
+ profile.note
270
+ if profile is not None
271
+ else "; ".join(release.blocking_reasons)
272
+ or "This release is catalogued but has no safe deterministic Studio constructor."
273
+ ),
274
+ }
275
+ )
276
+ return payload
277
+
278
+
279
+ def build_catalog(
280
+ prepared_data_root: str | Path | None = None,
281
+ *,
282
+ model_backend_gate: StudioModelBackendGate | None = None,
283
+ ) -> dict[str, Any]:
284
+ """Build the UI catalog from registries and verified local prepared caches."""
285
+
286
+ registry = AlgorithmRegistry.default()
287
+ backend_gate = model_backend_gate or StudioModelBackendGate.from_operator_config(None, None)
288
+ backends = [
289
+ dict(_OFFLINE_BACKEND),
290
+ dict(backend_gate.catalog_entry("openai-compatible")),
291
+ dict(backend_gate.catalog_entry("transformers-local")),
292
+ ]
293
+ prepared_benchmarks = discover_prepared_benchmarks(prepared_data_root)
294
+ prepared_production_cells = discover_prepared_production_cells(prepared_data_root)
295
+ algorithms: list[dict[str, Any]] = [
296
+ {
297
+ "id": "baseline.no_op",
298
+ "name": "Paired baseline",
299
+ "family": "baseline",
300
+ "tier": "builtin",
301
+ "status": "supported",
302
+ "paper_title": "AgentInputLab typed NO_OP",
303
+ "versions": [
304
+ {
305
+ "id": "baseline.no_op",
306
+ "status": "supported",
307
+ "delivery_tier": "builtin",
308
+ "profile_modes": ["controlled_adapter"],
309
+ "studio_ready": True,
310
+ "studio_reason": RUNTIME_PROFILES["baseline.no_op"].note,
311
+ }
312
+ ],
313
+ }
314
+ ]
315
+ for method in registry:
316
+ versions = [_version_payload(release) for release in method.releases]
317
+ if method.method_id == "rewrite.query2doc":
318
+ versions.insert(
319
+ 0,
320
+ {
321
+ "id": "fixture.query2doc.cached",
322
+ "status": "experimental",
323
+ "delivery_tier": "studio_fixture",
324
+ "profile_modes": ["fixture_unverified"],
325
+ "studio_ready": True,
326
+ "studio_reason": RUNTIME_PROFILES["fixture.query2doc.cached"].note,
327
+ },
328
+ )
329
+ algorithms.append(
330
+ {
331
+ "id": method.method_id,
332
+ "name": method.display_name,
333
+ "family": method.semantic_family.value,
334
+ "tier": method.release_tier.value,
335
+ "status": method.status.value,
336
+ "paper_title": method.paper_title,
337
+ "publication": method.publication.to_dict(),
338
+ "versions": versions,
339
+ }
340
+ )
341
+
342
+ datasets: list[dict[str, Any]] = [
343
+ {
344
+ "id": "studio.tiny_general",
345
+ "suite": "Studio fixture",
346
+ "task": "Offline query intervention + BM25 top-1 retrieval",
347
+ "size": f"{len(TINY_EXAMPLES)} queries; {len(TINY_DOCUMENTS)} documents",
348
+ "families": [item.value for item in SemanticFamily],
349
+ "metrics": [
350
+ "top1_accuracy",
351
+ "intent_token_recall",
352
+ "token_jaccard",
353
+ "compression_ratio",
354
+ ],
355
+ "metric_layers": {
356
+ "adapter": [
357
+ "top1_accuracy",
358
+ "intent_token_recall",
359
+ "token_jaccard",
360
+ "compression_ratio",
361
+ ],
362
+ "paired": [],
363
+ "study": [],
364
+ },
365
+ "family_support": "cell_declared",
366
+ "data_ready": True,
367
+ "execution_ready": True,
368
+ "ready": True,
369
+ "reason": "Bundled synthetic fixture; no download or external license gate.",
370
+ "studio_release_ids": sorted(RUNTIME_PROFILES),
371
+ "sample": {
372
+ "id": TINY_EXAMPLES[0].example_id,
373
+ "query": TINY_EXAMPLES[0].query,
374
+ "target_document": TINY_EXAMPLES[0].target_doc_id,
375
+ },
376
+ }
377
+ ]
378
+ for cell in BenchmarkRegistry.default().list():
379
+ execution_prepared = prepared_benchmarks.get(cell.cell_id)
380
+ production_prepared = prepared_production_cells.get(cell.cell_id)
381
+ execution_ready = execution_prepared is not None
382
+ data_ready = production_prepared is not None or execution_ready
383
+ if execution_ready:
384
+ reason = (
385
+ "Checksum-verified prepared cache; bounded baseline retrieval smoke is enabled."
386
+ )
387
+ data_reason = "The complete prepared source set passed checksum verification."
388
+ elif data_ready:
389
+ reason = (
390
+ "Prepared data is verified, but Studio has no configured downstream "
391
+ "predictor or retrieval index for this cell."
392
+ )
393
+ data_reason = "The complete prepared source set passed checksum verification."
394
+ else:
395
+ reason = (
396
+ cell.eligibility_gate
397
+ or "Registered benchmark; no complete checksum-verified prepared asset set "
398
+ "was found."
399
+ )
400
+ data_reason = "A complete checksum-verified prepared asset set is unavailable."
401
+ dataset_payload: dict[str, Any] = {
402
+ "id": cell.cell_id,
403
+ "suite": cell.suite,
404
+ "task": cell.task,
405
+ "size": cell.size,
406
+ "families": [family.value for family in cell.families],
407
+ "metrics": list(cell.metrics),
408
+ "metric_layers": {
409
+ "adapter": list(cell.adapter_metrics),
410
+ "paired": list(cell.paired_metrics),
411
+ "study": list(cell.study_metrics),
412
+ },
413
+ "family_support": cell.family_support,
414
+ "data_ready": data_ready,
415
+ "execution_ready": execution_ready,
416
+ "ready": execution_ready,
417
+ "reason": reason,
418
+ "data_reason": data_reason,
419
+ "source_revision": cell.source_revision,
420
+ "studio_release_ids": ["baseline.no_op"] if execution_ready else [],
421
+ }
422
+ if production_prepared is not None:
423
+ dataset_payload["prepared"] = production_prepared.audit_payload()
424
+ elif execution_prepared is not None:
425
+ prepared_asset = BenchmarkSmokeAssetRegistry.default().get("beir.arguana.official")
426
+ dataset_payload["prepared"] = prepared_catalog_payload(
427
+ prepared_asset,
428
+ execution_prepared,
429
+ )
430
+ datasets.append(dataset_payload)
431
+
432
+ selectors = [
433
+ {
434
+ "id": card.selector_id,
435
+ "name": card.display_name,
436
+ "kind": card.kind.value,
437
+ "requires_fit": card.requires_fit,
438
+ "optional_dependency": card.optional_dependency or None,
439
+ "artifact_lifecycle": ["fitted", "calibrated", "frozen"],
440
+ "studio_execution_ready": False,
441
+ "reason": (
442
+ "Catalog and exact artifact inspection are available; executing a "
443
+ "selector policy requires an operator-frozen artifact and locked "
444
+ "release builders."
445
+ ),
446
+ }
447
+ for card in list_selector_cards()
448
+ ]
449
+ return {
450
+ "schema_version": 1,
451
+ "algorithms": algorithms,
452
+ "datasets": datasets,
453
+ "selectors": selectors,
454
+ "backends": backends,
455
+ "parameters": {
456
+ "defaults": dict(DEFAULT_PARAMETERS),
457
+ "bounds": {
458
+ "seed": [0, 2_147_483_647],
459
+ "sample_limit": [1, max(len(TINY_EXAMPLES), MAX_PREPARED_SAMPLE_LIMIT)],
460
+ "top_k": [1, 10],
461
+ "temperature": [0.0, 2.0],
462
+ "soft_word_budget": [1, 4_096],
463
+ "clarification_threshold": [0.0, 1.0],
464
+ },
465
+ },
466
+ "fairness_defaults": dict(DEFAULT_FAIRNESS),
467
+ "default_config": {
468
+ "algorithm_id": "rewrite.query2doc",
469
+ "release_id": "fixture.query2doc.cached",
470
+ "backend_id": "offline-replay",
471
+ "dataset_id": "studio.tiny_general",
472
+ "parameters": dict(DEFAULT_PARAMETERS),
473
+ "fairness": dict(DEFAULT_FAIRNESS),
474
+ },
475
+ "execution_policy": {
476
+ "local_only": True,
477
+ "network_enabled": backend_gate.network_enabled,
478
+ "operator_model_backend_id": backend_gate.ready_backend_id,
479
+ "accepts_credentials": False,
480
+ "accepts_commands": False,
481
+ "queue_limit": MAX_RUNS,
482
+ "prepared_data_root_configured": prepared_data_root is not None,
483
+ "verified_prepared_cells": sorted(prepared_benchmarks),
484
+ "verified_production_data_cells": sorted(prepared_production_cells),
485
+ },
486
+ }
487
+
488
+
489
+ def _contains_secret(value: Any, path: str = "") -> str | None:
490
+ if isinstance(value, dict):
491
+ for key, item in value.items():
492
+ rendered = str(key)
493
+ child_path = f"{path}.{rendered}" if path else rendered
494
+ if _SECRET_KEY.search(rendered):
495
+ return child_path
496
+ found = _contains_secret(item, child_path)
497
+ if found:
498
+ return found
499
+ elif isinstance(value, list):
500
+ for index, item in enumerate(value):
501
+ found = _contains_secret(item, f"{path}[{index}]")
502
+ if found:
503
+ return found
504
+ return None
505
+
506
+
507
+ def _issue(path: str, code: str, message: str) -> dict[str, str]:
508
+ return {"path": path, "code": code, "message": message}
509
+
510
+
511
+ def _number(
512
+ value: Any,
513
+ *,
514
+ path: str,
515
+ minimum: float,
516
+ maximum: float,
517
+ integer: bool,
518
+ errors: list[dict[str, str]],
519
+ ) -> int | float:
520
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
521
+ errors.append(_issue(path, "type", "must be a number"))
522
+ return int(minimum) if integer else minimum
523
+ converted: int | float = int(value) if integer else float(value)
524
+ if integer and converted != value:
525
+ errors.append(_issue(path, "integer", "must be an integer"))
526
+ if not math.isfinite(float(converted)) or not minimum <= converted <= maximum:
527
+ errors.append(_issue(path, "range", f"must be between {minimum:g} and {maximum:g}"))
528
+ return converted
529
+
530
+
531
+ def validate_config(
532
+ raw: Any,
533
+ *,
534
+ catalog: dict[str, Any] | None = None,
535
+ ) -> dict[str, Any]:
536
+ """Validate, normalize, and explain one Studio run configuration."""
537
+
538
+ if not isinstance(raw, dict):
539
+ return {
540
+ "valid": False,
541
+ "errors": [_issue("$", "type", "configuration must be a JSON object")],
542
+ "warnings": [],
543
+ "normalized": None,
544
+ }
545
+ secret_path = _contains_secret(raw)
546
+ if secret_path:
547
+ raise StudioSecurityError(
548
+ f"credentials are not accepted by Studio; forbidden field: {secret_path}"
549
+ )
550
+
551
+ errors: list[dict[str, str]] = []
552
+ warnings: list[dict[str, str]] = []
553
+ allowed_top = {
554
+ "algorithm_id",
555
+ "release_id",
556
+ "backend_id",
557
+ "dataset_id",
558
+ "parameters",
559
+ "fairness",
560
+ }
561
+ for key in sorted(set(raw) - allowed_top):
562
+ errors.append(_issue(key, "unknown_field", "field is not accepted"))
563
+
564
+ def text_field(name: str, default: str) -> str:
565
+ value = raw.get(name, default)
566
+ if not isinstance(value, str) or not value.strip():
567
+ errors.append(_issue(name, "type", "must be a non-empty string"))
568
+ return default
569
+ if len(value) > 128:
570
+ errors.append(_issue(name, "length", "must be at most 128 characters"))
571
+ return value
572
+
573
+ algorithm_id = text_field("algorithm_id", "rewrite.query2doc")
574
+ release_id = text_field("release_id", "fixture.query2doc.cached")
575
+ backend_id = text_field("backend_id", "offline-replay")
576
+ dataset_id = text_field("dataset_id", "studio.tiny_general")
577
+
578
+ params_raw = raw.get("parameters", {})
579
+ if not isinstance(params_raw, dict):
580
+ errors.append(_issue("parameters", "type", "must be an object"))
581
+ params_raw = {}
582
+ for key in sorted(set(params_raw) - set(DEFAULT_PARAMETERS)):
583
+ errors.append(_issue(f"parameters.{key}", "unknown_field", "parameter is not accepted"))
584
+ parameters = {
585
+ "seed": _number(
586
+ params_raw.get("seed", DEFAULT_PARAMETERS["seed"]),
587
+ path="parameters.seed",
588
+ minimum=0,
589
+ maximum=2_147_483_647,
590
+ integer=True,
591
+ errors=errors,
592
+ ),
593
+ "sample_limit": _number(
594
+ params_raw.get("sample_limit", DEFAULT_PARAMETERS["sample_limit"]),
595
+ path="parameters.sample_limit",
596
+ minimum=1,
597
+ maximum=len(TINY_EXAMPLES),
598
+ integer=True,
599
+ errors=errors,
600
+ ),
601
+ "top_k": _number(
602
+ params_raw.get("top_k", DEFAULT_PARAMETERS["top_k"]),
603
+ path="parameters.top_k",
604
+ minimum=1,
605
+ maximum=10,
606
+ integer=True,
607
+ errors=errors,
608
+ ),
609
+ "temperature": _number(
610
+ params_raw.get("temperature", DEFAULT_PARAMETERS["temperature"]),
611
+ path="parameters.temperature",
612
+ minimum=0,
613
+ maximum=2,
614
+ integer=False,
615
+ errors=errors,
616
+ ),
617
+ "soft_word_budget": _number(
618
+ params_raw.get("soft_word_budget", DEFAULT_PARAMETERS["soft_word_budget"]),
619
+ path="parameters.soft_word_budget",
620
+ minimum=1,
621
+ maximum=4_096,
622
+ integer=True,
623
+ errors=errors,
624
+ ),
625
+ "clarification_threshold": _number(
626
+ params_raw.get(
627
+ "clarification_threshold", DEFAULT_PARAMETERS["clarification_threshold"]
628
+ ),
629
+ path="parameters.clarification_threshold",
630
+ minimum=0,
631
+ maximum=1,
632
+ integer=False,
633
+ errors=errors,
634
+ ),
635
+ }
636
+
637
+ fairness_raw = raw.get("fairness", {})
638
+ if not isinstance(fairness_raw, dict):
639
+ errors.append(_issue("fairness", "type", "must be an object"))
640
+ fairness_raw = {}
641
+ for key in sorted(set(fairness_raw) - set(DEFAULT_FAIRNESS)):
642
+ errors.append(_issue(f"fairness.{key}", "unknown_field", "setting is not accepted"))
643
+ fairness: dict[str, bool] = {}
644
+ for key, default in DEFAULT_FAIRNESS.items():
645
+ value = fairness_raw.get(key, default)
646
+ if not isinstance(value, bool):
647
+ errors.append(_issue(f"fairness.{key}", "type", "must be true or false"))
648
+ value = default
649
+ fairness[key] = value
650
+ if not value:
651
+ warnings.append(
652
+ _issue(
653
+ f"fairness.{key}",
654
+ "exploratory_only",
655
+ "disabled control makes this run exploratory, not confirmatory",
656
+ )
657
+ )
658
+
659
+ active_catalog = catalog or build_catalog()
660
+ profile = RUNTIME_PROFILES.get(release_id)
661
+ if profile is None:
662
+ errors.append(
663
+ _issue(
664
+ "release_id",
665
+ "not_executable",
666
+ "release is visible in the registry but lacks a safe local Studio constructor",
667
+ )
668
+ )
669
+ else:
670
+ if profile.algorithm_id != algorithm_id:
671
+ errors.append(
672
+ _issue(
673
+ "release_id",
674
+ "algorithm_release_mismatch",
675
+ f"release belongs to {profile.algorithm_id}",
676
+ )
677
+ )
678
+ if backend_id not in profile.backend_ids:
679
+ errors.append(
680
+ _issue(
681
+ "backend_id",
682
+ "backend_incompatible",
683
+ f"release supports: {', '.join(profile.backend_ids)}",
684
+ )
685
+ )
686
+
687
+ backend = next(
688
+ (item for item in active_catalog["backends"] if item["id"] == backend_id),
689
+ None,
690
+ )
691
+ if backend is None:
692
+ errors.append(_issue("backend_id", "unknown_backend", "backend is not registered"))
693
+ elif not backend["ready"]:
694
+ errors.append(
695
+ _issue(
696
+ "backend_id",
697
+ "backend_not_ready",
698
+ str(backend["description"]),
699
+ )
700
+ )
701
+ elif backend_id == "transformers-local" and parameters["temperature"] != 0:
702
+ errors.append(
703
+ _issue(
704
+ "parameters.temperature",
705
+ "backend_incompatible",
706
+ "local Transformers Studio runs require deterministic temperature=0",
707
+ )
708
+ )
709
+
710
+ dataset = next(
711
+ (item for item in active_catalog["datasets"] if item["id"] == dataset_id),
712
+ None,
713
+ )
714
+ if dataset is None:
715
+ errors.append(_issue("dataset_id", "unknown_dataset", "dataset is not registered"))
716
+ elif not dataset["ready"]:
717
+ errors.append(_issue("dataset_id", "dataset_not_ready", str(dataset["reason"])))
718
+ elif profile is not None and profile.family not in {"baseline", *dataset["families"]}:
719
+ errors.append(
720
+ _issue(
721
+ "dataset_id",
722
+ "family_mismatch",
723
+ f"dataset does not declare support for the {profile.family} family",
724
+ )
725
+ )
726
+ elif release_id not in dataset.get("studio_release_ids", []):
727
+ supported = dataset.get("studio_release_ids", [])
728
+ errors.append(
729
+ _issue(
730
+ "dataset_id",
731
+ "dataset_release_incompatible",
732
+ (
733
+ f"this prepared Studio adapter currently supports: {', '.join(supported)}"
734
+ if supported
735
+ else "this dataset has no executable Studio release"
736
+ ),
737
+ )
738
+ )
739
+
740
+ if not fairness["paired_baseline"]:
741
+ warnings.append(
742
+ _issue(
743
+ "fairness.paired_baseline",
744
+ "result_scope",
745
+ "Studio still records a diagnostic baseline, but it is not a confirmatory pair",
746
+ )
747
+ )
748
+
749
+ normalized = {
750
+ "algorithm_id": algorithm_id,
751
+ "release_id": release_id,
752
+ "backend_id": backend_id,
753
+ "dataset_id": dataset_id,
754
+ "parameters": parameters,
755
+ "fairness": fairness,
756
+ }
757
+ return {
758
+ "valid": not errors,
759
+ "errors": errors,
760
+ "warnings": warnings,
761
+ "normalized": normalized,
762
+ "compatibility": {
763
+ "profile_ready": profile is not None,
764
+ "backend_ready": bool(backend and backend["ready"]),
765
+ "dataset_data_ready": bool(dataset and dataset.get("data_ready")),
766
+ "dataset_execution_ready": bool(dataset and dataset.get("execution_ready")),
767
+ "dataset_ready": bool(dataset and dataset["ready"]),
768
+ "confirmatory": not warnings,
769
+ },
770
+ }
771
+
772
+
773
+ class _NoOpIntervention:
774
+ method_id = "baseline"
775
+ release_id = "baseline.no_op"
776
+ family = SemanticFamily.REFORMULATE
777
+
778
+ def propose(self, state: FrozenAgentState, context: InterventionContext) -> InputPatch:
779
+ return InputPatch.no_op(
780
+ state,
781
+ method_id=self.method_id,
782
+ release_id=self.release_id,
783
+ rationale="paired baseline selected in Studio",
784
+ )
785
+
786
+
787
+ def _udcs_replay_text() -> str:
788
+ return (
789
+ "<think>The fixture query is sufficiently specified.</think>"
790
+ "<u_request>0.08</u_request>"
791
+ "<u_request_explanation>No material ambiguity is present.</u_request_explanation>"
792
+ "<action>answer_directly</action>"
793
+ "<confidence>0.96</confidence>"
794
+ "<explanation>Proceed without asking a clarification question.</explanation>"
795
+ )
796
+
797
+
798
+ def _intervention_for(
799
+ release_id: str,
800
+ example: StudioExample,
801
+ parameters: dict[str, int | float],
802
+ ) -> Any:
803
+ if release_id == "baseline.no_op":
804
+ return _NoOpIntervention()
805
+ if release_id == "fixture.query2doc.cached":
806
+ cache = Query2DocCache(
807
+ (
808
+ OfficialQuery2DocRow(
809
+ query_id=example.example_id,
810
+ query=example.query,
811
+ pseudo_doc=example.pseudo_doc,
812
+ split="studio",
813
+ ),
814
+ )
815
+ )
816
+ return Query2DocFixtureCached(cache, split="studio")
817
+ if release_id == "plan.plan_and_solve.ps_prompt":
818
+ return PlanAndSolvePSPrompt()
819
+ if release_id == "plan.plan_and_budget.native_cached_soft_budget":
820
+ record = CachedPlanBudgetRecord(
821
+ record_id=example.example_id,
822
+ query=example.query,
823
+ steps=("Identify the required fact.", "Verify and state the final answer."),
824
+ credits=(45.0, 55.0),
825
+ level=1,
826
+ )
827
+ return PlanAndBudgetCachedSoftBudget(
828
+ {example.example_id: record},
829
+ total_word_budget=int(parameters["soft_word_budget"]),
830
+ )
831
+ if release_id == "clarify.udcs.repo_native_prompt_route":
832
+ return UDCSPromptNativePolicy(
833
+ clarification_threshold=float(parameters["clarification_threshold"]),
834
+ temperature=float(parameters["temperature"]),
835
+ )
836
+ raise StudioValidationError(f"unsupported Studio release: {release_id}")
837
+
838
+
839
+ def _runtime_backend_for(release_id: str) -> ReplayBackend | None:
840
+ if release_id == "clarify.udcs.repo_native_prompt_route":
841
+ return ReplayBackend((_udcs_replay_text(),), model_id="studio-replay-v1")
842
+ return None
843
+
844
+
845
+ def _token_count(text: str) -> int:
846
+ return len(_TOKEN.findall(text))
847
+
848
+
849
+ def _percentile_95(values: list[float]) -> float:
850
+ ordered = sorted(values)
851
+ index = max(0, math.ceil(0.95 * len(ordered)) - 1)
852
+ return ordered[index]
853
+
854
+
855
+ def _operator_safe_snapshot(snapshot: Mapping[str, Any]) -> dict[str, Any]:
856
+ """Remove provider error text while retaining typed observability evidence."""
857
+
858
+ public = copy.deepcopy(dict(snapshot))
859
+ failure = public.get("failure")
860
+ if isinstance(failure, dict) and failure:
861
+ failure["message"] = "operator-owned model backend call failed"
862
+ events = public.get("events")
863
+ if isinstance(events, list):
864
+ for event in events:
865
+ if not isinstance(event, dict):
866
+ continue
867
+ payload = event.get("payload")
868
+ if not isinstance(payload, dict):
869
+ continue
870
+ if "model_id" in payload:
871
+ payload["model_id"] = "<operator-owned-model>"
872
+ if event.get("kind") == "failure_recorded":
873
+ payload["message"] = "operator-owned model backend call failed"
874
+ return public
875
+
876
+
877
+ def execute_config(
878
+ config: dict[str, Any],
879
+ *,
880
+ prepared_data_root: str | Path | None = None,
881
+ model_backend_gate: StudioModelBackendGate | None = None,
882
+ ) -> dict[str, Any]:
883
+ """Execute a normalized configuration through real library contracts."""
884
+
885
+ sample_limit = int(config["parameters"]["sample_limit"])
886
+ dataset_id = str(config["dataset_id"])
887
+ if dataset_id == "studio.tiny_general":
888
+ examples = TINY_EXAMPLES[:sample_limit]
889
+ documents = TINY_DOCUMENTS
890
+ dataset_revision = "studio-tiny-v1"
891
+ dataset_audit: dict[str, Any] = {
892
+ "cell_id": dataset_id,
893
+ "kind": "bundled_synthetic_fixture",
894
+ "gold_separated": True,
895
+ "selection_rule": "bundled_order_first_n",
896
+ "selected_examples": len(examples),
897
+ }
898
+ result_scope = "synthetic_smoke_not_benchmark_claim"
899
+ primary_metric = "top1_accuracy"
900
+ elif dataset_id == "beir.arguana":
901
+ if config["release_id"] != "baseline.no_op":
902
+ raise StudioValidationError(
903
+ "the verified ArguAna Studio adapter currently supports baseline.no_op only"
904
+ )
905
+ if prepared_data_root is None:
906
+ raise StudioValidationError(
907
+ "beir.arguana requires a server-configured verified prepared-data root"
908
+ )
909
+ prepared_dataset = load_verified_studio_dataset(
910
+ cell_id=dataset_id,
911
+ prepared_root=prepared_data_root,
912
+ sample_limit=sample_limit,
913
+ )
914
+ examples = tuple(
915
+ StudioExample(
916
+ example_id=example.example_id,
917
+ query=example.query,
918
+ relevant_document_ids=example.relevant_document_ids,
919
+ instruction=example.instruction,
920
+ visible_metadata=example.visible_metadata,
921
+ )
922
+ for example in prepared_dataset.examples
923
+ )
924
+ documents = prepared_dataset.documents
925
+ dataset_revision = f"{prepared_dataset.asset_id}:{prepared_dataset.source_sha256}"
926
+ dataset_audit = prepared_dataset.audit_payload()
927
+ dataset_audit["kind"] = "checksum_verified_prepared_smoke"
928
+ result_scope = "verified_prepared_bounded_smoke_not_full_benchmark_claim"
929
+ primary_metric = "top1_relevance_diagnostic"
930
+ else:
931
+ raise StudioValidationError(f"dataset has no Studio execution adapter: {dataset_id}")
932
+
933
+ retriever = InMemoryBM25Retriever(documents)
934
+ retriever_fingerprint_before = dict(retriever.fingerprint())
935
+ runner = InterventionRunner()
936
+ seed = int(config["parameters"]["seed"])
937
+ top_k = int(config["parameters"]["top_k"])
938
+ release_id = str(config["release_id"])
939
+ backend_id = str(config["backend_id"])
940
+ operator_runtime: StudioModelRuntime | None = None
941
+ if backend_id != "offline-replay":
942
+ if model_backend_gate is None:
943
+ raise StudioModelBackendUnavailable(
944
+ "the selected operator-owned model backend is unavailable"
945
+ )
946
+ operator_runtime = model_backend_gate.build(backend_id, seed=seed)
947
+
948
+ started = time.perf_counter()
949
+ baseline_scores: list[float] = []
950
+ treatment_scores: list[float] = []
951
+ intent_recalls: list[float] = []
952
+ jaccards: list[float] = []
953
+ compression: list[float] = []
954
+ latencies: list[float] = []
955
+ samples: list[dict[str, Any]] = []
956
+ failures: list[dict[str, Any]] = []
957
+ total_input_tokens = 0
958
+ total_output_tokens = 0
959
+ core_usage = ResourceUsage()
960
+ core_durations: list[float] = []
961
+ core_snapshots: list[dict[str, Any]] = []
962
+
963
+ for index, example in enumerate(examples):
964
+ sample_started = time.perf_counter()
965
+ state = FrozenAgentState(
966
+ task_id=example.example_id,
967
+ query=example.query,
968
+ instruction=example.instruction,
969
+ metadata={"dataset_id": dataset_id, **dict(example.visible_metadata)},
970
+ )
971
+ baseline_results = retriever.retrieve(state.query, top_k=top_k)
972
+ baseline_top = baseline_results[0].document.doc_id if baseline_results else None
973
+ baseline_score = float(baseline_top in example.relevant_document_ids)
974
+ monitor_ids_before = {snapshot.run_id for snapshot in runner.monitor.list_snapshots()}
975
+ try:
976
+ intervention = _intervention_for(release_id, example, config["parameters"])
977
+ runtime_backend = (
978
+ operator_runtime.backend
979
+ if operator_runtime is not None
980
+ else _runtime_backend_for(release_id)
981
+ )
982
+ runtime_config = (
983
+ dict(operator_runtime.run_config) if operator_runtime is not None else {}
984
+ )
985
+ runtime_config["studio"] = {
986
+ "enabled": True,
987
+ "backend_id": backend_id,
988
+ "fairness": config["fairness"],
989
+ }
990
+ context = InterventionContext(
991
+ backend=runtime_backend,
992
+ seed=seed + index,
993
+ run_config=runtime_config,
994
+ intent_contract=(
995
+ operator_runtime.intent_contract if operator_runtime is not None else None
996
+ ),
997
+ pricing_catalog=(
998
+ operator_runtime.pricing_catalog if operator_runtime is not None else None
999
+ ),
1000
+ strict_intent=(
1001
+ operator_runtime.strict_intent if operator_runtime is not None else True
1002
+ ),
1003
+ )
1004
+ record = runner.run(intervention, state, context)
1005
+ monitor_snapshot = runner.monitor.snapshot(record.monitor_run_id).as_dict()
1006
+ if operator_runtime is not None:
1007
+ monitor_snapshot = _operator_safe_snapshot(monitor_snapshot)
1008
+ core_snapshots.append(monitor_snapshot)
1009
+ core_usage = core_usage + record.usage
1010
+ if record.duration_ms is not None:
1011
+ core_durations.append(record.duration_ms)
1012
+ transformed = record.patch.apply(state)
1013
+ treatment_results = retriever.retrieve(transformed.query, top_k=top_k)
1014
+ treatment_top = treatment_results[0].document.doc_id if treatment_results else None
1015
+ treatment_score = float(treatment_top in example.relevant_document_ids)
1016
+ latency_ms = (time.perf_counter() - sample_started) * 1_000
1017
+ latencies.append(latency_ms)
1018
+ baseline_scores.append(baseline_score)
1019
+ treatment_scores.append(treatment_score)
1020
+ intent_recalls.append(intent_token_recall(state.query, transformed.query))
1021
+ jaccards.append(token_jaccard(state.query, transformed.query))
1022
+ compression.append(compression_ratio(state.query, transformed.query))
1023
+ total_input_tokens += _token_count(f"{state.query} {state.instruction}")
1024
+ total_output_tokens += _token_count(f"{transformed.query} {transformed.instruction}")
1025
+ samples.append(
1026
+ {
1027
+ "example_id": example.example_id,
1028
+ "status": record.status.value,
1029
+ "original_query": state.query,
1030
+ "transformed_query": transformed.query,
1031
+ "transformed_instruction": transformed.instruction,
1032
+ "target_document_id": example.target_doc_id,
1033
+ "relevant_document_ids": list(example.relevant_document_ids),
1034
+ "baseline_top_document_id": baseline_top,
1035
+ "treatment_top_document_id": treatment_top,
1036
+ "baseline_score": baseline_score,
1037
+ "treatment_score": treatment_score,
1038
+ "effect": treatment_score - baseline_score,
1039
+ "latency_ms": round(latency_ms, 3),
1040
+ "patch_hash": record.patch.patch_hash,
1041
+ "input_state_hash": record.input_state_hash,
1042
+ "output_state_hash": record.output_state_hash,
1043
+ "trace": record.patch.trace.as_dict(),
1044
+ "observability": {
1045
+ "monitor_run_id": record.monitor_run_id,
1046
+ "duration_ms": record.duration_ms,
1047
+ "usage": record.usage.as_dict(),
1048
+ "intent_check": (
1049
+ to_jsonable(record.intent_check)
1050
+ if record.intent_check is not None
1051
+ else None
1052
+ ),
1053
+ "snapshot": monitor_snapshot,
1054
+ },
1055
+ }
1056
+ )
1057
+ except Exception as exc: # Boundary records failures rather than hiding them.
1058
+ latency_ms = (time.perf_counter() - sample_started) * 1_000
1059
+ latencies.append(latency_ms)
1060
+ new_snapshots = [
1061
+ (
1062
+ _operator_safe_snapshot(snapshot.as_dict())
1063
+ if operator_runtime is not None
1064
+ else snapshot.as_dict()
1065
+ )
1066
+ for snapshot in runner.monitor.list_snapshots()
1067
+ if snapshot.run_id not in monitor_ids_before
1068
+ ]
1069
+ if new_snapshots:
1070
+ core_snapshots.extend(new_snapshots)
1071
+ failure_usage = next(
1072
+ (
1073
+ snapshot.usage
1074
+ for snapshot in runner.monitor.list_snapshots()
1075
+ if snapshot.run_id == new_snapshots[-1]["run_id"]
1076
+ ),
1077
+ ResourceUsage(),
1078
+ )
1079
+ core_usage = core_usage + failure_usage
1080
+ duration = new_snapshots[-1].get("duration_ms")
1081
+ if isinstance(duration, (int, float)):
1082
+ core_durations.append(float(duration))
1083
+ public_failure_message = (
1084
+ "operator-owned model backend call failed"
1085
+ if operator_runtime is not None
1086
+ else str(exc)[:MAX_TEXT_LENGTH]
1087
+ )
1088
+ failures.append(
1089
+ {
1090
+ "example_id": example.example_id,
1091
+ "category": type(exc).__name__,
1092
+ "message": public_failure_message,
1093
+ "latency_ms": round(latency_ms, 3),
1094
+ "observability_snapshot": new_snapshots[-1] if new_snapshots else None,
1095
+ }
1096
+ )
1097
+
1098
+ elapsed_ms = (time.perf_counter() - started) * 1_000
1099
+ retriever_fingerprint_after = dict(retriever.fingerprint())
1100
+ fingerprint_drift = retriever_fingerprint_before != retriever_fingerprint_after
1101
+ if config["fairness"]["fail_on_fingerprint_drift"] and fingerprint_drift:
1102
+ raise RuntimeError("retriever fingerprint drifted during a paired run")
1103
+
1104
+ quality: dict[str, Any]
1105
+ if baseline_scores:
1106
+ paired = summarize_pairs(tuple(baseline_scores), tuple(treatment_scores))
1107
+ quality = {
1108
+ "primary_metric": primary_metric,
1109
+ "baseline": fmean(baseline_scores),
1110
+ "treatment": fmean(treatment_scores),
1111
+ "mean_effect": paired.mean_effect,
1112
+ "win_rate": paired.win_rate,
1113
+ "tie_rate": paired.tie_rate,
1114
+ "harm_rate": paired.harm_rate,
1115
+ "intent_token_recall": fmean(intent_recalls),
1116
+ "token_jaccard": fmean(jaccards),
1117
+ "compression_ratio": fmean(compression),
1118
+ }
1119
+ else:
1120
+ quality = {
1121
+ "primary_metric": primary_metric,
1122
+ "baseline": None,
1123
+ "treatment": None,
1124
+ "mean_effect": None,
1125
+ "win_rate": None,
1126
+ "tie_rate": None,
1127
+ "harm_rate": None,
1128
+ "intent_token_recall": None,
1129
+ "token_jaccard": None,
1130
+ "compression_ratio": None,
1131
+ }
1132
+
1133
+ if operator_runtime is None:
1134
+ backend_audit: Mapping[str, Any] = {
1135
+ "backend_id": "offline-replay",
1136
+ "backend": "replay",
1137
+ "model_id": "studio-replay-v1",
1138
+ "network": False,
1139
+ }
1140
+ execution_mode = "offline_replay"
1141
+ cost_basis = "offline replay; no billable provider calls"
1142
+ else:
1143
+ backend_audit = to_jsonable(operator_runtime.public_fingerprint)
1144
+ execution_mode = f"operator_{backend_id.replace('-', '_')}"
1145
+ cost_basis = (
1146
+ "provider/runtime usage reported by the operator-owned backend; "
1147
+ "unpriced calls remain explicit"
1148
+ )
1149
+
1150
+ return {
1151
+ "execution_mode": execution_mode,
1152
+ "result_scope": result_scope,
1153
+ "summary": {
1154
+ "examples_requested": len(examples),
1155
+ "examples_succeeded": len(samples),
1156
+ "examples_failed": len(failures),
1157
+ "quality": quality,
1158
+ "usage": {
1159
+ "model_calls": core_usage.model_calls,
1160
+ "optimizer_retrieval_calls": core_usage.retrieval_calls,
1161
+ "tool_calls": core_usage.tool_calls,
1162
+ "embedding_calls": core_usage.embedding_calls,
1163
+ "unpriced_model_calls": core_usage.unpriced_model_calls,
1164
+ "evaluation_retrieval_calls": len(examples) + len(samples),
1165
+ "core_input_tokens": core_usage.input_tokens,
1166
+ "core_output_tokens": core_usage.output_tokens,
1167
+ "input_tokens_estimate": total_input_tokens,
1168
+ "output_tokens_estimate": total_output_tokens,
1169
+ "token_estimator": "unicode_word_v1_not_provider_billing_tokens",
1170
+ "provider_cost_usd": core_usage.cost_usd,
1171
+ "compute_cost_usd": core_usage.compute_cost_usd,
1172
+ "cost_usd": core_usage.total_cost_usd,
1173
+ "cpu_seconds": core_usage.cpu_seconds,
1174
+ "accelerator_seconds": core_usage.accelerator_seconds,
1175
+ "cost_basis": cost_basis,
1176
+ },
1177
+ "timing": {
1178
+ "wall_ms": round(elapsed_ms, 3),
1179
+ "mean_sample_ms": round(fmean(latencies), 3) if latencies else 0.0,
1180
+ "median_sample_ms": round(median(latencies), 3) if latencies else 0.0,
1181
+ "p95_sample_ms": round(_percentile_95(latencies), 3) if latencies else 0.0,
1182
+ "core_intervention_ms": round(sum(core_durations), 3),
1183
+ "core_mean_intervention_ms": (
1184
+ round(fmean(core_durations), 3) if core_durations else 0.0
1185
+ ),
1186
+ },
1187
+ "failure_count": len(failures),
1188
+ },
1189
+ "metric_audit": {
1190
+ "primary": {
1191
+ "name": primary_metric,
1192
+ "owner": "AgentInputLab Studio",
1193
+ "role": "bounded retrieval diagnostic",
1194
+ "definition": "fraction of examples whose rank-1 document has positive relevance",
1195
+ },
1196
+ "intent_diagnostics": [
1197
+ "intent_token_recall",
1198
+ "token_jaccard",
1199
+ "compression_ratio",
1200
+ ],
1201
+ "declared_benchmark_metrics": (
1202
+ ["ndcg_at_10", "recall"] if dataset_id == "beir.arguana" else []
1203
+ ),
1204
+ "official_benchmark_metrics_computed": False,
1205
+ "scope_note": (
1206
+ "This bounded Studio smoke validates data, retrieval, pairing, and telemetry. "
1207
+ "It is not the full official benchmark evaluation."
1208
+ ),
1209
+ },
1210
+ "fairness_audit": {
1211
+ "settings": copy.deepcopy(config["fairness"]),
1212
+ "seed": seed,
1213
+ "retriever_before": retriever_fingerprint_before,
1214
+ "retriever_after": retriever_fingerprint_after,
1215
+ "fingerprint_drift": fingerprint_drift,
1216
+ "backend": backend_audit,
1217
+ "dataset_revision": dataset_revision,
1218
+ "dataset": dataset_audit,
1219
+ },
1220
+ "observability_audit": {
1221
+ "source": "agentinputlab.observability.RunMonitor + runner.RunRecord",
1222
+ "aggregate_usage": core_usage.as_dict(),
1223
+ "monitor_run_count": len(core_snapshots),
1224
+ "snapshots": core_snapshots,
1225
+ },
1226
+ "samples": samples,
1227
+ "failures": failures,
1228
+ }
1229
+
1230
+
1231
+ class RunManager:
1232
+ """Bounded in-memory run queue with immutable JSON-facing snapshots."""
1233
+
1234
+ def __init__(
1235
+ self,
1236
+ *,
1237
+ max_workers: int = 1,
1238
+ max_runs: int = MAX_RUNS,
1239
+ prepared_data_root: str | Path | None = None,
1240
+ model_backend_gate: StudioModelBackendGate | None = None,
1241
+ ) -> None:
1242
+ if max_workers < 1 or max_workers > 4:
1243
+ raise ValueError("Studio max_workers must be between 1 and 4")
1244
+ if max_runs < 1:
1245
+ raise ValueError("Studio max_runs must be positive")
1246
+ self._executor = ThreadPoolExecutor(
1247
+ max_workers=max_workers,
1248
+ thread_name_prefix="agentinputlab-studio",
1249
+ )
1250
+ self._max_runs = max_runs
1251
+ self._prepared_data_root = (
1252
+ Path(prepared_data_root).expanduser().resolve()
1253
+ if prepared_data_root is not None
1254
+ else None
1255
+ )
1256
+ self._model_backend_gate = (
1257
+ model_backend_gate or StudioModelBackendGate.from_operator_config(None, None)
1258
+ )
1259
+ self._catalog = build_catalog(
1260
+ self._prepared_data_root,
1261
+ model_backend_gate=self._model_backend_gate,
1262
+ )
1263
+ self._lock = threading.RLock()
1264
+ self._runs: dict[str, dict[str, Any]] = {}
1265
+ self._order: list[str] = []
1266
+
1267
+ def submit(self, config: Any) -> dict[str, Any]:
1268
+ validation = validate_config(config, catalog=self._catalog)
1269
+ if not validation["valid"]:
1270
+ raise StudioValidationError("configuration did not pass validation")
1271
+ normalized = copy.deepcopy(validation["normalized"])
1272
+ with self._lock:
1273
+ active = sum(run["status"] in {"queued", "running"} for run in self._runs.values())
1274
+ if active >= self._max_runs:
1275
+ raise StudioQueueFullError("local Studio queue is full")
1276
+ while len(self._runs) >= self._max_runs:
1277
+ oldest_terminal = next(
1278
+ (
1279
+ stored_id
1280
+ for stored_id in reversed(self._order)
1281
+ if self._runs[stored_id]["status"] not in {"queued", "running"}
1282
+ ),
1283
+ None,
1284
+ )
1285
+ if oldest_terminal is None:
1286
+ raise StudioQueueFullError("local Studio queue is full")
1287
+ self._order.remove(oldest_terminal)
1288
+ del self._runs[oldest_terminal]
1289
+ run_id = uuid.uuid4().hex
1290
+ record = {
1291
+ "id": run_id,
1292
+ "status": "queued",
1293
+ "created_at": _utc_now(),
1294
+ "started_at": None,
1295
+ "finished_at": None,
1296
+ "config": normalized,
1297
+ "validation": validation,
1298
+ "result": None,
1299
+ "error": None,
1300
+ "events": [
1301
+ {"at": _utc_now(), "state": "queued", "message": "Run accepted locally"}
1302
+ ],
1303
+ }
1304
+ self._runs[run_id] = record
1305
+ self._order.insert(0, run_id)
1306
+ self._executor.submit(self._execute, run_id)
1307
+ return self._snapshot(record, compact=True)
1308
+
1309
+ def _execute(self, run_id: str) -> None:
1310
+ with self._lock:
1311
+ record = self._runs[run_id]
1312
+ record["status"] = "running"
1313
+ record["started_at"] = _utc_now()
1314
+ record["events"].append(
1315
+ {"at": _utc_now(), "state": "running", "message": "Worker started"}
1316
+ )
1317
+ config = copy.deepcopy(record["config"])
1318
+ try:
1319
+ result = execute_config(
1320
+ config,
1321
+ prepared_data_root=self._prepared_data_root,
1322
+ model_backend_gate=self._model_backend_gate,
1323
+ )
1324
+ except Exception as exc: # Queue boundary records terminal failure.
1325
+ if isinstance(exc, StudioModelBackendUnavailable):
1326
+ _LOGGER.exception("Studio operator model backend execution failed")
1327
+ public_category = "StudioModelBackendUnavailable"
1328
+ public_message = (
1329
+ "operator-owned model backend is unavailable or failed re-attestation"
1330
+ )
1331
+ elif isinstance(exc, (ArtifactError, OSError)):
1332
+ _LOGGER.exception("Studio prepared benchmark execution failed")
1333
+ public_category = "PreparedBenchmarkUnavailable"
1334
+ public_message = (
1335
+ "prepared benchmark is unavailable or failed integrity verification"
1336
+ )
1337
+ else:
1338
+ public_category = type(exc).__name__
1339
+ public_message = str(exc)[:MAX_TEXT_LENGTH]
1340
+ with self._lock:
1341
+ record = self._runs[run_id]
1342
+ record["status"] = "failed"
1343
+ record["finished_at"] = _utc_now()
1344
+ record["error"] = {
1345
+ "category": public_category,
1346
+ "message": public_message,
1347
+ }
1348
+ record["events"].append(
1349
+ {"at": _utc_now(), "state": "failed", "message": public_category}
1350
+ )
1351
+ return
1352
+ with self._lock:
1353
+ record = self._runs[run_id]
1354
+ record["status"] = (
1355
+ "completed_with_failures" if result["summary"]["examples_failed"] else "completed"
1356
+ )
1357
+ record["finished_at"] = _utc_now()
1358
+ record["result"] = result
1359
+ record["events"].append(
1360
+ {"at": _utc_now(), "state": record["status"], "message": "Run finalized"}
1361
+ )
1362
+
1363
+ def list_runs(self) -> list[dict[str, Any]]:
1364
+ with self._lock:
1365
+ return [self._snapshot(self._runs[run_id], compact=True) for run_id in self._order]
1366
+
1367
+ @property
1368
+ def catalog(self) -> dict[str, Any]:
1369
+ return copy.deepcopy(self._catalog)
1370
+
1371
+ def get(self, run_id: str) -> dict[str, Any] | None:
1372
+ with self._lock:
1373
+ record = self._runs.get(run_id)
1374
+ return self._snapshot(record, compact=False) if record is not None else None
1375
+
1376
+ def wait(self, run_id: str, *, timeout: float = 5.0) -> dict[str, Any]:
1377
+ deadline = time.monotonic() + timeout
1378
+ while time.monotonic() < deadline:
1379
+ record = self.get(run_id)
1380
+ if record is None:
1381
+ raise KeyError(run_id)
1382
+ if record["status"] not in {"queued", "running"}:
1383
+ return record
1384
+ time.sleep(0.01)
1385
+ raise TimeoutError(f"run {run_id} did not finish within {timeout} seconds")
1386
+
1387
+ @staticmethod
1388
+ def _snapshot(record: dict[str, Any], *, compact: bool) -> dict[str, Any]:
1389
+ value = copy.deepcopy(record)
1390
+ if compact and value.get("result"):
1391
+ value["result"] = {"summary": value["result"]["summary"]}
1392
+ return value
1393
+
1394
+ def shutdown(self, *, wait: bool = True) -> None:
1395
+ self._executor.shutdown(wait=wait, cancel_futures=True)
1396
+
1397
+
1398
+ __all__ = [
1399
+ "MAX_RUNS",
1400
+ "RUNTIME_PROFILES",
1401
+ "RunManager",
1402
+ "StudioQueueFullError",
1403
+ "StudioSecurityError",
1404
+ "StudioValidationError",
1405
+ "build_catalog",
1406
+ "execute_config",
1407
+ "validate_config",
1408
+ ]