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,687 @@
|
|
|
1
|
+
"""Content-bound, resumable experiment artifact storage.
|
|
2
|
+
|
|
3
|
+
The store writes only after an operator has explicitly selected execution. A
|
|
4
|
+
resume request is accepted only when the frozen manifest, plan, and driver
|
|
5
|
+
fingerprint match the existing run exactly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import tempfile
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from threading import RLock
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from agentinputlab.contracts.errors import ArtifactError, ValidationError
|
|
22
|
+
from agentinputlab.contracts.serialization import (
|
|
23
|
+
canonical_json,
|
|
24
|
+
freeze_mapping,
|
|
25
|
+
to_jsonable,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from .plan import ExperimentCell, ExperimentPlan, validate_experiment_plan
|
|
29
|
+
from .schema import ExperimentManifest, require_sha256
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class ExperimentArtifactIdentity:
|
|
34
|
+
study_id: str
|
|
35
|
+
manifest_hash: str
|
|
36
|
+
plan_hash: str
|
|
37
|
+
driver_hash: str
|
|
38
|
+
|
|
39
|
+
def __post_init__(self) -> None:
|
|
40
|
+
if not isinstance(self.study_id, str) or not self.study_id.strip():
|
|
41
|
+
raise ValidationError("experiment artifact study_id must be non-empty")
|
|
42
|
+
for label in ("manifest_hash", "plan_hash", "driver_hash"):
|
|
43
|
+
require_sha256(getattr(self, label), label=label)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def identity_hash(self) -> str:
|
|
47
|
+
from agentinputlab.contracts.serialization import content_hash
|
|
48
|
+
|
|
49
|
+
return content_hash(self)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ExperimentArtifactStore:
|
|
53
|
+
"""Append-only cell results under one immutable run identity."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
root: str | Path,
|
|
58
|
+
*,
|
|
59
|
+
manifest: ExperimentManifest,
|
|
60
|
+
plan: ExperimentPlan,
|
|
61
|
+
driver_fingerprint: Mapping[str, Any],
|
|
62
|
+
) -> None:
|
|
63
|
+
from agentinputlab.contracts.serialization import content_hash
|
|
64
|
+
|
|
65
|
+
if plan.manifest_hash != manifest.manifest_hash:
|
|
66
|
+
raise ValidationError("experiment plan does not belong to manifest")
|
|
67
|
+
validate_experiment_plan(manifest, plan)
|
|
68
|
+
try:
|
|
69
|
+
fingerprint = freeze_mapping(driver_fingerprint)
|
|
70
|
+
driver_hash = content_hash(fingerprint)
|
|
71
|
+
except (TypeError, ValueError) as exc:
|
|
72
|
+
raise ValidationError("driver fingerprint must be canonical JSON") from exc
|
|
73
|
+
self.manifest = manifest
|
|
74
|
+
self.plan = plan
|
|
75
|
+
self.driver_fingerprint = fingerprint
|
|
76
|
+
self.identity = ExperimentArtifactIdentity(
|
|
77
|
+
study_id=manifest.study_id,
|
|
78
|
+
manifest_hash=manifest.manifest_hash,
|
|
79
|
+
plan_hash=plan.plan_hash,
|
|
80
|
+
driver_hash=driver_hash,
|
|
81
|
+
)
|
|
82
|
+
configured_root = Path(root).expanduser()
|
|
83
|
+
if not configured_root.is_absolute():
|
|
84
|
+
configured_root = Path(os.path.abspath(configured_root))
|
|
85
|
+
if configured_root.is_symlink():
|
|
86
|
+
raise ArtifactError("experiment output root must not be a symbolic link")
|
|
87
|
+
self.root = configured_root.resolve()
|
|
88
|
+
self.run_dir = (
|
|
89
|
+
self.root / manifest.study_id / manifest.manifest_hash / plan.plan_hash / driver_hash
|
|
90
|
+
)
|
|
91
|
+
self.results_dir = self.run_dir / "results"
|
|
92
|
+
self._lock = RLock()
|
|
93
|
+
|
|
94
|
+
def initialize(self, *, resume: bool) -> Path:
|
|
95
|
+
"""Create a new run or verify the exact identity of an existing one."""
|
|
96
|
+
|
|
97
|
+
self._assert_run_path_confined()
|
|
98
|
+
identity_path = self.run_dir / "run_identity.json"
|
|
99
|
+
if self.run_dir.exists():
|
|
100
|
+
if not resume:
|
|
101
|
+
raise ArtifactError(
|
|
102
|
+
"experiment run already exists; use --resume only for the same frozen run"
|
|
103
|
+
)
|
|
104
|
+
self._verify_control_file(
|
|
105
|
+
identity_path,
|
|
106
|
+
self._identity_payload(),
|
|
107
|
+
label="run identity",
|
|
108
|
+
)
|
|
109
|
+
self._verify_control_file(
|
|
110
|
+
self.run_dir / "manifest.json",
|
|
111
|
+
self.manifest.to_dict(),
|
|
112
|
+
label="experiment manifest",
|
|
113
|
+
)
|
|
114
|
+
self._verify_control_file(
|
|
115
|
+
self.run_dir / "plan.json",
|
|
116
|
+
self.plan.to_dict(),
|
|
117
|
+
label="experiment plan",
|
|
118
|
+
)
|
|
119
|
+
self.verify()
|
|
120
|
+
return self.run_dir
|
|
121
|
+
|
|
122
|
+
if resume:
|
|
123
|
+
raise ArtifactError("resume run identity is missing or invalid")
|
|
124
|
+
self.results_dir.mkdir(parents=True, exist_ok=False)
|
|
125
|
+
self._assert_run_path_confined()
|
|
126
|
+
self._write_new(identity_path, self._identity_payload())
|
|
127
|
+
self._write_new(self.run_dir / "manifest.json", self.manifest.to_dict())
|
|
128
|
+
self._write_new(self.run_dir / "plan.json", self.plan.to_dict())
|
|
129
|
+
self._write_new(self.run_dir / "result_index.json", {"schema_version": 1, "results": {}})
|
|
130
|
+
return self.run_dir
|
|
131
|
+
|
|
132
|
+
def completed_cell_ids(self) -> frozenset[str]:
|
|
133
|
+
self._assert_run_path_confined()
|
|
134
|
+
index = self._load_index()
|
|
135
|
+
completed: set[str] = set()
|
|
136
|
+
for cell_id, expected_hash in index.items():
|
|
137
|
+
path = self.results_dir / f"{cell_id}.json"
|
|
138
|
+
if path.is_symlink():
|
|
139
|
+
raise ArtifactError(f"result path must not be a symbolic link: {cell_id}")
|
|
140
|
+
if not path.is_file():
|
|
141
|
+
raise ArtifactError(f"result index references a missing cell: {cell_id}")
|
|
142
|
+
actual_hash = _sha256_bytes(path.read_bytes())
|
|
143
|
+
if actual_hash != expected_hash:
|
|
144
|
+
raise ArtifactError(f"result checksum mismatch for cell {cell_id}")
|
|
145
|
+
completed.add(cell_id)
|
|
146
|
+
return frozenset(completed)
|
|
147
|
+
|
|
148
|
+
def result_status_counts(self) -> Mapping[str, int]:
|
|
149
|
+
"""Return terminal status counts across all append-only cell records."""
|
|
150
|
+
|
|
151
|
+
counts = {"succeeded": 0, "failed": 0}
|
|
152
|
+
for cell_id in self.completed_cell_ids():
|
|
153
|
+
record = _read_json_object(
|
|
154
|
+
self.results_dir / f"{cell_id}.json",
|
|
155
|
+
label=f"result {cell_id}",
|
|
156
|
+
)
|
|
157
|
+
result = record.get("result")
|
|
158
|
+
status = result.get("status") if isinstance(result, Mapping) else None
|
|
159
|
+
if not isinstance(status, str) or status not in counts:
|
|
160
|
+
raise ArtifactError(f"result {cell_id} has an invalid terminal status")
|
|
161
|
+
counts[status] += 1
|
|
162
|
+
return freeze_mapping(counts)
|
|
163
|
+
|
|
164
|
+
def write_cell_result(self, cell_id: str, payload: Mapping[str, Any]) -> Path:
|
|
165
|
+
if cell_id not in {cell.cell_id for cell in self.plan.cells}:
|
|
166
|
+
raise ArtifactError("refusing result for a cell outside the frozen plan")
|
|
167
|
+
try:
|
|
168
|
+
serialized = (canonical_json(payload) + "\n").encode("utf-8")
|
|
169
|
+
except (TypeError, ValueError) as exc:
|
|
170
|
+
raise ArtifactError("cell result must be canonical JSON") from exc
|
|
171
|
+
destination = self.results_dir / f"{cell_id}.json"
|
|
172
|
+
with self._lock:
|
|
173
|
+
self._assert_run_path_confined()
|
|
174
|
+
index = self._load_index()
|
|
175
|
+
digest = _sha256_bytes(serialized)
|
|
176
|
+
if cell_id in index:
|
|
177
|
+
if index[cell_id] != digest or destination.read_bytes() != serialized:
|
|
178
|
+
raise ArtifactError(
|
|
179
|
+
"append-only result store refuses different content for a completed cell"
|
|
180
|
+
)
|
|
181
|
+
return destination
|
|
182
|
+
_atomic_write(destination, serialized)
|
|
183
|
+
index[cell_id] = digest
|
|
184
|
+
_atomic_write(
|
|
185
|
+
self.run_dir / "result_index.json",
|
|
186
|
+
(canonical_json({"schema_version": 1, "results": index}) + "\n").encode("utf-8"),
|
|
187
|
+
)
|
|
188
|
+
return destination
|
|
189
|
+
|
|
190
|
+
def append_event(self, payload: Mapping[str, Any]) -> None:
|
|
191
|
+
try:
|
|
192
|
+
line = (canonical_json(payload) + "\n").encode("utf-8")
|
|
193
|
+
except (TypeError, ValueError) as exc:
|
|
194
|
+
raise ArtifactError("experiment event must be canonical JSON") from exc
|
|
195
|
+
path = self.run_dir / "events.jsonl"
|
|
196
|
+
with self._lock:
|
|
197
|
+
self._assert_run_path_confined()
|
|
198
|
+
_append_no_follow(path, line)
|
|
199
|
+
|
|
200
|
+
def verify(self) -> Mapping[str, Any]:
|
|
201
|
+
verified = verify_experiment_run_directory(self.run_dir)
|
|
202
|
+
if (
|
|
203
|
+
verified["identity_hash"] != self.identity.identity_hash
|
|
204
|
+
or verified["manifest_hash"] != self.manifest.manifest_hash
|
|
205
|
+
or verified["plan_hash"] != self.plan.plan_hash
|
|
206
|
+
or verified["driver_hash"] != self.identity.driver_hash
|
|
207
|
+
):
|
|
208
|
+
raise ArtifactError("verified run differs from the active artifact store")
|
|
209
|
+
return verified
|
|
210
|
+
|
|
211
|
+
def _identity_payload(self) -> Mapping[str, Any]:
|
|
212
|
+
return {
|
|
213
|
+
"schema_version": 1,
|
|
214
|
+
"identity": to_jsonable(self.identity),
|
|
215
|
+
"identity_hash": self.identity.identity_hash,
|
|
216
|
+
"driver_fingerprint": self.driver_fingerprint,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def _assert_run_path_confined(self) -> None:
|
|
220
|
+
"""Reject existing symlinks or resolved paths outside the output root."""
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
relative = self.run_dir.relative_to(self.root)
|
|
224
|
+
except ValueError as exc:
|
|
225
|
+
raise ArtifactError("experiment run path escapes the output root") from exc
|
|
226
|
+
current = self.root
|
|
227
|
+
if current.is_symlink():
|
|
228
|
+
raise ArtifactError("experiment output root must not be a symbolic link")
|
|
229
|
+
for part in relative.parts:
|
|
230
|
+
current /= part
|
|
231
|
+
if current.is_symlink():
|
|
232
|
+
raise ArtifactError(
|
|
233
|
+
f"experiment run path must not contain a symbolic link: {current}"
|
|
234
|
+
)
|
|
235
|
+
for label, path in (
|
|
236
|
+
("run", self.run_dir),
|
|
237
|
+
("results", self.results_dir),
|
|
238
|
+
):
|
|
239
|
+
try:
|
|
240
|
+
path.resolve().relative_to(self.root)
|
|
241
|
+
except (OSError, ValueError) as exc:
|
|
242
|
+
raise ArtifactError(f"experiment {label} path escapes the output root") from exc
|
|
243
|
+
|
|
244
|
+
def _load_index(self) -> dict[str, str]:
|
|
245
|
+
path = self.run_dir / "result_index.json"
|
|
246
|
+
try:
|
|
247
|
+
raw = _read_json_object(path, label="experiment result index")
|
|
248
|
+
except ArtifactError as exc:
|
|
249
|
+
raise ArtifactError("experiment result index is invalid") from exc
|
|
250
|
+
if (
|
|
251
|
+
not isinstance(raw, dict)
|
|
252
|
+
or set(raw) != {"schema_version", "results"}
|
|
253
|
+
or raw["schema_version"] != 1
|
|
254
|
+
or not isinstance(raw["results"], dict)
|
|
255
|
+
):
|
|
256
|
+
raise ArtifactError("experiment result index schema is invalid")
|
|
257
|
+
result: dict[str, str] = {}
|
|
258
|
+
for cell_id, digest in raw["results"].items():
|
|
259
|
+
if not isinstance(cell_id, str) or not cell_id or not isinstance(digest, str):
|
|
260
|
+
raise ArtifactError("experiment result index entry is invalid")
|
|
261
|
+
require_sha256(digest, label="result checksum")
|
|
262
|
+
result[cell_id] = digest
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
def _write_new(self, path: Path, payload: Mapping[str, Any]) -> None:
|
|
266
|
+
if path.exists() or path.is_symlink():
|
|
267
|
+
raise ArtifactError(f"refusing to overwrite experiment control file: {path.name}")
|
|
268
|
+
_atomic_write(path, (canonical_json(payload) + "\n").encode("utf-8"))
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _verify_control_file(
|
|
272
|
+
path: Path,
|
|
273
|
+
expected: Mapping[str, Any],
|
|
274
|
+
*,
|
|
275
|
+
label: str,
|
|
276
|
+
) -> None:
|
|
277
|
+
try:
|
|
278
|
+
actual = _read_json_object(path, label=label)
|
|
279
|
+
except ArtifactError as exc:
|
|
280
|
+
raise ArtifactError(f"{label} is missing or invalid") from exc
|
|
281
|
+
if canonical_json(actual) != canonical_json(expected):
|
|
282
|
+
raise ArtifactError(f"{label} differs from the frozen run")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _atomic_write(path: Path, content: bytes) -> None:
|
|
286
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
287
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".partial-", dir=path.parent)
|
|
288
|
+
try:
|
|
289
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
290
|
+
handle.write(content)
|
|
291
|
+
handle.flush()
|
|
292
|
+
os.fsync(handle.fileno())
|
|
293
|
+
os.replace(temporary, path)
|
|
294
|
+
finally:
|
|
295
|
+
if os.path.exists(temporary):
|
|
296
|
+
os.unlink(temporary)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _append_no_follow(path: Path, content: bytes) -> None:
|
|
300
|
+
if path.is_symlink():
|
|
301
|
+
raise ArtifactError("experiment event path must not be a symbolic link")
|
|
302
|
+
if path.exists() and not path.is_file():
|
|
303
|
+
raise ArtifactError("experiment event path must be a regular file")
|
|
304
|
+
flags = os.O_APPEND | os.O_CREAT | os.O_WRONLY
|
|
305
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
306
|
+
flags |= os.O_NOFOLLOW
|
|
307
|
+
try:
|
|
308
|
+
descriptor = os.open(path, flags, 0o600)
|
|
309
|
+
except OSError as exc:
|
|
310
|
+
raise ArtifactError("experiment event path cannot be opened safely") from exc
|
|
311
|
+
try:
|
|
312
|
+
handle = os.fdopen(descriptor, "ab")
|
|
313
|
+
except Exception:
|
|
314
|
+
os.close(descriptor)
|
|
315
|
+
raise
|
|
316
|
+
with handle:
|
|
317
|
+
handle.write(content)
|
|
318
|
+
handle.flush()
|
|
319
|
+
os.fsync(handle.fileno())
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _sha256_bytes(content: bytes) -> str:
|
|
323
|
+
return hashlib.sha256(content).hexdigest()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def verify_experiment_run_directory(path: str | Path) -> Mapping[str, Any]:
|
|
327
|
+
"""Verify run-artifact integrity without claiming execution authenticity.
|
|
328
|
+
|
|
329
|
+
This validates paths, schemas, hashes, deterministic plan closure, and
|
|
330
|
+
result-record identities without importing the original driver. A valid
|
|
331
|
+
directory is checksum-consistent evidence, not proof that an external
|
|
332
|
+
worker, model, service, or official scorer actually executed as claimed.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
from agentinputlab.contracts.serialization import content_hash
|
|
336
|
+
|
|
337
|
+
supplied_root = Path(path).expanduser()
|
|
338
|
+
if not supplied_root.is_absolute():
|
|
339
|
+
supplied_root = Path(os.path.abspath(supplied_root))
|
|
340
|
+
if supplied_root.is_symlink():
|
|
341
|
+
raise ArtifactError("experiment run directory must not be a symbolic link")
|
|
342
|
+
root = supplied_root.resolve()
|
|
343
|
+
if not root.is_dir():
|
|
344
|
+
raise ArtifactError("experiment run directory is missing or invalid")
|
|
345
|
+
identity_path = _require_internal_file(
|
|
346
|
+
root,
|
|
347
|
+
Path("run_identity.json"),
|
|
348
|
+
label="run identity",
|
|
349
|
+
)
|
|
350
|
+
manifest_path = _require_internal_file(
|
|
351
|
+
root,
|
|
352
|
+
Path("manifest.json"),
|
|
353
|
+
label="experiment manifest",
|
|
354
|
+
)
|
|
355
|
+
plan_path = _require_internal_file(
|
|
356
|
+
root,
|
|
357
|
+
Path("plan.json"),
|
|
358
|
+
label="experiment plan",
|
|
359
|
+
)
|
|
360
|
+
results_root = _require_internal_directory(
|
|
361
|
+
root,
|
|
362
|
+
Path("results"),
|
|
363
|
+
label="experiment results directory",
|
|
364
|
+
)
|
|
365
|
+
index_path = _require_internal_file(
|
|
366
|
+
root,
|
|
367
|
+
Path("result_index.json"),
|
|
368
|
+
label="result index",
|
|
369
|
+
)
|
|
370
|
+
identity_raw = _read_json_object(identity_path, label="run identity")
|
|
371
|
+
manifest_raw = _read_json_object(manifest_path, label="experiment manifest")
|
|
372
|
+
plan_raw = _read_json_object(plan_path, label="experiment plan")
|
|
373
|
+
manifest = ExperimentManifest.from_mapping(manifest_raw)
|
|
374
|
+
if set(plan_raw) != {
|
|
375
|
+
"study_id",
|
|
376
|
+
"manifest_hash",
|
|
377
|
+
"cells",
|
|
378
|
+
"unit_inventories",
|
|
379
|
+
"unit_inventory_hashes",
|
|
380
|
+
"parent_plan_hash",
|
|
381
|
+
"parent_cell_ids",
|
|
382
|
+
"shard_index",
|
|
383
|
+
"shard_count",
|
|
384
|
+
"plan_hash",
|
|
385
|
+
"cell_count",
|
|
386
|
+
}:
|
|
387
|
+
raise ArtifactError("experiment plan control schema is invalid")
|
|
388
|
+
raw_cells = plan_raw["cells"]
|
|
389
|
+
if not isinstance(raw_cells, list):
|
|
390
|
+
raise ArtifactError("experiment plan cells must be an array")
|
|
391
|
+
cells: list[ExperimentCell] = []
|
|
392
|
+
for raw in raw_cells:
|
|
393
|
+
if not isinstance(raw, Mapping) or set(raw) != {
|
|
394
|
+
"study_id",
|
|
395
|
+
"benchmark_id",
|
|
396
|
+
"split",
|
|
397
|
+
"unit_id",
|
|
398
|
+
"model_slot",
|
|
399
|
+
"seed",
|
|
400
|
+
"arm_ids",
|
|
401
|
+
"manifest_hash",
|
|
402
|
+
"cell_id",
|
|
403
|
+
}:
|
|
404
|
+
raise ArtifactError("experiment plan cell schema is invalid")
|
|
405
|
+
cell = ExperimentCell(
|
|
406
|
+
study_id=raw["study_id"],
|
|
407
|
+
benchmark_id=raw["benchmark_id"],
|
|
408
|
+
split=raw["split"],
|
|
409
|
+
unit_id=raw["unit_id"],
|
|
410
|
+
model_slot=raw["model_slot"],
|
|
411
|
+
seed=raw["seed"],
|
|
412
|
+
arm_ids=tuple(raw["arm_ids"]),
|
|
413
|
+
manifest_hash=raw["manifest_hash"],
|
|
414
|
+
)
|
|
415
|
+
if raw["cell_id"] != cell.cell_id:
|
|
416
|
+
raise ArtifactError("experiment plan cell hash mismatch")
|
|
417
|
+
cells.append(cell)
|
|
418
|
+
raw_inventories = plan_raw["unit_inventories"]
|
|
419
|
+
inventory_hashes = plan_raw["unit_inventory_hashes"]
|
|
420
|
+
if not isinstance(raw_inventories, Mapping):
|
|
421
|
+
raise ArtifactError("experiment plan unit inventories must be an object")
|
|
422
|
+
if not isinstance(inventory_hashes, Mapping):
|
|
423
|
+
raise ArtifactError("experiment plan inventory hashes must be an object")
|
|
424
|
+
inventories: dict[str, tuple[str, ...]] = {}
|
|
425
|
+
for key, raw_units in raw_inventories.items():
|
|
426
|
+
if not isinstance(key, str) or not key.strip() or not isinstance(raw_units, list):
|
|
427
|
+
raise ArtifactError("experiment plan unit inventory entry is invalid")
|
|
428
|
+
inventories[key] = tuple(raw_units)
|
|
429
|
+
parent_cell_ids = plan_raw["parent_cell_ids"]
|
|
430
|
+
if not isinstance(parent_cell_ids, list):
|
|
431
|
+
raise ArtifactError("experiment plan parent_cell_ids must be an array")
|
|
432
|
+
plan = ExperimentPlan(
|
|
433
|
+
study_id=plan_raw["study_id"],
|
|
434
|
+
manifest_hash=plan_raw["manifest_hash"],
|
|
435
|
+
cells=tuple(cells),
|
|
436
|
+
unit_inventories=inventories,
|
|
437
|
+
unit_inventory_hashes=inventory_hashes,
|
|
438
|
+
parent_plan_hash=plan_raw["parent_plan_hash"],
|
|
439
|
+
parent_cell_ids=tuple(parent_cell_ids),
|
|
440
|
+
shard_index=plan_raw["shard_index"],
|
|
441
|
+
shard_count=plan_raw["shard_count"],
|
|
442
|
+
)
|
|
443
|
+
if (
|
|
444
|
+
plan.study_id != manifest.study_id
|
|
445
|
+
or plan.manifest_hash != manifest.manifest_hash
|
|
446
|
+
or plan_raw["plan_hash"] != plan.plan_hash
|
|
447
|
+
or plan_raw["cell_count"] != len(plan.cells)
|
|
448
|
+
):
|
|
449
|
+
raise ArtifactError("experiment plan identity differs from its manifest")
|
|
450
|
+
for target in manifest.benchmarks:
|
|
451
|
+
key = f"{target.cell_id}:{target.split}"
|
|
452
|
+
if target.unit_ids and tuple(sorted(target.unit_ids)) != tuple(
|
|
453
|
+
plan.unit_inventories.get(key, ())
|
|
454
|
+
):
|
|
455
|
+
raise ArtifactError(
|
|
456
|
+
f"experiment plan unit inventory differs from inline manifest units for {key}"
|
|
457
|
+
)
|
|
458
|
+
try:
|
|
459
|
+
validate_experiment_plan(manifest, plan)
|
|
460
|
+
except (TypeError, ValidationError) as exc:
|
|
461
|
+
raise ArtifactError(f"experiment plan verification failed: {exc}") from exc
|
|
462
|
+
|
|
463
|
+
if (
|
|
464
|
+
set(identity_raw)
|
|
465
|
+
!= {
|
|
466
|
+
"schema_version",
|
|
467
|
+
"identity",
|
|
468
|
+
"identity_hash",
|
|
469
|
+
"driver_fingerprint",
|
|
470
|
+
}
|
|
471
|
+
or identity_raw["schema_version"] != 1
|
|
472
|
+
):
|
|
473
|
+
raise ArtifactError("run identity schema is invalid")
|
|
474
|
+
raw_identity = identity_raw["identity"]
|
|
475
|
+
fingerprint = identity_raw["driver_fingerprint"]
|
|
476
|
+
if not isinstance(raw_identity, Mapping) or not isinstance(fingerprint, Mapping):
|
|
477
|
+
raise ArtifactError("run identity payload is invalid")
|
|
478
|
+
if set(raw_identity) != {
|
|
479
|
+
"study_id",
|
|
480
|
+
"manifest_hash",
|
|
481
|
+
"plan_hash",
|
|
482
|
+
"driver_hash",
|
|
483
|
+
}:
|
|
484
|
+
raise ArtifactError("run identity fields are invalid")
|
|
485
|
+
identity = ExperimentArtifactIdentity(
|
|
486
|
+
study_id=raw_identity.get("study_id"),
|
|
487
|
+
manifest_hash=raw_identity.get("manifest_hash"),
|
|
488
|
+
plan_hash=raw_identity.get("plan_hash"),
|
|
489
|
+
driver_hash=raw_identity.get("driver_hash"),
|
|
490
|
+
)
|
|
491
|
+
if (
|
|
492
|
+
identity.manifest_hash != manifest.manifest_hash
|
|
493
|
+
or identity.study_id != manifest.study_id
|
|
494
|
+
or identity.plan_hash != plan.plan_hash
|
|
495
|
+
or identity.driver_hash != content_hash(fingerprint)
|
|
496
|
+
or identity_raw["identity_hash"] != identity.identity_hash
|
|
497
|
+
):
|
|
498
|
+
raise ArtifactError("run identity hash mismatch")
|
|
499
|
+
|
|
500
|
+
index_raw = _read_json_object(index_path, label="result index")
|
|
501
|
+
if (
|
|
502
|
+
set(index_raw) != {"schema_version", "results"}
|
|
503
|
+
or index_raw["schema_version"] != 1
|
|
504
|
+
or not isinstance(index_raw["results"], Mapping)
|
|
505
|
+
):
|
|
506
|
+
raise ArtifactError("result index schema is invalid")
|
|
507
|
+
planned_by_id = {cell.cell_id: cell for cell in plan.cells}
|
|
508
|
+
planned = set(planned_by_id)
|
|
509
|
+
completed: set[str] = set()
|
|
510
|
+
status_counts = {"succeeded": 0, "failed": 0}
|
|
511
|
+
for cell_id, expected in index_raw["results"].items():
|
|
512
|
+
if cell_id not in planned or not isinstance(expected, str):
|
|
513
|
+
raise ArtifactError("result index references an unplanned cell")
|
|
514
|
+
require_sha256(expected, label="result checksum")
|
|
515
|
+
result_path = _require_internal_file(
|
|
516
|
+
root,
|
|
517
|
+
Path("results") / f"{cell_id}.json",
|
|
518
|
+
label=f"result {cell_id}",
|
|
519
|
+
)
|
|
520
|
+
try:
|
|
521
|
+
result_path.relative_to(results_root)
|
|
522
|
+
except ValueError as exc:
|
|
523
|
+
raise ArtifactError(f"result {cell_id} escapes the results directory") from exc
|
|
524
|
+
try:
|
|
525
|
+
content = result_path.read_bytes()
|
|
526
|
+
except OSError as exc:
|
|
527
|
+
raise ArtifactError(f"result index references missing cell {cell_id}") from exc
|
|
528
|
+
if _sha256_bytes(content) != expected:
|
|
529
|
+
raise ArtifactError(f"result checksum mismatch for cell {cell_id}")
|
|
530
|
+
result_record = _decode_json_object(content, label=f"result {cell_id}")
|
|
531
|
+
if set(result_record) != {
|
|
532
|
+
"schema_version",
|
|
533
|
+
"study_id",
|
|
534
|
+
"manifest_hash",
|
|
535
|
+
"plan_hash",
|
|
536
|
+
"cell",
|
|
537
|
+
"result",
|
|
538
|
+
"finished_at_utc",
|
|
539
|
+
"claims",
|
|
540
|
+
}:
|
|
541
|
+
raise ArtifactError(f"result {cell_id} record schema is invalid")
|
|
542
|
+
if (
|
|
543
|
+
result_record.get("schema_version") != 1
|
|
544
|
+
or result_record.get("study_id") != manifest.study_id
|
|
545
|
+
or result_record.get("manifest_hash") != manifest.manifest_hash
|
|
546
|
+
or result_record.get("plan_hash") != plan.plan_hash
|
|
547
|
+
):
|
|
548
|
+
raise ArtifactError(f"result {cell_id} control identity mismatch")
|
|
549
|
+
raw_cell = result_record.get("cell")
|
|
550
|
+
expected_cell = {
|
|
551
|
+
**to_jsonable(planned_by_id[cell_id]),
|
|
552
|
+
"cell_id": cell_id,
|
|
553
|
+
}
|
|
554
|
+
if not isinstance(raw_cell, Mapping) or canonical_json(raw_cell) != canonical_json(
|
|
555
|
+
expected_cell
|
|
556
|
+
):
|
|
557
|
+
raise ArtifactError(f"result {cell_id} frozen cell identity mismatch")
|
|
558
|
+
_require_utc_timestamp(
|
|
559
|
+
result_record.get("finished_at_utc"),
|
|
560
|
+
label=f"result {cell_id} finished_at_utc",
|
|
561
|
+
)
|
|
562
|
+
try:
|
|
563
|
+
from .executor import parse_experiment_cell_result
|
|
564
|
+
|
|
565
|
+
result = parse_experiment_cell_result(
|
|
566
|
+
planned_by_id[cell_id],
|
|
567
|
+
result_record.get("result"),
|
|
568
|
+
)
|
|
569
|
+
except (TypeError, ValidationError) as exc:
|
|
570
|
+
raise ArtifactError(f"result {cell_id} payload is invalid: {exc}") from exc
|
|
571
|
+
status = result.status
|
|
572
|
+
claims = result_record.get("claims")
|
|
573
|
+
expected_claims = {
|
|
574
|
+
"official_parity_verified": False,
|
|
575
|
+
"confirmatory_candidate_record": (
|
|
576
|
+
manifest.stage.value == "confirmatory" and status == "succeeded"
|
|
577
|
+
),
|
|
578
|
+
"paper_claim_authorized": False,
|
|
579
|
+
"resource_receipt_count": len(result.resource_receipts),
|
|
580
|
+
}
|
|
581
|
+
if not isinstance(claims, Mapping) or canonical_json(claims) != canonical_json(
|
|
582
|
+
expected_claims
|
|
583
|
+
):
|
|
584
|
+
raise ArtifactError(f"result {cell_id} claim metadata is invalid")
|
|
585
|
+
status_counts[status] += 1
|
|
586
|
+
completed.add(cell_id)
|
|
587
|
+
return freeze_mapping(
|
|
588
|
+
{
|
|
589
|
+
"study_id": manifest.study_id,
|
|
590
|
+
"identity_hash": identity.identity_hash,
|
|
591
|
+
"manifest_hash": manifest.manifest_hash,
|
|
592
|
+
"plan_hash": plan.plan_hash,
|
|
593
|
+
"driver_hash": identity.driver_hash,
|
|
594
|
+
"profile_kind": manifest.estimand_profile_kind.value,
|
|
595
|
+
"comparison_profile_hash": content_hash(
|
|
596
|
+
{
|
|
597
|
+
"schema_version": 1,
|
|
598
|
+
"manifest_hash": manifest.manifest_hash,
|
|
599
|
+
"driver_hash": identity.driver_hash,
|
|
600
|
+
"profile_kind": manifest.estimand_profile_kind.value,
|
|
601
|
+
}
|
|
602
|
+
),
|
|
603
|
+
"planned_cells": len(planned),
|
|
604
|
+
"completed_cells": len(completed),
|
|
605
|
+
"complete": completed == planned,
|
|
606
|
+
"succeeded_cells": status_counts["succeeded"],
|
|
607
|
+
"failed_cells": status_counts["failed"],
|
|
608
|
+
"all_succeeded": completed == planned and status_counts["failed"] == 0,
|
|
609
|
+
"is_shard": plan.parent_plan_hash is not None,
|
|
610
|
+
"parent_plan_hash": plan.parent_plan_hash,
|
|
611
|
+
"shard_index": plan.shard_index,
|
|
612
|
+
"shard_count": plan.shard_count,
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _require_internal_path(
|
|
618
|
+
root: Path,
|
|
619
|
+
relative: Path,
|
|
620
|
+
*,
|
|
621
|
+
label: str,
|
|
622
|
+
) -> Path:
|
|
623
|
+
if relative.is_absolute() or ".." in relative.parts:
|
|
624
|
+
raise ArtifactError(f"{label} path must remain under the experiment run directory")
|
|
625
|
+
current = root
|
|
626
|
+
for part in relative.parts:
|
|
627
|
+
current /= part
|
|
628
|
+
if current.is_symlink():
|
|
629
|
+
raise ArtifactError(f"{label} path must not contain a symbolic link")
|
|
630
|
+
resolved = current.resolve()
|
|
631
|
+
try:
|
|
632
|
+
resolved.relative_to(root)
|
|
633
|
+
except ValueError as exc:
|
|
634
|
+
raise ArtifactError(f"{label} path escapes the experiment run directory") from exc
|
|
635
|
+
return resolved
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _require_internal_file(root: Path, relative: Path, *, label: str) -> Path:
|
|
639
|
+
path = _require_internal_path(root, relative, label=label)
|
|
640
|
+
if not path.is_file():
|
|
641
|
+
raise ArtifactError(f"{label} is missing or invalid")
|
|
642
|
+
return path
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _require_internal_directory(root: Path, relative: Path, *, label: str) -> Path:
|
|
646
|
+
path = _require_internal_path(root, relative, label=label)
|
|
647
|
+
if not path.is_dir():
|
|
648
|
+
raise ArtifactError(f"{label} is missing or invalid")
|
|
649
|
+
return path
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _decode_json_object(content: bytes, *, label: str) -> Mapping[str, Any]:
|
|
653
|
+
try:
|
|
654
|
+
value = json.loads(content)
|
|
655
|
+
except (UnicodeError, json.JSONDecodeError) as exc:
|
|
656
|
+
raise ArtifactError(f"{label} is missing or invalid") from exc
|
|
657
|
+
if not isinstance(value, Mapping):
|
|
658
|
+
raise ArtifactError(f"{label} must be a JSON object")
|
|
659
|
+
return value
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _read_json_object(path: Path, *, label: str) -> Mapping[str, Any]:
|
|
663
|
+
if path.is_symlink():
|
|
664
|
+
raise ArtifactError(f"{label} path must not be a symbolic link")
|
|
665
|
+
try:
|
|
666
|
+
content = path.read_bytes()
|
|
667
|
+
except OSError as exc:
|
|
668
|
+
raise ArtifactError(f"{label} is missing or invalid") from exc
|
|
669
|
+
return _decode_json_object(content, label=label)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _require_utc_timestamp(value: Any, *, label: str) -> None:
|
|
673
|
+
if not isinstance(value, str) or not value.endswith("Z"):
|
|
674
|
+
raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp")
|
|
675
|
+
try:
|
|
676
|
+
parsed = datetime.fromisoformat(value[:-1] + "+00:00")
|
|
677
|
+
except ValueError as exc:
|
|
678
|
+
raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp") from exc
|
|
679
|
+
if parsed.utcoffset() is None or parsed.utcoffset().total_seconds() != 0:
|
|
680
|
+
raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp")
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
__all__ = [
|
|
684
|
+
"ExperimentArtifactIdentity",
|
|
685
|
+
"ExperimentArtifactStore",
|
|
686
|
+
"verify_experiment_run_directory",
|
|
687
|
+
]
|