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,1087 @@
|
|
|
1
|
+
"""Safe JSON codecs and append-only storage for frozen selector policies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import tempfile
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import MappingProxyType
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from agentinputlab.contracts.enums import SemanticFamily, StrEnum
|
|
17
|
+
from agentinputlab.contracts.errors import ArtifactError, ValidationError
|
|
18
|
+
from agentinputlab.contracts.serialization import canonical_json, content_hash, freeze_mapping
|
|
19
|
+
|
|
20
|
+
from .advanced import (
|
|
21
|
+
CostRiskAwareSelector,
|
|
22
|
+
GBDTTLearnerSelector,
|
|
23
|
+
KNNTrainingRow,
|
|
24
|
+
KNNUtilitySelector,
|
|
25
|
+
LogisticTLearnerSelector,
|
|
26
|
+
RidgeTLearnerSelector,
|
|
27
|
+
)
|
|
28
|
+
from .base import Selector
|
|
29
|
+
from .catalog import get_selector_card
|
|
30
|
+
from .learned import RidgeUtilitySelector
|
|
31
|
+
from .rules import RuleSelector
|
|
32
|
+
from .runtime import SelectorArtifactIdentity
|
|
33
|
+
|
|
34
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
35
|
+
_SAFE_SEGMENT = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
36
|
+
_CORE_FAMILIES = frozenset(
|
|
37
|
+
{
|
|
38
|
+
SemanticFamily.REFORMULATE,
|
|
39
|
+
SemanticFamily.DECOMPOSE,
|
|
40
|
+
SemanticFamily.ACQUIRE,
|
|
41
|
+
SemanticFamily.CLARIFY,
|
|
42
|
+
SemanticFamily.CURATE,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
_SELECTOR_KIND_BY_TYPE = {
|
|
46
|
+
RuleSelector: "rule",
|
|
47
|
+
RidgeUtilitySelector: "ridge_utility",
|
|
48
|
+
RidgeTLearnerSelector: "ridge_t_learner",
|
|
49
|
+
LogisticTLearnerSelector: "logistic_t_learner",
|
|
50
|
+
GBDTTLearnerSelector: "gbdt_t_learner",
|
|
51
|
+
KNNUtilitySelector: "knn_utility",
|
|
52
|
+
CostRiskAwareSelector: "cost_risk_guardrail",
|
|
53
|
+
}
|
|
54
|
+
_CARD_BY_KIND = {
|
|
55
|
+
"rule": "rule.v1",
|
|
56
|
+
"ridge_utility": "ridge_utility.v1",
|
|
57
|
+
"ridge_t_learner": "ridge_t_learner.v1",
|
|
58
|
+
"logistic_t_learner": "logistic_t_learner.v1",
|
|
59
|
+
"gbdt_t_learner": "gbdt_t_learner.v1",
|
|
60
|
+
"knn_utility": "knn_utility.v1",
|
|
61
|
+
"cost_risk_guardrail": "cost_risk_guardrail.v1",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SelectorArtifactStage(StrEnum):
|
|
66
|
+
FITTED = "fitted"
|
|
67
|
+
CALIBRATED = "calibrated"
|
|
68
|
+
FROZEN = "frozen"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def selector_model_hash(selector: Selector) -> str:
|
|
72
|
+
declared = getattr(selector, "model_hash", None)
|
|
73
|
+
if not isinstance(declared, str) or not _SHA256.fullmatch(declared):
|
|
74
|
+
raise ValidationError("selector must expose a lowercase SHA-256 model_hash")
|
|
75
|
+
return declared
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def selector_card_id(selector: Selector) -> str:
|
|
79
|
+
for selector_type, kind in _SELECTOR_KIND_BY_TYPE.items():
|
|
80
|
+
if type(selector) is selector_type:
|
|
81
|
+
return _CARD_BY_KIND[kind]
|
|
82
|
+
raise ValidationError(f"unsupported selector artifact type: {type(selector).__name__}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def selector_to_artifact(selector: Selector) -> dict[str, Any]:
|
|
86
|
+
"""Encode one built-in selector without pickle or executable objects."""
|
|
87
|
+
|
|
88
|
+
kind = _kind_for_selector(selector)
|
|
89
|
+
payload: dict[str, Any]
|
|
90
|
+
if isinstance(selector, RuleSelector):
|
|
91
|
+
payload = {
|
|
92
|
+
"threshold": selector.threshold,
|
|
93
|
+
"margin": selector.margin,
|
|
94
|
+
}
|
|
95
|
+
elif isinstance(selector, RidgeUtilitySelector):
|
|
96
|
+
payload = {
|
|
97
|
+
"coefficients": _encode_family_vectors(selector.coefficients),
|
|
98
|
+
"release_by_family": _encode_releases(selector.release_by_family),
|
|
99
|
+
"min_gain": selector.min_gain,
|
|
100
|
+
"margin": selector.margin,
|
|
101
|
+
}
|
|
102
|
+
elif isinstance(selector, RidgeTLearnerSelector):
|
|
103
|
+
payload = {
|
|
104
|
+
"outcome_coefficients": _encode_family_vectors(selector.outcome_coefficients),
|
|
105
|
+
"release_by_family": _encode_releases(selector.release_by_family),
|
|
106
|
+
"ridge": selector.ridge,
|
|
107
|
+
"min_gain": selector.min_gain,
|
|
108
|
+
"margin": selector.margin,
|
|
109
|
+
}
|
|
110
|
+
elif isinstance(selector, LogisticTLearnerSelector):
|
|
111
|
+
payload = {
|
|
112
|
+
"outcome_coefficients": _encode_family_vectors(selector.outcome_coefficients),
|
|
113
|
+
"release_by_family": _encode_releases(selector.release_by_family),
|
|
114
|
+
"l2": selector.l2,
|
|
115
|
+
"learning_rate": selector.learning_rate,
|
|
116
|
+
"iterations": selector.iterations,
|
|
117
|
+
"min_gain": selector.min_gain,
|
|
118
|
+
"margin": selector.margin,
|
|
119
|
+
}
|
|
120
|
+
elif isinstance(selector, GBDTTLearnerSelector):
|
|
121
|
+
payload = selector.to_artifact()
|
|
122
|
+
elif isinstance(selector, KNNUtilitySelector):
|
|
123
|
+
payload = {
|
|
124
|
+
"training_rows": [
|
|
125
|
+
{
|
|
126
|
+
"feature_vector": list(row.feature_vector),
|
|
127
|
+
"gains": {
|
|
128
|
+
family.value: value
|
|
129
|
+
for family, value in sorted(
|
|
130
|
+
row.gains.items(),
|
|
131
|
+
key=lambda item: item[0].value,
|
|
132
|
+
)
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
for row in selector.training_rows
|
|
136
|
+
],
|
|
137
|
+
"release_by_family": _encode_releases(selector.release_by_family),
|
|
138
|
+
"k": selector.k,
|
|
139
|
+
"distance_power": selector.distance_power,
|
|
140
|
+
"min_gain": selector.min_gain,
|
|
141
|
+
"margin": selector.margin,
|
|
142
|
+
}
|
|
143
|
+
elif isinstance(selector, CostRiskAwareSelector):
|
|
144
|
+
payload = {
|
|
145
|
+
"base_selector": selector_to_artifact(selector.base_selector),
|
|
146
|
+
"release_by_family": _encode_releases(selector.release_by_family),
|
|
147
|
+
"cost_by_family": _encode_family_scalars(selector.cost_by_family),
|
|
148
|
+
"risk_by_family": _encode_family_scalars(selector.risk_by_family),
|
|
149
|
+
"max_cost": selector.max_cost,
|
|
150
|
+
"max_risk": selector.max_risk,
|
|
151
|
+
"cost_weight": selector.cost_weight,
|
|
152
|
+
"risk_weight": selector.risk_weight,
|
|
153
|
+
"min_gain": selector.min_gain,
|
|
154
|
+
"margin": selector.margin,
|
|
155
|
+
}
|
|
156
|
+
else: # pragma: no cover - guarded by _kind_for_selector
|
|
157
|
+
raise ValidationError(f"unsupported selector artifact type: {type(selector).__name__}")
|
|
158
|
+
return {
|
|
159
|
+
"schema_version": 1,
|
|
160
|
+
"selector_kind": kind,
|
|
161
|
+
"payload": payload,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def selector_from_artifact(value: Mapping[str, Any]) -> Selector:
|
|
166
|
+
"""Strictly decode a built-in selector from canonical JSON-shaped data."""
|
|
167
|
+
|
|
168
|
+
artifact = _strict_mapping(
|
|
169
|
+
value,
|
|
170
|
+
expected={"schema_version", "selector_kind", "payload"},
|
|
171
|
+
label="selector artifact",
|
|
172
|
+
)
|
|
173
|
+
if _integer(artifact["schema_version"], label="schema_version") != 1:
|
|
174
|
+
raise ValidationError("unsupported selector artifact schema")
|
|
175
|
+
kind = _text(artifact["selector_kind"], label="selector_kind")
|
|
176
|
+
if kind not in _CARD_BY_KIND:
|
|
177
|
+
raise ValidationError(f"unsupported selector artifact kind: {kind}")
|
|
178
|
+
payload_value = artifact["payload"]
|
|
179
|
+
if not isinstance(payload_value, Mapping):
|
|
180
|
+
raise ValidationError("selector artifact payload must be an object")
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
if kind == "rule":
|
|
184
|
+
payload = _strict_mapping(
|
|
185
|
+
payload_value,
|
|
186
|
+
expected={"threshold", "margin"},
|
|
187
|
+
label="rule selector payload",
|
|
188
|
+
)
|
|
189
|
+
selector: Selector = RuleSelector(
|
|
190
|
+
threshold=_number(payload["threshold"], label="threshold"),
|
|
191
|
+
margin=_number(payload["margin"], label="margin"),
|
|
192
|
+
)
|
|
193
|
+
elif kind == "ridge_utility":
|
|
194
|
+
payload = _strict_mapping(
|
|
195
|
+
payload_value,
|
|
196
|
+
expected={
|
|
197
|
+
"coefficients",
|
|
198
|
+
"release_by_family",
|
|
199
|
+
"min_gain",
|
|
200
|
+
"margin",
|
|
201
|
+
},
|
|
202
|
+
label="ridge utility payload",
|
|
203
|
+
)
|
|
204
|
+
selector = RidgeUtilitySelector(
|
|
205
|
+
_decode_family_vectors(payload["coefficients"], include_no_op=False),
|
|
206
|
+
_decode_releases(payload["release_by_family"]),
|
|
207
|
+
min_gain=_number(payload["min_gain"], label="min_gain"),
|
|
208
|
+
margin=_number(payload["margin"], label="margin"),
|
|
209
|
+
)
|
|
210
|
+
elif kind == "ridge_t_learner":
|
|
211
|
+
payload = _strict_mapping(
|
|
212
|
+
payload_value,
|
|
213
|
+
expected={
|
|
214
|
+
"outcome_coefficients",
|
|
215
|
+
"release_by_family",
|
|
216
|
+
"ridge",
|
|
217
|
+
"min_gain",
|
|
218
|
+
"margin",
|
|
219
|
+
},
|
|
220
|
+
label="ridge T-learner payload",
|
|
221
|
+
)
|
|
222
|
+
selector = RidgeTLearnerSelector(
|
|
223
|
+
_decode_family_vectors(
|
|
224
|
+
payload["outcome_coefficients"],
|
|
225
|
+
include_no_op=True,
|
|
226
|
+
),
|
|
227
|
+
_decode_releases(payload["release_by_family"]),
|
|
228
|
+
ridge=_number(payload["ridge"], label="ridge"),
|
|
229
|
+
min_gain=_number(payload["min_gain"], label="min_gain"),
|
|
230
|
+
margin=_number(payload["margin"], label="margin"),
|
|
231
|
+
)
|
|
232
|
+
elif kind == "logistic_t_learner":
|
|
233
|
+
payload = _strict_mapping(
|
|
234
|
+
payload_value,
|
|
235
|
+
expected={
|
|
236
|
+
"outcome_coefficients",
|
|
237
|
+
"release_by_family",
|
|
238
|
+
"l2",
|
|
239
|
+
"learning_rate",
|
|
240
|
+
"iterations",
|
|
241
|
+
"min_gain",
|
|
242
|
+
"margin",
|
|
243
|
+
},
|
|
244
|
+
label="logistic T-learner payload",
|
|
245
|
+
)
|
|
246
|
+
selector = LogisticTLearnerSelector(
|
|
247
|
+
_decode_family_vectors(
|
|
248
|
+
payload["outcome_coefficients"],
|
|
249
|
+
include_no_op=True,
|
|
250
|
+
),
|
|
251
|
+
_decode_releases(payload["release_by_family"]),
|
|
252
|
+
l2=_number(payload["l2"], label="l2"),
|
|
253
|
+
learning_rate=_number(
|
|
254
|
+
payload["learning_rate"],
|
|
255
|
+
label="learning_rate",
|
|
256
|
+
),
|
|
257
|
+
iterations=_integer(payload["iterations"], label="iterations"),
|
|
258
|
+
min_gain=_number(payload["min_gain"], label="min_gain"),
|
|
259
|
+
margin=_number(payload["margin"], label="margin"),
|
|
260
|
+
)
|
|
261
|
+
elif kind == "gbdt_t_learner":
|
|
262
|
+
selector = GBDTTLearnerSelector.from_artifact(payload_value)
|
|
263
|
+
elif kind == "knn_utility":
|
|
264
|
+
payload = _strict_mapping(
|
|
265
|
+
payload_value,
|
|
266
|
+
expected={
|
|
267
|
+
"training_rows",
|
|
268
|
+
"release_by_family",
|
|
269
|
+
"k",
|
|
270
|
+
"distance_power",
|
|
271
|
+
"min_gain",
|
|
272
|
+
"margin",
|
|
273
|
+
},
|
|
274
|
+
label="KNN selector payload",
|
|
275
|
+
)
|
|
276
|
+
raw_rows = _array(payload["training_rows"], label="training_rows")
|
|
277
|
+
rows: list[KNNTrainingRow] = []
|
|
278
|
+
for raw_row in raw_rows:
|
|
279
|
+
row = _strict_mapping(
|
|
280
|
+
raw_row,
|
|
281
|
+
expected={"feature_vector", "gains"},
|
|
282
|
+
label="KNN training row",
|
|
283
|
+
)
|
|
284
|
+
rows.append(
|
|
285
|
+
KNNTrainingRow(
|
|
286
|
+
tuple(
|
|
287
|
+
_number(item, label="feature_vector item")
|
|
288
|
+
for item in _array(
|
|
289
|
+
row["feature_vector"],
|
|
290
|
+
label="feature_vector",
|
|
291
|
+
)
|
|
292
|
+
),
|
|
293
|
+
_decode_family_scalars(
|
|
294
|
+
row["gains"],
|
|
295
|
+
include_no_op=False,
|
|
296
|
+
label="KNN gains",
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
selector = KNNUtilitySelector(
|
|
301
|
+
tuple(rows),
|
|
302
|
+
_decode_releases(payload["release_by_family"]),
|
|
303
|
+
k=_integer(payload["k"], label="k"),
|
|
304
|
+
distance_power=_number(
|
|
305
|
+
payload["distance_power"],
|
|
306
|
+
label="distance_power",
|
|
307
|
+
),
|
|
308
|
+
min_gain=_number(payload["min_gain"], label="min_gain"),
|
|
309
|
+
margin=_number(payload["margin"], label="margin"),
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
payload = _strict_mapping(
|
|
313
|
+
payload_value,
|
|
314
|
+
expected={
|
|
315
|
+
"base_selector",
|
|
316
|
+
"release_by_family",
|
|
317
|
+
"cost_by_family",
|
|
318
|
+
"risk_by_family",
|
|
319
|
+
"max_cost",
|
|
320
|
+
"max_risk",
|
|
321
|
+
"cost_weight",
|
|
322
|
+
"risk_weight",
|
|
323
|
+
"min_gain",
|
|
324
|
+
"margin",
|
|
325
|
+
},
|
|
326
|
+
label="cost/risk selector payload",
|
|
327
|
+
)
|
|
328
|
+
raw_base = payload["base_selector"]
|
|
329
|
+
if not isinstance(raw_base, Mapping):
|
|
330
|
+
raise ValidationError("base_selector must be an object")
|
|
331
|
+
selector = CostRiskAwareSelector(
|
|
332
|
+
selector_from_artifact(raw_base),
|
|
333
|
+
_decode_releases(payload["release_by_family"]),
|
|
334
|
+
_decode_family_scalars(
|
|
335
|
+
payload["cost_by_family"],
|
|
336
|
+
include_no_op=False,
|
|
337
|
+
label="cost_by_family",
|
|
338
|
+
),
|
|
339
|
+
_decode_family_scalars(
|
|
340
|
+
payload["risk_by_family"],
|
|
341
|
+
include_no_op=False,
|
|
342
|
+
label="risk_by_family",
|
|
343
|
+
),
|
|
344
|
+
max_cost=_number(payload["max_cost"], label="max_cost"),
|
|
345
|
+
max_risk=_number(payload["max_risk"], label="max_risk"),
|
|
346
|
+
cost_weight=_number(payload["cost_weight"], label="cost_weight"),
|
|
347
|
+
risk_weight=_number(payload["risk_weight"], label="risk_weight"),
|
|
348
|
+
min_gain=_number(payload["min_gain"], label="min_gain"),
|
|
349
|
+
margin=_number(payload["margin"], label="margin"),
|
|
350
|
+
)
|
|
351
|
+
except (TypeError, ValueError) as exc:
|
|
352
|
+
if isinstance(exc, ValidationError):
|
|
353
|
+
raise
|
|
354
|
+
raise ValidationError(f"invalid {kind} selector artifact") from exc
|
|
355
|
+
|
|
356
|
+
if selector_card_id(selector) != _CARD_BY_KIND[kind]:
|
|
357
|
+
raise ValidationError("decoded selector type differs from its artifact kind")
|
|
358
|
+
return selector
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass(frozen=True, slots=True)
|
|
362
|
+
class SelectorArtifactBundle:
|
|
363
|
+
"""One content-addressed selector plus all inference-critical bindings."""
|
|
364
|
+
|
|
365
|
+
selector_card_id: str
|
|
366
|
+
stage: SelectorArtifactStage
|
|
367
|
+
revision: str
|
|
368
|
+
model_payload: Mapping[str, Any]
|
|
369
|
+
model_hash: str
|
|
370
|
+
extractor_id: str
|
|
371
|
+
extractor_revision: str
|
|
372
|
+
extractor_hash: str
|
|
373
|
+
feature_schema_id: str
|
|
374
|
+
feature_schema_hash: str
|
|
375
|
+
representative_by_family: Mapping[SemanticFamily, str]
|
|
376
|
+
training_table_sha256: str
|
|
377
|
+
split_provenance_hash: str
|
|
378
|
+
comparison_profile_hash: str
|
|
379
|
+
calibration_receipt: Mapping[str, Any] = field(default_factory=dict)
|
|
380
|
+
schema_version: int = 1
|
|
381
|
+
fit_lineage: Mapping[str, Any] = field(default_factory=dict)
|
|
382
|
+
|
|
383
|
+
def __post_init__(self) -> None:
|
|
384
|
+
if self.schema_version != 1 or isinstance(self.schema_version, bool):
|
|
385
|
+
raise ValidationError("unsupported selector bundle schema")
|
|
386
|
+
try:
|
|
387
|
+
get_selector_card(self.selector_card_id)
|
|
388
|
+
except KeyError as exc:
|
|
389
|
+
raise ValidationError("selector bundle references an unknown selector card") from exc
|
|
390
|
+
if not isinstance(self.stage, SelectorArtifactStage):
|
|
391
|
+
raise ValidationError("selector bundle stage must be SelectorArtifactStage")
|
|
392
|
+
if (
|
|
393
|
+
not isinstance(self.revision, str)
|
|
394
|
+
or not _SAFE_SEGMENT.fullmatch(self.revision)
|
|
395
|
+
or ".." in self.revision
|
|
396
|
+
):
|
|
397
|
+
raise ValidationError("selector bundle revision must be a safe flat identifier")
|
|
398
|
+
for label in (
|
|
399
|
+
"extractor_id",
|
|
400
|
+
"extractor_revision",
|
|
401
|
+
"feature_schema_id",
|
|
402
|
+
):
|
|
403
|
+
value = getattr(self, label)
|
|
404
|
+
if not isinstance(value, str) or not value.strip():
|
|
405
|
+
raise ValidationError(f"selector bundle {label} must be non-empty")
|
|
406
|
+
for label in (
|
|
407
|
+
"model_hash",
|
|
408
|
+
"extractor_hash",
|
|
409
|
+
"feature_schema_hash",
|
|
410
|
+
"training_table_sha256",
|
|
411
|
+
"split_provenance_hash",
|
|
412
|
+
"comparison_profile_hash",
|
|
413
|
+
):
|
|
414
|
+
value = getattr(self, label)
|
|
415
|
+
if not isinstance(value, str) or not _SHA256.fullmatch(value):
|
|
416
|
+
raise ValidationError(f"selector bundle {label} must be SHA-256")
|
|
417
|
+
try:
|
|
418
|
+
normalized_payload = json.loads(canonical_json(self.model_payload))
|
|
419
|
+
normalized_receipt = json.loads(canonical_json(self.calibration_receipt))
|
|
420
|
+
normalized_lineage = json.loads(canonical_json(self.fit_lineage))
|
|
421
|
+
frozen_payload = freeze_mapping(normalized_payload)
|
|
422
|
+
receipt = freeze_mapping(normalized_receipt)
|
|
423
|
+
lineage = freeze_mapping(normalized_lineage)
|
|
424
|
+
except (TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
425
|
+
raise ValidationError("selector bundle values must be canonical JSON") from exc
|
|
426
|
+
representatives = _validated_representatives(self.representative_by_family)
|
|
427
|
+
selector = selector_from_artifact(normalized_payload)
|
|
428
|
+
if selector_card_id(selector) != self.selector_card_id:
|
|
429
|
+
raise ValidationError("selector bundle card and model payload differ")
|
|
430
|
+
if selector_model_hash(selector) != self.model_hash:
|
|
431
|
+
raise ValidationError("selector bundle model hash does not match its payload")
|
|
432
|
+
declared_releases = getattr(selector, "release_by_family", None)
|
|
433
|
+
if isinstance(declared_releases, Mapping) and dict(declared_releases) != representatives:
|
|
434
|
+
raise ValidationError(
|
|
435
|
+
"selector bundle representatives differ from model release mapping"
|
|
436
|
+
)
|
|
437
|
+
if self.stage is SelectorArtifactStage.FITTED and receipt:
|
|
438
|
+
raise ValidationError("fitted selector bundles cannot contain calibration receipts")
|
|
439
|
+
if self.stage is not SelectorArtifactStage.FITTED and not receipt:
|
|
440
|
+
raise ValidationError(
|
|
441
|
+
"calibrated/frozen selector bundles require a calibration receipt"
|
|
442
|
+
)
|
|
443
|
+
_validate_fit_lineage(
|
|
444
|
+
lineage,
|
|
445
|
+
selector=selector,
|
|
446
|
+
selector_card=self.selector_card_id,
|
|
447
|
+
)
|
|
448
|
+
object.__setattr__(self, "model_payload", frozen_payload)
|
|
449
|
+
object.__setattr__(self, "calibration_receipt", receipt)
|
|
450
|
+
object.__setattr__(self, "fit_lineage", lineage)
|
|
451
|
+
object.__setattr__(
|
|
452
|
+
self,
|
|
453
|
+
"representative_by_family",
|
|
454
|
+
MappingProxyType(representatives),
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
@classmethod
|
|
458
|
+
def create(
|
|
459
|
+
cls,
|
|
460
|
+
selector: Selector,
|
|
461
|
+
*,
|
|
462
|
+
stage: SelectorArtifactStage,
|
|
463
|
+
revision: str,
|
|
464
|
+
extractor_id: str,
|
|
465
|
+
extractor_revision: str,
|
|
466
|
+
extractor_hash: str,
|
|
467
|
+
feature_schema_id: str,
|
|
468
|
+
feature_schema_hash: str,
|
|
469
|
+
representative_by_family: Mapping[SemanticFamily, str],
|
|
470
|
+
training_table_sha256: str,
|
|
471
|
+
split_provenance_hash: str,
|
|
472
|
+
comparison_profile_hash: str,
|
|
473
|
+
calibration_receipt: Mapping[str, Any] | None = None,
|
|
474
|
+
fit_lineage: Mapping[str, Any] | None = None,
|
|
475
|
+
) -> SelectorArtifactBundle:
|
|
476
|
+
return cls(
|
|
477
|
+
selector_card_id=selector_card_id(selector),
|
|
478
|
+
stage=stage,
|
|
479
|
+
revision=revision,
|
|
480
|
+
model_payload=selector_to_artifact(selector),
|
|
481
|
+
model_hash=selector_model_hash(selector),
|
|
482
|
+
extractor_id=extractor_id,
|
|
483
|
+
extractor_revision=extractor_revision,
|
|
484
|
+
extractor_hash=extractor_hash,
|
|
485
|
+
feature_schema_id=feature_schema_id,
|
|
486
|
+
feature_schema_hash=feature_schema_hash,
|
|
487
|
+
representative_by_family=representative_by_family,
|
|
488
|
+
training_table_sha256=training_table_sha256,
|
|
489
|
+
split_provenance_hash=split_provenance_hash,
|
|
490
|
+
comparison_profile_hash=comparison_profile_hash,
|
|
491
|
+
calibration_receipt=calibration_receipt or {},
|
|
492
|
+
fit_lineage=fit_lineage or {},
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def bundle_hash(self) -> str:
|
|
497
|
+
return content_hash(self.to_artifact())
|
|
498
|
+
|
|
499
|
+
def selector(self) -> Selector:
|
|
500
|
+
normalized = json.loads(canonical_json(self.model_payload))
|
|
501
|
+
return selector_from_artifact(normalized)
|
|
502
|
+
|
|
503
|
+
def to_artifact(self) -> dict[str, Any]:
|
|
504
|
+
artifact = {
|
|
505
|
+
"schema_version": self.schema_version,
|
|
506
|
+
"selector_card_id": self.selector_card_id,
|
|
507
|
+
"stage": self.stage.value,
|
|
508
|
+
"revision": self.revision,
|
|
509
|
+
"model_payload": self.model_payload,
|
|
510
|
+
"model_hash": self.model_hash,
|
|
511
|
+
"extractor": {
|
|
512
|
+
"id": self.extractor_id,
|
|
513
|
+
"revision": self.extractor_revision,
|
|
514
|
+
"sha256": self.extractor_hash,
|
|
515
|
+
},
|
|
516
|
+
"feature_schema": {
|
|
517
|
+
"id": self.feature_schema_id,
|
|
518
|
+
"sha256": self.feature_schema_hash,
|
|
519
|
+
},
|
|
520
|
+
"representative_by_family": _encode_releases(self.representative_by_family),
|
|
521
|
+
"training_table_sha256": self.training_table_sha256,
|
|
522
|
+
"split_provenance_hash": self.split_provenance_hash,
|
|
523
|
+
"comparison_profile_hash": self.comparison_profile_hash,
|
|
524
|
+
"calibration_receipt": self.calibration_receipt,
|
|
525
|
+
}
|
|
526
|
+
if self.fit_lineage:
|
|
527
|
+
artifact["fit_lineage"] = self.fit_lineage
|
|
528
|
+
return artifact
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def from_artifact(cls, value: Mapping[str, Any]) -> SelectorArtifactBundle:
|
|
532
|
+
artifact = _string_mapping(value, label="selector bundle")
|
|
533
|
+
required_fields = {
|
|
534
|
+
"schema_version",
|
|
535
|
+
"selector_card_id",
|
|
536
|
+
"stage",
|
|
537
|
+
"revision",
|
|
538
|
+
"model_payload",
|
|
539
|
+
"model_hash",
|
|
540
|
+
"extractor",
|
|
541
|
+
"feature_schema",
|
|
542
|
+
"representative_by_family",
|
|
543
|
+
"training_table_sha256",
|
|
544
|
+
"split_provenance_hash",
|
|
545
|
+
"comparison_profile_hash",
|
|
546
|
+
"calibration_receipt",
|
|
547
|
+
}
|
|
548
|
+
if set(artifact) not in (required_fields, required_fields | {"fit_lineage"}):
|
|
549
|
+
raise ValidationError(
|
|
550
|
+
"selector bundle fields differ: expected the legacy fields with "
|
|
551
|
+
"optional fit_lineage"
|
|
552
|
+
)
|
|
553
|
+
extractor = _strict_mapping(
|
|
554
|
+
artifact["extractor"],
|
|
555
|
+
expected={"id", "revision", "sha256"},
|
|
556
|
+
label="selector bundle extractor",
|
|
557
|
+
)
|
|
558
|
+
schema = _strict_mapping(
|
|
559
|
+
artifact["feature_schema"],
|
|
560
|
+
expected={"id", "sha256"},
|
|
561
|
+
label="selector bundle feature schema",
|
|
562
|
+
)
|
|
563
|
+
model_payload = artifact["model_payload"]
|
|
564
|
+
calibration_receipt = artifact["calibration_receipt"]
|
|
565
|
+
fit_lineage = artifact.get("fit_lineage", {})
|
|
566
|
+
if (
|
|
567
|
+
not isinstance(model_payload, Mapping)
|
|
568
|
+
or not isinstance(calibration_receipt, Mapping)
|
|
569
|
+
or not isinstance(fit_lineage, Mapping)
|
|
570
|
+
):
|
|
571
|
+
raise ValidationError("selector bundle model/receipt values must be objects")
|
|
572
|
+
try:
|
|
573
|
+
stage = SelectorArtifactStage(_text(artifact["stage"], label="stage"))
|
|
574
|
+
except ValueError as exc:
|
|
575
|
+
raise ValidationError("selector bundle stage is unsupported") from exc
|
|
576
|
+
return cls(
|
|
577
|
+
schema_version=_integer(
|
|
578
|
+
artifact["schema_version"],
|
|
579
|
+
label="schema_version",
|
|
580
|
+
),
|
|
581
|
+
selector_card_id=_text(
|
|
582
|
+
artifact["selector_card_id"],
|
|
583
|
+
label="selector_card_id",
|
|
584
|
+
),
|
|
585
|
+
stage=stage,
|
|
586
|
+
revision=_text(artifact["revision"], label="revision"),
|
|
587
|
+
model_payload=model_payload,
|
|
588
|
+
model_hash=_text(artifact["model_hash"], label="model_hash"),
|
|
589
|
+
extractor_id=_text(extractor["id"], label="extractor.id"),
|
|
590
|
+
extractor_revision=_text(
|
|
591
|
+
extractor["revision"],
|
|
592
|
+
label="extractor.revision",
|
|
593
|
+
),
|
|
594
|
+
extractor_hash=_text(extractor["sha256"], label="extractor.sha256"),
|
|
595
|
+
feature_schema_id=_text(schema["id"], label="feature_schema.id"),
|
|
596
|
+
feature_schema_hash=_text(
|
|
597
|
+
schema["sha256"],
|
|
598
|
+
label="feature_schema.sha256",
|
|
599
|
+
),
|
|
600
|
+
representative_by_family=_decode_releases(artifact["representative_by_family"]),
|
|
601
|
+
training_table_sha256=_text(
|
|
602
|
+
artifact["training_table_sha256"],
|
|
603
|
+
label="training_table_sha256",
|
|
604
|
+
),
|
|
605
|
+
split_provenance_hash=_text(
|
|
606
|
+
artifact["split_provenance_hash"],
|
|
607
|
+
label="split_provenance_hash",
|
|
608
|
+
),
|
|
609
|
+
comparison_profile_hash=_text(
|
|
610
|
+
artifact["comparison_profile_hash"],
|
|
611
|
+
label="comparison_profile_hash",
|
|
612
|
+
),
|
|
613
|
+
calibration_receipt=calibration_receipt,
|
|
614
|
+
fit_lineage=fit_lineage,
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _validate_fit_lineage(
|
|
619
|
+
value: Mapping[str, Any],
|
|
620
|
+
*,
|
|
621
|
+
selector: Selector,
|
|
622
|
+
selector_card: str,
|
|
623
|
+
) -> None:
|
|
624
|
+
if not value:
|
|
625
|
+
return
|
|
626
|
+
lineage = _string_mapping(value, label="selector fit_lineage")
|
|
627
|
+
common_fields = {
|
|
628
|
+
"schema_version",
|
|
629
|
+
"kind",
|
|
630
|
+
"discovery",
|
|
631
|
+
"fit_config_sha256",
|
|
632
|
+
}
|
|
633
|
+
kind = _text(lineage.get("kind"), label="fit_lineage.kind")
|
|
634
|
+
if kind == "selector_fit":
|
|
635
|
+
if set(lineage) != common_fields:
|
|
636
|
+
raise ValidationError("selector_fit lineage fields differ")
|
|
637
|
+
if selector_card == _CARD_BY_KIND["cost_risk_guardrail"]:
|
|
638
|
+
raise ValidationError("cost/risk guardrail lineage must bind its exact base bundle")
|
|
639
|
+
elif kind == "cost_risk_guardrail_base_bundle":
|
|
640
|
+
if set(lineage) != common_fields | {"base_selector_bundle"}:
|
|
641
|
+
raise ValidationError("cost/risk selector fit_lineage fields differ")
|
|
642
|
+
if selector_card != _CARD_BY_KIND["cost_risk_guardrail"] or not isinstance(
|
|
643
|
+
selector,
|
|
644
|
+
CostRiskAwareSelector,
|
|
645
|
+
):
|
|
646
|
+
raise ValidationError("guardrail base lineage is valid only for a cost/risk selector")
|
|
647
|
+
else:
|
|
648
|
+
raise ValidationError("selector fit_lineage kind is unsupported")
|
|
649
|
+
if _integer(lineage["schema_version"], label="fit_lineage.schema_version") != 1:
|
|
650
|
+
raise ValidationError("unsupported selector fit_lineage schema")
|
|
651
|
+
discovery = _strict_mapping(
|
|
652
|
+
lineage["discovery"],
|
|
653
|
+
expected={"revision", "sha256"},
|
|
654
|
+
label="selector fit_lineage discovery",
|
|
655
|
+
)
|
|
656
|
+
for label, value_to_check in (
|
|
657
|
+
("fit_lineage.fit_config_sha256", lineage["fit_config_sha256"]),
|
|
658
|
+
("fit_lineage.discovery.sha256", discovery["sha256"]),
|
|
659
|
+
):
|
|
660
|
+
if not isinstance(value_to_check, str) or not _SHA256.fullmatch(value_to_check):
|
|
661
|
+
raise ValidationError(f"{label} must be SHA-256")
|
|
662
|
+
value_to_check = discovery["revision"]
|
|
663
|
+
if (
|
|
664
|
+
not isinstance(value_to_check, str)
|
|
665
|
+
or not _SAFE_SEGMENT.fullmatch(value_to_check)
|
|
666
|
+
or ".." in value_to_check
|
|
667
|
+
):
|
|
668
|
+
raise ValidationError("fit_lineage.discovery.revision must be a safe flat identifier")
|
|
669
|
+
if kind == "selector_fit":
|
|
670
|
+
return
|
|
671
|
+
|
|
672
|
+
base = _strict_mapping(
|
|
673
|
+
lineage["base_selector_bundle"],
|
|
674
|
+
expected={
|
|
675
|
+
"selector_id",
|
|
676
|
+
"revision",
|
|
677
|
+
"sha256",
|
|
678
|
+
"stage",
|
|
679
|
+
"model_hash",
|
|
680
|
+
},
|
|
681
|
+
label="selector fit_lineage base_selector_bundle",
|
|
682
|
+
)
|
|
683
|
+
for label, value_to_check in (
|
|
684
|
+
("fit_lineage.base_selector_bundle.sha256", base["sha256"]),
|
|
685
|
+
("fit_lineage.base_selector_bundle.model_hash", base["model_hash"]),
|
|
686
|
+
):
|
|
687
|
+
if not isinstance(value_to_check, str) or not _SHA256.fullmatch(value_to_check):
|
|
688
|
+
raise ValidationError(f"{label} must be SHA-256")
|
|
689
|
+
for label, value_to_check in (
|
|
690
|
+
("fit_lineage.base_selector_bundle.selector_id", base["selector_id"]),
|
|
691
|
+
("fit_lineage.base_selector_bundle.revision", base["revision"]),
|
|
692
|
+
):
|
|
693
|
+
if (
|
|
694
|
+
not isinstance(value_to_check, str)
|
|
695
|
+
or not _SAFE_SEGMENT.fullmatch(value_to_check)
|
|
696
|
+
or ".." in value_to_check
|
|
697
|
+
):
|
|
698
|
+
raise ValidationError(f"{label} must be a safe flat identifier")
|
|
699
|
+
try:
|
|
700
|
+
base_stage = SelectorArtifactStage(
|
|
701
|
+
_text(base["stage"], label="fit_lineage.base_selector_bundle.stage")
|
|
702
|
+
)
|
|
703
|
+
except ValueError as exc:
|
|
704
|
+
raise ValidationError("fit_lineage base selector stage is unsupported") from exc
|
|
705
|
+
if base_stage not in {
|
|
706
|
+
SelectorArtifactStage.FITTED,
|
|
707
|
+
SelectorArtifactStage.CALIBRATED,
|
|
708
|
+
SelectorArtifactStage.FROZEN,
|
|
709
|
+
}: # pragma: no cover - exhaustive enum guard
|
|
710
|
+
raise ValidationError("fit_lineage base selector stage is unsupported")
|
|
711
|
+
embedded_base = selector.base_selector
|
|
712
|
+
if base["selector_id"] != selector_card_id(embedded_base):
|
|
713
|
+
raise ValidationError("fit_lineage base selector card differs from the embedded base model")
|
|
714
|
+
if base["model_hash"] != selector_model_hash(embedded_base):
|
|
715
|
+
raise ValidationError("fit_lineage base model hash differs from the embedded base model")
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
@dataclass(slots=True)
|
|
719
|
+
class SelectorArtifactStore:
|
|
720
|
+
"""Content-addressed selector storage without mutable aliases or ``latest``."""
|
|
721
|
+
|
|
722
|
+
root: Path
|
|
723
|
+
|
|
724
|
+
def __init__(self, root: str | Path) -> None:
|
|
725
|
+
self.root = Path(root).expanduser().resolve()
|
|
726
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
727
|
+
|
|
728
|
+
def path_for(self, identity: SelectorArtifactIdentity) -> Path:
|
|
729
|
+
for label, value in (
|
|
730
|
+
("selector_id", identity.selector_id),
|
|
731
|
+
("revision", identity.revision),
|
|
732
|
+
):
|
|
733
|
+
if not _SAFE_SEGMENT.fullmatch(value) or ".." in value:
|
|
734
|
+
raise ArtifactError(f"selector artifact {label} is not a safe path segment")
|
|
735
|
+
candidate = (
|
|
736
|
+
self.root / identity.selector_id / identity.revision / identity.sha256 / "bundle.json"
|
|
737
|
+
).resolve()
|
|
738
|
+
if self.root not in candidate.parents:
|
|
739
|
+
raise ArtifactError("selector artifact path escaped store root")
|
|
740
|
+
return candidate
|
|
741
|
+
|
|
742
|
+
def save(
|
|
743
|
+
self,
|
|
744
|
+
bundle: SelectorArtifactBundle,
|
|
745
|
+
*,
|
|
746
|
+
previous_identity: SelectorArtifactIdentity | None = None,
|
|
747
|
+
) -> SelectorArtifactIdentity:
|
|
748
|
+
if not isinstance(bundle, SelectorArtifactBundle):
|
|
749
|
+
raise TypeError("selector artifact store requires SelectorArtifactBundle")
|
|
750
|
+
self._validate_transition(bundle, previous_identity)
|
|
751
|
+
identity = SelectorArtifactIdentity(
|
|
752
|
+
selector_id=bundle.selector_card_id,
|
|
753
|
+
revision=bundle.revision,
|
|
754
|
+
sha256=bundle.bundle_hash,
|
|
755
|
+
)
|
|
756
|
+
destination = self.path_for(identity)
|
|
757
|
+
revision_root = destination.parent.parent
|
|
758
|
+
if revision_root.exists():
|
|
759
|
+
existing_hashes = {
|
|
760
|
+
path.name
|
|
761
|
+
for path in revision_root.iterdir()
|
|
762
|
+
if path.is_dir() and _SHA256.fullmatch(path.name)
|
|
763
|
+
}
|
|
764
|
+
if existing_hashes - {identity.sha256}:
|
|
765
|
+
raise ArtifactError(
|
|
766
|
+
"selector artifact revision is append-only and already has different content"
|
|
767
|
+
)
|
|
768
|
+
payload = (canonical_json(bundle.to_artifact()) + "\n").encode("utf-8")
|
|
769
|
+
if destination.exists():
|
|
770
|
+
if destination.read_bytes() != payload:
|
|
771
|
+
raise ArtifactError("stored selector artifact bytes differ from bundle identity")
|
|
772
|
+
self.load(identity)
|
|
773
|
+
return identity
|
|
774
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
775
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
776
|
+
prefix=".partial-",
|
|
777
|
+
dir=destination.parent,
|
|
778
|
+
)
|
|
779
|
+
try:
|
|
780
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
781
|
+
handle.write(payload)
|
|
782
|
+
handle.flush()
|
|
783
|
+
os.fsync(handle.fileno())
|
|
784
|
+
try:
|
|
785
|
+
os.link(temporary_name, destination)
|
|
786
|
+
except FileExistsError:
|
|
787
|
+
if destination.read_bytes() != payload:
|
|
788
|
+
raise ArtifactError(
|
|
789
|
+
"concurrent selector artifact materialization differs"
|
|
790
|
+
) from None
|
|
791
|
+
finally:
|
|
792
|
+
if os.path.exists(temporary_name):
|
|
793
|
+
os.unlink(temporary_name)
|
|
794
|
+
self.load(identity)
|
|
795
|
+
return identity
|
|
796
|
+
|
|
797
|
+
def load(
|
|
798
|
+
self,
|
|
799
|
+
identity: SelectorArtifactIdentity,
|
|
800
|
+
*,
|
|
801
|
+
require_stage: SelectorArtifactStage | None = None,
|
|
802
|
+
) -> tuple[Selector, SelectorArtifactBundle]:
|
|
803
|
+
if not isinstance(identity, SelectorArtifactIdentity):
|
|
804
|
+
raise TypeError("selector artifact load requires SelectorArtifactIdentity")
|
|
805
|
+
if require_stage is not None and not isinstance(require_stage, SelectorArtifactStage):
|
|
806
|
+
raise TypeError("require_stage must be SelectorArtifactStage or None")
|
|
807
|
+
path = self.path_for(identity)
|
|
808
|
+
if not path.is_file():
|
|
809
|
+
raise ArtifactError(
|
|
810
|
+
f"selector artifact missing: {identity.selector_id}@{identity.revision}"
|
|
811
|
+
)
|
|
812
|
+
try:
|
|
813
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
814
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
815
|
+
raise ArtifactError("selector artifact is not valid UTF-8 JSON") from exc
|
|
816
|
+
if not isinstance(raw, Mapping):
|
|
817
|
+
raise ArtifactError("selector artifact root must be an object")
|
|
818
|
+
try:
|
|
819
|
+
bundle = SelectorArtifactBundle.from_artifact(raw)
|
|
820
|
+
except (TypeError, ValueError) as exc:
|
|
821
|
+
raise ArtifactError("selector artifact bundle failed validation") from exc
|
|
822
|
+
if (
|
|
823
|
+
bundle.selector_card_id != identity.selector_id
|
|
824
|
+
or bundle.revision != identity.revision
|
|
825
|
+
or bundle.bundle_hash != identity.sha256
|
|
826
|
+
):
|
|
827
|
+
raise ArtifactError("selector artifact identity does not match stored content")
|
|
828
|
+
if require_stage is not None and bundle.stage is not require_stage:
|
|
829
|
+
raise ArtifactError(
|
|
830
|
+
f"selector artifact stage is {bundle.stage.value}, "
|
|
831
|
+
f"not required {require_stage.value}"
|
|
832
|
+
)
|
|
833
|
+
return bundle.selector(), bundle
|
|
834
|
+
|
|
835
|
+
def _validate_transition(
|
|
836
|
+
self,
|
|
837
|
+
bundle: SelectorArtifactBundle,
|
|
838
|
+
previous_identity: SelectorArtifactIdentity | None,
|
|
839
|
+
) -> None:
|
|
840
|
+
if bundle.stage is SelectorArtifactStage.FITTED:
|
|
841
|
+
if previous_identity is not None:
|
|
842
|
+
raise ArtifactError("fitted selector artifact cannot have a predecessor")
|
|
843
|
+
return
|
|
844
|
+
if previous_identity is None:
|
|
845
|
+
raise ArtifactError(
|
|
846
|
+
f"{bundle.stage.value} selector artifact requires its exact predecessor"
|
|
847
|
+
)
|
|
848
|
+
_, previous = self.load(previous_identity)
|
|
849
|
+
expected_previous = (
|
|
850
|
+
SelectorArtifactStage.FITTED
|
|
851
|
+
if bundle.stage is SelectorArtifactStage.CALIBRATED
|
|
852
|
+
else SelectorArtifactStage.CALIBRATED
|
|
853
|
+
)
|
|
854
|
+
if previous.stage is not expected_previous:
|
|
855
|
+
raise ArtifactError(
|
|
856
|
+
f"selector transition requires {expected_previous.value} predecessor"
|
|
857
|
+
)
|
|
858
|
+
if previous.selector_card_id != bundle.selector_card_id:
|
|
859
|
+
raise ArtifactError("selector transition cannot change selector card")
|
|
860
|
+
if previous.revision == bundle.revision:
|
|
861
|
+
raise ArtifactError("selector lifecycle stages require distinct revisions")
|
|
862
|
+
binding_fields = (
|
|
863
|
+
"extractor_id",
|
|
864
|
+
"extractor_revision",
|
|
865
|
+
"extractor_hash",
|
|
866
|
+
"feature_schema_id",
|
|
867
|
+
"feature_schema_hash",
|
|
868
|
+
"representative_by_family",
|
|
869
|
+
"training_table_sha256",
|
|
870
|
+
"split_provenance_hash",
|
|
871
|
+
"comparison_profile_hash",
|
|
872
|
+
"fit_lineage",
|
|
873
|
+
)
|
|
874
|
+
if any(getattr(previous, name) != getattr(bundle, name) for name in binding_fields):
|
|
875
|
+
raise ArtifactError("selector transition changed a frozen data/runtime binding")
|
|
876
|
+
if bundle.stage is SelectorArtifactStage.CALIBRATED and _calibration_invariant_payload(
|
|
877
|
+
previous.model_payload
|
|
878
|
+
) != _calibration_invariant_payload(bundle.model_payload):
|
|
879
|
+
raise ArtifactError(
|
|
880
|
+
"selector calibration changed fitted model parameters; only the "
|
|
881
|
+
"predefined calibration/guardrail fields may change"
|
|
882
|
+
)
|
|
883
|
+
if bundle.stage is SelectorArtifactStage.FROZEN and (
|
|
884
|
+
previous.model_hash != bundle.model_hash
|
|
885
|
+
or previous.model_payload != bundle.model_payload
|
|
886
|
+
or previous.calibration_receipt != bundle.calibration_receipt
|
|
887
|
+
):
|
|
888
|
+
raise ArtifactError("freezing cannot change a calibrated selector policy")
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _calibration_invariant_payload(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
892
|
+
"""Return the portion of a selector artifact that calibration cannot change."""
|
|
893
|
+
|
|
894
|
+
normalized = json.loads(canonical_json(value))
|
|
895
|
+
if not isinstance(normalized, dict): # pragma: no cover - bundle validation invariant
|
|
896
|
+
raise ArtifactError("selector model payload must be an object")
|
|
897
|
+
kind = normalized.get("selector_kind")
|
|
898
|
+
payload = normalized.get("payload")
|
|
899
|
+
if not isinstance(kind, str) or not isinstance(payload, dict):
|
|
900
|
+
raise ArtifactError("selector model payload has an invalid calibration schema")
|
|
901
|
+
direct_fields = {
|
|
902
|
+
"rule": {"threshold", "margin"},
|
|
903
|
+
"ridge_utility": {"min_gain", "margin"},
|
|
904
|
+
"ridge_t_learner": {"min_gain", "margin"},
|
|
905
|
+
"logistic_t_learner": {"min_gain", "margin"},
|
|
906
|
+
"knn_utility": {"min_gain", "margin"},
|
|
907
|
+
"cost_risk_guardrail": {
|
|
908
|
+
"max_cost",
|
|
909
|
+
"max_risk",
|
|
910
|
+
"min_gain",
|
|
911
|
+
"margin",
|
|
912
|
+
},
|
|
913
|
+
}
|
|
914
|
+
if kind == "gbdt_t_learner":
|
|
915
|
+
abstention = payload.get("abstention")
|
|
916
|
+
if not isinstance(abstention, dict):
|
|
917
|
+
raise ArtifactError("GBDT selector payload lacks abstention fields")
|
|
918
|
+
for field in ("min_gain", "margin"):
|
|
919
|
+
abstention.pop(field, None)
|
|
920
|
+
return normalized
|
|
921
|
+
try:
|
|
922
|
+
allowed = direct_fields[kind]
|
|
923
|
+
except KeyError as exc: # pragma: no cover - selector codec rejects unknown kinds
|
|
924
|
+
raise ArtifactError(f"unsupported selector calibration kind: {kind}") from exc
|
|
925
|
+
for field in allowed:
|
|
926
|
+
payload.pop(field, None)
|
|
927
|
+
return normalized
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def _kind_for_selector(selector: Selector) -> str:
|
|
931
|
+
try:
|
|
932
|
+
return _SELECTOR_KIND_BY_TYPE[type(selector)]
|
|
933
|
+
except KeyError as exc:
|
|
934
|
+
raise ValidationError(
|
|
935
|
+
f"unsupported selector artifact type: {type(selector).__name__}"
|
|
936
|
+
) from exc
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _encode_family_vectors(
|
|
940
|
+
values: Mapping[SemanticFamily, tuple[float, ...]],
|
|
941
|
+
) -> dict[str, list[float]]:
|
|
942
|
+
return {
|
|
943
|
+
family.value: list(vector)
|
|
944
|
+
for family, vector in sorted(values.items(), key=lambda item: item[0].value)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _encode_releases(values: Mapping[SemanticFamily, str]) -> dict[str, str]:
|
|
949
|
+
return {
|
|
950
|
+
family.value: release
|
|
951
|
+
for family, release in sorted(values.items(), key=lambda item: item[0].value)
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
def _encode_family_scalars(
|
|
956
|
+
values: Mapping[SemanticFamily, float],
|
|
957
|
+
) -> dict[str, float]:
|
|
958
|
+
return {
|
|
959
|
+
family.value: value
|
|
960
|
+
for family, value in sorted(values.items(), key=lambda item: item[0].value)
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _decode_family_vectors(
|
|
965
|
+
value: object,
|
|
966
|
+
*,
|
|
967
|
+
include_no_op: bool,
|
|
968
|
+
) -> dict[SemanticFamily, tuple[float, ...]]:
|
|
969
|
+
mapping = _string_mapping(value, label="family vectors")
|
|
970
|
+
return {
|
|
971
|
+
_family(name, include_no_op=include_no_op): tuple(
|
|
972
|
+
_number(item, label=f"{name} coefficient")
|
|
973
|
+
for item in _array(vector, label=f"{name} coefficients")
|
|
974
|
+
)
|
|
975
|
+
for name, vector in mapping.items()
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def _decode_releases(value: object) -> dict[SemanticFamily, str]:
|
|
980
|
+
mapping = _string_mapping(value, label="release_by_family")
|
|
981
|
+
releases = {
|
|
982
|
+
_family(name, include_no_op=False): _text(
|
|
983
|
+
release,
|
|
984
|
+
label=f"{name} release",
|
|
985
|
+
)
|
|
986
|
+
for name, release in mapping.items()
|
|
987
|
+
}
|
|
988
|
+
return _validated_representatives(releases)
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def _decode_family_scalars(
|
|
992
|
+
value: object,
|
|
993
|
+
*,
|
|
994
|
+
include_no_op: bool,
|
|
995
|
+
label: str,
|
|
996
|
+
) -> dict[SemanticFamily, float]:
|
|
997
|
+
mapping = _string_mapping(value, label=label)
|
|
998
|
+
return {
|
|
999
|
+
_family(name, include_no_op=include_no_op): _number(
|
|
1000
|
+
scalar,
|
|
1001
|
+
label=f"{label}.{name}",
|
|
1002
|
+
)
|
|
1003
|
+
for name, scalar in mapping.items()
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def _validated_representatives(
|
|
1008
|
+
values: Mapping[SemanticFamily, str],
|
|
1009
|
+
) -> dict[SemanticFamily, str]:
|
|
1010
|
+
copied = dict(values)
|
|
1011
|
+
if any(
|
|
1012
|
+
family not in _CORE_FAMILIES
|
|
1013
|
+
or not isinstance(release, str)
|
|
1014
|
+
or not release.strip()
|
|
1015
|
+
or release != release.strip()
|
|
1016
|
+
for family, release in copied.items()
|
|
1017
|
+
):
|
|
1018
|
+
raise ValidationError(
|
|
1019
|
+
"selector representatives require core families and trimmed release IDs"
|
|
1020
|
+
)
|
|
1021
|
+
return copied
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def _family(value: str, *, include_no_op: bool) -> SemanticFamily:
|
|
1025
|
+
try:
|
|
1026
|
+
family = SemanticFamily(value)
|
|
1027
|
+
except ValueError as exc:
|
|
1028
|
+
raise ValidationError(f"unknown selector family: {value!r}") from exc
|
|
1029
|
+
allowed = _CORE_FAMILIES | ({SemanticFamily.NO_OP} if include_no_op else set())
|
|
1030
|
+
if family not in allowed:
|
|
1031
|
+
raise ValidationError(f"family {value!r} is not valid in this selector artifact")
|
|
1032
|
+
return family
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def _strict_mapping(
|
|
1036
|
+
value: object,
|
|
1037
|
+
*,
|
|
1038
|
+
expected: set[str],
|
|
1039
|
+
label: str,
|
|
1040
|
+
) -> dict[str, Any]:
|
|
1041
|
+
mapping = _string_mapping(value, label=label)
|
|
1042
|
+
if set(mapping) != expected:
|
|
1043
|
+
raise ValidationError(
|
|
1044
|
+
f"{label} fields differ: expected {sorted(expected)!r}, got {sorted(mapping)!r}"
|
|
1045
|
+
)
|
|
1046
|
+
return mapping
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _string_mapping(value: object, *, label: str) -> dict[str, Any]:
|
|
1050
|
+
if not isinstance(value, Mapping) or any(not isinstance(key, str) for key in value):
|
|
1051
|
+
raise ValidationError(f"{label} must be an object with string keys")
|
|
1052
|
+
return dict(value)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def _array(value: object, *, label: str) -> list[Any]:
|
|
1056
|
+
if not isinstance(value, list):
|
|
1057
|
+
raise ValidationError(f"{label} must be a JSON array")
|
|
1058
|
+
return value
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def _text(value: object, *, label: str) -> str:
|
|
1062
|
+
if not isinstance(value, str) or not value.strip() or value != value.strip():
|
|
1063
|
+
raise ValidationError(f"{label} must be a trimmed non-empty string")
|
|
1064
|
+
return value
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _integer(value: object, *, label: str) -> int:
|
|
1068
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
1069
|
+
raise ValidationError(f"{label} must be a JSON integer")
|
|
1070
|
+
return value
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def _number(value: object, *, label: str) -> float:
|
|
1074
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
1075
|
+
raise ValidationError(f"{label} must be a finite JSON number")
|
|
1076
|
+
return float(value)
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
__all__ = [
|
|
1080
|
+
"SelectorArtifactBundle",
|
|
1081
|
+
"SelectorArtifactStage",
|
|
1082
|
+
"SelectorArtifactStore",
|
|
1083
|
+
"selector_card_id",
|
|
1084
|
+
"selector_from_artifact",
|
|
1085
|
+
"selector_model_hash",
|
|
1086
|
+
"selector_to_artifact",
|
|
1087
|
+
]
|