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,161 @@
1
+ """Lazy adapters to established metric implementations.
2
+
3
+ The core package stays dependency-free. Install ``agentinputlab[evaluation]``
4
+ to use these adapters; benchmark runs record the resolved package versions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ import re
11
+ from collections.abc import Mapping, Sequence
12
+ from dataclasses import dataclass
13
+ from importlib import metadata
14
+ from typing import Any
15
+
16
+ from agentinputlab.contracts.errors import ExternalDependencyError
17
+
18
+ _IR_EXPRESSION = re.compile(r"^(nDCG|R|AP)(?:@([1-9][0-9]*))?$")
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class RougeScore:
23
+ precision: float
24
+ recall: float
25
+ f1: float
26
+
27
+
28
+ def retrieval_aggregates(
29
+ qrels: Mapping[str, Mapping[str, int | float]],
30
+ run: Mapping[str, Mapping[str, int | float]],
31
+ *,
32
+ measures: Mapping[str, str] | None = None,
33
+ ) -> Mapping[str, float]:
34
+ """Compute aggregate IR measures through ``ir-measures`` providers.
35
+
36
+ ``measures`` maps stable output names to ir-measures expressions, for
37
+ example ``{"ndcg_at_10": "nDCG@10", "recall_at_100": "R@100"}``.
38
+ """
39
+
40
+ ir_measures = _optional_import("ir_measures", extra="evaluation")
41
+ normalized_qrels = _validate_nested_scores(qrels, label="qrels", require_integer=True)
42
+ normalized_run = _validate_nested_scores(run, label="run", require_integer=False)
43
+ if not normalized_qrels:
44
+ raise ValueError("qrels must contain at least one query")
45
+ specifications = dict(
46
+ measures
47
+ or {
48
+ "ndcg_at_10": "nDCG@10",
49
+ "recall_at_100": "R@100",
50
+ "map": "AP",
51
+ }
52
+ )
53
+ if not specifications:
54
+ raise ValueError("at least one retrieval measure is required")
55
+ parsed: dict[str, Any] = {}
56
+ for name, expression in specifications.items():
57
+ if not isinstance(name, str) or not name:
58
+ raise ValueError("retrieval measure names must be non-empty strings")
59
+ if not isinstance(expression, str) or not expression:
60
+ raise ValueError("retrieval measure expressions must be non-empty strings")
61
+ parsed[name] = _parse_ir_measure(ir_measures, expression)
62
+ aggregate = ir_measures.calc_aggregate(
63
+ list(parsed.values()),
64
+ normalized_qrels,
65
+ normalized_run,
66
+ )
67
+ return {name: float(aggregate[measure]) for name, measure in parsed.items()}
68
+
69
+
70
+ def rouge_l(
71
+ prediction: str,
72
+ references: Sequence[str],
73
+ *,
74
+ use_stemmer: bool = True,
75
+ ) -> RougeScore:
76
+ """Return the best-reference ROUGE-L score via Google's ``rouge-score``."""
77
+
78
+ rouge_scorer = _optional_import("rouge_score.rouge_scorer", extra="evaluation")
79
+ if not isinstance(prediction, str):
80
+ raise TypeError("prediction must be a string")
81
+ if isinstance(references, str):
82
+ raise TypeError("ROUGE references must be a sequence of strings, not one string")
83
+ if not references:
84
+ raise ValueError("at least one ROUGE reference is required")
85
+ if not all(isinstance(reference, str) for reference in references):
86
+ raise TypeError("ROUGE references must be strings")
87
+ scorer = rouge_scorer.RougeScorer(["rougeL"], use_stemmer=use_stemmer)
88
+ candidates = [scorer.score(reference, prediction)["rougeL"] for reference in references]
89
+ best = max(candidates, key=lambda score: (score.fmeasure, score.recall, score.precision))
90
+ return RougeScore(
91
+ precision=float(best.precision),
92
+ recall=float(best.recall),
93
+ f1=float(best.fmeasure),
94
+ )
95
+
96
+
97
+ def evaluation_dependency_versions() -> Mapping[str, str]:
98
+ versions: dict[str, str] = {}
99
+ for distribution in ("ir-measures", "pytrec-eval-terrier", "rouge-score"):
100
+ try:
101
+ versions[distribution] = metadata.version(distribution)
102
+ except metadata.PackageNotFoundError:
103
+ versions[distribution] = "not-installed"
104
+ return versions
105
+
106
+
107
+ def _optional_import(module: str, *, extra: str) -> Any:
108
+ try:
109
+ return __import__(module, fromlist=[module.rsplit(".", 1)[-1]])
110
+ except ImportError as exc:
111
+ raise ExternalDependencyError(
112
+ f"optional metric dependency {module!r} is unavailable; install agentinputlab[{extra}]"
113
+ ) from exc
114
+
115
+
116
+ def _parse_ir_measure(ir_measures: Any, expression: str) -> Any:
117
+ """Parse the audited IR subset without relying on Python AST internals.
118
+
119
+ ``ir_measures.parse_measure`` versions through 0.4.3 still reference
120
+ ``ast.Num``, which Python 3.14 removed. Constructing the public measure
121
+ objects directly is both narrower and cross-version stable.
122
+ """
123
+
124
+ match = _IR_EXPRESSION.fullmatch(expression)
125
+ if match is None:
126
+ raise ValueError(
127
+ f"invalid ir-measures expression: {expression}; supported: nDCG, R, AP with optional @k"
128
+ )
129
+ measure = getattr(ir_measures, match.group(1))
130
+ cutoff = match.group(2)
131
+ return measure if cutoff is None else measure @ int(cutoff)
132
+
133
+
134
+ def _validate_nested_scores(
135
+ value: Mapping[str, Mapping[str, int | float]],
136
+ *,
137
+ label: str,
138
+ require_integer: bool,
139
+ ) -> dict[str, dict[str, int | float]]:
140
+ if not isinstance(value, Mapping):
141
+ raise TypeError(f"{label} must be a mapping")
142
+ result: dict[str, dict[str, int | float]] = {}
143
+ for query_id, documents in value.items():
144
+ if not isinstance(query_id, str) or not query_id:
145
+ raise ValueError(f"{label} query IDs must be non-empty strings")
146
+ if not isinstance(documents, Mapping):
147
+ raise TypeError(f"{label} values must be document-score mappings")
148
+ converted: dict[str, int | float] = {}
149
+ for document_id, score in documents.items():
150
+ if not isinstance(document_id, str) or not document_id:
151
+ raise ValueError(f"{label} document IDs must be non-empty strings")
152
+ if isinstance(score, bool) or not isinstance(score, (int, float)):
153
+ raise TypeError(f"{label} scores must be numeric")
154
+ if require_integer and not isinstance(score, int):
155
+ raise TypeError(f"{label} relevance labels must be integers")
156
+ numeric = float(score)
157
+ if not math.isfinite(numeric):
158
+ raise ValueError(f"{label} scores must be finite")
159
+ converted[document_id] = score if require_integer else numeric
160
+ result[query_id] = converted
161
+ return result
@@ -0,0 +1,4 @@
1
+ """Compatibility exports; prefer :mod:`agentinputlab.backends.llm`."""
2
+
3
+ from agentinputlab.backends.llm import * # noqa: F403
4
+ from agentinputlab.backends.llm import __all__ as __all__
@@ -0,0 +1,56 @@
1
+ """Run timing, usage, cost, budget, failure, and trace observability."""
2
+
3
+ from .budget import BudgetLedger, ComputePrice, ModelPrice, PricingCatalog
4
+ from .instrumentation import (
5
+ ObservedDenseEmbedder,
6
+ ObservedExternalRuntime,
7
+ ObservedLLMBackend,
8
+ ObservedRetriever,
9
+ ObservedToolConnector,
10
+ ObservedVectorSearcher,
11
+ external_usage_counters,
12
+ instrument_context,
13
+ normalize_external_usage,
14
+ )
15
+ from .models import (
16
+ AcceleratorSample,
17
+ EventKind,
18
+ FailureRecord,
19
+ ObservedRunStatus,
20
+ ResourceExitReason,
21
+ ResourceLimits,
22
+ ResourceReceipt,
23
+ ResourceUsage,
24
+ RunEvent,
25
+ RunSnapshot,
26
+ )
27
+ from .monitor import EventSink, JsonlEventSink, RunMonitor
28
+
29
+ __all__ = [
30
+ "AcceleratorSample",
31
+ "BudgetLedger",
32
+ "ComputePrice",
33
+ "EventKind",
34
+ "EventSink",
35
+ "FailureRecord",
36
+ "JsonlEventSink",
37
+ "ModelPrice",
38
+ "ObservedDenseEmbedder",
39
+ "ObservedExternalRuntime",
40
+ "ObservedLLMBackend",
41
+ "ObservedRetriever",
42
+ "ObservedRunStatus",
43
+ "ObservedToolConnector",
44
+ "ObservedVectorSearcher",
45
+ "PricingCatalog",
46
+ "ResourceExitReason",
47
+ "ResourceLimits",
48
+ "ResourceReceipt",
49
+ "ResourceUsage",
50
+ "RunEvent",
51
+ "RunMonitor",
52
+ "RunSnapshot",
53
+ "external_usage_counters",
54
+ "instrument_context",
55
+ "normalize_external_usage",
56
+ ]
@@ -0,0 +1,316 @@
1
+ """Atomic resource accounting and user-supplied model price estimation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field, fields
8
+ from threading import RLock
9
+ from types import MappingProxyType
10
+
11
+ from agentinputlab.contracts.errors import BudgetExceededError, ValidationError
12
+ from agentinputlab.contracts.state import ExecutionBudget
13
+
14
+ from .models import ResourceUsage
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ModelPrice:
19
+ """USD per one million input/output tokens for a pinned deployment."""
20
+
21
+ input_per_million: float
22
+ output_per_million: float
23
+
24
+ def __post_init__(self) -> None:
25
+ values = (self.input_per_million, self.output_per_million)
26
+ if any(
27
+ isinstance(value, bool)
28
+ or not isinstance(value, (int, float))
29
+ or value < 0
30
+ or not math.isfinite(value)
31
+ for value in values
32
+ ):
33
+ raise ValidationError("model prices must be finite and non-negative")
34
+
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class ComputePrice:
38
+ """Pinned effective infrastructure rates for self-hosted experiments."""
39
+
40
+ cpu_per_hour: float = 0.0
41
+ accelerator_per_hour: float = 0.0
42
+
43
+ def __post_init__(self) -> None:
44
+ values = (self.cpu_per_hour, self.accelerator_per_hour)
45
+ if any(
46
+ isinstance(value, bool)
47
+ or not isinstance(value, (int, float))
48
+ or value < 0
49
+ or not math.isfinite(value)
50
+ for value in values
51
+ ):
52
+ raise ValidationError("compute prices must be finite and non-negative")
53
+
54
+ def estimate(self, *, cpu_seconds: float, accelerator_seconds: float) -> float:
55
+ return (
56
+ cpu_seconds * self.cpu_per_hour + accelerator_seconds * self.accelerator_per_hour
57
+ ) / 3600
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class PricingCatalog:
62
+ """No rolling vendor prices are baked into the library.
63
+
64
+ Experiment manifests must provide the price attached to their pinned model
65
+ deployment, keeping historical cost reports reproducible.
66
+ """
67
+
68
+ prices: Mapping[str, ModelPrice] = field(default_factory=dict)
69
+
70
+ def __post_init__(self) -> None:
71
+ if not all(isinstance(value, ModelPrice) for value in self.prices.values()):
72
+ raise ValidationError("pricing catalog values must be ModelPrice instances")
73
+ object.__setattr__(self, "prices", MappingProxyType(dict(self.prices)))
74
+
75
+ def estimate(self, model_id: str, *, input_tokens: int, output_tokens: int) -> float:
76
+ price = self.prices.get(model_id)
77
+ if price is None:
78
+ return 0.0
79
+ return (
80
+ input_tokens * price.input_per_million + output_tokens * price.output_per_million
81
+ ) / 1_000_000
82
+
83
+ def has_price(self, model_id: str) -> bool:
84
+ return model_id in self.prices
85
+
86
+
87
+ class BudgetLedger:
88
+ """Thread-safe ledger separating reservations from irreversible consumption.
89
+
90
+ ``reserve`` is used before a call with side effects. A rejected reservation
91
+ leaves usage unchanged. ``charge_actual`` is used after a response has
92
+ already consumed tokens or money: it always records that usage and makes any
93
+ resulting violation sticky so an intervention cannot hide it by catching the
94
+ immediate :class:`BudgetExceededError`.
95
+
96
+ ``charge`` remains a backwards-compatible alias for ``reserve``.
97
+ """
98
+
99
+ def __init__(self, budget: ExecutionBudget) -> None:
100
+ if not isinstance(budget, ExecutionBudget):
101
+ raise TypeError("budget must be an ExecutionBudget")
102
+ self.budget = budget
103
+ self._usage = ResourceUsage()
104
+ self._reservation_rejections: tuple[str, ...] = ()
105
+ self._actual_violations: tuple[str, ...] = ()
106
+ self._pending_usage = ResourceUsage()
107
+ self._owner_run_id: str | None = None
108
+ self._lock = RLock()
109
+
110
+ @property
111
+ def owner_run_id(self) -> str | None:
112
+ """Monitored run that exclusively owns this ledger, if claimed."""
113
+
114
+ with self._lock:
115
+ return self._owner_run_id
116
+
117
+ @property
118
+ def is_pristine(self) -> bool:
119
+ """Whether no run has claimed or charged this injectable ledger."""
120
+
121
+ with self._lock:
122
+ return self._is_pristine_unlocked()
123
+
124
+ def claim_for_run(self, run_id: str) -> None:
125
+ """Bind a pristine ledger to exactly one monitored run.
126
+
127
+ A caller may inject a ledger to inspect accounting in a test, but a
128
+ ledger is mutable run state rather than reusable configuration. The
129
+ one-way claim prevents sequential or concurrent runs from sharing
130
+ counters and therefore turning a per-run budget into a cross-run one.
131
+ """
132
+
133
+ if not isinstance(run_id, str) or not run_id.strip():
134
+ raise ValidationError("run_id must be a non-empty string")
135
+ with self._lock:
136
+ if self._owner_run_id is not None:
137
+ raise ValidationError(
138
+ "BudgetLedger is already owned by a monitored run; "
139
+ "construct a fresh BudgetLedger for every run"
140
+ )
141
+ if not self._is_pristine_unlocked():
142
+ raise ValidationError(
143
+ "an injected BudgetLedger must be pristine; construct a fresh "
144
+ "BudgetLedger for every run"
145
+ )
146
+ self._owner_run_id = run_id
147
+
148
+ def _is_pristine_unlocked(self) -> bool:
149
+ return (
150
+ self._owner_run_id is None
151
+ and self._usage == ResourceUsage()
152
+ and self._pending_usage == ResourceUsage()
153
+ and not self._reservation_rejections
154
+ and not self._actual_violations
155
+ )
156
+
157
+ @property
158
+ def usage(self) -> ResourceUsage:
159
+ with self._lock:
160
+ return self._usage
161
+
162
+ @property
163
+ def actual_violations(self) -> tuple[str, ...]:
164
+ """Budget dimensions ever exceeded by already-consumed resources."""
165
+
166
+ with self._lock:
167
+ return self._actual_violations
168
+
169
+ @property
170
+ def reservation_rejections(self) -> tuple[str, ...]:
171
+ """Budget dimensions for calls denied before external side effects."""
172
+
173
+ with self._lock:
174
+ return self._reservation_rejections
175
+
176
+ def reserve(self, delta: ResourceUsage) -> ResourceUsage:
177
+ """Atomically reserve prospective usage, rejecting without mutation."""
178
+
179
+ if not isinstance(delta, ResourceUsage):
180
+ raise TypeError("delta must be a ResourceUsage")
181
+ with self._lock:
182
+ current = self._usage + delta
183
+ violations = self._violations(current)
184
+ if violations:
185
+ self._reservation_rejections = tuple(
186
+ dict.fromkeys((*self._reservation_rejections, *violations))
187
+ )
188
+ raise BudgetExceededError(
189
+ "execution budget reservation rejected: " + ", ".join(violations)
190
+ )
191
+ self._usage = current
192
+ return current
193
+
194
+ def reserve_pending(self, delta: ResourceUsage) -> ResourceUsage:
195
+ """Reserve a lower-bound call plan that may later be committed or cancelled."""
196
+
197
+ if not isinstance(delta, ResourceUsage):
198
+ raise TypeError("delta must be a ResourceUsage")
199
+ with self._lock:
200
+ current = self._usage + delta
201
+ violations = self._violations(current)
202
+ if violations:
203
+ self._reservation_rejections = tuple(
204
+ dict.fromkeys((*self._reservation_rejections, *violations))
205
+ )
206
+ raise BudgetExceededError(
207
+ "execution budget reservation rejected: " + ", ".join(violations)
208
+ )
209
+ self._usage = current
210
+ self._pending_usage = self._pending_usage + delta
211
+ return current
212
+
213
+ def commit_pending(self, delta: ResourceUsage) -> ResourceUsage:
214
+ """Mark a pending lower-bound reservation as confirmed actual usage."""
215
+
216
+ if not isinstance(delta, ResourceUsage):
217
+ raise TypeError("delta must be a ResourceUsage")
218
+ with self._lock:
219
+ self._pending_usage = _subtract_usage(
220
+ self._pending_usage,
221
+ delta,
222
+ label="pending reservation",
223
+ )
224
+ return self._usage
225
+
226
+ def cancel_pending(self, delta: ResourceUsage) -> ResourceUsage:
227
+ """Cancel a reservation when a typed worker failure reports no consumption."""
228
+
229
+ if not isinstance(delta, ResourceUsage):
230
+ raise TypeError("delta must be a ResourceUsage")
231
+ with self._lock:
232
+ self._pending_usage = _subtract_usage(
233
+ self._pending_usage,
234
+ delta,
235
+ label="pending reservation",
236
+ )
237
+ self._usage = _subtract_usage(self._usage, delta, label="recorded usage")
238
+ return self._usage
239
+
240
+ def charge(self, delta: ResourceUsage) -> ResourceUsage:
241
+ """Backwards-compatible alias for :meth:`reserve`."""
242
+
243
+ return self.reserve(delta)
244
+
245
+ def charge_actual(self, delta: ResourceUsage) -> ResourceUsage:
246
+ """Record irreversible consumption, retaining overages before raising."""
247
+
248
+ if not isinstance(delta, ResourceUsage):
249
+ raise TypeError("delta must be a ResourceUsage")
250
+ with self._lock:
251
+ current = self._usage + delta
252
+ self._usage = current
253
+ violations = self._violations(current)
254
+ if violations:
255
+ self._actual_violations = tuple(
256
+ dict.fromkeys((*self._actual_violations, *violations))
257
+ )
258
+ raise BudgetExceededError(
259
+ "actual execution budget exceeded: " + ", ".join(violations)
260
+ )
261
+ if self._actual_violations:
262
+ raise BudgetExceededError(
263
+ "actual execution budget was previously exceeded: "
264
+ + ", ".join(self._actual_violations)
265
+ )
266
+ return current
267
+
268
+ def _violations(self, usage: ResourceUsage) -> tuple[str, ...]:
269
+ violations: list[str] = []
270
+ if usage.model_calls > self.budget.max_model_calls:
271
+ violations.append("model_calls")
272
+ if usage.retrieval_calls > self.budget.max_retrieval_calls:
273
+ violations.append("retrieval_calls")
274
+ if usage.tool_calls > self.budget.max_tool_calls:
275
+ violations.append("tool_calls")
276
+ if usage.output_tokens > self.budget.max_output_tokens:
277
+ violations.append("output_tokens")
278
+ if (
279
+ self.budget.max_context_tokens is not None
280
+ and usage.input_tokens > self.budget.max_context_tokens
281
+ ):
282
+ violations.append("context_tokens")
283
+ if self.budget.max_cost_usd is not None and usage.total_cost_usd > self.budget.max_cost_usd:
284
+ violations.append("cost_usd")
285
+ if self.budget.max_cost_usd is not None and usage.unpriced_model_calls:
286
+ violations.append("unknown_cost")
287
+ return tuple(violations)
288
+
289
+ def require_within_budget(self) -> None:
290
+ with self._lock:
291
+ violations = tuple(
292
+ dict.fromkeys(
293
+ (
294
+ *self._reservation_rejections,
295
+ *self._actual_violations,
296
+ *self._violations(self._usage),
297
+ )
298
+ )
299
+ )
300
+ if violations:
301
+ raise BudgetExceededError("execution budget exceeded: " + ", ".join(violations))
302
+
303
+
304
+ def _subtract_usage(
305
+ current: ResourceUsage,
306
+ delta: ResourceUsage,
307
+ *,
308
+ label: str,
309
+ ) -> ResourceUsage:
310
+ values: dict[str, int | float] = {}
311
+ for declared in fields(ResourceUsage):
312
+ difference = getattr(current, declared.name) - getattr(delta, declared.name)
313
+ if difference < 0:
314
+ raise ValidationError(f"cannot release more than the {label}")
315
+ values[declared.name] = difference
316
+ return ResourceUsage(**values)