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.
- agentinputlab/__init__.py +28 -0
- agentinputlab/algorithms/__init__.py +6 -0
- agentinputlab/algorithms/acquire/__init__.py +145 -0
- agentinputlab/algorithms/acquire/adaptive_rag/__init__.py +43 -0
- agentinputlab/algorithms/acquire/adaptive_rag/replay.py +367 -0
- agentinputlab/algorithms/acquire/adaptive_rag/runtime.py +1202 -0
- agentinputlab/algorithms/acquire/adaptive_rag_common_support.py +542 -0
- agentinputlab/algorithms/acquire/hyde.py +296 -0
- agentinputlab/algorithms/acquire/hyde_controlled.py +200 -0
- agentinputlab/algorithms/acquire/hyde_external.py +148 -0
- agentinputlab/algorithms/acquire/ircot.py +28 -0
- agentinputlab/algorithms/acquire/ircot_common_support.py +478 -0
- agentinputlab/algorithms/acquire/ircot_native.py +355 -0
- agentinputlab/algorithms/acquire/ircot_replay.py +327 -0
- agentinputlab/algorithms/acquire/qrag/__init__.py +79 -0
- agentinputlab/algorithms/acquire/qrag/bridge.py +828 -0
- agentinputlab/algorithms/acquire/qrag/common_support.py +891 -0
- agentinputlab/algorithms/acquire/qrag/native_axis.py +71 -0
- agentinputlab/algorithms/acquire/qrag/reader_adapter.py +139 -0
- agentinputlab/algorithms/acquire/r2ag/__init__.py +57 -0
- agentinputlab/algorithms/acquire/r2ag/bridge.py +1312 -0
- agentinputlab/algorithms/acquire/r2ag/common_support.py +1029 -0
- agentinputlab/algorithms/acquire/r2ag/socket_runtime.py +255 -0
- agentinputlab/algorithms/clarify/__init__.py +61 -0
- agentinputlab/algorithms/clarify/_structured.py +64 -0
- agentinputlab/algorithms/clarify/active_task_disambiguation.py +371 -0
- agentinputlab/algorithms/clarify/ask_or_assume/__init__.py +17 -0
- agentinputlab/algorithms/clarify/ask_or_assume/monitor.py +410 -0
- agentinputlab/algorithms/clarify/future_turns.py +379 -0
- agentinputlab/algorithms/clarify/udcs.py +255 -0
- agentinputlab/algorithms/clarify/udcs_deterministic.py +196 -0
- agentinputlab/algorithms/curate/__init__.py +65 -0
- agentinputlab/algorithms/curate/acon/__init__.py +27 -0
- agentinputlab/algorithms/curate/acon/common.py +284 -0
- agentinputlab/algorithms/curate/acon/common_support.py +136 -0
- agentinputlab/algorithms/curate/acon/history_bridge.py +309 -0
- agentinputlab/algorithms/curate/acon/observation_bridge.py +290 -0
- agentinputlab/algorithms/curate/llmlingua/__init__.py +30 -0
- agentinputlab/algorithms/curate/llmlingua/adapters.py +537 -0
- agentinputlab/algorithms/curate/llmlingua/budget.py +48 -0
- agentinputlab/algorithms/curate/llmlingua/controlled.py +175 -0
- agentinputlab/algorithms/curate/llmlingua/multilingual.py +105 -0
- agentinputlab/algorithms/curate/llmlingua/span_map.py +167 -0
- agentinputlab/algorithms/curate/recomp/__init__.py +47 -0
- agentinputlab/algorithms/curate/recomp/external.py +1129 -0
- agentinputlab/algorithms/curate/recomp/selective.py +304 -0
- agentinputlab/algorithms/decompose/__init__.py +107 -0
- agentinputlab/algorithms/decompose/decomp/__init__.py +53 -0
- agentinputlab/algorithms/decompose/decomp/common_support.py +478 -0
- agentinputlab/algorithms/decompose/decomp/planner_component.py +199 -0
- agentinputlab/algorithms/decompose/decomp/worker_bridge.py +416 -0
- agentinputlab/algorithms/decompose/llm_compiler/__init__.py +35 -0
- agentinputlab/algorithms/decompose/llm_compiler/bridge.py +520 -0
- agentinputlab/algorithms/decompose/llm_compiler/common_support.py +504 -0
- agentinputlab/algorithms/decompose/plan_and_budget/__init__.py +43 -0
- agentinputlab/algorithms/decompose/plan_and_budget/cached_soft_budget.py +362 -0
- agentinputlab/algorithms/decompose/plan_and_budget/decomposer_component.py +107 -0
- agentinputlab/algorithms/decompose/plan_and_budget/full.py +205 -0
- agentinputlab/algorithms/decompose/plan_and_budget/hard_cap.py +246 -0
- agentinputlab/algorithms/decompose/plan_and_budget/regenerated_soft_budget.py +448 -0
- agentinputlab/algorithms/decompose/plan_and_solve/__init__.py +33 -0
- agentinputlab/algorithms/decompose/plan_and_solve/plan_component.py +192 -0
- agentinputlab/algorithms/decompose/plan_and_solve/ps_prompt.py +127 -0
- agentinputlab/algorithms/decompose/self_ask/__init__.py +27 -0
- agentinputlab/algorithms/decompose/self_ask/loop.py +362 -0
- agentinputlab/algorithms/decompose/self_ask/planner_component.py +117 -0
- agentinputlab/algorithms/reformulate/__init__.py +85 -0
- agentinputlab/algorithms/reformulate/infocqr/__init__.py +40 -0
- agentinputlab/algorithms/reformulate/infocqr/cache_replay.py +362 -0
- agentinputlab/algorithms/reformulate/infocqr/external.py +675 -0
- agentinputlab/algorithms/reformulate/infocqr/student.py +189 -0
- agentinputlab/algorithms/reformulate/query2doc/__init__.py +42 -0
- agentinputlab/algorithms/reformulate/query2doc/controlled_live.py +190 -0
- agentinputlab/algorithms/reformulate/query2doc/models.py +95 -0
- agentinputlab/algorithms/reformulate/query2doc/official_cached.py +335 -0
- agentinputlab/algorithms/reformulate/query2doc/querygym_bridge.py +223 -0
- agentinputlab/algorithms/reformulate/query2doc/querygym_runtime.py +326 -0
- agentinputlab/algorithms/reformulate/reformer/__init__.py +23 -0
- agentinputlab/algorithms/reformulate/reformer/external.py +417 -0
- agentinputlab/algorithms/reformulate/rrr.py +696 -0
- agentinputlab/annotation/__init__.py +5 -0
- agentinputlab/annotation/independent.py +428 -0
- agentinputlab/annotation/server.py +233 -0
- agentinputlab/annotation/static/app.js +121 -0
- agentinputlab/annotation/static/index.html +60 -0
- agentinputlab/annotation/static/review.css +2 -0
- agentinputlab/annotation/static/review.html +10 -0
- agentinputlab/annotation/static/review.js +11 -0
- agentinputlab/annotation/static/styles.css +55 -0
- agentinputlab/annotation/store.py +444 -0
- agentinputlab/api.py +368 -0
- agentinputlab/artifacts/__init__.py +15 -0
- agentinputlab/artifacts/json_file.py +104 -0
- agentinputlab/artifacts/manifest.py +98 -0
- agentinputlab/artifacts/store.py +157 -0
- agentinputlab/backends/__init__.py +98 -0
- agentinputlab/backends/embedding/__init__.py +25 -0
- agentinputlab/backends/embedding/base.py +145 -0
- agentinputlab/backends/embedding/callable.py +75 -0
- agentinputlab/backends/embedding/replay.py +129 -0
- agentinputlab/backends/llm/__init__.py +31 -0
- agentinputlab/backends/llm/base.py +149 -0
- agentinputlab/backends/llm/callable.py +108 -0
- agentinputlab/backends/llm/decomp_completion.py +88 -0
- agentinputlab/backends/llm/final_content_stop.py +72 -0
- agentinputlab/backends/llm/gpt_oss_final_channel.py +84 -0
- agentinputlab/backends/llm/openai_compatible.py +332 -0
- agentinputlab/backends/llm/transformers.py +171 -0
- agentinputlab/backends/retrieval/__init__.py +35 -0
- agentinputlab/backends/retrieval/base.py +50 -0
- agentinputlab/backends/retrieval/dense.py +777 -0
- agentinputlab/backends/retrieval/memory.py +119 -0
- agentinputlab/backends/retrieval/sqlite_fts.py +155 -0
- agentinputlab/backends/user/__init__.py +15 -0
- agentinputlab/backends/user/base.py +66 -0
- agentinputlab/backends/user/callable.py +54 -0
- agentinputlab/backends/user/replay.py +89 -0
- agentinputlab/backends/vector.py +38 -0
- agentinputlab/benchmarks/__init__.py +246 -0
- agentinputlab/benchmarks/acquisition.py +106 -0
- agentinputlab/benchmarks/ale.py +540 -0
- agentinputlab/benchmarks/appworld_ul.py +669 -0
- agentinputlab/benchmarks/assets.py +226 -0
- agentinputlab/benchmarks/base.py +68 -0
- agentinputlab/benchmarks/beir_archive.py +235 -0
- agentinputlab/benchmarks/beir_sample.py +269 -0
- agentinputlab/benchmarks/bfcl_native.py +245 -0
- agentinputlab/benchmarks/bfcl_v3.py +607 -0
- agentinputlab/benchmarks/cache.py +218 -0
- agentinputlab/benchmarks/clariq.py +203 -0
- agentinputlab/benchmarks/data/registry.json +321 -0
- agentinputlab/benchmarks/data/smoke_assets.json +511 -0
- agentinputlab/benchmarks/data/statebench_v0_8_1_task_ids.json +475 -0
- agentinputlab/benchmarks/data/toolsandbox_165848b_scenario_names.json +1 -0
- agentinputlab/benchmarks/execution.py +1111 -0
- agentinputlab/benchmarks/interactive.py +257 -0
- agentinputlab/benchmarks/ir_query.py +204 -0
- agentinputlab/benchmarks/longbench.py +203 -0
- agentinputlab/benchmarks/paired_replay.py +479 -0
- agentinputlab/benchmarks/preprocess.py +2313 -0
- agentinputlab/benchmarks/production.py +236 -0
- agentinputlab/benchmarks/qa.py +963 -0
- agentinputlab/benchmarks/registry.py +110 -0
- agentinputlab/benchmarks/retrieval.py +459 -0
- agentinputlab/benchmarks/runtime.py +509 -0
- agentinputlab/benchmarks/statebench.py +938 -0
- agentinputlab/benchmarks/tau3.py +725 -0
- agentinputlab/benchmarks/toolsandbox.py +626 -0
- agentinputlab/benchmarks/trec_dl_sample.py +263 -0
- agentinputlab/campaign.py +654 -0
- agentinputlab/campaign_runner.py +953 -0
- agentinputlab/cli/__init__.py +6 -0
- agentinputlab/cli/main.py +933 -0
- agentinputlab/connectors/__init__.py +46 -0
- agentinputlab/connectors/knowledge/__init__.py +4 -0
- agentinputlab/connectors/tools/__init__.py +30 -0
- agentinputlab/connectors/tools/base.py +57 -0
- agentinputlab/connectors/tools/mcp.py +222 -0
- agentinputlab/connectors/tools/memory.py +41 -0
- agentinputlab/connectors/tools/openapi.py +695 -0
- agentinputlab/connectors/tools/schema.py +102 -0
- agentinputlab/contracts/__init__.py +91 -0
- agentinputlab/contracts/availability.py +24 -0
- agentinputlab/contracts/dependency.py +58 -0
- agentinputlab/contracts/enums.py +82 -0
- agentinputlab/contracts/errors.py +63 -0
- agentinputlab/contracts/evidence.py +43 -0
- agentinputlab/contracts/intent.py +110 -0
- agentinputlab/contracts/lifecycle.py +164 -0
- agentinputlab/contracts/patch.py +167 -0
- agentinputlab/contracts/serialization.py +93 -0
- agentinputlab/contracts/state.py +127 -0
- agentinputlab/contracts/trace.py +59 -0
- agentinputlab/contracts/worker.py +42 -0
- agentinputlab/core/__init__.py +7 -0
- agentinputlab/data/__init__.py +32 -0
- agentinputlab/data/checksums.py +9 -0
- agentinputlab/data/downloader.py +235 -0
- agentinputlab/data/http.py +171 -0
- agentinputlab/data/index.py +21 -0
- agentinputlab/data/overlap.py +28 -0
- agentinputlab/data/preprocess.py +32 -0
- agentinputlab/data/storage.py +57 -0
- agentinputlab/data/trec_sample.py +427 -0
- agentinputlab/evaluation/__init__.py +5 -0
- agentinputlab/evaluation/outcomes.py +32 -0
- agentinputlab/experiments/__init__.py +278 -0
- agentinputlab/experiments/analysis.py +981 -0
- agentinputlab/experiments/artifacts.py +687 -0
- agentinputlab/experiments/automatic_measurement.py +678 -0
- agentinputlab/experiments/auxiliary_matrix.py +203 -0
- agentinputlab/experiments/bindings.py +430 -0
- agentinputlab/experiments/blueprints.py +147 -0
- agentinputlab/experiments/builders/__init__.py +45 -0
- agentinputlab/experiments/builders/executable_external_payload.py +423 -0
- agentinputlab/experiments/builders/external_process_arena.py +1018 -0
- agentinputlab/experiments/builders/registry_intervention.py +463 -0
- agentinputlab/experiments/cli.py +158 -0
- agentinputlab/experiments/controls.py +340 -0
- agentinputlab/experiments/driver_guard.py +205 -0
- agentinputlab/experiments/drivers/__init__.py +89 -0
- agentinputlab/experiments/drivers/external_arena.py +2908 -0
- agentinputlab/experiments/drivers/method_process.py +662 -0
- agentinputlab/experiments/drivers/parity_process.py +1484 -0
- agentinputlab/experiments/drivers/static_production.py +1574 -0
- agentinputlab/experiments/executor.py +448 -0
- agentinputlab/experiments/loader.py +605 -0
- agentinputlab/experiments/measurement.py +675 -0
- agentinputlab/experiments/paper_analysis.py +1818 -0
- agentinputlab/experiments/parity_identity.py +605 -0
- agentinputlab/experiments/plan.py +277 -0
- agentinputlab/experiments/production_builders.py +3739 -0
- agentinputlab/experiments/readiness.py +432 -0
- agentinputlab/experiments/schema.py +514 -0
- agentinputlab/experiments/secrets.py +177 -0
- agentinputlab/experiments/stateful_analysis.py +1738 -0
- agentinputlab/experiments/transfer_analysis.py +1316 -0
- agentinputlab/experiments/unseen_analysis.py +1025 -0
- agentinputlab/integrations/__init__.py +1 -0
- agentinputlab/integrations/action_space/__init__.py +1 -0
- agentinputlab/integrations/external_workers/__init__.py +76 -0
- agentinputlab/integrations/external_workers/ace.py +431 -0
- agentinputlab/integrations/external_workers/base.py +181 -0
- agentinputlab/integrations/external_workers/decomp_live.py +249 -0
- agentinputlab/integrations/external_workers/decomp_planner.py +30 -0
- agentinputlab/integrations/external_workers/infocqr_student.py +280 -0
- agentinputlab/integrations/external_workers/ircot.py +576 -0
- agentinputlab/integrations/external_workers/llmcompiler_live.py +360 -0
- agentinputlab/integrations/external_workers/longllmlingua.py +162 -0
- agentinputlab/integrations/external_workers/method.py +314 -0
- agentinputlab/integrations/external_workers/process.py +678 -0
- agentinputlab/integrations/external_workers/supervisor.py +1071 -0
- agentinputlab/integrations/full_architecture/__init__.py +1 -0
- agentinputlab/integrations/full_architecture/hyde.py +212 -0
- agentinputlab/integrations/full_architecture/plan_and_budget.py +188 -0
- agentinputlab/integrations/live_history.py +222 -0
- agentinputlab/integrations/prompt_optimization/__init__.py +1 -0
- agentinputlab/integrations/training/__init__.py +1 -0
- agentinputlab/metrics/__init__.py +50 -0
- agentinputlab/metrics/catalog.py +313 -0
- agentinputlab/metrics/harm.py +95 -0
- agentinputlab/metrics/paired.py +40 -0
- agentinputlab/metrics/qa.py +76 -0
- agentinputlab/metrics/text.py +33 -0
- agentinputlab/metrics/third_party.py +161 -0
- agentinputlab/models/__init__.py +4 -0
- agentinputlab/observability/__init__.py +56 -0
- agentinputlab/observability/budget.py +316 -0
- agentinputlab/observability/instrumentation.py +754 -0
- agentinputlab/observability/models.py +403 -0
- agentinputlab/observability/monitor.py +380 -0
- agentinputlab/optimizers/__init__.py +1 -0
- agentinputlab/optimizers/clarify/__init__.py +12 -0
- agentinputlab/optimizers/context/__init__.py +22 -0
- agentinputlab/optimizers/decompose/__init__.py +64 -0
- agentinputlab/optimizers/evidence/__init__.py +22 -0
- agentinputlab/optimizers/rewrite/__init__.py +30 -0
- agentinputlab/parity/__init__.py +5 -0
- agentinputlab/parity/base.py +206 -0
- agentinputlab/production_campaign.py +763 -0
- agentinputlab/py.typed +1 -0
- agentinputlab/recipes/__init__.py +333 -0
- agentinputlab/recipes/ace.py +418 -0
- agentinputlab/recipes/ace_bfcl.py +273 -0
- agentinputlab/recipes/ace_common_support.py +169 -0
- agentinputlab/recipes/ace_native_plan.py +322 -0
- agentinputlab/recipes/ace_online.py +711 -0
- agentinputlab/recipes/ace_retrieval.py +330 -0
- agentinputlab/recipes/ace_statebench.py +382 -0
- agentinputlab/recipes/ace_tau.py +463 -0
- agentinputlab/recipes/ace_toolsandbox.py +432 -0
- agentinputlab/recipes/acon_guideline.py +22 -0
- agentinputlab/recipes/adaptive_rag.py +822 -0
- agentinputlab/recipes/llmlingua2_inference.py +146 -0
- agentinputlab/recipes/llmlingua2_training.py +796 -0
- agentinputlab/recipes/mce.py +1486 -0
- agentinputlab/recipes/mce_final.py +335 -0
- agentinputlab/recipes/mce_text_fit.py +499 -0
- agentinputlab/recipes/mce_text_only.py +298 -0
- agentinputlab/recipes/qrag.py +729 -0
- agentinputlab/recipes/qrag_inference.py +364 -0
- agentinputlab/recipes/r2ag_features.py +236 -0
- agentinputlab/recipes/r2ag_inference.py +503 -0
- agentinputlab/recipes/r2ag_training.py +948 -0
- agentinputlab/recipes/recomp_abstractive.py +592 -0
- agentinputlab/recipes/recomp_extractive.py +525 -0
- agentinputlab/recipes/reformer.py +17 -0
- agentinputlab/recipes/rqrag.py +1516 -0
- agentinputlab/recipes/rqrag_common_support.py +193 -0
- agentinputlab/recipes/rqrag_completion.py +147 -0
- agentinputlab/recipes/skillopt.py +462 -0
- agentinputlab/recipes/skillopt_common_support.py +154 -0
- agentinputlab/recipes/skillopt_environment.py +165 -0
- agentinputlab/recipes/skillopt_environment_cli.py +5 -0
- agentinputlab/recipes/skillopt_retrieval.py +188 -0
- agentinputlab/recipes/smartsearch.py +897 -0
- agentinputlab/recipes/smartsearch_curriculum.py +283 -0
- agentinputlab/recipes/smartsearch_grpo_contracts.py +58 -0
- agentinputlab/recipes/smartsearch_grpo_data.py +314 -0
- agentinputlab/recipes/smartsearch_grpo_meter.py +324 -0
- agentinputlab/recipes/smartsearch_grpo_prefix.py +144 -0
- agentinputlab/recipes/smartsearch_grpo_rollout.py +119 -0
- agentinputlab/recipes/smartsearch_grpo_tools.py +364 -0
- agentinputlab/recipes/smartsearch_grpo_training.py +305 -0
- agentinputlab/recipes/smartsearch_grpo_transport.py +248 -0
- agentinputlab/recipes/smartsearch_grpo_worker.py +364 -0
- agentinputlab/recipes/smartsearch_inference.py +524 -0
- agentinputlab/recipes/smartsearch_native_data.py +172 -0
- agentinputlab/recipes/smartsearch_policy.py +616 -0
- agentinputlab/recipes/smartsearch_policy_worker.py +9 -0
- agentinputlab/recipes/smartsearch_sampling.py +232 -0
- agentinputlab/recipes/smartsearch_teacher.py +203 -0
- agentinputlab/recipes/smartsearch_training.py +265 -0
- agentinputlab/registry/__init__.py +46 -0
- agentinputlab/registry/beta.py +727 -0
- agentinputlab/registry/data/algorithms.json +1959 -0
- agentinputlab/registry/data/auxiliary_method_profiles.json +34 -0
- agentinputlab/registry/loader.py +100 -0
- agentinputlab/registry/result_manifest.py +184 -0
- agentinputlab/registry/runtime.py +195 -0
- agentinputlab/registry/schema.py +449 -0
- agentinputlab/runner/__init__.py +30 -0
- agentinputlab/runner/balance.py +248 -0
- agentinputlab/runner/cache.py +135 -0
- agentinputlab/runner/composite.py +130 -0
- agentinputlab/runner/engine.py +285 -0
- agentinputlab/runner/runtime_guard.py +268 -0
- agentinputlab/runner/worker.py +80 -0
- agentinputlab/runtime_bootstrap.py +288 -0
- agentinputlab/selectors/__init__.py +196 -0
- agentinputlab/selectors/advanced.py +1514 -0
- agentinputlab/selectors/artifacts.py +1087 -0
- agentinputlab/selectors/base.py +112 -0
- agentinputlab/selectors/campaigns.py +1893 -0
- agentinputlab/selectors/catalog.py +86 -0
- agentinputlab/selectors/discovery.py +1860 -0
- agentinputlab/selectors/evaluation.py +1348 -0
- agentinputlab/selectors/features.py +383 -0
- agentinputlab/selectors/learned.py +502 -0
- agentinputlab/selectors/no_op.py +28 -0
- agentinputlab/selectors/outcome_table.py +475 -0
- agentinputlab/selectors/producer.py +853 -0
- agentinputlab/selectors/rules.py +109 -0
- agentinputlab/selectors/runtime.py +420 -0
- agentinputlab/studies/__init__.py +16 -0
- agentinputlab/studies/design.py +165 -0
- agentinputlab/studies/paired.py +322 -0
- agentinputlab/studio/__init__.py +6 -0
- agentinputlab/studio/campaigns.py +819 -0
- agentinputlab/studio/engine.py +1408 -0
- agentinputlab/studio/model_backends.py +425 -0
- agentinputlab/studio/prepared.py +410 -0
- agentinputlab/studio/server.py +613 -0
- agentinputlab/studio/static/app.js +397 -0
- agentinputlab/studio/static/campaigns.html +175 -0
- agentinputlab/studio/static/campaigns.js +426 -0
- agentinputlab/studio/static/index.html +192 -0
- agentinputlab/studio/static/result.html +103 -0
- agentinputlab/studio/static/result.js +273 -0
- agentinputlab/studio/static/styles.css +467 -0
- agentinputlab-0.1.0a1.data/data/share/agentinputlab/CITATION.cff +22 -0
- agentinputlab-0.1.0a1.dist-info/METADATA +392 -0
- agentinputlab-0.1.0a1.dist-info/RECORD +469 -0
- agentinputlab-0.1.0a1.dist-info/WHEEL +5 -0
- agentinputlab-0.1.0a1.dist-info/entry_points.txt +6 -0
- agentinputlab-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- agentinputlab-0.1.0a1.dist-info/licenses/NOTICE +31 -0
- agentinputlab-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +94 -0
- agentinputlab-0.1.0a1.dist-info/top_level.txt +2 -0
- workers/README.md +94 -0
- workers/ace/README.md +261 -0
- workers/ace/__init__.py +1 -0
- workers/ace/compatibility_manifest.json +39 -0
- workers/ace/driver.py +1682 -0
- workers/acon/README.md +215 -0
- workers/acon/__init__.py +5 -0
- workers/acon/compatibility_manifest.json +93 -0
- workers/acon/driver.py +1859 -0
- workers/acon/fitted.py +395 -0
- workers/acon/guideline.py +470 -0
- workers/acon/guideline_source_manifest.json +20 -0
- workers/adaptive_rag/README.md +96 -0
- workers/adaptive_rag/__init__.py +15 -0
- workers/adaptive_rag/compatibility_manifest.json +61 -0
- workers/adaptive_rag/driver.py +1470 -0
- workers/adaptive_rag/fixture_runtime.py +163 -0
- workers/adaptive_rag/runtime.py +252 -0
- workers/ask_or_assume/README.md +124 -0
- workers/ask_or_assume/__init__.py +1 -0
- workers/ask_or_assume/compatibility_manifest.json +36 -0
- workers/ask_or_assume/driver.py +1227 -0
- workers/decomp/README.md +133 -0
- workers/decomp/UPSTREAM_LICENSE.Apache-2.0.txt +201 -0
- workers/decomp/__init__.py +5 -0
- workers/decomp/compatibility_manifest.json +205 -0
- workers/decomp/driver.py +1015 -0
- workers/decomp/live.py +137 -0
- workers/decomp/runtime.py +231 -0
- workers/infocqr/README.md +106 -0
- workers/infocqr/__init__.py +1 -0
- workers/infocqr/compatibility_manifest.json +84 -0
- workers/infocqr/driver.py +1766 -0
- workers/ircot/README.md +167 -0
- workers/ircot/__init__.py +7 -0
- workers/ircot/compatibility_manifest.json +121 -0
- workers/ircot/controlled.py +457 -0
- workers/ircot/driver.py +1373 -0
- workers/ircot/fixture_bindings.json +39 -0
- workers/ircot/fixture_config.json +48 -0
- workers/ircot/fixture_prompt.txt +2 -0
- workers/ircot/native_qa.py +398 -0
- workers/ircot/qa_compatibility_manifest.json +57 -0
- workers/ircot/runtime.py +97 -0
- workers/llmcompiler/README.md +160 -0
- workers/llmcompiler/__init__.py +1 -0
- workers/llmcompiler/compatibility_manifest.json +45 -0
- workers/llmcompiler/driver.py +1514 -0
- workers/llmcompiler/export_prompts.py +74 -0
- workers/llmcompiler/live.py +174 -0
- workers/llmcompiler/patch_manifest.json +147 -0
- workers/mce/README.md +198 -0
- workers/mce/__init__.py +29 -0
- workers/mce/compatibility_manifest.json +115 -0
- workers/mce/driver.py +2585 -0
- workers/mce/final_eval.py +173 -0
- workers/official_arenas/README.md +25 -0
- workers/official_arenas/__init__.py +1 -0
- workers/official_arenas/capture_records.py +110 -0
- workers/official_arenas/driver.py +619 -0
- workers/official_arenas/protocol.py +334 -0
- workers/official_arenas/providers/__init__.py +1 -0
- workers/official_arenas/providers/_live_history.py +228 -0
- workers/official_arenas/providers/_message_compat.py +77 -0
- workers/official_arenas/providers/appworld_ul.py +196 -0
- workers/official_arenas/providers/fixture.py +112 -0
- workers/official_arenas/providers/statebench.py +960 -0
- workers/official_arenas/providers/tau3.py +1672 -0
- workers/official_arenas/providers/tau_retail.py +586 -0
- workers/official_arenas/providers/toolsandbox.py +1321 -0
- workers/official_arenas/providers/tooltalk.py +699 -0
- workers/official_arenas/retail_capture.py +188 -0
- workers/official_arenas/retail_driver.py +275 -0
- workers/qrag/README.md +172 -0
- workers/qrag/__init__.py +1 -0
- workers/qrag/compatibility_manifest.json +64 -0
- workers/qrag/driver.py +673 -0
- workers/r2ag/README.md +154 -0
- workers/r2ag/__init__.py +5 -0
- workers/r2ag/compatibility_manifest.json +55 -0
- workers/r2ag/driver.py +1287 -0
- workers/r2ag/feature_schema.json +8 -0
- workers/r2ag/runtime.py +589 -0
- workers/recomp/README.md +120 -0
- workers/recomp/__init__.py +1 -0
- workers/recomp/compatibility_manifest.json +43 -0
- workers/recomp/driver.py +1653 -0
- workers/reformer/README.md +125 -0
- workers/reformer/__init__.py +1 -0
- workers/reformer/compatibility_manifest.json +68 -0
- workers/reformer/driver.py +1459 -0
- workers/reformer/induction.py +457 -0
- workers/rrr/README.md +126 -0
- workers/rrr/__init__.py +1 -0
- workers/rrr/compatibility_manifest.json +57 -0
- workers/rrr/driver.py +1133 -0
- workers/smartsearch/README.md +331 -0
- workers/smartsearch/__init__.py +1 -0
- workers/smartsearch/compatibility_manifest.json +41 -0
- 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
|
+
]
|