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,3739 @@
|
|
|
1
|
+
"""Production component builders for static cells and official external arenas.
|
|
2
|
+
|
|
3
|
+
The static experiment driver deliberately accepts operator-owned builders
|
|
4
|
+
instead of guessing deployments. This module provides a strict, reusable
|
|
5
|
+
implementation of the common production builders:
|
|
6
|
+
|
|
7
|
+
* :class:`ProductionBackendBuilder` creates a checksum-bound Replay,
|
|
8
|
+
OpenAI-compatible, or local Transformers backend. It may also attach a
|
|
9
|
+
benchmark-keyed set of checksum-bound retrievers or one checksum-bound
|
|
10
|
+
dense embedder/vector-search runtime for acquisition methods such as HyDE.
|
|
11
|
+
* :class:`PreparedCorpusRetrieverBuilder` builds BM25 or dense retrieval over
|
|
12
|
+
an explicit normalized corpus and checksum-locked local model assets.
|
|
13
|
+
* :class:`ProductionPredictorBuilder` creates the evaluator-blind downstream
|
|
14
|
+
predictor used after an intervention. Retrieval cells emit ranked document
|
|
15
|
+
scores; QA cells generate a strict answer/support/evidence JSON object from
|
|
16
|
+
visible state only.
|
|
17
|
+
|
|
18
|
+
Construction and preflight never contact a model endpoint or download model
|
|
19
|
+
weights. Secrets are accepted only through environment-variable names.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import hashlib
|
|
25
|
+
import importlib
|
|
26
|
+
import importlib.util
|
|
27
|
+
import json
|
|
28
|
+
import math
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
import stat
|
|
32
|
+
import urllib.parse
|
|
33
|
+
from collections.abc import Mapping, Sequence
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from functools import lru_cache
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from types import MappingProxyType
|
|
38
|
+
from typing import Any, Protocol, runtime_checkable
|
|
39
|
+
|
|
40
|
+
from agentinputlab.backends.llm import (
|
|
41
|
+
GenerationRequest,
|
|
42
|
+
GenerationResponse,
|
|
43
|
+
LLMBackend,
|
|
44
|
+
ModelCapability,
|
|
45
|
+
OpenAICompatibleBackend,
|
|
46
|
+
ReplayBackend,
|
|
47
|
+
TransformersBackend,
|
|
48
|
+
Usage,
|
|
49
|
+
require_capabilities,
|
|
50
|
+
)
|
|
51
|
+
from agentinputlab.backends.retrieval import (
|
|
52
|
+
Document,
|
|
53
|
+
RetrievalResult,
|
|
54
|
+
Retriever,
|
|
55
|
+
build_retriever,
|
|
56
|
+
retriever_profile,
|
|
57
|
+
)
|
|
58
|
+
from agentinputlab.backends.vector import DenseEmbedder, VectorSearcher
|
|
59
|
+
from agentinputlab.benchmarks import (
|
|
60
|
+
DownstreamPrediction,
|
|
61
|
+
QAPrediction,
|
|
62
|
+
RetrievalPrediction,
|
|
63
|
+
)
|
|
64
|
+
from agentinputlab.connectors.tools import SchemaOnlyToolConnector
|
|
65
|
+
from agentinputlab.contracts.errors import (
|
|
66
|
+
ArtifactError,
|
|
67
|
+
CapabilityError,
|
|
68
|
+
ExternalDependencyError,
|
|
69
|
+
ValidationError,
|
|
70
|
+
)
|
|
71
|
+
from agentinputlab.contracts.serialization import (
|
|
72
|
+
canonical_json,
|
|
73
|
+
content_hash,
|
|
74
|
+
freeze_mapping,
|
|
75
|
+
to_jsonable,
|
|
76
|
+
)
|
|
77
|
+
from agentinputlab.contracts.state import FrozenAgentState
|
|
78
|
+
from agentinputlab.experiments.drivers.static_production import (
|
|
79
|
+
StaticBackendBuildRequest,
|
|
80
|
+
StaticPredictorBuildRequest,
|
|
81
|
+
StaticRuntimeComponents,
|
|
82
|
+
static_predictor_product_sha256,
|
|
83
|
+
static_runtime_product_sha256,
|
|
84
|
+
)
|
|
85
|
+
from agentinputlab.experiments.loader import resolve_driver_entrypoint
|
|
86
|
+
from agentinputlab.observability import ResourceUsage
|
|
87
|
+
|
|
88
|
+
_SCHEMA_VERSION = 1
|
|
89
|
+
_BACKEND_IMPLEMENTATION = "agentinputlab.production_backend_builder.v4"
|
|
90
|
+
_PREDICTOR_IMPLEMENTATION = "agentinputlab.production_downstream_predictor.v7"
|
|
91
|
+
_CORPUS_RETRIEVER_IMPLEMENTATION = "agentinputlab.prepared_corpus_retriever_builder.v1"
|
|
92
|
+
_RETRIEVER_ROUTER_IMPLEMENTATION = "agentinputlab.production_retriever_router.v2"
|
|
93
|
+
_TRANSFORMERS_IMPLEMENTATION = "agentinputlab.local_only_transformers.v2"
|
|
94
|
+
_QUERYGYM_RUNTIME_IMPLEMENTATION = "agentinputlab.querygym.pinned_runtime.v1"
|
|
95
|
+
_RECOMP_RUNTIME_IMPLEMENTATION = "agentinputlab.recomp.current_transformers_loader.v1"
|
|
96
|
+
_MAX_REPLAY_BYTES = 64 * 1024 * 1024
|
|
97
|
+
_MAX_CORPUS_LINE_BYTES = 16 * 1024 * 1024
|
|
98
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
99
|
+
_ENTRYPOINT = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*:[A-Za-z_][A-Za-z0-9_.]*$")
|
|
100
|
+
_ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
101
|
+
_SENSITIVE_KEY = re.compile(
|
|
102
|
+
r"(?i)(?:^|[_-])(?:api[_-]?key|authorization|password|secret|"
|
|
103
|
+
r"access[_-]?token|bearer|credential)(?:$|[_-])"
|
|
104
|
+
)
|
|
105
|
+
_SECRET_VALUE_PATTERNS = (
|
|
106
|
+
re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]{8,}"),
|
|
107
|
+
re.compile(r"\bsk-[A-Za-z0-9_-]{8,}"),
|
|
108
|
+
re.compile(r"\bAIza[A-Za-z0-9_-]{20,}\b"),
|
|
109
|
+
re.compile(r"\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b"),
|
|
110
|
+
)
|
|
111
|
+
_SENSITIVE_HEADER = re.compile(
|
|
112
|
+
r"(?i)^(?:authorization|proxy-authorization|x-api-key|api-key|"
|
|
113
|
+
r"x-goog-api-key|x-auth-token|x-amz-security-token|cookie|set-cookie)$"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
_CELL_SPLITS = MappingProxyType(
|
|
117
|
+
{
|
|
118
|
+
"beir.arguana": "test",
|
|
119
|
+
"bright.earth_science": "test",
|
|
120
|
+
"bright.economics": "test",
|
|
121
|
+
"musique.answerable": "dev",
|
|
122
|
+
"2wiki.multihop": "dev",
|
|
123
|
+
"hotpotqa.distractor": "validation",
|
|
124
|
+
"qasper.v0_3_dev": "dev",
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
_RETRIEVAL_CELLS = frozenset(
|
|
128
|
+
{
|
|
129
|
+
"beir.arguana",
|
|
130
|
+
"bright.earth_science",
|
|
131
|
+
"bright.economics",
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
_QA_CELLS = frozenset(set(_CELL_SPLITS) - _RETRIEVAL_CELLS)
|
|
135
|
+
_INTERACTIVE_RETRIEVER_CELLS = frozenset(
|
|
136
|
+
{
|
|
137
|
+
"tau3.banking_knowledge",
|
|
138
|
+
"tau3.retail",
|
|
139
|
+
"state_bench.common_support",
|
|
140
|
+
"toolsandbox.common_support",
|
|
141
|
+
"tooltalk.full",
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
_RETRIEVER_ROUTE_CELLS = frozenset({*_CELL_SPLITS, *_INTERACTIVE_RETRIEVER_CELLS})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class ProductionRetrieverBuildRequest:
|
|
149
|
+
"""Typed request passed to an operator or built-in retriever builder."""
|
|
150
|
+
|
|
151
|
+
benchmark_id: str
|
|
152
|
+
model_slot: str
|
|
153
|
+
seed: int
|
|
154
|
+
builder_parameters: Mapping[str, Any]
|
|
155
|
+
|
|
156
|
+
def __post_init__(self) -> None:
|
|
157
|
+
_require_text(self.benchmark_id, label="retriever benchmark_id")
|
|
158
|
+
_require_text(self.model_slot, label="model_slot")
|
|
159
|
+
_require_seed(self.seed)
|
|
160
|
+
object.__setattr__(
|
|
161
|
+
self,
|
|
162
|
+
"builder_parameters",
|
|
163
|
+
_canonical_mapping(self.builder_parameters, label="retriever builder parameters"),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@runtime_checkable
|
|
168
|
+
class ProductionRetrieverBuilderProtocol(Protocol):
|
|
169
|
+
"""Builder seam used by :class:`ProductionRetrieverRouter`."""
|
|
170
|
+
|
|
171
|
+
builder_kind: str
|
|
172
|
+
|
|
173
|
+
def fingerprint(self) -> Mapping[str, Any]: ...
|
|
174
|
+
|
|
175
|
+
def preflight(self, parameters: Mapping[str, Any]) -> Sequence[str]: ...
|
|
176
|
+
|
|
177
|
+
def build(self, request: ProductionRetrieverBuildRequest) -> Retriever: ...
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True, slots=True)
|
|
181
|
+
class LockedRetrieverSpec:
|
|
182
|
+
"""One retriever builder and its exact expected runtime product."""
|
|
183
|
+
|
|
184
|
+
entrypoint: str
|
|
185
|
+
fingerprint_sha256: str
|
|
186
|
+
expected_product_sha256: str
|
|
187
|
+
parameters: Mapping[str, Any] = field(default_factory=dict)
|
|
188
|
+
|
|
189
|
+
def __post_init__(self) -> None:
|
|
190
|
+
if not isinstance(self.entrypoint, str) or _ENTRYPOINT.fullmatch(self.entrypoint) is None:
|
|
191
|
+
raise ValidationError("retriever entrypoint must use module:factory syntax")
|
|
192
|
+
_require_sha256(self.fingerprint_sha256, label="retriever fingerprint_sha256")
|
|
193
|
+
_require_sha256(
|
|
194
|
+
self.expected_product_sha256,
|
|
195
|
+
label="retriever expected_product_sha256",
|
|
196
|
+
)
|
|
197
|
+
_reject_embedded_secrets(self.parameters, path="retriever parameters")
|
|
198
|
+
object.__setattr__(
|
|
199
|
+
self,
|
|
200
|
+
"parameters",
|
|
201
|
+
_canonical_mapping(self.parameters, label="retriever parameters"),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_mapping(cls, value: Any, *, path: str) -> LockedRetrieverSpec:
|
|
206
|
+
data = _strict_mapping(
|
|
207
|
+
value,
|
|
208
|
+
path=path,
|
|
209
|
+
allowed={
|
|
210
|
+
"entrypoint",
|
|
211
|
+
"fingerprint_sha256",
|
|
212
|
+
"expected_product_sha256",
|
|
213
|
+
"parameters",
|
|
214
|
+
},
|
|
215
|
+
required={
|
|
216
|
+
"entrypoint",
|
|
217
|
+
"fingerprint_sha256",
|
|
218
|
+
"expected_product_sha256",
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
return cls(
|
|
222
|
+
entrypoint=_require_text(data["entrypoint"], label=f"{path}.entrypoint"),
|
|
223
|
+
fingerprint_sha256=_require_text(
|
|
224
|
+
data["fingerprint_sha256"],
|
|
225
|
+
label=f"{path}.fingerprint_sha256",
|
|
226
|
+
),
|
|
227
|
+
expected_product_sha256=_require_text(
|
|
228
|
+
data["expected_product_sha256"],
|
|
229
|
+
label=f"{path}.expected_product_sha256",
|
|
230
|
+
),
|
|
231
|
+
parameters=_mapping_or_empty(data.get("parameters"), path=f"{path}.parameters"),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def binding_identity(self) -> Mapping[str, Any]:
|
|
236
|
+
return freeze_mapping(
|
|
237
|
+
{
|
|
238
|
+
"entrypoint": self.entrypoint,
|
|
239
|
+
"fingerprint_sha256": self.fingerprint_sha256,
|
|
240
|
+
"expected_product_sha256": self.expected_product_sha256,
|
|
241
|
+
"parameters_sha256": content_hash(self.parameters),
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class _BoundLLMBackend:
|
|
247
|
+
"""Add an immutable operator binding without exposing secret values."""
|
|
248
|
+
|
|
249
|
+
__slots__ = ("_binding", "_inner")
|
|
250
|
+
|
|
251
|
+
def __init__(self, inner: LLMBackend, binding: Mapping[str, Any]) -> None:
|
|
252
|
+
if not isinstance(inner, LLMBackend):
|
|
253
|
+
raise TypeError("bound backend requires an LLMBackend")
|
|
254
|
+
self._inner = inner
|
|
255
|
+
self._binding = _canonical_mapping(binding, label="backend binding")
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def model_id(self) -> str:
|
|
259
|
+
return self._inner.model_id
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def capabilities(self) -> frozenset[ModelCapability]:
|
|
263
|
+
return self._inner.capabilities
|
|
264
|
+
|
|
265
|
+
def generate(self, request: GenerationRequest) -> GenerationResponse:
|
|
266
|
+
return self._inner.generate(request)
|
|
267
|
+
|
|
268
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
269
|
+
return {
|
|
270
|
+
"implementation": "agentinputlab.bound_llm_backend.v1",
|
|
271
|
+
"backend": _fingerprint(self._inner, label="inner backend"),
|
|
272
|
+
"binding": self._binding,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class _LocalOnlyTransformersBackend(TransformersBackend):
|
|
277
|
+
"""Transformers backend bound to one complete local snapshot tree."""
|
|
278
|
+
|
|
279
|
+
__slots__ = (
|
|
280
|
+
"_artifact_files",
|
|
281
|
+
"_model_path",
|
|
282
|
+
"_snapshot_sha256",
|
|
283
|
+
"local_files_only",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def __init__(
|
|
287
|
+
self,
|
|
288
|
+
*,
|
|
289
|
+
model_id: str,
|
|
290
|
+
model_path: Path,
|
|
291
|
+
model_revision: str,
|
|
292
|
+
tokenizer_revision: str,
|
|
293
|
+
artifact_files: tuple[Mapping[str, Any], ...],
|
|
294
|
+
generation_kwargs: Mapping[str, Any],
|
|
295
|
+
capabilities: frozenset[ModelCapability],
|
|
296
|
+
local_files_only: bool,
|
|
297
|
+
) -> None:
|
|
298
|
+
super().__init__(
|
|
299
|
+
model_id=model_id,
|
|
300
|
+
model_revision=model_revision,
|
|
301
|
+
tokenizer_revision=tokenizer_revision,
|
|
302
|
+
generation_kwargs=generation_kwargs,
|
|
303
|
+
capabilities=capabilities,
|
|
304
|
+
)
|
|
305
|
+
if not isinstance(local_files_only, bool):
|
|
306
|
+
raise ValidationError("transformers local_files_only must be boolean")
|
|
307
|
+
if not isinstance(model_path, Path) or not model_path.is_absolute():
|
|
308
|
+
raise ValidationError("transformers model_path must be an absolute Path")
|
|
309
|
+
self._model_path = model_path
|
|
310
|
+
self._artifact_files = artifact_files
|
|
311
|
+
self._snapshot_sha256 = _transformers_snapshot_sha256(artifact_files)
|
|
312
|
+
self.local_files_only = local_files_only
|
|
313
|
+
|
|
314
|
+
def _load(self) -> None:
|
|
315
|
+
if self.model is not None and self.tokenizer is not None:
|
|
316
|
+
return
|
|
317
|
+
_verify_transformers_snapshot(
|
|
318
|
+
self._model_path,
|
|
319
|
+
self._artifact_files,
|
|
320
|
+
)
|
|
321
|
+
try:
|
|
322
|
+
transformers = importlib.import_module("transformers")
|
|
323
|
+
except ImportError as exc:
|
|
324
|
+
raise ExternalDependencyError(
|
|
325
|
+
"Transformers backend requires `pip install agentinputlab[local]`"
|
|
326
|
+
) from exc
|
|
327
|
+
tokenizer_factory = getattr(transformers, "AutoTokenizer", None)
|
|
328
|
+
model_factory = getattr(transformers, "AutoModelForCausalLM", None)
|
|
329
|
+
if tokenizer_factory is None or model_factory is None:
|
|
330
|
+
raise ExternalDependencyError(
|
|
331
|
+
"transformers does not expose AutoTokenizer and AutoModelForCausalLM"
|
|
332
|
+
)
|
|
333
|
+
try:
|
|
334
|
+
self.tokenizer = tokenizer_factory.from_pretrained(
|
|
335
|
+
str(self._model_path),
|
|
336
|
+
revision=self.tokenizer_revision,
|
|
337
|
+
trust_remote_code=False,
|
|
338
|
+
local_files_only=self.local_files_only,
|
|
339
|
+
)
|
|
340
|
+
self.model = model_factory.from_pretrained(
|
|
341
|
+
str(self._model_path),
|
|
342
|
+
revision=self.model_revision,
|
|
343
|
+
trust_remote_code=False,
|
|
344
|
+
local_files_only=self.local_files_only,
|
|
345
|
+
)
|
|
346
|
+
except Exception as exc:
|
|
347
|
+
raise ExternalDependencyError(
|
|
348
|
+
"the checksum-bound Transformers deployment is unavailable locally"
|
|
349
|
+
) from exc
|
|
350
|
+
|
|
351
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
352
|
+
return {
|
|
353
|
+
**dict(super().fingerprint()),
|
|
354
|
+
"implementation": _TRANSFORMERS_IMPLEMENTATION,
|
|
355
|
+
"local_files_only": self.local_files_only,
|
|
356
|
+
"snapshot_sha256": self._snapshot_sha256,
|
|
357
|
+
"trust_remote_code": False,
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class ProductionRetrieverRouter:
|
|
362
|
+
"""Lazy, benchmark-keyed retriever with an identity stable across binding.
|
|
363
|
+
|
|
364
|
+
``StaticBackendBuildRequest`` intentionally contains a model slot but not a
|
|
365
|
+
benchmark ID. The predictor builder therefore binds this router exactly
|
|
366
|
+
once using the driver-validated benchmark ID. The fingerprint covers all
|
|
367
|
+
available routes and expected products and does not change after binding.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
retriever_id = "production-retriever-router"
|
|
371
|
+
|
|
372
|
+
def __init__(
|
|
373
|
+
self,
|
|
374
|
+
specs: Mapping[str, LockedRetrieverSpec],
|
|
375
|
+
*,
|
|
376
|
+
model_slot: str,
|
|
377
|
+
seed: int,
|
|
378
|
+
) -> None:
|
|
379
|
+
routes = dict(specs)
|
|
380
|
+
if not routes:
|
|
381
|
+
raise ValidationError("production retriever router requires at least one route")
|
|
382
|
+
for benchmark_id, spec in routes.items():
|
|
383
|
+
_require_retriever_cell(benchmark_id)
|
|
384
|
+
if not isinstance(spec, LockedRetrieverSpec):
|
|
385
|
+
raise TypeError("retriever route must contain LockedRetrieverSpec values")
|
|
386
|
+
_require_text(model_slot, label="model_slot")
|
|
387
|
+
_require_seed(seed)
|
|
388
|
+
self._specs = MappingProxyType(routes)
|
|
389
|
+
self._model_slot = model_slot
|
|
390
|
+
self._seed = seed
|
|
391
|
+
self._active_benchmark_id: str | None = None
|
|
392
|
+
self._instances: dict[str, Retriever] = {}
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def route_ids(self) -> tuple[str, ...]:
|
|
396
|
+
return tuple(sorted(self._specs))
|
|
397
|
+
|
|
398
|
+
def has_route(self, benchmark_id: str) -> bool:
|
|
399
|
+
return benchmark_id in self._specs
|
|
400
|
+
|
|
401
|
+
def bind(self, benchmark_id: str) -> None:
|
|
402
|
+
_require_retriever_cell(benchmark_id)
|
|
403
|
+
if benchmark_id not in self._specs:
|
|
404
|
+
raise CapabilityError(
|
|
405
|
+
f"runtime has no checksum-bound retriever route for {benchmark_id!r}"
|
|
406
|
+
)
|
|
407
|
+
if self._active_benchmark_id is not None and self._active_benchmark_id != benchmark_id:
|
|
408
|
+
raise ValidationError("one runtime retriever router cannot be rebound to another cell")
|
|
409
|
+
self._active_benchmark_id = benchmark_id
|
|
410
|
+
|
|
411
|
+
def retrieve(self, query: str, *, top_k: int = 5) -> tuple[RetrievalResult, ...]:
|
|
412
|
+
if self._active_benchmark_id is None:
|
|
413
|
+
raise CapabilityError(
|
|
414
|
+
"production retriever route is unbound; build a benchmark predictor first"
|
|
415
|
+
)
|
|
416
|
+
retriever = self._retriever_for(self._active_benchmark_id)
|
|
417
|
+
return retriever.retrieve(query, top_k=top_k)
|
|
418
|
+
|
|
419
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
420
|
+
return {
|
|
421
|
+
"implementation": _RETRIEVER_ROUTER_IMPLEMENTATION,
|
|
422
|
+
"retriever_id": self.retriever_id,
|
|
423
|
+
"binding_policy": "driver_validated_benchmark_id.v1",
|
|
424
|
+
"routes": {
|
|
425
|
+
benchmark_id: spec.binding_identity
|
|
426
|
+
for benchmark_id, spec in sorted(self._specs.items())
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
def _retriever_for(self, benchmark_id: str) -> Retriever:
|
|
431
|
+
cached = self._instances.get(benchmark_id)
|
|
432
|
+
if cached is not None:
|
|
433
|
+
return cached
|
|
434
|
+
spec = self._specs[benchmark_id]
|
|
435
|
+
builder = _resolve_retriever_builder(spec)
|
|
436
|
+
product = builder.build(
|
|
437
|
+
ProductionRetrieverBuildRequest(
|
|
438
|
+
benchmark_id=benchmark_id,
|
|
439
|
+
model_slot=self._model_slot,
|
|
440
|
+
seed=self._seed,
|
|
441
|
+
builder_parameters=spec.parameters,
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
if not isinstance(product, Retriever):
|
|
445
|
+
raise ValidationError(
|
|
446
|
+
"production retriever builder must return a Retriever, "
|
|
447
|
+
f"got {type(product).__name__}"
|
|
448
|
+
)
|
|
449
|
+
observed = retriever_product_sha256(product)
|
|
450
|
+
_validate_product_hash(
|
|
451
|
+
observed,
|
|
452
|
+
spec.expected_product_sha256,
|
|
453
|
+
label=f"retriever route {benchmark_id}",
|
|
454
|
+
)
|
|
455
|
+
self._instances[benchmark_id] = product
|
|
456
|
+
return product
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class _BoundRetriever:
|
|
460
|
+
"""Bind local corpus/model receipts to a built-in retriever fingerprint."""
|
|
461
|
+
|
|
462
|
+
__slots__ = ("_binding", "_inner")
|
|
463
|
+
|
|
464
|
+
def __init__(self, inner: Retriever, binding: Mapping[str, Any]) -> None:
|
|
465
|
+
if not isinstance(inner, Retriever):
|
|
466
|
+
raise TypeError("bound retriever requires a Retriever")
|
|
467
|
+
self._inner = inner
|
|
468
|
+
self._binding = _canonical_mapping(binding, label="retriever binding")
|
|
469
|
+
|
|
470
|
+
@property
|
|
471
|
+
def retriever_id(self) -> str:
|
|
472
|
+
return self._inner.retriever_id
|
|
473
|
+
|
|
474
|
+
def retrieve(self, query: str, *, top_k: int = 5) -> tuple[RetrievalResult, ...]:
|
|
475
|
+
return self._inner.retrieve(query, top_k=top_k)
|
|
476
|
+
|
|
477
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
478
|
+
return {
|
|
479
|
+
"implementation": "agentinputlab.bound_retriever.v1",
|
|
480
|
+
"retriever": _fingerprint(self._inner, label="inner retriever"),
|
|
481
|
+
"binding": self._binding,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class _BoundVectorRuntime(_BoundRetriever):
|
|
486
|
+
"""Bind one dense embedder/index pair to corpus and model asset receipts."""
|
|
487
|
+
|
|
488
|
+
def __init__(self, inner: Retriever, binding: Mapping[str, Any]) -> None:
|
|
489
|
+
super().__init__(inner, binding)
|
|
490
|
+
if not isinstance(inner, DenseEmbedder) or not isinstance(inner, VectorSearcher):
|
|
491
|
+
raise TypeError("bound vector runtime requires dense embedder and searcher protocols")
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def embedder_id(self) -> str:
|
|
495
|
+
return self._inner.embedder_id
|
|
496
|
+
|
|
497
|
+
@property
|
|
498
|
+
def searcher_id(self) -> str:
|
|
499
|
+
return self._inner.searcher_id
|
|
500
|
+
|
|
501
|
+
def embed(self, texts: Sequence[str]) -> Sequence[Sequence[float]]:
|
|
502
|
+
return self._inner.embed(texts)
|
|
503
|
+
|
|
504
|
+
def search(
|
|
505
|
+
self,
|
|
506
|
+
vector: Sequence[float],
|
|
507
|
+
*,
|
|
508
|
+
top_k: int,
|
|
509
|
+
) -> Sequence[RetrievalResult]:
|
|
510
|
+
return self._inner.search(vector, top_k=top_k)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class ProductionBackendBuilder:
|
|
514
|
+
"""Strict backend/runtime builder used by static production experiments."""
|
|
515
|
+
|
|
516
|
+
builder_kind = "backend"
|
|
517
|
+
|
|
518
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
519
|
+
return {
|
|
520
|
+
"implementation": _BACKEND_IMPLEMENTATION,
|
|
521
|
+
"schema_version": _SCHEMA_VERSION,
|
|
522
|
+
"backend_kinds": ("openai_compatible", "replay", "transformers"),
|
|
523
|
+
"tool_connector_kinds": ("schema_only",),
|
|
524
|
+
"external_runtime_kinds": (
|
|
525
|
+
"querygym_query2doc",
|
|
526
|
+
"qrag_common_support",
|
|
527
|
+
"r2ag_common_support",
|
|
528
|
+
"ask_or_assume_worker",
|
|
529
|
+
"acon_observation_worker",
|
|
530
|
+
"reformer_worker",
|
|
531
|
+
"infocqr_worker",
|
|
532
|
+
"recomp_extractive_worker",
|
|
533
|
+
"smartsearch_worker",
|
|
534
|
+
),
|
|
535
|
+
"secret_policy": "environment_indirection_only.v1",
|
|
536
|
+
"network_policy": "no_preflight_or_construction_network.v1",
|
|
537
|
+
"retriever_binding": _RETRIEVER_ROUTER_IMPLEMENTATION,
|
|
538
|
+
"vector_runtime_binding": "checksum_locked_dense_embedder_and_index.v1",
|
|
539
|
+
"recomp_compatibility_loader": _RECOMP_RUNTIME_IMPLEMENTATION,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
def preflight(self, parameters: Mapping[str, Any]) -> Sequence[str]:
|
|
543
|
+
try:
|
|
544
|
+
config = _parse_backend_parameters(parameters)
|
|
545
|
+
except (ArtifactError, TypeError, ValueError) as exc:
|
|
546
|
+
return (f"invalid production backend configuration: {exc}",)
|
|
547
|
+
return tuple(_backend_preflight_blockers(config))
|
|
548
|
+
|
|
549
|
+
def build(self, request: StaticBackendBuildRequest) -> StaticRuntimeComponents:
|
|
550
|
+
if not isinstance(request, StaticBackendBuildRequest):
|
|
551
|
+
raise TypeError("production backend builder requires StaticBackendBuildRequest")
|
|
552
|
+
config = _parse_backend_parameters(request.builder_parameters)
|
|
553
|
+
blockers = _backend_preflight_blockers(config)
|
|
554
|
+
if blockers:
|
|
555
|
+
raise ArtifactError("production backend preflight failed: " + "; ".join(blockers))
|
|
556
|
+
backend = _build_llm_backend(config["backend"])
|
|
557
|
+
required = config["required_capabilities"]
|
|
558
|
+
require_capabilities(backend, required)
|
|
559
|
+
external_runtime = _build_external_runtime(config["external_runtime"], backend)
|
|
560
|
+
retriever_specs = config["retrievers"]
|
|
561
|
+
retriever = (
|
|
562
|
+
ProductionRetrieverRouter(
|
|
563
|
+
retriever_specs,
|
|
564
|
+
model_slot=request.model_slot,
|
|
565
|
+
seed=request.seed,
|
|
566
|
+
)
|
|
567
|
+
if retriever_specs
|
|
568
|
+
else None
|
|
569
|
+
)
|
|
570
|
+
vector_runtime = _build_locked_vector_runtime(
|
|
571
|
+
config["vector_runtime"],
|
|
572
|
+
model_slot=request.model_slot,
|
|
573
|
+
seed=request.seed,
|
|
574
|
+
)
|
|
575
|
+
tools = _build_tool_connector(config["tools"])
|
|
576
|
+
return StaticRuntimeComponents(
|
|
577
|
+
backend=backend,
|
|
578
|
+
dense_embedder=vector_runtime,
|
|
579
|
+
retriever=retriever,
|
|
580
|
+
vector_searcher=vector_runtime,
|
|
581
|
+
tools=tools,
|
|
582
|
+
external_runtime=external_runtime,
|
|
583
|
+
run_config=config["run_config"],
|
|
584
|
+
strict_intent=config["strict_intent"],
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
def product_sha256(self, request: StaticBackendBuildRequest) -> str:
|
|
588
|
+
"""Build, validate, and hash the exact runtime product."""
|
|
589
|
+
|
|
590
|
+
return static_runtime_product_sha256(self.build(request))
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _build_locked_vector_runtime(
|
|
594
|
+
spec: LockedRetrieverSpec | None,
|
|
595
|
+
*,
|
|
596
|
+
model_slot: str,
|
|
597
|
+
seed: int,
|
|
598
|
+
) -> DenseEmbedder | VectorSearcher | None:
|
|
599
|
+
if spec is None:
|
|
600
|
+
return None
|
|
601
|
+
benchmark_id = _require_text(
|
|
602
|
+
spec.parameters.get("benchmark_id"),
|
|
603
|
+
label="vector runtime benchmark_id",
|
|
604
|
+
)
|
|
605
|
+
builder = _resolve_retriever_builder(spec)
|
|
606
|
+
product = builder.build(
|
|
607
|
+
ProductionRetrieverBuildRequest(
|
|
608
|
+
benchmark_id=benchmark_id,
|
|
609
|
+
model_slot=model_slot,
|
|
610
|
+
seed=seed,
|
|
611
|
+
builder_parameters=spec.parameters,
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
if not isinstance(product, DenseEmbedder) or not isinstance(product, VectorSearcher):
|
|
615
|
+
raise ValidationError(
|
|
616
|
+
"production vector runtime must implement DenseEmbedder and VectorSearcher"
|
|
617
|
+
)
|
|
618
|
+
observed = retriever_product_sha256(product)
|
|
619
|
+
_validate_product_hash(observed, spec.expected_product_sha256, label="vector runtime")
|
|
620
|
+
return product
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _build_tool_connector(config: Mapping[str, Any] | None) -> SchemaOnlyToolConnector | None:
|
|
624
|
+
if config is None:
|
|
625
|
+
return None
|
|
626
|
+
if config["kind"] != "schema_only": # pragma: no cover - parser invariant
|
|
627
|
+
raise ValidationError("unsupported production tool connector kind")
|
|
628
|
+
return SchemaOnlyToolConnector.from_openai_definitions(
|
|
629
|
+
config["definitions"],
|
|
630
|
+
connector_id=config["connector_id"],
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
class PreparedCorpusRetrieverBuilder:
|
|
635
|
+
"""Build BM25/BGE/E5/Qwen/Contriever over one locked normalized corpus."""
|
|
636
|
+
|
|
637
|
+
builder_kind = "retriever"
|
|
638
|
+
|
|
639
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
640
|
+
return {
|
|
641
|
+
"implementation": _CORPUS_RETRIEVER_IMPLEMENTATION,
|
|
642
|
+
"schema_version": _SCHEMA_VERSION,
|
|
643
|
+
"corpus_schema": "id-title-text.jsonl.v1",
|
|
644
|
+
"network_policy": "local_files_only.v1",
|
|
645
|
+
"retriever_factory": "agentinputlab.build_retriever.v1",
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
def preflight(self, parameters: Mapping[str, Any]) -> Sequence[str]:
|
|
649
|
+
try:
|
|
650
|
+
config = _parse_corpus_retriever_parameters(parameters)
|
|
651
|
+
except (ArtifactError, TypeError, ValueError) as exc:
|
|
652
|
+
return (f"invalid prepared corpus retriever configuration: {exc}",)
|
|
653
|
+
blockers: list[str] = []
|
|
654
|
+
try:
|
|
655
|
+
documents = _load_locked_corpus(config)
|
|
656
|
+
if len(documents) != config["expected_document_count"]:
|
|
657
|
+
blockers.append(
|
|
658
|
+
"prepared corpus document count mismatch: "
|
|
659
|
+
f"expected {config['expected_document_count']}, got {len(documents)}"
|
|
660
|
+
)
|
|
661
|
+
except (ArtifactError, OSError, UnicodeError, ValueError) as exc:
|
|
662
|
+
blockers.append(str(exc))
|
|
663
|
+
profile = retriever_profile(config["profile_id"])
|
|
664
|
+
if profile.backend == "sentence_transformers_dense":
|
|
665
|
+
if importlib.util.find_spec("sentence_transformers") is None:
|
|
666
|
+
blockers.append(
|
|
667
|
+
"sentence-transformers is unavailable; install agentinputlab[dense-retrieval]"
|
|
668
|
+
)
|
|
669
|
+
if not config["artifact_files"]:
|
|
670
|
+
blockers.append(
|
|
671
|
+
"dense production retrieval requires checksum-locked artifact_files"
|
|
672
|
+
)
|
|
673
|
+
for artifact in config["artifact_files"]:
|
|
674
|
+
try:
|
|
675
|
+
_verify_checksum_file(
|
|
676
|
+
artifact["path"],
|
|
677
|
+
artifact["sha256"],
|
|
678
|
+
label="retriever artifact",
|
|
679
|
+
)
|
|
680
|
+
except ArtifactError as exc:
|
|
681
|
+
blockers.append(str(exc))
|
|
682
|
+
return tuple(blockers)
|
|
683
|
+
|
|
684
|
+
def build(self, request: ProductionRetrieverBuildRequest) -> Retriever:
|
|
685
|
+
if not isinstance(request, ProductionRetrieverBuildRequest):
|
|
686
|
+
raise TypeError(
|
|
687
|
+
"prepared corpus retriever builder requires ProductionRetrieverBuildRequest"
|
|
688
|
+
)
|
|
689
|
+
config = _parse_corpus_retriever_parameters(request.builder_parameters)
|
|
690
|
+
if config["benchmark_id"] != request.benchmark_id:
|
|
691
|
+
raise ValidationError(
|
|
692
|
+
"prepared corpus retriever benchmark differs from the requested route"
|
|
693
|
+
)
|
|
694
|
+
blockers = self.preflight(request.builder_parameters)
|
|
695
|
+
if blockers:
|
|
696
|
+
raise ArtifactError(
|
|
697
|
+
"prepared corpus retriever preflight failed: " + "; ".join(blockers)
|
|
698
|
+
)
|
|
699
|
+
documents = _load_locked_corpus(config)
|
|
700
|
+
retriever = build_retriever(
|
|
701
|
+
config["profile_id"],
|
|
702
|
+
documents,
|
|
703
|
+
**dict(config["options"]),
|
|
704
|
+
)
|
|
705
|
+
bound_type = (
|
|
706
|
+
_BoundVectorRuntime
|
|
707
|
+
if retriever_profile(config["profile_id"]).backend == "sentence_transformers_dense"
|
|
708
|
+
else _BoundRetriever
|
|
709
|
+
)
|
|
710
|
+
return bound_type(
|
|
711
|
+
retriever,
|
|
712
|
+
{
|
|
713
|
+
"builder": _CORPUS_RETRIEVER_IMPLEMENTATION,
|
|
714
|
+
"benchmark_id": request.benchmark_id,
|
|
715
|
+
"profile_id": config["profile_id"],
|
|
716
|
+
"corpus_sha256": config["corpus_sha256"],
|
|
717
|
+
"corpus_revision": config["corpus_revision"],
|
|
718
|
+
"document_count": len(documents),
|
|
719
|
+
"artifact_files": tuple(
|
|
720
|
+
{
|
|
721
|
+
"name": artifact["name"],
|
|
722
|
+
"sha256": artifact["sha256"],
|
|
723
|
+
}
|
|
724
|
+
for artifact in config["artifact_files"]
|
|
725
|
+
),
|
|
726
|
+
"options": config["options"],
|
|
727
|
+
},
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
def product_sha256(self, request: ProductionRetrieverBuildRequest) -> str:
|
|
731
|
+
return retriever_product_sha256(self.build(request))
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
class ProductionPredictorBuilder:
|
|
735
|
+
"""Build one dataset-aware predictor without exposing evaluator gold."""
|
|
736
|
+
|
|
737
|
+
builder_kind = "predictor"
|
|
738
|
+
|
|
739
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
740
|
+
return {
|
|
741
|
+
"implementation": "agentinputlab.production_predictor_builder.v1",
|
|
742
|
+
"schema_version": _SCHEMA_VERSION,
|
|
743
|
+
"predictor_implementation": _PREDICTOR_IMPLEMENTATION,
|
|
744
|
+
"supported_cells": tuple(sorted(_CELL_SPLITS)),
|
|
745
|
+
"gold_boundary": "frozen_agent_state_only.v1",
|
|
746
|
+
"qa_output": (
|
|
747
|
+
"structured_json_object.v1",
|
|
748
|
+
"structured_json_schema.v1",
|
|
749
|
+
"answer_only.v1",
|
|
750
|
+
),
|
|
751
|
+
"retrieval_output": "ranked_document_scores.v1",
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
def preflight(self, parameters: Mapping[str, Any]) -> Sequence[str]:
|
|
755
|
+
try:
|
|
756
|
+
_parse_predictor_parameters(parameters)
|
|
757
|
+
except (ArtifactError, TypeError, ValueError) as exc:
|
|
758
|
+
return (f"invalid production predictor configuration: {exc}",)
|
|
759
|
+
return ()
|
|
760
|
+
|
|
761
|
+
def build(self, request: StaticPredictorBuildRequest) -> ProductionDownstreamPredictor:
|
|
762
|
+
if not isinstance(request, StaticPredictorBuildRequest):
|
|
763
|
+
raise TypeError("production predictor builder requires StaticPredictorBuildRequest")
|
|
764
|
+
config = _parse_predictor_parameters(request.builder_parameters)
|
|
765
|
+
_require_cell(request.benchmark_id)
|
|
766
|
+
expected_split = _CELL_SPLITS[request.benchmark_id]
|
|
767
|
+
if request.split != expected_split:
|
|
768
|
+
raise ValidationError(
|
|
769
|
+
f"production predictor expects split {expected_split!r} for "
|
|
770
|
+
f"{request.benchmark_id!r}"
|
|
771
|
+
)
|
|
772
|
+
inferred_task = "retrieval" if request.benchmark_id in _RETRIEVAL_CELLS else "qa"
|
|
773
|
+
if config["task"] not in {"auto", inferred_task}:
|
|
774
|
+
raise ValidationError(
|
|
775
|
+
f"predictor task {config['task']!r} is incompatible with {request.benchmark_id!r}"
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
retriever = request.runtime.retriever
|
|
779
|
+
if isinstance(retriever, ProductionRetrieverRouter) and retriever.has_route(
|
|
780
|
+
request.benchmark_id
|
|
781
|
+
):
|
|
782
|
+
retriever.bind(request.benchmark_id)
|
|
783
|
+
if inferred_task == "retrieval":
|
|
784
|
+
if retriever is None or not isinstance(retriever, Retriever):
|
|
785
|
+
raise CapabilityError(
|
|
786
|
+
f"retrieval cell {request.benchmark_id!r} requires a locked runtime retriever"
|
|
787
|
+
)
|
|
788
|
+
if isinstance(retriever, ProductionRetrieverRouter):
|
|
789
|
+
retriever.bind(request.benchmark_id)
|
|
790
|
+
else:
|
|
791
|
+
backend = request.runtime.backend
|
|
792
|
+
if not isinstance(backend, LLMBackend):
|
|
793
|
+
raise CapabilityError("QA prediction requires an LLMBackend")
|
|
794
|
+
required = set(config["qa"]["required_capabilities"])
|
|
795
|
+
if config["qa"]["use_seed"]:
|
|
796
|
+
required.add(ModelCapability.SEED)
|
|
797
|
+
require_capabilities(backend, required)
|
|
798
|
+
|
|
799
|
+
return ProductionDownstreamPredictor(
|
|
800
|
+
benchmark_id=request.benchmark_id,
|
|
801
|
+
split=request.split,
|
|
802
|
+
seed=request.seed,
|
|
803
|
+
task=inferred_task,
|
|
804
|
+
runtime=request.runtime,
|
|
805
|
+
config=config,
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
def product_sha256(self, request: StaticPredictorBuildRequest) -> str:
|
|
809
|
+
return static_predictor_product_sha256(self.build(request))
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
class ProductionDownstreamPredictor:
|
|
813
|
+
"""Evaluator-blind predictor shared by all seven static cells."""
|
|
814
|
+
|
|
815
|
+
__slots__ = ("_benchmark_id", "_config", "_runtime", "_seed", "_split", "_task")
|
|
816
|
+
|
|
817
|
+
def __init__(
|
|
818
|
+
self,
|
|
819
|
+
*,
|
|
820
|
+
benchmark_id: str,
|
|
821
|
+
split: str,
|
|
822
|
+
seed: int,
|
|
823
|
+
task: str,
|
|
824
|
+
runtime: StaticRuntimeComponents,
|
|
825
|
+
config: Mapping[str, Any],
|
|
826
|
+
) -> None:
|
|
827
|
+
_require_cell(benchmark_id)
|
|
828
|
+
if split != _CELL_SPLITS[benchmark_id]:
|
|
829
|
+
raise ValidationError("production predictor split differs from the locked cell")
|
|
830
|
+
_require_seed(seed)
|
|
831
|
+
if task not in {"qa", "retrieval"}:
|
|
832
|
+
raise ValidationError("production predictor task must be qa or retrieval")
|
|
833
|
+
if not isinstance(runtime, StaticRuntimeComponents):
|
|
834
|
+
raise TypeError("production predictor requires StaticRuntimeComponents")
|
|
835
|
+
self._benchmark_id = benchmark_id
|
|
836
|
+
self._split = split
|
|
837
|
+
self._seed = seed
|
|
838
|
+
self._task = task
|
|
839
|
+
self._runtime = runtime
|
|
840
|
+
self._config = _canonical_mapping(config, label="predictor configuration")
|
|
841
|
+
|
|
842
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
843
|
+
return {
|
|
844
|
+
"implementation": _PREDICTOR_IMPLEMENTATION,
|
|
845
|
+
"benchmark_id": self._benchmark_id,
|
|
846
|
+
"split": self._split,
|
|
847
|
+
"task": self._task,
|
|
848
|
+
"configuration_sha256": content_hash(self._config),
|
|
849
|
+
# The concrete backend/retriever is already locked by the model-slot
|
|
850
|
+
# runtime product. Keeping it out of the predictor recipe allows one
|
|
851
|
+
# predictor lock to be reused fairly across multiple model slots.
|
|
852
|
+
"runtime_binding_slot": "backend" if self._task == "qa" else "retriever",
|
|
853
|
+
"visible_input_schema": "frozen_agent_state_without_task_or_metadata.v1",
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
def predict(self, state: FrozenAgentState) -> DownstreamPrediction:
|
|
857
|
+
if not isinstance(state, FrozenAgentState):
|
|
858
|
+
raise TypeError("production downstream predictor requires FrozenAgentState")
|
|
859
|
+
if self._task == "retrieval":
|
|
860
|
+
return self._predict_retrieval(state)
|
|
861
|
+
return self._predict_qa(state)
|
|
862
|
+
|
|
863
|
+
def _predict_retrieval(self, state: FrozenAgentState) -> DownstreamPrediction:
|
|
864
|
+
options = self._config["retrieval"]
|
|
865
|
+
if options["source_mode"] == "context_or_retriever":
|
|
866
|
+
context_ranking = _retrieval_prediction_from_context(
|
|
867
|
+
state,
|
|
868
|
+
top_k=options["top_k"],
|
|
869
|
+
)
|
|
870
|
+
if context_ranking is not None:
|
|
871
|
+
prediction, document_ids = context_ranking
|
|
872
|
+
return DownstreamPrediction(
|
|
873
|
+
prediction=prediction,
|
|
874
|
+
usage=ResourceUsage(),
|
|
875
|
+
metadata={
|
|
876
|
+
"prediction_kind": "retrieval_visible_context",
|
|
877
|
+
"visible_context_sha256": content_hash(
|
|
878
|
+
tuple(
|
|
879
|
+
{
|
|
880
|
+
"block_id": block.block_id,
|
|
881
|
+
"text": block.text,
|
|
882
|
+
"metadata": block.metadata,
|
|
883
|
+
}
|
|
884
|
+
for block in state.context
|
|
885
|
+
)
|
|
886
|
+
),
|
|
887
|
+
"returned_documents": len(document_ids),
|
|
888
|
+
"top_k": options["top_k"],
|
|
889
|
+
},
|
|
890
|
+
)
|
|
891
|
+
retriever = self._runtime.retriever
|
|
892
|
+
if not isinstance(retriever, Retriever):
|
|
893
|
+
raise CapabilityError("production retrieval predictor has no runtime retriever")
|
|
894
|
+
if state.budget.max_retrieval_calls < 1:
|
|
895
|
+
raise CapabilityError("retrieval prediction has no remaining retrieval-call budget")
|
|
896
|
+
query = (
|
|
897
|
+
state.query
|
|
898
|
+
if options["query_mode"] == "query_only"
|
|
899
|
+
else "\n".join(part for part in (state.instruction, state.query) if part)
|
|
900
|
+
)
|
|
901
|
+
if not query.strip():
|
|
902
|
+
raise ValidationError("production retrieval query is empty")
|
|
903
|
+
results = tuple(retriever.retrieve(query, top_k=options["top_k"]))
|
|
904
|
+
if any(not isinstance(item, RetrievalResult) for item in results):
|
|
905
|
+
raise ValidationError("runtime retriever returned a non-RetrievalResult")
|
|
906
|
+
document_ids = tuple(item.document.doc_id for item in results)
|
|
907
|
+
if len(document_ids) != len(set(document_ids)):
|
|
908
|
+
raise ValidationError("runtime retriever returned duplicate document IDs")
|
|
909
|
+
expected_ranks = tuple(range(1, len(results) + 1))
|
|
910
|
+
if tuple(item.rank for item in results) != expected_ranks:
|
|
911
|
+
raise ValidationError("runtime retriever ranks must be contiguous from one")
|
|
912
|
+
prediction = RetrievalPrediction({item.document.doc_id: item.score for item in results})
|
|
913
|
+
return DownstreamPrediction(
|
|
914
|
+
prediction=prediction,
|
|
915
|
+
usage=ResourceUsage(retrieval_calls=1),
|
|
916
|
+
metadata={
|
|
917
|
+
"prediction_kind": "retrieval",
|
|
918
|
+
"query_sha256": content_hash(query),
|
|
919
|
+
"returned_documents": len(results),
|
|
920
|
+
"top_k": options["top_k"],
|
|
921
|
+
},
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
def _predict_qa(self, state: FrozenAgentState) -> DownstreamPrediction:
|
|
925
|
+
backend = self._runtime.backend
|
|
926
|
+
if not isinstance(backend, LLMBackend):
|
|
927
|
+
raise CapabilityError("production QA predictor has no LLM backend")
|
|
928
|
+
if state.budget.max_model_calls < 1:
|
|
929
|
+
raise CapabilityError("QA prediction has no remaining model-call budget")
|
|
930
|
+
options = self._config["qa"]
|
|
931
|
+
request_limit = min(options["max_tokens"], state.budget.max_output_tokens)
|
|
932
|
+
if request_limit < 1:
|
|
933
|
+
raise CapabilityError("QA prediction has no remaining output-token budget")
|
|
934
|
+
request = GenerationRequest(
|
|
935
|
+
prompt=_render_visible_qa_prompt(state),
|
|
936
|
+
system=options["system_prompt"],
|
|
937
|
+
temperature=options["temperature"],
|
|
938
|
+
max_tokens=request_limit,
|
|
939
|
+
seed=self._seed if options["use_seed"] else None,
|
|
940
|
+
stop=options["stop"],
|
|
941
|
+
response_format=(
|
|
942
|
+
_qa_response_format(
|
|
943
|
+
benchmark_id=self._benchmark_id,
|
|
944
|
+
output_format=options["output_format"],
|
|
945
|
+
structured_output_mode=options["structured_output_mode"],
|
|
946
|
+
)
|
|
947
|
+
),
|
|
948
|
+
metadata={
|
|
949
|
+
"benchmark_id_sha256": content_hash(self._benchmark_id),
|
|
950
|
+
"prompt_revision": options["prompt_revision"],
|
|
951
|
+
},
|
|
952
|
+
)
|
|
953
|
+
response = backend.generate(request)
|
|
954
|
+
if not isinstance(response, GenerationResponse):
|
|
955
|
+
raise ValidationError("LLM backend returned a non-GenerationResponse")
|
|
956
|
+
if not isinstance(response.text, str):
|
|
957
|
+
raise ValidationError("LLM backend response text must be a string")
|
|
958
|
+
usage = response.usage
|
|
959
|
+
if not isinstance(usage, Usage):
|
|
960
|
+
raise ValidationError("LLM backend response usage must be Usage")
|
|
961
|
+
cost = usage.cost_usd
|
|
962
|
+
prediction, support_audit = _parse_visible_qa_prediction(
|
|
963
|
+
response.text,
|
|
964
|
+
benchmark_id=self._benchmark_id,
|
|
965
|
+
state=state,
|
|
966
|
+
output_format=options["output_format"],
|
|
967
|
+
identifier_policy=options["identifier_policy"],
|
|
968
|
+
)
|
|
969
|
+
return DownstreamPrediction(
|
|
970
|
+
prediction=prediction,
|
|
971
|
+
usage=ResourceUsage(
|
|
972
|
+
model_calls=1,
|
|
973
|
+
unpriced_model_calls=1 if cost is None else 0,
|
|
974
|
+
input_tokens=usage.input_tokens,
|
|
975
|
+
output_tokens=usage.output_tokens,
|
|
976
|
+
cost_usd=0.0 if cost is None else cost,
|
|
977
|
+
),
|
|
978
|
+
metadata={
|
|
979
|
+
"prediction_kind": f"qa_{options['output_format']}",
|
|
980
|
+
"request_sha256": request.request_hash,
|
|
981
|
+
"model": response.model,
|
|
982
|
+
"finish_reason": response.finish_reason,
|
|
983
|
+
**support_audit,
|
|
984
|
+
},
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _retrieval_prediction_from_context(
|
|
989
|
+
state: FrozenAgentState,
|
|
990
|
+
*,
|
|
991
|
+
top_k: int,
|
|
992
|
+
) -> tuple[RetrievalPrediction, tuple[str, ...]] | None:
|
|
993
|
+
"""Recover a ranked candidate list from provenance-preserving context.
|
|
994
|
+
|
|
995
|
+
Auxiliary retrieval tasks preload the same candidate set for both arms
|
|
996
|
+
when a curation method is evaluated. Acquisition methods may instead add
|
|
997
|
+
their own retrieved documents. Only visible provenance is consumed.
|
|
998
|
+
"""
|
|
999
|
+
|
|
1000
|
+
document_ids: list[str] = []
|
|
1001
|
+
for block in state.context:
|
|
1002
|
+
raw_ids: tuple[str, ...]
|
|
1003
|
+
direct = block.metadata.get("document_id")
|
|
1004
|
+
if not isinstance(direct, str) or not direct.strip():
|
|
1005
|
+
direct = block.metadata.get("source_context_block_id")
|
|
1006
|
+
if isinstance(direct, str) and direct.strip():
|
|
1007
|
+
raw_ids = (direct,)
|
|
1008
|
+
else:
|
|
1009
|
+
lineage = block.metadata.get("source_block_ids")
|
|
1010
|
+
if isinstance(lineage, (tuple, list)) and all(
|
|
1011
|
+
isinstance(item, str) and item.strip() for item in lineage
|
|
1012
|
+
):
|
|
1013
|
+
raw_ids = tuple(lineage)
|
|
1014
|
+
else:
|
|
1015
|
+
raw_ids = ()
|
|
1016
|
+
for document_id in raw_ids:
|
|
1017
|
+
if document_id not in document_ids:
|
|
1018
|
+
document_ids.append(document_id)
|
|
1019
|
+
if len(document_ids) >= top_k:
|
|
1020
|
+
break
|
|
1021
|
+
if len(document_ids) >= top_k:
|
|
1022
|
+
break
|
|
1023
|
+
if not document_ids:
|
|
1024
|
+
return None
|
|
1025
|
+
size = len(document_ids)
|
|
1026
|
+
return (
|
|
1027
|
+
RetrievalPrediction(
|
|
1028
|
+
{document_id: float(size - rank) for rank, document_id in enumerate(document_ids)}
|
|
1029
|
+
),
|
|
1030
|
+
tuple(document_ids),
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def production_backend_builder_factory() -> ProductionBackendBuilder:
|
|
1035
|
+
"""Zero-argument entrypoint for ``LockedBuilderSpec``."""
|
|
1036
|
+
|
|
1037
|
+
return ProductionBackendBuilder()
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def production_predictor_builder_factory() -> ProductionPredictorBuilder:
|
|
1041
|
+
"""Zero-argument entrypoint for ``LockedBuilderSpec``."""
|
|
1042
|
+
|
|
1043
|
+
return ProductionPredictorBuilder()
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def prepared_corpus_retriever_builder_factory() -> PreparedCorpusRetrieverBuilder:
|
|
1047
|
+
"""Zero-argument entrypoint for a nested ``LockedRetrieverSpec``."""
|
|
1048
|
+
|
|
1049
|
+
return PreparedCorpusRetrieverBuilder()
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def builder_fingerprint_sha256(builder: Any) -> str:
|
|
1053
|
+
"""Return the checksum placed in a locked builder specification."""
|
|
1054
|
+
|
|
1055
|
+
return content_hash(_fingerprint(builder, label="builder"))
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def retriever_product_sha256(retriever: Retriever) -> str:
|
|
1059
|
+
"""Hash an exact retriever product without performing retrieval."""
|
|
1060
|
+
|
|
1061
|
+
if not isinstance(retriever, Retriever):
|
|
1062
|
+
raise TypeError("retriever product hashing requires a Retriever")
|
|
1063
|
+
return content_hash(_fingerprint(retriever, label="retriever"))
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def validate_runtime_product_sha256(
|
|
1067
|
+
runtime: StaticRuntimeComponents,
|
|
1068
|
+
expected_sha256: str,
|
|
1069
|
+
) -> str:
|
|
1070
|
+
"""Validate and return the static driver's exact runtime product hash."""
|
|
1071
|
+
|
|
1072
|
+
observed = static_runtime_product_sha256(runtime)
|
|
1073
|
+
return _validate_product_hash(observed, expected_sha256, label="runtime product")
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
def validate_predictor_product_sha256(
|
|
1077
|
+
predictor: ProductionDownstreamPredictor,
|
|
1078
|
+
expected_sha256: str,
|
|
1079
|
+
) -> str:
|
|
1080
|
+
"""Validate and return the static driver's exact predictor product hash."""
|
|
1081
|
+
|
|
1082
|
+
observed = static_predictor_product_sha256(predictor)
|
|
1083
|
+
return _validate_product_hash(observed, expected_sha256, label="predictor product")
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def validate_retriever_product_sha256(
|
|
1087
|
+
retriever: Retriever,
|
|
1088
|
+
expected_sha256: str,
|
|
1089
|
+
) -> str:
|
|
1090
|
+
"""Validate and return an exact retriever product hash."""
|
|
1091
|
+
|
|
1092
|
+
observed = retriever_product_sha256(retriever)
|
|
1093
|
+
return _validate_product_hash(observed, expected_sha256, label="retriever product")
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def _parse_backend_parameters(parameters: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
1097
|
+
_reject_embedded_secrets(parameters, path="backend parameters")
|
|
1098
|
+
data = _strict_mapping(
|
|
1099
|
+
parameters,
|
|
1100
|
+
path="backend parameters",
|
|
1101
|
+
allowed={
|
|
1102
|
+
"schema_version",
|
|
1103
|
+
"backend",
|
|
1104
|
+
"external_runtime",
|
|
1105
|
+
"required_capabilities",
|
|
1106
|
+
"required_env",
|
|
1107
|
+
"retrievers",
|
|
1108
|
+
"vector_runtime",
|
|
1109
|
+
"run_config",
|
|
1110
|
+
"strict_intent",
|
|
1111
|
+
"tools",
|
|
1112
|
+
},
|
|
1113
|
+
required={"schema_version", "backend"},
|
|
1114
|
+
)
|
|
1115
|
+
_require_schema_version(data["schema_version"], path="backend parameters.schema_version")
|
|
1116
|
+
backend = _parse_backend_block(data["backend"])
|
|
1117
|
+
external_runtime = _parse_external_runtime_block(data.get("external_runtime"))
|
|
1118
|
+
required_capabilities = _capabilities(
|
|
1119
|
+
data.get("required_capabilities", (ModelCapability.COMPLETION.value,)),
|
|
1120
|
+
path="backend parameters.required_capabilities",
|
|
1121
|
+
)
|
|
1122
|
+
if not required_capabilities:
|
|
1123
|
+
raise ValidationError("backend required_capabilities must not be empty")
|
|
1124
|
+
retriever_values = _mapping_or_empty(
|
|
1125
|
+
data.get("retrievers"),
|
|
1126
|
+
path="backend parameters.retrievers",
|
|
1127
|
+
)
|
|
1128
|
+
retrievers: dict[str, LockedRetrieverSpec] = {}
|
|
1129
|
+
for benchmark_id, value in retriever_values.items():
|
|
1130
|
+
_require_retriever_cell(benchmark_id)
|
|
1131
|
+
retrievers[benchmark_id] = LockedRetrieverSpec.from_mapping(
|
|
1132
|
+
value,
|
|
1133
|
+
path=f"backend parameters.retrievers.{benchmark_id}",
|
|
1134
|
+
)
|
|
1135
|
+
vector_runtime = None
|
|
1136
|
+
if data.get("vector_runtime") is not None:
|
|
1137
|
+
vector_runtime = LockedRetrieverSpec.from_mapping(
|
|
1138
|
+
data["vector_runtime"],
|
|
1139
|
+
path="backend parameters.vector_runtime",
|
|
1140
|
+
)
|
|
1141
|
+
_require_text(
|
|
1142
|
+
vector_runtime.parameters.get("benchmark_id"),
|
|
1143
|
+
label="vector runtime benchmark_id",
|
|
1144
|
+
)
|
|
1145
|
+
tools = _parse_tool_connector_block(data.get("tools"))
|
|
1146
|
+
required_env = _environment_names(
|
|
1147
|
+
data.get("required_env", ()),
|
|
1148
|
+
path="backend parameters.required_env",
|
|
1149
|
+
)
|
|
1150
|
+
if backend["kind"] == "openai_compatible" and backend["require_api_key"]:
|
|
1151
|
+
required_env = tuple(sorted({*required_env, backend["api_key_env"]}))
|
|
1152
|
+
if external_runtime is not None and external_runtime["kind"] in {
|
|
1153
|
+
"acon_observation_worker",
|
|
1154
|
+
"ask_or_assume_worker",
|
|
1155
|
+
"reformer_worker",
|
|
1156
|
+
"infocqr_worker",
|
|
1157
|
+
}:
|
|
1158
|
+
api_key_env = external_runtime.get("api_key_env")
|
|
1159
|
+
if api_key_env is not None:
|
|
1160
|
+
required_env = tuple(sorted({*required_env, api_key_env}))
|
|
1161
|
+
if backend["kind"] == "openai_compatible" and backend["deployment_revision_env"] is not None:
|
|
1162
|
+
required_env = tuple(sorted({*required_env, backend["deployment_revision_env"]}))
|
|
1163
|
+
strict_intent = data.get("strict_intent", False)
|
|
1164
|
+
if not isinstance(strict_intent, bool):
|
|
1165
|
+
raise ValidationError("backend parameters.strict_intent must be boolean")
|
|
1166
|
+
if strict_intent:
|
|
1167
|
+
raise ValidationError(
|
|
1168
|
+
"ProductionBackendBuilder cannot infer a per-example IntentContract; "
|
|
1169
|
+
"use a custom locked runtime builder that supplies intent_contract, or set "
|
|
1170
|
+
"strict_intent=false and report intent preservation as an evaluation metric"
|
|
1171
|
+
)
|
|
1172
|
+
return MappingProxyType(
|
|
1173
|
+
{
|
|
1174
|
+
"backend": backend,
|
|
1175
|
+
"external_runtime": external_runtime,
|
|
1176
|
+
"required_capabilities": required_capabilities,
|
|
1177
|
+
"required_env": required_env,
|
|
1178
|
+
"retrievers": MappingProxyType(retrievers),
|
|
1179
|
+
"vector_runtime": vector_runtime,
|
|
1180
|
+
"tools": tools,
|
|
1181
|
+
"run_config": _canonical_mapping(
|
|
1182
|
+
_mapping_or_empty(
|
|
1183
|
+
data.get("run_config"),
|
|
1184
|
+
path="backend parameters.run_config",
|
|
1185
|
+
),
|
|
1186
|
+
label="backend run_config",
|
|
1187
|
+
),
|
|
1188
|
+
"strict_intent": strict_intent,
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
def _parse_tool_connector_block(value: Any) -> Mapping[str, Any] | None:
|
|
1194
|
+
if value is None:
|
|
1195
|
+
return None
|
|
1196
|
+
data = _strict_mapping(
|
|
1197
|
+
value,
|
|
1198
|
+
path="backend parameters.tools",
|
|
1199
|
+
allowed={"kind", "connector_id", "definitions"},
|
|
1200
|
+
required={"kind", "connector_id", "definitions"},
|
|
1201
|
+
)
|
|
1202
|
+
kind = _require_text(data["kind"], label="tool connector kind")
|
|
1203
|
+
if kind != "schema_only":
|
|
1204
|
+
raise ValidationError("production tool connector kind must be schema_only")
|
|
1205
|
+
connector_id = _require_text(data["connector_id"], label="tool connector ID")
|
|
1206
|
+
definitions = data["definitions"]
|
|
1207
|
+
if isinstance(definitions, (str, bytes)) or not isinstance(definitions, Sequence):
|
|
1208
|
+
raise ValidationError("tool connector definitions must be a sequence")
|
|
1209
|
+
normalized: list[Mapping[str, Any]] = []
|
|
1210
|
+
for index, definition in enumerate(definitions):
|
|
1211
|
+
if not isinstance(definition, Mapping):
|
|
1212
|
+
raise ValidationError(f"tool connector definition {index} must be an object")
|
|
1213
|
+
normalized.append(
|
|
1214
|
+
_canonical_mapping(definition, label=f"tool connector definition {index}")
|
|
1215
|
+
)
|
|
1216
|
+
# Construct once during parsing so malformed OpenAI declarations fail preflight.
|
|
1217
|
+
SchemaOnlyToolConnector.from_openai_definitions(
|
|
1218
|
+
normalized,
|
|
1219
|
+
connector_id=connector_id,
|
|
1220
|
+
)
|
|
1221
|
+
return MappingProxyType(
|
|
1222
|
+
{
|
|
1223
|
+
"kind": kind,
|
|
1224
|
+
"connector_id": connector_id,
|
|
1225
|
+
"definitions": tuple(normalized),
|
|
1226
|
+
}
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def _parse_external_runtime_block(value: Any) -> Mapping[str, Any] | None:
|
|
1231
|
+
if value is None:
|
|
1232
|
+
return None
|
|
1233
|
+
data = _strict_mapping(
|
|
1234
|
+
value,
|
|
1235
|
+
path="backend parameters.external_runtime",
|
|
1236
|
+
allowed={
|
|
1237
|
+
"kind",
|
|
1238
|
+
"checkout",
|
|
1239
|
+
"artifact_root",
|
|
1240
|
+
"manifest_sha256",
|
|
1241
|
+
"source_root",
|
|
1242
|
+
"base_model_root",
|
|
1243
|
+
"base_model_load_set_sha256",
|
|
1244
|
+
"feature_model_root",
|
|
1245
|
+
"feature_model_load_set_sha256",
|
|
1246
|
+
"reader_device",
|
|
1247
|
+
"feature_device",
|
|
1248
|
+
"checkpoint_root",
|
|
1249
|
+
"checkpoint_manifest_path",
|
|
1250
|
+
"checkpoint_manifest_sha256",
|
|
1251
|
+
"runtime_manifest_path",
|
|
1252
|
+
"runtime_manifest_sha256",
|
|
1253
|
+
"embedder_root",
|
|
1254
|
+
"compatibility_manifest_path",
|
|
1255
|
+
"compatibility_manifest_sha256",
|
|
1256
|
+
"embedder_manifest_sha256",
|
|
1257
|
+
"device",
|
|
1258
|
+
"driver_path",
|
|
1259
|
+
"driver_sha256",
|
|
1260
|
+
"python_executable",
|
|
1261
|
+
"python_executable_sha256",
|
|
1262
|
+
"base_url",
|
|
1263
|
+
"api_key_env",
|
|
1264
|
+
"model_id",
|
|
1265
|
+
"optimizer_model_revision",
|
|
1266
|
+
"target_model_revision",
|
|
1267
|
+
"tokenizer_revision",
|
|
1268
|
+
"require_api_key",
|
|
1269
|
+
"extra_body",
|
|
1270
|
+
"timeout_seconds",
|
|
1271
|
+
"max_input_tokens_per_call",
|
|
1272
|
+
"max_output_tokens",
|
|
1273
|
+
"temperature",
|
|
1274
|
+
"input_cost_per_million",
|
|
1275
|
+
"output_cost_per_million",
|
|
1276
|
+
},
|
|
1277
|
+
required={"kind"},
|
|
1278
|
+
)
|
|
1279
|
+
kind = _require_text(data["kind"], label="external runtime kind")
|
|
1280
|
+
if kind == "querygym_query2doc":
|
|
1281
|
+
_require_exact_backend_fields(
|
|
1282
|
+
data,
|
|
1283
|
+
{"kind", "checkout"},
|
|
1284
|
+
required={"kind", "checkout"},
|
|
1285
|
+
kind="QueryGym external runtime",
|
|
1286
|
+
)
|
|
1287
|
+
return MappingProxyType(
|
|
1288
|
+
{
|
|
1289
|
+
"kind": kind,
|
|
1290
|
+
"checkout": _absolute_path(data["checkout"], label="QueryGym checkout"),
|
|
1291
|
+
}
|
|
1292
|
+
)
|
|
1293
|
+
if kind == "recomp_extractive_worker":
|
|
1294
|
+
required = {
|
|
1295
|
+
"kind",
|
|
1296
|
+
"checkout",
|
|
1297
|
+
"checkpoint_root",
|
|
1298
|
+
"checkpoint_manifest_path",
|
|
1299
|
+
"checkpoint_manifest_sha256",
|
|
1300
|
+
"compatibility_manifest_path",
|
|
1301
|
+
"compatibility_manifest_sha256",
|
|
1302
|
+
"device",
|
|
1303
|
+
}
|
|
1304
|
+
_require_exact_backend_fields(
|
|
1305
|
+
data,
|
|
1306
|
+
required,
|
|
1307
|
+
required=required,
|
|
1308
|
+
kind="RECOMP extractive external runtime",
|
|
1309
|
+
)
|
|
1310
|
+
device = _require_text(data["device"], label="RECOMP device")
|
|
1311
|
+
if re.fullmatch(r"cpu|cuda(?::[0-9]+)?", device) is None:
|
|
1312
|
+
raise ValidationError("RECOMP device must be cpu, cuda, or cuda:<index>")
|
|
1313
|
+
return MappingProxyType(
|
|
1314
|
+
{
|
|
1315
|
+
"kind": kind,
|
|
1316
|
+
"checkout": _absolute_path(data["checkout"], label="RECOMP checkout"),
|
|
1317
|
+
"checkpoint_root": _absolute_path(
|
|
1318
|
+
data["checkpoint_root"], label="RECOMP checkpoint root"
|
|
1319
|
+
),
|
|
1320
|
+
"checkpoint_manifest_path": _absolute_path(
|
|
1321
|
+
data["checkpoint_manifest_path"], label="RECOMP checkpoint manifest"
|
|
1322
|
+
),
|
|
1323
|
+
"checkpoint_manifest_sha256": _sha256_text(
|
|
1324
|
+
data["checkpoint_manifest_sha256"],
|
|
1325
|
+
label="RECOMP checkpoint manifest SHA-256",
|
|
1326
|
+
),
|
|
1327
|
+
"compatibility_manifest_path": _absolute_path(
|
|
1328
|
+
data["compatibility_manifest_path"],
|
|
1329
|
+
label="RECOMP compatibility manifest",
|
|
1330
|
+
),
|
|
1331
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1332
|
+
data["compatibility_manifest_sha256"],
|
|
1333
|
+
label="RECOMP compatibility manifest SHA-256",
|
|
1334
|
+
),
|
|
1335
|
+
"device": device,
|
|
1336
|
+
}
|
|
1337
|
+
)
|
|
1338
|
+
if kind == "acon_observation_worker":
|
|
1339
|
+
required = {
|
|
1340
|
+
"kind",
|
|
1341
|
+
"checkout",
|
|
1342
|
+
"driver_path",
|
|
1343
|
+
"driver_sha256",
|
|
1344
|
+
"python_executable",
|
|
1345
|
+
"python_executable_sha256",
|
|
1346
|
+
"compatibility_manifest_path",
|
|
1347
|
+
"compatibility_manifest_sha256",
|
|
1348
|
+
"base_url",
|
|
1349
|
+
"model_id",
|
|
1350
|
+
"optimizer_model_revision",
|
|
1351
|
+
"target_model_revision",
|
|
1352
|
+
"tokenizer_revision",
|
|
1353
|
+
}
|
|
1354
|
+
optional = {
|
|
1355
|
+
"extra_body",
|
|
1356
|
+
"require_api_key",
|
|
1357
|
+
"timeout_seconds",
|
|
1358
|
+
"max_output_tokens",
|
|
1359
|
+
"temperature",
|
|
1360
|
+
}
|
|
1361
|
+
_require_exact_backend_fields(
|
|
1362
|
+
data,
|
|
1363
|
+
required | optional,
|
|
1364
|
+
required=required,
|
|
1365
|
+
kind="ACON observation external runtime",
|
|
1366
|
+
)
|
|
1367
|
+
driver_path = _absolute_path(data["driver_path"], label="ACON driver")
|
|
1368
|
+
compatibility_manifest_path = _absolute_path(
|
|
1369
|
+
data["compatibility_manifest_path"], label="ACON compatibility manifest"
|
|
1370
|
+
)
|
|
1371
|
+
if compatibility_manifest_path != driver_path.with_name("compatibility_manifest.json"):
|
|
1372
|
+
raise ValidationError(
|
|
1373
|
+
"ACON compatibility manifest must be the worker driver's sibling manifest"
|
|
1374
|
+
)
|
|
1375
|
+
require_api_key = data.get("require_api_key", False)
|
|
1376
|
+
if not isinstance(require_api_key, bool):
|
|
1377
|
+
raise ValidationError("ACON require_api_key must be boolean")
|
|
1378
|
+
temperature = _nonnegative_number(data.get("temperature", 0.0), label="ACON temperature")
|
|
1379
|
+
if temperature > 2:
|
|
1380
|
+
raise ValidationError("ACON temperature must not exceed 2")
|
|
1381
|
+
return MappingProxyType(
|
|
1382
|
+
{
|
|
1383
|
+
"kind": kind,
|
|
1384
|
+
"checkout": _absolute_path(data["checkout"], label="ACON checkout"),
|
|
1385
|
+
"driver_path": driver_path,
|
|
1386
|
+
"driver_sha256": _sha256_text(data["driver_sha256"], label="ACON driver SHA-256"),
|
|
1387
|
+
"python_executable": _absolute_path(
|
|
1388
|
+
data["python_executable"], label="ACON Python executable"
|
|
1389
|
+
),
|
|
1390
|
+
"python_executable_sha256": _sha256_text(
|
|
1391
|
+
data["python_executable_sha256"], label="ACON Python executable SHA-256"
|
|
1392
|
+
),
|
|
1393
|
+
"compatibility_manifest_path": compatibility_manifest_path,
|
|
1394
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1395
|
+
data["compatibility_manifest_sha256"],
|
|
1396
|
+
label="ACON compatibility manifest SHA-256",
|
|
1397
|
+
),
|
|
1398
|
+
"base_url": _absolute_http_url(data["base_url"], label="ACON base_url"),
|
|
1399
|
+
"api_key_env": "AGENTINPUTLAB_ACON_API_KEY" if require_api_key else None,
|
|
1400
|
+
"model_id": _require_text(data["model_id"], label="ACON model_id"),
|
|
1401
|
+
"optimizer_model_revision": _require_text(
|
|
1402
|
+
data["optimizer_model_revision"], label="ACON optimizer_model_revision"
|
|
1403
|
+
),
|
|
1404
|
+
"target_model_revision": _require_text(
|
|
1405
|
+
data["target_model_revision"], label="ACON target_model_revision"
|
|
1406
|
+
),
|
|
1407
|
+
"tokenizer_revision": _require_text(
|
|
1408
|
+
data["tokenizer_revision"], label="ACON tokenizer_revision"
|
|
1409
|
+
),
|
|
1410
|
+
"extra_body": _canonical_mapping(
|
|
1411
|
+
_mapping_or_empty(data.get("extra_body"), path="ACON extra_body"),
|
|
1412
|
+
label="ACON extra_body",
|
|
1413
|
+
),
|
|
1414
|
+
"timeout_seconds": _positive_number(
|
|
1415
|
+
data.get("timeout_seconds", 600.0), label="ACON timeout_seconds"
|
|
1416
|
+
),
|
|
1417
|
+
"max_output_tokens": _positive_int(
|
|
1418
|
+
data.get("max_output_tokens", 512), label="ACON max_output_tokens"
|
|
1419
|
+
),
|
|
1420
|
+
"temperature": temperature,
|
|
1421
|
+
}
|
|
1422
|
+
)
|
|
1423
|
+
if kind == "ask_or_assume_worker":
|
|
1424
|
+
required = {
|
|
1425
|
+
"kind",
|
|
1426
|
+
"checkout",
|
|
1427
|
+
"driver_path",
|
|
1428
|
+
"driver_sha256",
|
|
1429
|
+
"python_executable",
|
|
1430
|
+
"python_executable_sha256",
|
|
1431
|
+
"compatibility_manifest_path",
|
|
1432
|
+
"compatibility_manifest_sha256",
|
|
1433
|
+
"base_url",
|
|
1434
|
+
"api_key_env",
|
|
1435
|
+
"model_id",
|
|
1436
|
+
}
|
|
1437
|
+
optional = {
|
|
1438
|
+
"timeout_seconds",
|
|
1439
|
+
"max_output_tokens",
|
|
1440
|
+
"input_cost_per_million",
|
|
1441
|
+
"output_cost_per_million",
|
|
1442
|
+
}
|
|
1443
|
+
_require_exact_backend_fields(
|
|
1444
|
+
data,
|
|
1445
|
+
required | optional,
|
|
1446
|
+
required=required,
|
|
1447
|
+
kind="Ask-or-Assume external runtime",
|
|
1448
|
+
)
|
|
1449
|
+
return MappingProxyType(
|
|
1450
|
+
{
|
|
1451
|
+
"kind": kind,
|
|
1452
|
+
"checkout": _absolute_path(data["checkout"], label="Ask-or-Assume checkout"),
|
|
1453
|
+
"driver_path": _absolute_path(data["driver_path"], label="Ask-or-Assume driver"),
|
|
1454
|
+
"driver_sha256": _sha256_text(
|
|
1455
|
+
data["driver_sha256"], label="Ask-or-Assume driver SHA-256"
|
|
1456
|
+
),
|
|
1457
|
+
"python_executable": _absolute_path(
|
|
1458
|
+
data["python_executable"], label="Ask-or-Assume Python executable"
|
|
1459
|
+
),
|
|
1460
|
+
"python_executable_sha256": _sha256_text(
|
|
1461
|
+
data["python_executable_sha256"],
|
|
1462
|
+
label="Ask-or-Assume Python executable SHA-256",
|
|
1463
|
+
),
|
|
1464
|
+
"compatibility_manifest_path": _absolute_path(
|
|
1465
|
+
data["compatibility_manifest_path"],
|
|
1466
|
+
label="Ask-or-Assume compatibility manifest",
|
|
1467
|
+
),
|
|
1468
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1469
|
+
data["compatibility_manifest_sha256"],
|
|
1470
|
+
label="Ask-or-Assume compatibility manifest SHA-256",
|
|
1471
|
+
),
|
|
1472
|
+
"base_url": _absolute_http_url(data["base_url"], label="Ask-or-Assume base_url"),
|
|
1473
|
+
"api_key_env": _require_env_name(
|
|
1474
|
+
data["api_key_env"], label="Ask-or-Assume api_key_env"
|
|
1475
|
+
),
|
|
1476
|
+
"model_id": _require_text(data["model_id"], label="Ask-or-Assume model_id"),
|
|
1477
|
+
"timeout_seconds": _positive_number(
|
|
1478
|
+
data.get("timeout_seconds", 600.0),
|
|
1479
|
+
label="Ask-or-Assume timeout_seconds",
|
|
1480
|
+
),
|
|
1481
|
+
"max_output_tokens": _positive_int(
|
|
1482
|
+
data.get("max_output_tokens", 512),
|
|
1483
|
+
label="Ask-or-Assume max_output_tokens",
|
|
1484
|
+
),
|
|
1485
|
+
"input_cost_per_million": _nonnegative_number(
|
|
1486
|
+
data.get("input_cost_per_million", 0.0),
|
|
1487
|
+
label="Ask-or-Assume input_cost_per_million",
|
|
1488
|
+
),
|
|
1489
|
+
"output_cost_per_million": _nonnegative_number(
|
|
1490
|
+
data.get("output_cost_per_million", 0.0),
|
|
1491
|
+
label="Ask-or-Assume output_cost_per_million",
|
|
1492
|
+
),
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1495
|
+
if kind in {"reformer_worker", "infocqr_worker"}:
|
|
1496
|
+
required = {
|
|
1497
|
+
"kind",
|
|
1498
|
+
"checkout",
|
|
1499
|
+
"driver_path",
|
|
1500
|
+
"driver_sha256",
|
|
1501
|
+
"python_executable",
|
|
1502
|
+
"python_executable_sha256",
|
|
1503
|
+
"compatibility_manifest_path",
|
|
1504
|
+
"compatibility_manifest_sha256",
|
|
1505
|
+
"base_url",
|
|
1506
|
+
"api_key_env",
|
|
1507
|
+
"model_id",
|
|
1508
|
+
}
|
|
1509
|
+
optional = {
|
|
1510
|
+
"extra_body",
|
|
1511
|
+
"timeout_seconds",
|
|
1512
|
+
"max_input_tokens_per_call",
|
|
1513
|
+
"max_output_tokens",
|
|
1514
|
+
"temperature",
|
|
1515
|
+
"input_cost_per_million",
|
|
1516
|
+
"output_cost_per_million",
|
|
1517
|
+
}
|
|
1518
|
+
_require_exact_backend_fields(
|
|
1519
|
+
data, required | optional, required=required, kind="ReFormeR external runtime"
|
|
1520
|
+
)
|
|
1521
|
+
temperature = _nonnegative_number(
|
|
1522
|
+
data.get("temperature", 0.0), label="ReFormeR temperature"
|
|
1523
|
+
)
|
|
1524
|
+
if temperature > 2:
|
|
1525
|
+
raise ValidationError("ReFormeR temperature must not exceed 2")
|
|
1526
|
+
return MappingProxyType(
|
|
1527
|
+
{
|
|
1528
|
+
"kind": kind,
|
|
1529
|
+
"checkout": _absolute_path(data["checkout"], label="ReFormeR checkout"),
|
|
1530
|
+
"driver_path": _absolute_path(data["driver_path"], label="ReFormeR driver"),
|
|
1531
|
+
"driver_sha256": _sha256_text(
|
|
1532
|
+
data["driver_sha256"], label="ReFormeR driver SHA-256"
|
|
1533
|
+
),
|
|
1534
|
+
"python_executable": _absolute_path(
|
|
1535
|
+
data["python_executable"], label="ReFormeR Python executable"
|
|
1536
|
+
),
|
|
1537
|
+
"python_executable_sha256": _sha256_text(
|
|
1538
|
+
data["python_executable_sha256"],
|
|
1539
|
+
label="ReFormeR Python executable SHA-256",
|
|
1540
|
+
),
|
|
1541
|
+
"compatibility_manifest_path": _absolute_path(
|
|
1542
|
+
data["compatibility_manifest_path"],
|
|
1543
|
+
label="ReFormeR compatibility manifest",
|
|
1544
|
+
),
|
|
1545
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1546
|
+
data["compatibility_manifest_sha256"],
|
|
1547
|
+
label="ReFormeR compatibility manifest SHA-256",
|
|
1548
|
+
),
|
|
1549
|
+
"base_url": _absolute_http_url(data["base_url"], label="ReFormeR base_url"),
|
|
1550
|
+
"api_key_env": _require_env_name(data["api_key_env"], label="ReFormeR api_key_env"),
|
|
1551
|
+
"model_id": _require_text(data["model_id"], label="ReFormeR model_id"),
|
|
1552
|
+
"extra_body": _canonical_mapping(
|
|
1553
|
+
_mapping_or_empty(data.get("extra_body"), path="ReFormeR extra_body"),
|
|
1554
|
+
label="ReFormeR extra_body",
|
|
1555
|
+
),
|
|
1556
|
+
"timeout_seconds": _positive_number(
|
|
1557
|
+
data.get("timeout_seconds", 600.0), label="ReFormeR timeout_seconds"
|
|
1558
|
+
),
|
|
1559
|
+
"max_input_tokens_per_call": _positive_int(
|
|
1560
|
+
data.get("max_input_tokens_per_call", 16384),
|
|
1561
|
+
label="ReFormeR max_input_tokens_per_call",
|
|
1562
|
+
),
|
|
1563
|
+
"max_output_tokens": _positive_int(
|
|
1564
|
+
data.get("max_output_tokens", 512), label="ReFormeR max_output_tokens"
|
|
1565
|
+
),
|
|
1566
|
+
"temperature": temperature,
|
|
1567
|
+
"input_cost_per_million": _nonnegative_number(
|
|
1568
|
+
data.get("input_cost_per_million", 0.0),
|
|
1569
|
+
label="ReFormeR input_cost_per_million",
|
|
1570
|
+
),
|
|
1571
|
+
"output_cost_per_million": _nonnegative_number(
|
|
1572
|
+
data.get("output_cost_per_million", 0.0),
|
|
1573
|
+
label="ReFormeR output_cost_per_million",
|
|
1574
|
+
),
|
|
1575
|
+
}
|
|
1576
|
+
)
|
|
1577
|
+
if kind == "qrag_common_support":
|
|
1578
|
+
required = {
|
|
1579
|
+
"kind",
|
|
1580
|
+
"source_root",
|
|
1581
|
+
"checkpoint_root",
|
|
1582
|
+
"embedder_root",
|
|
1583
|
+
"compatibility_manifest_path",
|
|
1584
|
+
"compatibility_manifest_sha256",
|
|
1585
|
+
"embedder_manifest_sha256",
|
|
1586
|
+
}
|
|
1587
|
+
_require_exact_backend_fields(
|
|
1588
|
+
data,
|
|
1589
|
+
required | {"device"},
|
|
1590
|
+
required=required,
|
|
1591
|
+
kind="Q-RAG external runtime",
|
|
1592
|
+
)
|
|
1593
|
+
return MappingProxyType(
|
|
1594
|
+
{
|
|
1595
|
+
"kind": kind,
|
|
1596
|
+
"source_root": _absolute_path(data["source_root"], label="Q-RAG source root"),
|
|
1597
|
+
"checkpoint_root": _absolute_path(
|
|
1598
|
+
data["checkpoint_root"], label="Q-RAG checkpoint root"
|
|
1599
|
+
),
|
|
1600
|
+
"embedder_root": _absolute_path(data["embedder_root"], label="Q-RAG embedder root"),
|
|
1601
|
+
"compatibility_manifest_path": _absolute_path(
|
|
1602
|
+
data["compatibility_manifest_path"],
|
|
1603
|
+
label="Q-RAG compatibility manifest",
|
|
1604
|
+
),
|
|
1605
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1606
|
+
data["compatibility_manifest_sha256"],
|
|
1607
|
+
label="Q-RAG compatibility manifest SHA-256",
|
|
1608
|
+
),
|
|
1609
|
+
"embedder_manifest_sha256": _sha256_text(
|
|
1610
|
+
data["embedder_manifest_sha256"],
|
|
1611
|
+
label="Q-RAG embedder manifest SHA-256",
|
|
1612
|
+
),
|
|
1613
|
+
"device": _require_text(data.get("device", "cuda"), label="Q-RAG device"),
|
|
1614
|
+
}
|
|
1615
|
+
)
|
|
1616
|
+
if kind == "smartsearch_worker":
|
|
1617
|
+
required = {
|
|
1618
|
+
"kind",
|
|
1619
|
+
"checkout",
|
|
1620
|
+
"driver_path",
|
|
1621
|
+
"driver_sha256",
|
|
1622
|
+
"python_executable",
|
|
1623
|
+
"python_executable_sha256",
|
|
1624
|
+
"compatibility_manifest_path",
|
|
1625
|
+
"compatibility_manifest_sha256",
|
|
1626
|
+
"runtime_manifest_path",
|
|
1627
|
+
"runtime_manifest_sha256",
|
|
1628
|
+
}
|
|
1629
|
+
_require_exact_backend_fields(
|
|
1630
|
+
data,
|
|
1631
|
+
required | {"timeout_seconds"},
|
|
1632
|
+
required=required,
|
|
1633
|
+
kind="SmartSearch external runtime",
|
|
1634
|
+
)
|
|
1635
|
+
driver_path = _absolute_path(data["driver_path"], label="SmartSearch driver")
|
|
1636
|
+
compatibility_manifest_path = _absolute_path(
|
|
1637
|
+
data["compatibility_manifest_path"], label="SmartSearch compatibility manifest"
|
|
1638
|
+
)
|
|
1639
|
+
if compatibility_manifest_path != driver_path.with_name("compatibility_manifest.json"):
|
|
1640
|
+
raise ValidationError(
|
|
1641
|
+
"SmartSearch compatibility manifest must be the worker driver's sibling manifest"
|
|
1642
|
+
)
|
|
1643
|
+
return MappingProxyType(
|
|
1644
|
+
{
|
|
1645
|
+
"kind": kind,
|
|
1646
|
+
"checkout": _absolute_path(data["checkout"], label="SmartSearch checkout"),
|
|
1647
|
+
"driver_path": driver_path,
|
|
1648
|
+
"driver_sha256": _sha256_text(
|
|
1649
|
+
data["driver_sha256"], label="SmartSearch driver SHA-256"
|
|
1650
|
+
),
|
|
1651
|
+
"python_executable": _absolute_path(
|
|
1652
|
+
data["python_executable"], label="SmartSearch Python executable"
|
|
1653
|
+
),
|
|
1654
|
+
"python_executable_sha256": _sha256_text(
|
|
1655
|
+
data["python_executable_sha256"],
|
|
1656
|
+
label="SmartSearch Python executable SHA-256",
|
|
1657
|
+
),
|
|
1658
|
+
"compatibility_manifest_path": compatibility_manifest_path,
|
|
1659
|
+
"compatibility_manifest_sha256": _sha256_text(
|
|
1660
|
+
data["compatibility_manifest_sha256"],
|
|
1661
|
+
label="SmartSearch compatibility manifest SHA-256",
|
|
1662
|
+
),
|
|
1663
|
+
"runtime_manifest_path": _absolute_path(
|
|
1664
|
+
data["runtime_manifest_path"], label="SmartSearch runtime manifest"
|
|
1665
|
+
),
|
|
1666
|
+
"runtime_manifest_sha256": _sha256_text(
|
|
1667
|
+
data["runtime_manifest_sha256"],
|
|
1668
|
+
label="SmartSearch runtime manifest SHA-256",
|
|
1669
|
+
),
|
|
1670
|
+
"timeout_seconds": _positive_number(
|
|
1671
|
+
data.get("timeout_seconds", 1800.0),
|
|
1672
|
+
label="SmartSearch timeout_seconds",
|
|
1673
|
+
),
|
|
1674
|
+
}
|
|
1675
|
+
)
|
|
1676
|
+
if kind == "r2ag_common_support":
|
|
1677
|
+
required = {
|
|
1678
|
+
"kind",
|
|
1679
|
+
"artifact_root",
|
|
1680
|
+
"manifest_sha256",
|
|
1681
|
+
"source_root",
|
|
1682
|
+
"base_model_root",
|
|
1683
|
+
"base_model_load_set_sha256",
|
|
1684
|
+
"feature_model_root",
|
|
1685
|
+
"feature_model_load_set_sha256",
|
|
1686
|
+
}
|
|
1687
|
+
_require_exact_backend_fields(
|
|
1688
|
+
data,
|
|
1689
|
+
required | {"reader_device", "feature_device"},
|
|
1690
|
+
required=required,
|
|
1691
|
+
kind="R²AG external runtime",
|
|
1692
|
+
)
|
|
1693
|
+
return MappingProxyType(
|
|
1694
|
+
{
|
|
1695
|
+
"kind": kind,
|
|
1696
|
+
"artifact_root": _absolute_path(data["artifact_root"], label="R²AG artifact root"),
|
|
1697
|
+
"manifest_sha256": _sha256_text(
|
|
1698
|
+
data["manifest_sha256"], label="R²AG manifest_sha256"
|
|
1699
|
+
),
|
|
1700
|
+
"source_root": _absolute_path(data["source_root"], label="R²AG source root"),
|
|
1701
|
+
"base_model_root": _absolute_path(
|
|
1702
|
+
data["base_model_root"], label="R²AG base model root"
|
|
1703
|
+
),
|
|
1704
|
+
"base_model_load_set_sha256": _sha256_text(
|
|
1705
|
+
data["base_model_load_set_sha256"],
|
|
1706
|
+
label="R²AG base model load-set SHA-256",
|
|
1707
|
+
),
|
|
1708
|
+
"feature_model_root": _absolute_path(
|
|
1709
|
+
data["feature_model_root"], label="R²AG feature model root"
|
|
1710
|
+
),
|
|
1711
|
+
"feature_model_load_set_sha256": _sha256_text(
|
|
1712
|
+
data["feature_model_load_set_sha256"],
|
|
1713
|
+
label="R²AG feature model load-set SHA-256",
|
|
1714
|
+
),
|
|
1715
|
+
"reader_device": _require_text(
|
|
1716
|
+
data.get("reader_device", "cuda"), label="R²AG reader device"
|
|
1717
|
+
),
|
|
1718
|
+
"feature_device": _require_text(
|
|
1719
|
+
data.get("feature_device", "cuda"), label="R²AG feature device"
|
|
1720
|
+
),
|
|
1721
|
+
}
|
|
1722
|
+
)
|
|
1723
|
+
raise ValidationError(f"unsupported production external runtime kind: {kind!r}")
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
def _parse_backend_block(value: Any) -> Mapping[str, Any]:
|
|
1727
|
+
base = _strict_mapping(
|
|
1728
|
+
value,
|
|
1729
|
+
path="backend parameters.backend",
|
|
1730
|
+
allowed={
|
|
1731
|
+
"kind",
|
|
1732
|
+
"model_id",
|
|
1733
|
+
"capabilities",
|
|
1734
|
+
"fixture_path",
|
|
1735
|
+
"fixture_sha256",
|
|
1736
|
+
"base_url",
|
|
1737
|
+
"deployment_identity",
|
|
1738
|
+
"deployment_revision_env",
|
|
1739
|
+
"api_key_env",
|
|
1740
|
+
"require_api_key",
|
|
1741
|
+
"timeout_seconds",
|
|
1742
|
+
"extra_headers",
|
|
1743
|
+
"extra_body",
|
|
1744
|
+
"model_revision",
|
|
1745
|
+
"tokenizer_revision",
|
|
1746
|
+
"model_path",
|
|
1747
|
+
"generation_kwargs",
|
|
1748
|
+
"local_files_only",
|
|
1749
|
+
"artifact_files",
|
|
1750
|
+
},
|
|
1751
|
+
required={"kind", "model_id"},
|
|
1752
|
+
)
|
|
1753
|
+
kind = _require_text(base["kind"], label="backend kind")
|
|
1754
|
+
if kind not in {"replay", "openai_compatible", "transformers"}:
|
|
1755
|
+
raise ValidationError(f"unsupported production backend kind: {kind!r}")
|
|
1756
|
+
model_id = _require_text(base["model_id"], label="backend model_id")
|
|
1757
|
+
defaults = {
|
|
1758
|
+
"replay": (
|
|
1759
|
+
ModelCapability.CHAT,
|
|
1760
|
+
ModelCapability.COMPLETION,
|
|
1761
|
+
ModelCapability.SEED,
|
|
1762
|
+
),
|
|
1763
|
+
"openai_compatible": (
|
|
1764
|
+
ModelCapability.CHAT,
|
|
1765
|
+
ModelCapability.COMPLETION,
|
|
1766
|
+
ModelCapability.JSON_OBJECT,
|
|
1767
|
+
ModelCapability.TOOL_CALLING,
|
|
1768
|
+
ModelCapability.SEED,
|
|
1769
|
+
),
|
|
1770
|
+
"transformers": (
|
|
1771
|
+
ModelCapability.CHAT,
|
|
1772
|
+
ModelCapability.COMPLETION,
|
|
1773
|
+
),
|
|
1774
|
+
}
|
|
1775
|
+
capabilities = _capabilities(
|
|
1776
|
+
base.get("capabilities", tuple(item.value for item in defaults[kind])),
|
|
1777
|
+
path="backend capabilities",
|
|
1778
|
+
)
|
|
1779
|
+
common = {
|
|
1780
|
+
"kind": kind,
|
|
1781
|
+
"model_id": model_id,
|
|
1782
|
+
"capabilities": capabilities,
|
|
1783
|
+
}
|
|
1784
|
+
if kind == "replay":
|
|
1785
|
+
_require_exact_backend_fields(
|
|
1786
|
+
base,
|
|
1787
|
+
{"kind", "model_id", "capabilities", "fixture_path", "fixture_sha256"},
|
|
1788
|
+
required={"fixture_path", "fixture_sha256"},
|
|
1789
|
+
kind=kind,
|
|
1790
|
+
)
|
|
1791
|
+
return MappingProxyType(
|
|
1792
|
+
{
|
|
1793
|
+
**common,
|
|
1794
|
+
"fixture_path": _absolute_path(
|
|
1795
|
+
base["fixture_path"],
|
|
1796
|
+
label="replay fixture_path",
|
|
1797
|
+
),
|
|
1798
|
+
"fixture_sha256": _sha256_text(
|
|
1799
|
+
base["fixture_sha256"],
|
|
1800
|
+
label="replay fixture_sha256",
|
|
1801
|
+
),
|
|
1802
|
+
}
|
|
1803
|
+
)
|
|
1804
|
+
if kind == "openai_compatible":
|
|
1805
|
+
_require_exact_backend_fields(
|
|
1806
|
+
base,
|
|
1807
|
+
{
|
|
1808
|
+
"kind",
|
|
1809
|
+
"model_id",
|
|
1810
|
+
"capabilities",
|
|
1811
|
+
"base_url",
|
|
1812
|
+
"deployment_identity",
|
|
1813
|
+
"deployment_revision_env",
|
|
1814
|
+
"api_key_env",
|
|
1815
|
+
"require_api_key",
|
|
1816
|
+
"timeout_seconds",
|
|
1817
|
+
"extra_headers",
|
|
1818
|
+
"extra_body",
|
|
1819
|
+
},
|
|
1820
|
+
required={"base_url", "deployment_identity"},
|
|
1821
|
+
kind=kind,
|
|
1822
|
+
)
|
|
1823
|
+
api_key_env = base.get("api_key_env", "OPENAI_API_KEY")
|
|
1824
|
+
_require_env_name(api_key_env, label="OpenAI-compatible api_key_env")
|
|
1825
|
+
require_api_key = base.get("require_api_key", False)
|
|
1826
|
+
if not isinstance(require_api_key, bool):
|
|
1827
|
+
raise ValidationError("OpenAI-compatible require_api_key must be boolean")
|
|
1828
|
+
timeout = _positive_number(
|
|
1829
|
+
base.get("timeout_seconds", 120.0),
|
|
1830
|
+
label="OpenAI-compatible timeout_seconds",
|
|
1831
|
+
)
|
|
1832
|
+
headers = _mapping_or_empty(
|
|
1833
|
+
base.get("extra_headers"),
|
|
1834
|
+
path="OpenAI-compatible extra_headers",
|
|
1835
|
+
)
|
|
1836
|
+
for name, header_value in headers.items():
|
|
1837
|
+
_require_text(name, label="OpenAI-compatible header name")
|
|
1838
|
+
if _SENSITIVE_HEADER.fullmatch(name):
|
|
1839
|
+
raise ValidationError(f"sensitive header {name!r} is forbidden; use api_key_env")
|
|
1840
|
+
_require_text(header_value, label=f"OpenAI-compatible header {name}")
|
|
1841
|
+
identity = _canonical_mapping(
|
|
1842
|
+
_mapping_or_empty(
|
|
1843
|
+
base["deployment_identity"],
|
|
1844
|
+
path="OpenAI-compatible deployment_identity",
|
|
1845
|
+
),
|
|
1846
|
+
label="OpenAI-compatible deployment_identity",
|
|
1847
|
+
)
|
|
1848
|
+
if not identity.get("revision"):
|
|
1849
|
+
raise ValidationError(
|
|
1850
|
+
"OpenAI-compatible deployment_identity requires a non-empty revision"
|
|
1851
|
+
)
|
|
1852
|
+
deployment_revision_env = base.get("deployment_revision_env")
|
|
1853
|
+
if deployment_revision_env is not None:
|
|
1854
|
+
_require_env_name(
|
|
1855
|
+
deployment_revision_env,
|
|
1856
|
+
label="OpenAI-compatible deployment_revision_env",
|
|
1857
|
+
)
|
|
1858
|
+
return MappingProxyType(
|
|
1859
|
+
{
|
|
1860
|
+
**common,
|
|
1861
|
+
"base_url": _absolute_http_url(
|
|
1862
|
+
base["base_url"],
|
|
1863
|
+
label="OpenAI-compatible base_url",
|
|
1864
|
+
),
|
|
1865
|
+
"deployment_identity": identity,
|
|
1866
|
+
"deployment_revision_env": deployment_revision_env,
|
|
1867
|
+
"api_key_env": api_key_env,
|
|
1868
|
+
"require_api_key": require_api_key,
|
|
1869
|
+
"timeout_seconds": timeout,
|
|
1870
|
+
"extra_headers": _canonical_mapping(headers, label="extra_headers"),
|
|
1871
|
+
"extra_body": _canonical_mapping(
|
|
1872
|
+
_mapping_or_empty(
|
|
1873
|
+
base.get("extra_body"),
|
|
1874
|
+
path="OpenAI-compatible extra_body",
|
|
1875
|
+
),
|
|
1876
|
+
label="extra_body",
|
|
1877
|
+
),
|
|
1878
|
+
}
|
|
1879
|
+
)
|
|
1880
|
+
_require_exact_backend_fields(
|
|
1881
|
+
base,
|
|
1882
|
+
{
|
|
1883
|
+
"kind",
|
|
1884
|
+
"model_id",
|
|
1885
|
+
"capabilities",
|
|
1886
|
+
"model_revision",
|
|
1887
|
+
"tokenizer_revision",
|
|
1888
|
+
"model_path",
|
|
1889
|
+
"generation_kwargs",
|
|
1890
|
+
"local_files_only",
|
|
1891
|
+
"artifact_files",
|
|
1892
|
+
},
|
|
1893
|
+
required={
|
|
1894
|
+
"model_path",
|
|
1895
|
+
"model_revision",
|
|
1896
|
+
"tokenizer_revision",
|
|
1897
|
+
"artifact_files",
|
|
1898
|
+
},
|
|
1899
|
+
kind=kind,
|
|
1900
|
+
)
|
|
1901
|
+
local_files_only = base.get("local_files_only", True)
|
|
1902
|
+
if local_files_only is not True:
|
|
1903
|
+
raise ValidationError("production Transformers backend requires local_files_only=true")
|
|
1904
|
+
artifacts = _artifact_file_locks(
|
|
1905
|
+
base["artifact_files"],
|
|
1906
|
+
path="Transformers artifact_files",
|
|
1907
|
+
)
|
|
1908
|
+
if not artifacts:
|
|
1909
|
+
raise ValidationError(
|
|
1910
|
+
"production Transformers backend requires checksum-locked artifact_files"
|
|
1911
|
+
)
|
|
1912
|
+
model_path = _absolute_path(
|
|
1913
|
+
base["model_path"],
|
|
1914
|
+
label="Transformers model_path",
|
|
1915
|
+
)
|
|
1916
|
+
_validate_transformers_artifact_layout(model_path, artifacts)
|
|
1917
|
+
return MappingProxyType(
|
|
1918
|
+
{
|
|
1919
|
+
**common,
|
|
1920
|
+
"model_path": model_path,
|
|
1921
|
+
"model_revision": _require_text(
|
|
1922
|
+
base["model_revision"],
|
|
1923
|
+
label="Transformers model_revision",
|
|
1924
|
+
),
|
|
1925
|
+
"tokenizer_revision": _require_text(
|
|
1926
|
+
base["tokenizer_revision"],
|
|
1927
|
+
label="Transformers tokenizer_revision",
|
|
1928
|
+
),
|
|
1929
|
+
"generation_kwargs": _canonical_mapping(
|
|
1930
|
+
_mapping_or_empty(
|
|
1931
|
+
base.get("generation_kwargs"),
|
|
1932
|
+
path="Transformers generation_kwargs",
|
|
1933
|
+
),
|
|
1934
|
+
label="Transformers generation_kwargs",
|
|
1935
|
+
),
|
|
1936
|
+
"local_files_only": True,
|
|
1937
|
+
"artifact_files": artifacts,
|
|
1938
|
+
}
|
|
1939
|
+
)
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
def _require_exact_backend_fields(
|
|
1943
|
+
data: Mapping[str, Any],
|
|
1944
|
+
allowed: set[str],
|
|
1945
|
+
*,
|
|
1946
|
+
required: set[str],
|
|
1947
|
+
kind: str,
|
|
1948
|
+
) -> None:
|
|
1949
|
+
unknown = set(data) - allowed
|
|
1950
|
+
missing = required - set(data)
|
|
1951
|
+
if unknown:
|
|
1952
|
+
raise ValidationError(
|
|
1953
|
+
f"{kind} backend has incompatible fields: {', '.join(sorted(unknown))}"
|
|
1954
|
+
)
|
|
1955
|
+
if missing:
|
|
1956
|
+
raise ValidationError(f"{kind} backend is missing fields: {', '.join(sorted(missing))}")
|
|
1957
|
+
|
|
1958
|
+
|
|
1959
|
+
def _backend_preflight_blockers(config: Mapping[str, Any]) -> list[str]:
|
|
1960
|
+
blockers = [
|
|
1961
|
+
f"required environment variable {name!r} is not set"
|
|
1962
|
+
for name in config["required_env"]
|
|
1963
|
+
if not os.environ.get(name)
|
|
1964
|
+
]
|
|
1965
|
+
backend_config = config["backend"]
|
|
1966
|
+
if (
|
|
1967
|
+
backend_config["kind"] == "openai_compatible"
|
|
1968
|
+
and backend_config["deployment_revision_env"] is not None
|
|
1969
|
+
):
|
|
1970
|
+
revision_environment = backend_config["deployment_revision_env"]
|
|
1971
|
+
observed_revision = os.environ.get(revision_environment)
|
|
1972
|
+
if (
|
|
1973
|
+
observed_revision
|
|
1974
|
+
and observed_revision != backend_config["deployment_identity"]["revision"]
|
|
1975
|
+
):
|
|
1976
|
+
blockers.append(
|
|
1977
|
+
"OpenAI-compatible deployment revision differs from its environment lock"
|
|
1978
|
+
)
|
|
1979
|
+
try:
|
|
1980
|
+
backend = _build_llm_backend(backend_config)
|
|
1981
|
+
require_capabilities(backend, config["required_capabilities"])
|
|
1982
|
+
_build_external_runtime(config["external_runtime"], backend)
|
|
1983
|
+
except (ArtifactError, CapabilityError, ExternalDependencyError, ValidationError) as exc:
|
|
1984
|
+
blockers.append(str(exc))
|
|
1985
|
+
if backend_config["kind"] == "transformers":
|
|
1986
|
+
if importlib.util.find_spec("transformers") is None:
|
|
1987
|
+
blockers.append("transformers is unavailable; install agentinputlab[local]")
|
|
1988
|
+
try:
|
|
1989
|
+
_verify_transformers_snapshot(
|
|
1990
|
+
backend_config["model_path"],
|
|
1991
|
+
backend_config["artifact_files"],
|
|
1992
|
+
)
|
|
1993
|
+
except ArtifactError as exc:
|
|
1994
|
+
blockers.append(str(exc))
|
|
1995
|
+
for benchmark_id, spec in config["retrievers"].items():
|
|
1996
|
+
try:
|
|
1997
|
+
builder = _resolve_retriever_builder(spec)
|
|
1998
|
+
messages = builder.preflight(spec.parameters)
|
|
1999
|
+
if isinstance(messages, (str, bytes)) or not isinstance(messages, Sequence):
|
|
2000
|
+
raise ValidationError("retriever builder preflight must return a sequence")
|
|
2001
|
+
for message in messages:
|
|
2002
|
+
if not isinstance(message, str) or not message.strip():
|
|
2003
|
+
raise ValidationError(
|
|
2004
|
+
"retriever builder preflight blockers must be non-empty strings"
|
|
2005
|
+
)
|
|
2006
|
+
blockers.append(f"retriever {benchmark_id}: {message}")
|
|
2007
|
+
except (ArtifactError, TypeError, ValueError) as exc:
|
|
2008
|
+
blockers.append(f"retriever {benchmark_id}: {exc}")
|
|
2009
|
+
vector_spec = config["vector_runtime"]
|
|
2010
|
+
if vector_spec is not None:
|
|
2011
|
+
try:
|
|
2012
|
+
builder = _resolve_retriever_builder(vector_spec)
|
|
2013
|
+
messages = builder.preflight(vector_spec.parameters)
|
|
2014
|
+
if isinstance(messages, (str, bytes)) or not isinstance(messages, Sequence):
|
|
2015
|
+
raise ValidationError("vector runtime preflight must return a sequence")
|
|
2016
|
+
for message in messages:
|
|
2017
|
+
if not isinstance(message, str) or not message.strip():
|
|
2018
|
+
raise ValidationError(
|
|
2019
|
+
"vector runtime preflight blockers must be non-empty strings"
|
|
2020
|
+
)
|
|
2021
|
+
blockers.append(f"vector runtime: {message}")
|
|
2022
|
+
except (ArtifactError, TypeError, ValueError) as exc:
|
|
2023
|
+
blockers.append(f"vector runtime: {exc}")
|
|
2024
|
+
return blockers
|
|
2025
|
+
|
|
2026
|
+
|
|
2027
|
+
def _build_llm_backend(config: Mapping[str, Any]) -> LLMBackend:
|
|
2028
|
+
kind = config["kind"]
|
|
2029
|
+
if kind == "replay":
|
|
2030
|
+
fixture = _load_replay_fixture(
|
|
2031
|
+
config["fixture_path"],
|
|
2032
|
+
config["fixture_sha256"],
|
|
2033
|
+
)
|
|
2034
|
+
inner: LLMBackend = ReplayBackend(
|
|
2035
|
+
fixture["responses"],
|
|
2036
|
+
model_id=config["model_id"],
|
|
2037
|
+
capabilities=frozenset(config["capabilities"]),
|
|
2038
|
+
strict_request_hashes=fixture["strict_request_hashes"],
|
|
2039
|
+
)
|
|
2040
|
+
binding = {
|
|
2041
|
+
"kind": kind,
|
|
2042
|
+
"fixture_sha256": config["fixture_sha256"],
|
|
2043
|
+
"fixture_schema_version": _SCHEMA_VERSION,
|
|
2044
|
+
}
|
|
2045
|
+
elif kind == "openai_compatible":
|
|
2046
|
+
inner = OpenAICompatibleBackend(
|
|
2047
|
+
model_id=config["model_id"],
|
|
2048
|
+
base_url=config["base_url"],
|
|
2049
|
+
deployment_identity=config["deployment_identity"],
|
|
2050
|
+
api_key=None,
|
|
2051
|
+
api_key_env=config["api_key_env"],
|
|
2052
|
+
timeout_seconds=config["timeout_seconds"],
|
|
2053
|
+
extra_headers=config["extra_headers"],
|
|
2054
|
+
extra_body=config["extra_body"],
|
|
2055
|
+
capabilities=frozenset(config["capabilities"]),
|
|
2056
|
+
)
|
|
2057
|
+
binding = {
|
|
2058
|
+
"kind": kind,
|
|
2059
|
+
"deployment_revision_source": (
|
|
2060
|
+
{
|
|
2061
|
+
"kind": "environment",
|
|
2062
|
+
"name": config["deployment_revision_env"],
|
|
2063
|
+
}
|
|
2064
|
+
if config["deployment_revision_env"] is not None
|
|
2065
|
+
else {"kind": "operator_declaration"}
|
|
2066
|
+
),
|
|
2067
|
+
"api_key_source": {
|
|
2068
|
+
"kind": "environment",
|
|
2069
|
+
"name": config["api_key_env"],
|
|
2070
|
+
"required": config["require_api_key"],
|
|
2071
|
+
},
|
|
2072
|
+
}
|
|
2073
|
+
else:
|
|
2074
|
+
inner = _LocalOnlyTransformersBackend(
|
|
2075
|
+
model_id=config["model_id"],
|
|
2076
|
+
model_path=config["model_path"],
|
|
2077
|
+
model_revision=config["model_revision"],
|
|
2078
|
+
tokenizer_revision=config["tokenizer_revision"],
|
|
2079
|
+
artifact_files=config["artifact_files"],
|
|
2080
|
+
generation_kwargs=config["generation_kwargs"],
|
|
2081
|
+
capabilities=frozenset(config["capabilities"]),
|
|
2082
|
+
local_files_only=config["local_files_only"],
|
|
2083
|
+
)
|
|
2084
|
+
binding = {
|
|
2085
|
+
"kind": kind,
|
|
2086
|
+
"snapshot_sha256": _transformers_snapshot_sha256(config["artifact_files"]),
|
|
2087
|
+
"artifact_files": tuple(
|
|
2088
|
+
{"name": artifact["name"], "sha256": artifact["sha256"]}
|
|
2089
|
+
for artifact in config["artifact_files"]
|
|
2090
|
+
),
|
|
2091
|
+
"local_files_only": True,
|
|
2092
|
+
}
|
|
2093
|
+
return _BoundLLMBackend(inner, binding)
|
|
2094
|
+
|
|
2095
|
+
|
|
2096
|
+
@lru_cache(maxsize=4)
|
|
2097
|
+
def _cached_recomp_extractive_model(
|
|
2098
|
+
checkpoint_root: str,
|
|
2099
|
+
device: str,
|
|
2100
|
+
) -> tuple[Any, Any]:
|
|
2101
|
+
"""Load the frozen RECOMP artifact with the installed Transformers API.
|
|
2102
|
+
|
|
2103
|
+
RECOMP's scorer is invoked unchanged by its worker. Only the historical
|
|
2104
|
+
``from_pretrained`` compatibility surface is adapted; this identity and
|
|
2105
|
+
the installed package versions remain visible in the worker fingerprint.
|
|
2106
|
+
"""
|
|
2107
|
+
|
|
2108
|
+
try:
|
|
2109
|
+
from transformers import AutoModel, AutoTokenizer
|
|
2110
|
+
except (ImportError, ModuleNotFoundError) as exc:
|
|
2111
|
+
raise ExternalDependencyError(
|
|
2112
|
+
"RECOMP current-runtime loader requires transformers"
|
|
2113
|
+
) from exc
|
|
2114
|
+
tokenizer = AutoTokenizer.from_pretrained(
|
|
2115
|
+
checkpoint_root,
|
|
2116
|
+
local_files_only=True,
|
|
2117
|
+
trust_remote_code=False,
|
|
2118
|
+
)
|
|
2119
|
+
model = AutoModel.from_pretrained(
|
|
2120
|
+
checkpoint_root,
|
|
2121
|
+
local_files_only=True,
|
|
2122
|
+
trust_remote_code=False,
|
|
2123
|
+
)
|
|
2124
|
+
model = model.to(device)
|
|
2125
|
+
model.eval()
|
|
2126
|
+
return model, tokenizer
|
|
2127
|
+
|
|
2128
|
+
|
|
2129
|
+
def _load_recomp_extractive_current_runtime(
|
|
2130
|
+
mode: str,
|
|
2131
|
+
checkpoint_root: Path,
|
|
2132
|
+
device: str,
|
|
2133
|
+
) -> tuple[Any, Any]:
|
|
2134
|
+
if mode != "extractive":
|
|
2135
|
+
raise ValidationError("RECOMP production runtime only accepts the extractive release")
|
|
2136
|
+
return _cached_recomp_extractive_model(str(checkpoint_root), device)
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
def _build_external_runtime(
|
|
2140
|
+
config: Mapping[str, Any] | None,
|
|
2141
|
+
backend: LLMBackend,
|
|
2142
|
+
) -> Any:
|
|
2143
|
+
if config is None:
|
|
2144
|
+
return None
|
|
2145
|
+
if config["kind"] == "querygym_query2doc":
|
|
2146
|
+
from agentinputlab.algorithms.reformulate.query2doc import PinnedQueryGymRuntime
|
|
2147
|
+
|
|
2148
|
+
runtime = PinnedQueryGymRuntime(config["checkout"], backend)
|
|
2149
|
+
identity = _fingerprint(runtime, label="QueryGym external runtime")
|
|
2150
|
+
if identity.get("implementation") != _QUERYGYM_RUNTIME_IMPLEMENTATION:
|
|
2151
|
+
raise ValidationError("QueryGym runtime implementation identity is invalid")
|
|
2152
|
+
return runtime
|
|
2153
|
+
if config["kind"] == "recomp_extractive_worker":
|
|
2154
|
+
from workers.recomp.driver import RECOMPDriver
|
|
2155
|
+
|
|
2156
|
+
_verify_locked_file(
|
|
2157
|
+
config["checkpoint_manifest_path"],
|
|
2158
|
+
config["checkpoint_manifest_sha256"],
|
|
2159
|
+
label="RECOMP checkpoint manifest",
|
|
2160
|
+
)
|
|
2161
|
+
_verify_locked_file(
|
|
2162
|
+
config["compatibility_manifest_path"],
|
|
2163
|
+
config["compatibility_manifest_sha256"],
|
|
2164
|
+
label="RECOMP compatibility manifest",
|
|
2165
|
+
)
|
|
2166
|
+
return RECOMPDriver(
|
|
2167
|
+
checkout_path=config["checkout"],
|
|
2168
|
+
checkpoint_path=config["checkpoint_root"],
|
|
2169
|
+
checkpoint_manifest_path=config["checkpoint_manifest_path"],
|
|
2170
|
+
device=config["device"],
|
|
2171
|
+
manifest_path=config["compatibility_manifest_path"],
|
|
2172
|
+
model_loader=_load_recomp_extractive_current_runtime,
|
|
2173
|
+
checkpoint_license_acknowledged=True,
|
|
2174
|
+
)
|
|
2175
|
+
if config["kind"] == "acon_observation_worker":
|
|
2176
|
+
from agentinputlab.integrations.external_workers import JsonLineSubprocessWorker
|
|
2177
|
+
|
|
2178
|
+
driver_sha256 = _verify_locked_file(
|
|
2179
|
+
config["driver_path"], config["driver_sha256"], label="ACON driver"
|
|
2180
|
+
)
|
|
2181
|
+
executable_sha256 = _verify_locked_file(
|
|
2182
|
+
config["python_executable"],
|
|
2183
|
+
config["python_executable_sha256"],
|
|
2184
|
+
label="ACON Python executable",
|
|
2185
|
+
)
|
|
2186
|
+
manifest_sha256 = _verify_locked_file(
|
|
2187
|
+
config["compatibility_manifest_path"],
|
|
2188
|
+
config["compatibility_manifest_sha256"],
|
|
2189
|
+
label="ACON compatibility manifest",
|
|
2190
|
+
)
|
|
2191
|
+
model_fingerprint = dict(_fingerprint(backend, label="ACON target backend"))
|
|
2192
|
+
environment = {
|
|
2193
|
+
"AGENTINPUTLAB_ACON_CHECKOUT": str(config["checkout"]),
|
|
2194
|
+
"AGENTINPUTLAB_ACON_OPENAI_BASE_URL": config["base_url"],
|
|
2195
|
+
"AGENTINPUTLAB_ACON_OPTIMIZER_MODEL": config["model_id"],
|
|
2196
|
+
"AGENTINPUTLAB_ACON_OPTIMIZER_MODEL_REVISION": (config["optimizer_model_revision"]),
|
|
2197
|
+
"AGENTINPUTLAB_ACON_TARGET_MODEL_REVISION": config["target_model_revision"],
|
|
2198
|
+
"AGENTINPUTLAB_ACON_TOKENIZER_REVISION": config["tokenizer_revision"],
|
|
2199
|
+
"AGENTINPUTLAB_ACON_TEMPERATURE": str(config["temperature"]),
|
|
2200
|
+
"AGENTINPUTLAB_ACON_TIMEOUT_SECONDS": str(config["timeout_seconds"]),
|
|
2201
|
+
"AGENTINPUTLAB_ACON_MAX_TOKENS": str(config["max_output_tokens"]),
|
|
2202
|
+
"AGENTINPUTLAB_ACON_EXTRA_BODY_JSON": json.dumps(
|
|
2203
|
+
to_jsonable(config["extra_body"]),
|
|
2204
|
+
ensure_ascii=False,
|
|
2205
|
+
sort_keys=True,
|
|
2206
|
+
separators=(",", ":"),
|
|
2207
|
+
allow_nan=False,
|
|
2208
|
+
),
|
|
2209
|
+
"PYTHONDONTWRITEBYTECODE": "1",
|
|
2210
|
+
}
|
|
2211
|
+
pass_environment = (config["api_key_env"],) if config["api_key_env"] is not None else ()
|
|
2212
|
+
return JsonLineSubprocessWorker(
|
|
2213
|
+
command=(str(config["python_executable"]), str(config["driver_path"])),
|
|
2214
|
+
worker_id="acon-common-support-observation",
|
|
2215
|
+
identity={
|
|
2216
|
+
"implementation": "agentinputlab.acon.process_worker.v1",
|
|
2217
|
+
"release_id": "curate.acon.common_support_observation",
|
|
2218
|
+
"component_release_id": "curate.acon.observation_runtime",
|
|
2219
|
+
"upstream_commit": "d63f9ae18959dc7215ff62899c94c5e8c56847ae",
|
|
2220
|
+
"driver_sha256": driver_sha256,
|
|
2221
|
+
"python_executable_sha256": executable_sha256,
|
|
2222
|
+
"compatibility_manifest_sha256": manifest_sha256,
|
|
2223
|
+
"optimizer_endpoint": config["base_url"],
|
|
2224
|
+
"optimizer_model_revision": config["optimizer_model_revision"],
|
|
2225
|
+
"target_model_revision": config["target_model_revision"],
|
|
2226
|
+
"tokenizer_revision": config["tokenizer_revision"],
|
|
2227
|
+
"optimizer_extra_body_hash": content_hash(config["extra_body"]),
|
|
2228
|
+
"target_backend": model_fingerprint,
|
|
2229
|
+
},
|
|
2230
|
+
timeout_seconds=config["timeout_seconds"],
|
|
2231
|
+
pass_environment=pass_environment,
|
|
2232
|
+
working_directory=str(config["driver_path"].parents[2]),
|
|
2233
|
+
environment=environment,
|
|
2234
|
+
)
|
|
2235
|
+
if config["kind"] == "ask_or_assume_worker":
|
|
2236
|
+
from agentinputlab.integrations.external_workers import JsonLineSubprocessWorker
|
|
2237
|
+
|
|
2238
|
+
driver_sha256 = _verify_locked_file(
|
|
2239
|
+
config["driver_path"],
|
|
2240
|
+
config["driver_sha256"],
|
|
2241
|
+
label="Ask-or-Assume driver",
|
|
2242
|
+
)
|
|
2243
|
+
executable_sha256 = _verify_locked_file(
|
|
2244
|
+
config["python_executable"],
|
|
2245
|
+
config["python_executable_sha256"],
|
|
2246
|
+
label="Ask-or-Assume Python executable",
|
|
2247
|
+
)
|
|
2248
|
+
manifest_sha256 = _verify_locked_file(
|
|
2249
|
+
config["compatibility_manifest_path"],
|
|
2250
|
+
config["compatibility_manifest_sha256"],
|
|
2251
|
+
label="Ask-or-Assume compatibility manifest",
|
|
2252
|
+
)
|
|
2253
|
+
model_fingerprint = dict(_fingerprint(backend, label="Ask-or-Assume backend"))
|
|
2254
|
+
role = {
|
|
2255
|
+
"model": config["model_id"],
|
|
2256
|
+
"fingerprint": model_fingerprint,
|
|
2257
|
+
"max_output_tokens": config["max_output_tokens"],
|
|
2258
|
+
"input_cost_per_million": config["input_cost_per_million"],
|
|
2259
|
+
"output_cost_per_million": config["output_cost_per_million"],
|
|
2260
|
+
}
|
|
2261
|
+
backend_config = {
|
|
2262
|
+
"base_url": config["base_url"],
|
|
2263
|
+
"api_key_env": config["api_key_env"],
|
|
2264
|
+
"timeout_seconds": config["timeout_seconds"],
|
|
2265
|
+
"roles": {"intent": role, "coding": role},
|
|
2266
|
+
}
|
|
2267
|
+
return JsonLineSubprocessWorker(
|
|
2268
|
+
command=(
|
|
2269
|
+
str(config["python_executable"]),
|
|
2270
|
+
str(config["driver_path"]),
|
|
2271
|
+
"--checkout",
|
|
2272
|
+
str(config["checkout"]),
|
|
2273
|
+
"--backend-config-json",
|
|
2274
|
+
json.dumps(
|
|
2275
|
+
to_jsonable(backend_config),
|
|
2276
|
+
ensure_ascii=False,
|
|
2277
|
+
sort_keys=True,
|
|
2278
|
+
separators=(",", ":"),
|
|
2279
|
+
allow_nan=False,
|
|
2280
|
+
),
|
|
2281
|
+
),
|
|
2282
|
+
worker_id="ask-or-assume-intent-monitor",
|
|
2283
|
+
identity={
|
|
2284
|
+
"implementation": "agentinputlab.ask_or_assume.process_worker.v1",
|
|
2285
|
+
"release_id": "clarify.ask_or_assume.intent_monitor_component",
|
|
2286
|
+
"upstream_commit": "728464a755f15fd448aff61a5cb32dce97fd9c1d",
|
|
2287
|
+
"driver_sha256": driver_sha256,
|
|
2288
|
+
"python_executable_sha256": executable_sha256,
|
|
2289
|
+
"compatibility_manifest_sha256": manifest_sha256,
|
|
2290
|
+
"backend": model_fingerprint,
|
|
2291
|
+
},
|
|
2292
|
+
timeout_seconds=config["timeout_seconds"],
|
|
2293
|
+
pass_environment=(config["api_key_env"],),
|
|
2294
|
+
working_directory=str(config["driver_path"].parents[2]),
|
|
2295
|
+
)
|
|
2296
|
+
if config["kind"] in {"reformer_worker", "infocqr_worker"}:
|
|
2297
|
+
from agentinputlab.integrations.external_workers import JsonLineSubprocessWorker
|
|
2298
|
+
|
|
2299
|
+
is_infocqr = config["kind"] == "infocqr_worker"
|
|
2300
|
+
runtime_label = "InfoCQR" if is_infocqr else "ReFormeR"
|
|
2301
|
+
driver_sha256 = _verify_locked_file(
|
|
2302
|
+
config["driver_path"], config["driver_sha256"], label=f"{runtime_label} driver"
|
|
2303
|
+
)
|
|
2304
|
+
executable_sha256 = _verify_locked_file(
|
|
2305
|
+
config["python_executable"],
|
|
2306
|
+
config["python_executable_sha256"],
|
|
2307
|
+
label="ReFormeR Python executable",
|
|
2308
|
+
)
|
|
2309
|
+
manifest_sha256 = _verify_locked_file(
|
|
2310
|
+
config["compatibility_manifest_path"],
|
|
2311
|
+
config["compatibility_manifest_sha256"],
|
|
2312
|
+
label=f"{runtime_label} compatibility manifest",
|
|
2313
|
+
)
|
|
2314
|
+
model_fingerprint = dict(_fingerprint(backend, label="ReFormeR backend"))
|
|
2315
|
+
backend_config = {
|
|
2316
|
+
"base_url": config["base_url"],
|
|
2317
|
+
"api_key_env": config["api_key_env"],
|
|
2318
|
+
"model": config["model_id"],
|
|
2319
|
+
"fingerprint": model_fingerprint,
|
|
2320
|
+
"extra_body": config["extra_body"],
|
|
2321
|
+
"timeout_seconds": config["timeout_seconds"],
|
|
2322
|
+
"max_input_tokens_per_call": config["max_input_tokens_per_call"],
|
|
2323
|
+
"max_output_tokens": config["max_output_tokens"],
|
|
2324
|
+
"temperature": config["temperature"],
|
|
2325
|
+
"input_cost_per_million": config["input_cost_per_million"],
|
|
2326
|
+
"output_cost_per_million": config["output_cost_per_million"],
|
|
2327
|
+
}
|
|
2328
|
+
return JsonLineSubprocessWorker(
|
|
2329
|
+
command=(
|
|
2330
|
+
str(config["python_executable"]),
|
|
2331
|
+
str(config["driver_path"]),
|
|
2332
|
+
"--checkout",
|
|
2333
|
+
str(config["checkout"]),
|
|
2334
|
+
"--manifest" if is_infocqr else "--compatibility-manifest",
|
|
2335
|
+
str(config["compatibility_manifest_path"]),
|
|
2336
|
+
"--backend-config-json",
|
|
2337
|
+
json.dumps(
|
|
2338
|
+
to_jsonable(backend_config),
|
|
2339
|
+
ensure_ascii=False,
|
|
2340
|
+
sort_keys=True,
|
|
2341
|
+
separators=(",", ":"),
|
|
2342
|
+
allow_nan=False,
|
|
2343
|
+
),
|
|
2344
|
+
"--acknowledge-noassertion-license",
|
|
2345
|
+
),
|
|
2346
|
+
worker_id=(
|
|
2347
|
+
"infocqr-common-support-rewriter" if is_infocqr else "reformer-pattern-reformulator"
|
|
2348
|
+
),
|
|
2349
|
+
identity={
|
|
2350
|
+
"implementation": (
|
|
2351
|
+
"agentinputlab.infocqr.process_worker.v1"
|
|
2352
|
+
if is_infocqr
|
|
2353
|
+
else "agentinputlab.reformer.process_worker.v1"
|
|
2354
|
+
),
|
|
2355
|
+
"release_id": (
|
|
2356
|
+
"rewrite.infocqr.common_support_rw_zsl"
|
|
2357
|
+
if is_infocqr
|
|
2358
|
+
else "rewrite.reformer.repo_native_r_only"
|
|
2359
|
+
),
|
|
2360
|
+
"upstream_commit": (
|
|
2361
|
+
"2953c461aa0c6817b1b7656dbd5168050227b31d"
|
|
2362
|
+
if is_infocqr
|
|
2363
|
+
else "72e52450a922bc0051b5b39c3a6307186a555137"
|
|
2364
|
+
),
|
|
2365
|
+
"driver_sha256": driver_sha256,
|
|
2366
|
+
"python_executable_sha256": executable_sha256,
|
|
2367
|
+
"compatibility_manifest_sha256": manifest_sha256,
|
|
2368
|
+
"backend": model_fingerprint,
|
|
2369
|
+
},
|
|
2370
|
+
timeout_seconds=config["timeout_seconds"],
|
|
2371
|
+
pass_environment=(config["api_key_env"],),
|
|
2372
|
+
working_directory=str(config["driver_path"].parents[2]),
|
|
2373
|
+
environment={"PYTHONDONTWRITEBYTECODE": "1"},
|
|
2374
|
+
)
|
|
2375
|
+
if config["kind"] == "qrag_common_support":
|
|
2376
|
+
from agentinputlab.algorithms.acquire.qrag import LocalQRAGFrozenPolicyRuntime
|
|
2377
|
+
|
|
2378
|
+
return LocalQRAGFrozenPolicyRuntime(
|
|
2379
|
+
source_root=config["source_root"],
|
|
2380
|
+
checkpoint_root=config["checkpoint_root"],
|
|
2381
|
+
embedder_root=config["embedder_root"],
|
|
2382
|
+
compatibility_manifest_path=config["compatibility_manifest_path"],
|
|
2383
|
+
compatibility_manifest_sha256=config["compatibility_manifest_sha256"],
|
|
2384
|
+
embedder_manifest_sha256=config["embedder_manifest_sha256"],
|
|
2385
|
+
device=config["device"],
|
|
2386
|
+
)
|
|
2387
|
+
if config["kind"] == "smartsearch_worker":
|
|
2388
|
+
from agentinputlab.integrations.external_workers import JsonLineSubprocessWorker
|
|
2389
|
+
|
|
2390
|
+
driver_sha256 = _verify_locked_file(
|
|
2391
|
+
config["driver_path"], config["driver_sha256"], label="SmartSearch driver"
|
|
2392
|
+
)
|
|
2393
|
+
executable_sha256 = _verify_locked_file(
|
|
2394
|
+
config["python_executable"],
|
|
2395
|
+
config["python_executable_sha256"],
|
|
2396
|
+
label="SmartSearch Python executable",
|
|
2397
|
+
)
|
|
2398
|
+
manifest_sha256 = _verify_locked_file(
|
|
2399
|
+
config["compatibility_manifest_path"],
|
|
2400
|
+
config["compatibility_manifest_sha256"],
|
|
2401
|
+
label="SmartSearch compatibility manifest",
|
|
2402
|
+
)
|
|
2403
|
+
runtime_manifest_sha256 = _verify_locked_file(
|
|
2404
|
+
config["runtime_manifest_path"],
|
|
2405
|
+
config["runtime_manifest_sha256"],
|
|
2406
|
+
label="SmartSearch runtime manifest",
|
|
2407
|
+
)
|
|
2408
|
+
model_fingerprint = dict(_fingerprint(backend, label="SmartSearch target backend"))
|
|
2409
|
+
return JsonLineSubprocessWorker(
|
|
2410
|
+
command=(str(config["python_executable"]), str(config["driver_path"])),
|
|
2411
|
+
worker_id="smartsearch-common-support-queries",
|
|
2412
|
+
identity={
|
|
2413
|
+
"implementation": "agentinputlab.smartsearch.process_worker.v2",
|
|
2414
|
+
"release_id": "rewrite.smartsearch.common_support_queries",
|
|
2415
|
+
"component_release_id": "recipe.smartsearch.frozen_3b_inference",
|
|
2416
|
+
"upstream_commit": "5e9e7ec0d6af34f45fad145d4bff442529e7e6ff",
|
|
2417
|
+
"driver_sha256": driver_sha256,
|
|
2418
|
+
"python_executable_sha256": executable_sha256,
|
|
2419
|
+
"compatibility_manifest_sha256": manifest_sha256,
|
|
2420
|
+
"runtime_manifest_sha256": runtime_manifest_sha256,
|
|
2421
|
+
"target_backend": model_fingerprint,
|
|
2422
|
+
},
|
|
2423
|
+
timeout_seconds=config["timeout_seconds"],
|
|
2424
|
+
working_directory=str(config["driver_path"].parents[2]),
|
|
2425
|
+
environment={
|
|
2426
|
+
"AGENTINPUTLAB_SMARTSEARCH_CHECKOUT": str(config["checkout"]),
|
|
2427
|
+
"AGENTINPUTLAB_SMARTSEARCH_RUNTIME_MANIFEST": str(config["runtime_manifest_path"]),
|
|
2428
|
+
"PYTHONDONTWRITEBYTECODE": "1",
|
|
2429
|
+
},
|
|
2430
|
+
)
|
|
2431
|
+
if config["kind"] == "r2ag_common_support":
|
|
2432
|
+
import os
|
|
2433
|
+
|
|
2434
|
+
from agentinputlab.algorithms.acquire.r2ag import (
|
|
2435
|
+
R2AG_RUNTIME_SOCKET_ENV,
|
|
2436
|
+
R2AG_RUNTIME_TIMEOUT_ENV,
|
|
2437
|
+
LocalR2AGCommonSupportReader,
|
|
2438
|
+
LocalR2AGCommonSupportRuntime,
|
|
2439
|
+
LocalSentenceTransformerR2AGFeatureEncoder,
|
|
2440
|
+
UnixSocketR2AGCommonSupportRuntime,
|
|
2441
|
+
)
|
|
2442
|
+
|
|
2443
|
+
socket_path = os.environ.get(R2AG_RUNTIME_SOCKET_ENV)
|
|
2444
|
+
if socket_path:
|
|
2445
|
+
return UnixSocketR2AGCommonSupportRuntime(
|
|
2446
|
+
socket_path=Path(socket_path),
|
|
2447
|
+
timeout_seconds=float(os.environ.get(R2AG_RUNTIME_TIMEOUT_ENV, "3600")),
|
|
2448
|
+
)
|
|
2449
|
+
|
|
2450
|
+
return LocalR2AGCommonSupportRuntime(
|
|
2451
|
+
reader=LocalR2AGCommonSupportReader(
|
|
2452
|
+
artifact_root=config["artifact_root"],
|
|
2453
|
+
manifest_sha256=config["manifest_sha256"],
|
|
2454
|
+
source_root=config["source_root"],
|
|
2455
|
+
base_model_root=config["base_model_root"],
|
|
2456
|
+
base_model_load_set_sha256=config["base_model_load_set_sha256"],
|
|
2457
|
+
device=config["reader_device"],
|
|
2458
|
+
),
|
|
2459
|
+
feature_encoder=LocalSentenceTransformerR2AGFeatureEncoder(
|
|
2460
|
+
model_root=config["feature_model_root"],
|
|
2461
|
+
model_load_set_sha256=config["feature_model_load_set_sha256"],
|
|
2462
|
+
device=config["feature_device"],
|
|
2463
|
+
),
|
|
2464
|
+
)
|
|
2465
|
+
raise ValidationError(f"unsupported external runtime kind: {config['kind']!r}")
|
|
2466
|
+
|
|
2467
|
+
|
|
2468
|
+
def _parse_predictor_parameters(parameters: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
2469
|
+
_reject_embedded_secrets(parameters, path="predictor parameters")
|
|
2470
|
+
data = _strict_mapping(
|
|
2471
|
+
parameters,
|
|
2472
|
+
path="predictor parameters",
|
|
2473
|
+
allowed={"schema_version", "task", "qa", "retrieval"},
|
|
2474
|
+
required={"schema_version"},
|
|
2475
|
+
)
|
|
2476
|
+
_require_schema_version(data["schema_version"], path="predictor parameters.schema_version")
|
|
2477
|
+
task = data.get("task", "auto")
|
|
2478
|
+
if task not in {"auto", "qa", "retrieval"}:
|
|
2479
|
+
raise ValidationError("predictor task must be auto, qa, or retrieval")
|
|
2480
|
+
qa = _strict_mapping(
|
|
2481
|
+
data.get("qa", {}),
|
|
2482
|
+
path="predictor parameters.qa",
|
|
2483
|
+
allowed={
|
|
2484
|
+
"system_prompt",
|
|
2485
|
+
"prompt_revision",
|
|
2486
|
+
"output_format",
|
|
2487
|
+
"structured_output_mode",
|
|
2488
|
+
"identifier_policy",
|
|
2489
|
+
"max_tokens",
|
|
2490
|
+
"temperature",
|
|
2491
|
+
"stop",
|
|
2492
|
+
"use_seed",
|
|
2493
|
+
"required_capabilities",
|
|
2494
|
+
},
|
|
2495
|
+
required=set(),
|
|
2496
|
+
)
|
|
2497
|
+
output_format = qa.get("output_format", "structured_json")
|
|
2498
|
+
if output_format not in {"structured_json", "answer_only"}:
|
|
2499
|
+
raise ValidationError("QA output_format must be structured_json or answer_only")
|
|
2500
|
+
structured_output_mode = qa.get("structured_output_mode", "json_object")
|
|
2501
|
+
if structured_output_mode not in {"json_object", "json_schema"}:
|
|
2502
|
+
raise ValidationError("QA structured_output_mode must be json_object or json_schema")
|
|
2503
|
+
identifier_policy = qa.get("identifier_policy", "native_sets.v1")
|
|
2504
|
+
if not isinstance(identifier_policy, str) or identifier_policy not in {
|
|
2505
|
+
"native_sets.v1", "strict_strings.v1"
|
|
2506
|
+
}:
|
|
2507
|
+
raise ValidationError("QA identifier_policy must be native_sets.v1 or strict_strings.v1")
|
|
2508
|
+
default_system_prompt = (
|
|
2509
|
+
"Answer using only the visible request and context. Return exactly one JSON "
|
|
2510
|
+
"object with keys answer, support_ids, evidence_ids, and evidence_texts. "
|
|
2511
|
+
"Each non-answer value must be an array of unique strings. Use the visible "
|
|
2512
|
+
"support identifiers when present; otherwise use visible block IDs. "
|
|
2513
|
+
"For 2Wiki evidence_ids, use canonical JSON strings of inferred "
|
|
2514
|
+
"[subject, relation, object] triples. Do not add markdown."
|
|
2515
|
+
if output_format == "structured_json"
|
|
2516
|
+
else (
|
|
2517
|
+
"Answer the question using only the visible request and context. "
|
|
2518
|
+
"Return only the answer text."
|
|
2519
|
+
)
|
|
2520
|
+
)
|
|
2521
|
+
system_prompt = qa.get("system_prompt", default_system_prompt)
|
|
2522
|
+
if not isinstance(system_prompt, str):
|
|
2523
|
+
raise ValidationError("QA system_prompt must be a string")
|
|
2524
|
+
prompt_revision = _require_text(
|
|
2525
|
+
qa.get("prompt_revision", "visible_qa_text.v1"),
|
|
2526
|
+
label="QA prompt_revision",
|
|
2527
|
+
)
|
|
2528
|
+
max_tokens = _positive_int(qa.get("max_tokens", 512), label="QA max_tokens")
|
|
2529
|
+
temperature = _nonnegative_number(qa.get("temperature", 0.0), label="QA temperature")
|
|
2530
|
+
stop = _text_sequence(qa.get("stop", ()), path="QA stop", allow_empty=True)
|
|
2531
|
+
use_seed = qa.get("use_seed", False)
|
|
2532
|
+
if not isinstance(use_seed, bool):
|
|
2533
|
+
raise ValidationError("QA use_seed must be boolean")
|
|
2534
|
+
required = _capabilities(
|
|
2535
|
+
qa.get("required_capabilities", (ModelCapability.COMPLETION.value,)),
|
|
2536
|
+
path="QA required_capabilities",
|
|
2537
|
+
)
|
|
2538
|
+
if not required:
|
|
2539
|
+
raise ValidationError("QA required_capabilities must not be empty")
|
|
2540
|
+
|
|
2541
|
+
retrieval = _strict_mapping(
|
|
2542
|
+
data.get("retrieval", {}),
|
|
2543
|
+
path="predictor parameters.retrieval",
|
|
2544
|
+
allowed={"top_k", "query_mode", "source_mode"},
|
|
2545
|
+
required=set(),
|
|
2546
|
+
)
|
|
2547
|
+
top_k = _positive_int(retrieval.get("top_k", 100), label="retrieval top_k")
|
|
2548
|
+
if top_k > 10_000:
|
|
2549
|
+
raise ValidationError("retrieval top_k must not exceed 10000")
|
|
2550
|
+
query_mode = retrieval.get("query_mode", "query_only")
|
|
2551
|
+
if query_mode not in {"query_only", "instruction_and_query"}:
|
|
2552
|
+
raise ValidationError("retrieval query_mode must be query_only or instruction_and_query")
|
|
2553
|
+
source_mode = retrieval.get("source_mode", "retriever")
|
|
2554
|
+
if source_mode not in {"retriever", "context_or_retriever"}:
|
|
2555
|
+
raise ValidationError("retrieval source_mode must be retriever or context_or_retriever")
|
|
2556
|
+
return MappingProxyType(
|
|
2557
|
+
{
|
|
2558
|
+
"schema_version": _SCHEMA_VERSION,
|
|
2559
|
+
"task": task,
|
|
2560
|
+
"qa": MappingProxyType(
|
|
2561
|
+
{
|
|
2562
|
+
"system_prompt": system_prompt,
|
|
2563
|
+
"prompt_revision": prompt_revision,
|
|
2564
|
+
"output_format": output_format,
|
|
2565
|
+
"structured_output_mode": structured_output_mode,
|
|
2566
|
+
"identifier_policy": identifier_policy,
|
|
2567
|
+
"max_tokens": max_tokens,
|
|
2568
|
+
"temperature": temperature,
|
|
2569
|
+
"stop": stop,
|
|
2570
|
+
"use_seed": use_seed,
|
|
2571
|
+
"required_capabilities": required,
|
|
2572
|
+
}
|
|
2573
|
+
),
|
|
2574
|
+
"retrieval": MappingProxyType(
|
|
2575
|
+
{
|
|
2576
|
+
"top_k": top_k,
|
|
2577
|
+
"query_mode": query_mode,
|
|
2578
|
+
"source_mode": source_mode,
|
|
2579
|
+
}
|
|
2580
|
+
),
|
|
2581
|
+
}
|
|
2582
|
+
)
|
|
2583
|
+
|
|
2584
|
+
|
|
2585
|
+
def _parse_corpus_retriever_parameters(parameters: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
2586
|
+
_reject_embedded_secrets(parameters, path="corpus retriever parameters")
|
|
2587
|
+
data = _strict_mapping(
|
|
2588
|
+
parameters,
|
|
2589
|
+
path="corpus retriever parameters",
|
|
2590
|
+
allowed={
|
|
2591
|
+
"schema_version",
|
|
2592
|
+
"benchmark_id",
|
|
2593
|
+
"profile_id",
|
|
2594
|
+
"corpus_path",
|
|
2595
|
+
"corpus_sha256",
|
|
2596
|
+
"corpus_revision",
|
|
2597
|
+
"expected_document_count",
|
|
2598
|
+
"options",
|
|
2599
|
+
"artifact_files",
|
|
2600
|
+
},
|
|
2601
|
+
required={
|
|
2602
|
+
"schema_version",
|
|
2603
|
+
"benchmark_id",
|
|
2604
|
+
"profile_id",
|
|
2605
|
+
"corpus_path",
|
|
2606
|
+
"corpus_sha256",
|
|
2607
|
+
"corpus_revision",
|
|
2608
|
+
"expected_document_count",
|
|
2609
|
+
},
|
|
2610
|
+
)
|
|
2611
|
+
_require_schema_version(
|
|
2612
|
+
data["schema_version"],
|
|
2613
|
+
path="corpus retriever parameters.schema_version",
|
|
2614
|
+
)
|
|
2615
|
+
benchmark_id = _require_text(data["benchmark_id"], label="retriever benchmark_id")
|
|
2616
|
+
profile_id = _require_text(data["profile_id"], label="retriever profile_id")
|
|
2617
|
+
profile = retriever_profile(profile_id)
|
|
2618
|
+
options = _mapping_or_empty(
|
|
2619
|
+
data.get("options"),
|
|
2620
|
+
path="corpus retriever options",
|
|
2621
|
+
)
|
|
2622
|
+
if profile.backend == "memory_bm25":
|
|
2623
|
+
allowed_options = {"retriever_id", "k1", "b"}
|
|
2624
|
+
else:
|
|
2625
|
+
allowed_options = {
|
|
2626
|
+
"retriever_id",
|
|
2627
|
+
"batch_size",
|
|
2628
|
+
"device",
|
|
2629
|
+
"local_files_only",
|
|
2630
|
+
"model_path",
|
|
2631
|
+
}
|
|
2632
|
+
unknown_options = set(options) - allowed_options
|
|
2633
|
+
if unknown_options:
|
|
2634
|
+
raise ValidationError(
|
|
2635
|
+
"corpus retriever options contain unsupported fields: "
|
|
2636
|
+
+ ", ".join(sorted(unknown_options))
|
|
2637
|
+
)
|
|
2638
|
+
normalized_options = dict(options)
|
|
2639
|
+
if profile.backend == "sentence_transformers_dense":
|
|
2640
|
+
if normalized_options.get("local_files_only", True) is not True:
|
|
2641
|
+
raise ValidationError("production dense retriever requires local_files_only=true")
|
|
2642
|
+
normalized_options["local_files_only"] = True
|
|
2643
|
+
if "model_path" in normalized_options:
|
|
2644
|
+
normalized_options["model_path"] = str(
|
|
2645
|
+
_absolute_path(normalized_options["model_path"], label="dense model_path")
|
|
2646
|
+
)
|
|
2647
|
+
return freeze_mapping(
|
|
2648
|
+
{
|
|
2649
|
+
"benchmark_id": benchmark_id,
|
|
2650
|
+
"profile_id": profile_id,
|
|
2651
|
+
"corpus_path": _absolute_path(data["corpus_path"], label="corpus_path"),
|
|
2652
|
+
"corpus_sha256": _sha256_text(
|
|
2653
|
+
data["corpus_sha256"],
|
|
2654
|
+
label="corpus_sha256",
|
|
2655
|
+
),
|
|
2656
|
+
"corpus_revision": _require_text(
|
|
2657
|
+
data["corpus_revision"],
|
|
2658
|
+
label="corpus_revision",
|
|
2659
|
+
),
|
|
2660
|
+
"expected_document_count": _positive_int(
|
|
2661
|
+
data["expected_document_count"],
|
|
2662
|
+
label="expected_document_count",
|
|
2663
|
+
),
|
|
2664
|
+
"options": _canonical_mapping(
|
|
2665
|
+
normalized_options,
|
|
2666
|
+
label="corpus retriever options",
|
|
2667
|
+
),
|
|
2668
|
+
"artifact_files": _artifact_file_locks(
|
|
2669
|
+
data.get("artifact_files", ()),
|
|
2670
|
+
path="corpus retriever artifact_files",
|
|
2671
|
+
),
|
|
2672
|
+
}
|
|
2673
|
+
)
|
|
2674
|
+
|
|
2675
|
+
|
|
2676
|
+
def _load_replay_fixture(path: Path, expected_sha256: str) -> Mapping[str, Any]:
|
|
2677
|
+
raw = _read_checksum_bytes(
|
|
2678
|
+
path,
|
|
2679
|
+
expected_sha256,
|
|
2680
|
+
max_bytes=_MAX_REPLAY_BYTES,
|
|
2681
|
+
label="replay fixture",
|
|
2682
|
+
)
|
|
2683
|
+
parsed = _strict_json_bytes(raw, label="replay fixture")
|
|
2684
|
+
data = _strict_mapping(
|
|
2685
|
+
parsed,
|
|
2686
|
+
path="replay fixture",
|
|
2687
|
+
allowed={"schema_version", "responses", "strict_request_hashes"},
|
|
2688
|
+
required={"schema_version", "responses"},
|
|
2689
|
+
)
|
|
2690
|
+
_require_schema_version(data["schema_version"], path="replay fixture.schema_version")
|
|
2691
|
+
values = data["responses"]
|
|
2692
|
+
if isinstance(values, (str, bytes)) or not isinstance(values, Sequence) or not values:
|
|
2693
|
+
raise ArtifactError("replay fixture responses must be a non-empty array")
|
|
2694
|
+
responses: list[str | GenerationResponse] = []
|
|
2695
|
+
for index, value in enumerate(values):
|
|
2696
|
+
if isinstance(value, str):
|
|
2697
|
+
responses.append(value)
|
|
2698
|
+
continue
|
|
2699
|
+
item = _strict_mapping(
|
|
2700
|
+
value,
|
|
2701
|
+
path=f"replay fixture.responses[{index}]",
|
|
2702
|
+
allowed={"text", "model", "finish_reason", "usage", "raw"},
|
|
2703
|
+
required={"text"},
|
|
2704
|
+
)
|
|
2705
|
+
text = item["text"]
|
|
2706
|
+
if not isinstance(text, str):
|
|
2707
|
+
raise ArtifactError(f"replay fixture.responses[{index}].text must be a string")
|
|
2708
|
+
usage_data = _strict_mapping(
|
|
2709
|
+
item.get("usage", {}),
|
|
2710
|
+
path=f"replay fixture.responses[{index}].usage",
|
|
2711
|
+
allowed={"input_tokens", "output_tokens", "cost_usd"},
|
|
2712
|
+
required=set(),
|
|
2713
|
+
)
|
|
2714
|
+
try:
|
|
2715
|
+
usage = Usage(**usage_data)
|
|
2716
|
+
except (TypeError, ValueError) as exc:
|
|
2717
|
+
raise ArtifactError(
|
|
2718
|
+
f"replay fixture.responses[{index}].usage is invalid: {exc}"
|
|
2719
|
+
) from exc
|
|
2720
|
+
responses.append(
|
|
2721
|
+
GenerationResponse(
|
|
2722
|
+
text=text,
|
|
2723
|
+
model=str(item.get("model", "")),
|
|
2724
|
+
finish_reason=str(item.get("finish_reason", "replay")),
|
|
2725
|
+
usage=usage,
|
|
2726
|
+
raw=_mapping_or_empty(
|
|
2727
|
+
item.get("raw"),
|
|
2728
|
+
path=f"replay fixture.responses[{index}].raw",
|
|
2729
|
+
),
|
|
2730
|
+
)
|
|
2731
|
+
)
|
|
2732
|
+
hashes = _text_sequence(
|
|
2733
|
+
data.get("strict_request_hashes", ()),
|
|
2734
|
+
path="replay fixture.strict_request_hashes",
|
|
2735
|
+
allow_empty=True,
|
|
2736
|
+
)
|
|
2737
|
+
if hashes and len(hashes) != len(responses):
|
|
2738
|
+
raise ArtifactError("replay fixture strict_request_hashes must match response count")
|
|
2739
|
+
for value in hashes:
|
|
2740
|
+
_require_sha256(value, label="replay strict request hash")
|
|
2741
|
+
return MappingProxyType(
|
|
2742
|
+
{
|
|
2743
|
+
"responses": tuple(responses),
|
|
2744
|
+
"strict_request_hashes": hashes,
|
|
2745
|
+
}
|
|
2746
|
+
)
|
|
2747
|
+
|
|
2748
|
+
|
|
2749
|
+
def _load_locked_corpus(config: Mapping[str, Any]) -> tuple[Document, ...]:
|
|
2750
|
+
path = Path(config["corpus_path"])
|
|
2751
|
+
identity = _regular_file_identity(path, label="prepared corpus")
|
|
2752
|
+
return _load_locked_corpus_snapshot(
|
|
2753
|
+
str(path),
|
|
2754
|
+
config["corpus_sha256"],
|
|
2755
|
+
config["corpus_revision"],
|
|
2756
|
+
identity,
|
|
2757
|
+
)
|
|
2758
|
+
|
|
2759
|
+
|
|
2760
|
+
@lru_cache(maxsize=16)
|
|
2761
|
+
def _load_locked_corpus_snapshot(
|
|
2762
|
+
path_text: str,
|
|
2763
|
+
expected_sha256: str,
|
|
2764
|
+
corpus_revision: str,
|
|
2765
|
+
identity: tuple[int, int, int, int, int],
|
|
2766
|
+
) -> tuple[Document, ...]:
|
|
2767
|
+
"""Stream and cache one immutable corpus snapshot across arm runtimes."""
|
|
2768
|
+
|
|
2769
|
+
path = Path(path_text)
|
|
2770
|
+
digest = hashlib.sha256()
|
|
2771
|
+
documents: list[Document] = []
|
|
2772
|
+
identifiers: set[str] = set()
|
|
2773
|
+
try:
|
|
2774
|
+
with path.open("rb") as handle:
|
|
2775
|
+
if _stat_identity(os.fstat(handle.fileno())) != identity:
|
|
2776
|
+
raise ArtifactError("prepared corpus changed before it could be read")
|
|
2777
|
+
for line_number, raw_line in enumerate(handle, start=1):
|
|
2778
|
+
digest.update(raw_line)
|
|
2779
|
+
line = raw_line.rstrip(b"\r\n")
|
|
2780
|
+
if not line:
|
|
2781
|
+
raise ArtifactError(f"prepared corpus contains a blank line at {line_number}")
|
|
2782
|
+
if len(line) > _MAX_CORPUS_LINE_BYTES:
|
|
2783
|
+
raise ArtifactError(f"prepared corpus line {line_number} exceeds size limit")
|
|
2784
|
+
value = _strict_json_bytes(
|
|
2785
|
+
line,
|
|
2786
|
+
label=f"prepared corpus line {line_number}",
|
|
2787
|
+
)
|
|
2788
|
+
item = _strict_mapping(
|
|
2789
|
+
value,
|
|
2790
|
+
path=f"prepared corpus line {line_number}",
|
|
2791
|
+
allowed={"id", "title", "text"},
|
|
2792
|
+
required={"id", "title", "text"},
|
|
2793
|
+
)
|
|
2794
|
+
document_id = _require_text(
|
|
2795
|
+
item["id"],
|
|
2796
|
+
label=f"prepared corpus line {line_number}.id",
|
|
2797
|
+
)
|
|
2798
|
+
if document_id in identifiers:
|
|
2799
|
+
raise ArtifactError(f"prepared corpus contains duplicate ID {document_id!r}")
|
|
2800
|
+
identifiers.add(document_id)
|
|
2801
|
+
title = item["title"]
|
|
2802
|
+
text = item["text"]
|
|
2803
|
+
if not isinstance(title, str):
|
|
2804
|
+
raise ArtifactError(
|
|
2805
|
+
f"prepared corpus line {line_number}.title must be a string"
|
|
2806
|
+
)
|
|
2807
|
+
if not isinstance(text, str) or not text:
|
|
2808
|
+
raise ArtifactError(
|
|
2809
|
+
f"prepared corpus line {line_number}.text must be non-empty text"
|
|
2810
|
+
)
|
|
2811
|
+
documents.append(
|
|
2812
|
+
Document(
|
|
2813
|
+
doc_id=document_id,
|
|
2814
|
+
title=title,
|
|
2815
|
+
text=text,
|
|
2816
|
+
corpus_revision=corpus_revision,
|
|
2817
|
+
)
|
|
2818
|
+
)
|
|
2819
|
+
if _stat_identity(os.fstat(handle.fileno())) != identity:
|
|
2820
|
+
raise ArtifactError("prepared corpus changed while it was being read")
|
|
2821
|
+
except ArtifactError:
|
|
2822
|
+
raise
|
|
2823
|
+
except OSError as exc:
|
|
2824
|
+
raise ArtifactError(f"prepared corpus is unavailable: {path}") from exc
|
|
2825
|
+
if digest.hexdigest() != expected_sha256:
|
|
2826
|
+
raise ArtifactError(f"prepared corpus checksum mismatch: {path}")
|
|
2827
|
+
if not documents:
|
|
2828
|
+
raise ArtifactError("prepared corpus contains no documents")
|
|
2829
|
+
return tuple(documents)
|
|
2830
|
+
|
|
2831
|
+
|
|
2832
|
+
def _regular_file_identity(path: Path, *, label: str) -> tuple[int, int, int, int, int]:
|
|
2833
|
+
if not path.is_absolute():
|
|
2834
|
+
raise ArtifactError(f"{label} path must be absolute")
|
|
2835
|
+
try:
|
|
2836
|
+
metadata = path.stat()
|
|
2837
|
+
except OSError as exc:
|
|
2838
|
+
raise ArtifactError(f"{label} is unavailable: {path}") from exc
|
|
2839
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
2840
|
+
raise ArtifactError(f"{label} is not a regular file: {path}")
|
|
2841
|
+
return _stat_identity(metadata)
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
def _stat_identity(metadata: os.stat_result) -> tuple[int, int, int, int, int]:
|
|
2845
|
+
return (
|
|
2846
|
+
metadata.st_dev,
|
|
2847
|
+
metadata.st_ino,
|
|
2848
|
+
metadata.st_size,
|
|
2849
|
+
metadata.st_mtime_ns,
|
|
2850
|
+
metadata.st_ctime_ns,
|
|
2851
|
+
)
|
|
2852
|
+
|
|
2853
|
+
|
|
2854
|
+
def _verify_checksum_file(
|
|
2855
|
+
path: str | Path,
|
|
2856
|
+
expected_sha256: str,
|
|
2857
|
+
*,
|
|
2858
|
+
label: str,
|
|
2859
|
+
) -> None:
|
|
2860
|
+
source = Path(path)
|
|
2861
|
+
identity = _regular_file_identity(source, label=label)
|
|
2862
|
+
_require_sha256(expected_sha256, label=f"{label} SHA-256")
|
|
2863
|
+
observed = _hash_file_snapshot(str(source), identity)
|
|
2864
|
+
if observed != expected_sha256:
|
|
2865
|
+
raise ArtifactError(f"{label} checksum mismatch: {source}")
|
|
2866
|
+
|
|
2867
|
+
|
|
2868
|
+
def _hash_file_snapshot(
|
|
2869
|
+
path_text: str,
|
|
2870
|
+
identity: tuple[int, int, int, int, int],
|
|
2871
|
+
) -> str:
|
|
2872
|
+
path = Path(path_text)
|
|
2873
|
+
digest = hashlib.sha256()
|
|
2874
|
+
try:
|
|
2875
|
+
with path.open("rb") as handle:
|
|
2876
|
+
if _stat_identity(os.fstat(handle.fileno())) != identity:
|
|
2877
|
+
raise ArtifactError(f"artifact changed before it could be read: {path}")
|
|
2878
|
+
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
|
2879
|
+
digest.update(block)
|
|
2880
|
+
if _stat_identity(os.fstat(handle.fileno())) != identity:
|
|
2881
|
+
raise ArtifactError(f"artifact changed while it was being read: {path}")
|
|
2882
|
+
except ArtifactError:
|
|
2883
|
+
raise
|
|
2884
|
+
except OSError as exc:
|
|
2885
|
+
raise ArtifactError(f"artifact is unavailable: {path}") from exc
|
|
2886
|
+
return digest.hexdigest()
|
|
2887
|
+
|
|
2888
|
+
|
|
2889
|
+
def _render_visible_qa_prompt(state: FrozenAgentState) -> str:
|
|
2890
|
+
"""Render only fields deliberately exposed by ``FrozenAgentState``.
|
|
2891
|
+
|
|
2892
|
+
``task_id`` and ``metadata`` are intentionally omitted because either may
|
|
2893
|
+
carry benchmark identity. Evaluator gold is physically absent from this
|
|
2894
|
+
type and never enters the predictor API.
|
|
2895
|
+
"""
|
|
2896
|
+
|
|
2897
|
+
sections: list[str] = []
|
|
2898
|
+
if state.instruction:
|
|
2899
|
+
sections.append(f"Instruction:\n{state.instruction}")
|
|
2900
|
+
if state.messages:
|
|
2901
|
+
conversation = "\n".join(f"{message.role}: {message.content}" for message in state.messages)
|
|
2902
|
+
sections.append(f"Conversation:\n{conversation}")
|
|
2903
|
+
sections.append(f"Question:\n{state.query}")
|
|
2904
|
+
if state.context:
|
|
2905
|
+
context = "\n\n".join(
|
|
2906
|
+
_render_visible_context_block(block, index=index)
|
|
2907
|
+
for index, block in enumerate(state.context, start=1)
|
|
2908
|
+
)
|
|
2909
|
+
sections.append(f"Visible context:\n{context}")
|
|
2910
|
+
if state.tool_names:
|
|
2911
|
+
sections.append("Available tool names:\n" + "\n".join(state.tool_names))
|
|
2912
|
+
return "\n\n".join(sections)
|
|
2913
|
+
|
|
2914
|
+
|
|
2915
|
+
def _qa_response_format(
|
|
2916
|
+
*,
|
|
2917
|
+
benchmark_id: str,
|
|
2918
|
+
output_format: str,
|
|
2919
|
+
structured_output_mode: str,
|
|
2920
|
+
) -> Mapping[str, Any] | None:
|
|
2921
|
+
if output_format == "answer_only":
|
|
2922
|
+
return None
|
|
2923
|
+
if structured_output_mode == "json_object":
|
|
2924
|
+
return {"type": "json_object"}
|
|
2925
|
+
if structured_output_mode != "json_schema": # pragma: no cover - parsed invariant
|
|
2926
|
+
raise ValidationError("unsupported structured QA output mode")
|
|
2927
|
+
identifier_limit = 0 if benchmark_id == "qasper.v0_3_dev" else 16
|
|
2928
|
+
evidence_text_limit = 3 if benchmark_id == "qasper.v0_3_dev" else 16
|
|
2929
|
+
schema = {
|
|
2930
|
+
"type": "object",
|
|
2931
|
+
"additionalProperties": False,
|
|
2932
|
+
"required": ["answer", "support_ids", "evidence_ids", "evidence_texts"],
|
|
2933
|
+
"properties": {
|
|
2934
|
+
"answer": {"type": "string", "minLength": 1},
|
|
2935
|
+
"support_ids": {
|
|
2936
|
+
"type": "array",
|
|
2937
|
+
"maxItems": identifier_limit,
|
|
2938
|
+
"items": {"type": "string", "minLength": 1},
|
|
2939
|
+
},
|
|
2940
|
+
"evidence_ids": {
|
|
2941
|
+
"type": "array",
|
|
2942
|
+
"maxItems": identifier_limit,
|
|
2943
|
+
"items": {"type": "string", "minLength": 1},
|
|
2944
|
+
},
|
|
2945
|
+
"evidence_texts": {
|
|
2946
|
+
"type": "array",
|
|
2947
|
+
"maxItems": evidence_text_limit,
|
|
2948
|
+
"items": {"type": "string", "minLength": 1},
|
|
2949
|
+
},
|
|
2950
|
+
},
|
|
2951
|
+
}
|
|
2952
|
+
return {
|
|
2953
|
+
"type": "json_schema",
|
|
2954
|
+
"json_schema": {
|
|
2955
|
+
"name": "qa_prediction",
|
|
2956
|
+
"strict": True,
|
|
2957
|
+
"schema": schema,
|
|
2958
|
+
},
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
|
|
2962
|
+
def _render_visible_context_block(block: Any, *, index: int) -> str:
|
|
2963
|
+
support_id = block.metadata.get("official_support_id", block.block_id)
|
|
2964
|
+
source = f"\nSource: {block.source}" if block.source else ""
|
|
2965
|
+
return (
|
|
2966
|
+
f"[Context {index}]\nBlock ID: {block.block_id}\n"
|
|
2967
|
+
f"Support ID: {support_id}{source}\nText: {block.text}"
|
|
2968
|
+
)
|
|
2969
|
+
|
|
2970
|
+
|
|
2971
|
+
def _parse_visible_qa_prediction(
|
|
2972
|
+
text: str,
|
|
2973
|
+
*,
|
|
2974
|
+
benchmark_id: str,
|
|
2975
|
+
state: FrozenAgentState,
|
|
2976
|
+
output_format: str,
|
|
2977
|
+
identifier_policy: str = "native_sets.v1",
|
|
2978
|
+
) -> tuple[QAPrediction, Mapping[str, Any]]:
|
|
2979
|
+
if not isinstance(identifier_policy, str) or identifier_policy not in {
|
|
2980
|
+
"native_sets.v1", "strict_strings.v1"
|
|
2981
|
+
}:
|
|
2982
|
+
raise ValidationError("unsupported QA identifier policy")
|
|
2983
|
+
if output_format == "answer_only":
|
|
2984
|
+
return QAPrediction(answer=text.strip()), {
|
|
2985
|
+
"identifier_policy": identifier_policy,
|
|
2986
|
+
"support_id_validation": "not_applicable_answer_only.v1",
|
|
2987
|
+
"valid_support_id_count": 0,
|
|
2988
|
+
"invalid_support_id_count": 0,
|
|
2989
|
+
"invalid_support_ids_sha256": None,
|
|
2990
|
+
"evidence_id_validation": "not_applicable_answer_only.v1",
|
|
2991
|
+
"valid_evidence_id_count": 0,
|
|
2992
|
+
"invalid_evidence_id_count": 0,
|
|
2993
|
+
"invalid_evidence_ids_sha256": None,
|
|
2994
|
+
}
|
|
2995
|
+
if output_format != "structured_json": # pragma: no cover - parsed config invariant
|
|
2996
|
+
raise ValidationError("unsupported QA output format")
|
|
2997
|
+
try:
|
|
2998
|
+
value = json.loads(
|
|
2999
|
+
text,
|
|
3000
|
+
object_pairs_hook=_reject_duplicate_keys,
|
|
3001
|
+
parse_constant=lambda item: _reject_json_constant(item),
|
|
3002
|
+
)
|
|
3003
|
+
except (TypeError, json.JSONDecodeError) as exc:
|
|
3004
|
+
raise ValidationError("QA backend response is not strict JSON") from exc
|
|
3005
|
+
if not isinstance(value, Mapping):
|
|
3006
|
+
raise ValidationError("structured QA response must be a JSON object")
|
|
3007
|
+
expected = {"answer", "support_ids", "evidence_ids", "evidence_texts"}
|
|
3008
|
+
if set(value) != expected:
|
|
3009
|
+
raise ValidationError(
|
|
3010
|
+
"structured QA response schema mismatch: "
|
|
3011
|
+
f"missing={sorted(expected - set(value))}, "
|
|
3012
|
+
f"unknown={sorted(set(value) - expected)}"
|
|
3013
|
+
)
|
|
3014
|
+
answer = value["answer"]
|
|
3015
|
+
if not isinstance(answer, str) or not answer.strip():
|
|
3016
|
+
raise ValidationError("structured QA answer must be non-empty text")
|
|
3017
|
+
identifier_audit: dict[str, Any] = {"identifier_policy": identifier_policy}
|
|
3018
|
+
malformed_support_ids: tuple[Any, ...] = ()
|
|
3019
|
+
malformed_evidence_ids: tuple[Any, ...] = ()
|
|
3020
|
+
if identifier_policy == "strict_strings.v1":
|
|
3021
|
+
support_ids = _prediction_string_array(
|
|
3022
|
+
value["support_ids"], label="structured QA support_ids"
|
|
3023
|
+
)
|
|
3024
|
+
evidence_ids = _prediction_string_array(
|
|
3025
|
+
value["evidence_ids"], label="structured QA evidence_ids"
|
|
3026
|
+
)
|
|
3027
|
+
else:
|
|
3028
|
+
support_ids, malformed_support_ids, support_audit = _normalize_qa_identifier_array(
|
|
3029
|
+
value["support_ids"],
|
|
3030
|
+
label="structured QA support_ids",
|
|
3031
|
+
kind=(
|
|
3032
|
+
"support_pair"
|
|
3033
|
+
if benchmark_id in {"2wiki.multihop", "hotpotqa.distractor"}
|
|
3034
|
+
else "opaque"
|
|
3035
|
+
),
|
|
3036
|
+
)
|
|
3037
|
+
evidence_ids, malformed_evidence_ids, evidence_audit = _normalize_qa_identifier_array(
|
|
3038
|
+
value["evidence_ids"],
|
|
3039
|
+
label="structured QA evidence_ids",
|
|
3040
|
+
kind="evidence_triple" if benchmark_id == "2wiki.multihop" else "opaque",
|
|
3041
|
+
)
|
|
3042
|
+
identifier_audit.update(
|
|
3043
|
+
{f"support_{key}": item for key, item in support_audit.items()}
|
|
3044
|
+
)
|
|
3045
|
+
identifier_audit.update(
|
|
3046
|
+
{f"evidence_{key}": item for key, item in evidence_audit.items()}
|
|
3047
|
+
)
|
|
3048
|
+
evidence_texts, duplicate_evidence_texts = _prediction_string_array_keep_first(
|
|
3049
|
+
value["evidence_texts"],
|
|
3050
|
+
label="structured QA evidence_texts",
|
|
3051
|
+
)
|
|
3052
|
+
visible_support_ids = {
|
|
3053
|
+
str(block.metadata.get("official_support_id", block.block_id)) for block in state.context
|
|
3054
|
+
}
|
|
3055
|
+
valid_support_ids, invalid_support_ids = _partition_visible_support_ids(
|
|
3056
|
+
support_ids,
|
|
3057
|
+
visible_support_ids=visible_support_ids,
|
|
3058
|
+
benchmark_id=benchmark_id,
|
|
3059
|
+
)
|
|
3060
|
+
all_invalid_support_ids = (*malformed_support_ids, *invalid_support_ids)
|
|
3061
|
+
support_validation = (
|
|
3062
|
+
"filter_unknown_or_invalid_canonical_support.v1"
|
|
3063
|
+
if benchmark_id in {"2wiki.multihop", "hotpotqa.distractor"}
|
|
3064
|
+
else "filter_unknown_visible_only.v1"
|
|
3065
|
+
)
|
|
3066
|
+
valid_evidence_ids = evidence_ids
|
|
3067
|
+
invalid_evidence_ids: tuple[str, ...] = ()
|
|
3068
|
+
evidence_validation = "not_applicable_non_2wiki.v1"
|
|
3069
|
+
if benchmark_id == "2wiki.multihop":
|
|
3070
|
+
valid_evidence_ids, invalid_evidence_ids = _partition_two_wiki_evidence_ids(evidence_ids)
|
|
3071
|
+
evidence_validation = "filter_invalid_2wiki_canonical_triples.v1"
|
|
3072
|
+
all_invalid_evidence_ids = (*malformed_evidence_ids, *invalid_evidence_ids)
|
|
3073
|
+
return (
|
|
3074
|
+
QAPrediction(
|
|
3075
|
+
answer=answer.strip(),
|
|
3076
|
+
support_ids=valid_support_ids,
|
|
3077
|
+
evidence_ids=valid_evidence_ids,
|
|
3078
|
+
evidence_texts=evidence_texts,
|
|
3079
|
+
),
|
|
3080
|
+
{
|
|
3081
|
+
**identifier_audit,
|
|
3082
|
+
"support_id_validation": support_validation,
|
|
3083
|
+
"valid_support_id_count": len(valid_support_ids),
|
|
3084
|
+
"invalid_support_id_count": len(all_invalid_support_ids),
|
|
3085
|
+
"invalid_support_ids_sha256": (
|
|
3086
|
+
content_hash(all_invalid_support_ids) if all_invalid_support_ids else None
|
|
3087
|
+
),
|
|
3088
|
+
"evidence_id_validation": evidence_validation,
|
|
3089
|
+
"valid_evidence_id_count": len(valid_evidence_ids),
|
|
3090
|
+
"invalid_evidence_id_count": len(all_invalid_evidence_ids),
|
|
3091
|
+
"invalid_evidence_ids_sha256": (
|
|
3092
|
+
content_hash(all_invalid_evidence_ids) if all_invalid_evidence_ids else None
|
|
3093
|
+
),
|
|
3094
|
+
"evidence_text_duplicate_policy": "deduplicate_exact_trimmed_keep_first.v1",
|
|
3095
|
+
"duplicate_evidence_text_count": len(duplicate_evidence_texts),
|
|
3096
|
+
"duplicate_evidence_texts_sha256": (
|
|
3097
|
+
content_hash(duplicate_evidence_texts) if duplicate_evidence_texts else None
|
|
3098
|
+
),
|
|
3099
|
+
},
|
|
3100
|
+
)
|
|
3101
|
+
|
|
3102
|
+
|
|
3103
|
+
def _normalize_qa_identifier_array(
|
|
3104
|
+
value: Any,
|
|
3105
|
+
*,
|
|
3106
|
+
label: str,
|
|
3107
|
+
kind: str,
|
|
3108
|
+
) -> tuple[tuple[str, ...], tuple[Any, ...], Mapping[str, Any]]:
|
|
3109
|
+
"""Normalize set-like IDs without inferring evidence or reading references.
|
|
3110
|
+
|
|
3111
|
+
Native support pairs may be JSON strings, nested pairs, or an unambiguous
|
|
3112
|
+
flat title/integer stream. Native 2Wiki triples must be JSON strings or
|
|
3113
|
+
nested triples: a flat string stream is ambiguous and is never grouped.
|
|
3114
|
+
Unknown IDs still pass through the visible-support filter. Invalid typed
|
|
3115
|
+
entries are excluded and hash-audited; no coercion or title-only lookup is
|
|
3116
|
+
permitted. All policies keep strict JSON and the required response fields.
|
|
3117
|
+
"""
|
|
3118
|
+
|
|
3119
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
3120
|
+
raise ValidationError(f"{label} must be an array")
|
|
3121
|
+
identifiers: list[str] = []
|
|
3122
|
+
invalid: list[Any] = []
|
|
3123
|
+
duplicates: list[str] = []
|
|
3124
|
+
seen: set[str] = set()
|
|
3125
|
+
normalized_count = 0
|
|
3126
|
+
index = 0
|
|
3127
|
+
while index < len(value):
|
|
3128
|
+
raw = value[index]
|
|
3129
|
+
index += 1
|
|
3130
|
+
# Only a non-boolean, non-negative integer makes this shape unambiguous.
|
|
3131
|
+
# Never turn e.g. [title, true], [title, "0"], or title alone into an ID.
|
|
3132
|
+
if (
|
|
3133
|
+
kind == "support_pair"
|
|
3134
|
+
and isinstance(raw, str)
|
|
3135
|
+
and raw.strip()
|
|
3136
|
+
and index < len(value)
|
|
3137
|
+
and isinstance(value[index], int)
|
|
3138
|
+
and not isinstance(value[index], bool)
|
|
3139
|
+
and value[index] >= 0
|
|
3140
|
+
):
|
|
3141
|
+
raw = [raw, value[index]]
|
|
3142
|
+
index += 1
|
|
3143
|
+
if isinstance(raw, str) and raw.strip():
|
|
3144
|
+
identifier = raw.strip()
|
|
3145
|
+
if kind != "opaque":
|
|
3146
|
+
try:
|
|
3147
|
+
native = json.loads(identifier)
|
|
3148
|
+
except json.JSONDecodeError:
|
|
3149
|
+
native = None
|
|
3150
|
+
if _is_native_qa_identifier(native, kind=kind):
|
|
3151
|
+
identifier = canonical_json(native)
|
|
3152
|
+
elif _is_native_qa_identifier(raw, kind=kind):
|
|
3153
|
+
identifier = canonical_json(raw)
|
|
3154
|
+
else:
|
|
3155
|
+
invalid.append(raw)
|
|
3156
|
+
continue
|
|
3157
|
+
if not isinstance(raw, str) or identifier != raw:
|
|
3158
|
+
normalized_count += 1
|
|
3159
|
+
if identifier in seen:
|
|
3160
|
+
duplicates.append(identifier)
|
|
3161
|
+
continue
|
|
3162
|
+
seen.add(identifier)
|
|
3163
|
+
identifiers.append(identifier)
|
|
3164
|
+
return (
|
|
3165
|
+
tuple(identifiers),
|
|
3166
|
+
tuple(invalid),
|
|
3167
|
+
{
|
|
3168
|
+
"raw_identifier_array_sha256": content_hash(value),
|
|
3169
|
+
"normalized_id_count": normalized_count,
|
|
3170
|
+
"duplicate_id_count": len(duplicates),
|
|
3171
|
+
"duplicate_ids_sha256": content_hash(duplicates) if duplicates else None,
|
|
3172
|
+
},
|
|
3173
|
+
)
|
|
3174
|
+
|
|
3175
|
+
|
|
3176
|
+
def _is_native_qa_identifier(value: Any, *, kind: str) -> bool:
|
|
3177
|
+
if not isinstance(value, list):
|
|
3178
|
+
return False
|
|
3179
|
+
if kind == "support_pair":
|
|
3180
|
+
return (
|
|
3181
|
+
len(value) == 2
|
|
3182
|
+
and isinstance(value[0], str)
|
|
3183
|
+
and bool(value[0].strip())
|
|
3184
|
+
and isinstance(value[1], int)
|
|
3185
|
+
and not isinstance(value[1], bool)
|
|
3186
|
+
and value[1] >= 0
|
|
3187
|
+
)
|
|
3188
|
+
return (
|
|
3189
|
+
kind == "evidence_triple"
|
|
3190
|
+
and len(value) == 3
|
|
3191
|
+
and all(isinstance(item, str) and item.strip() for item in value)
|
|
3192
|
+
)
|
|
3193
|
+
|
|
3194
|
+
|
|
3195
|
+
def _partition_visible_support_ids(
|
|
3196
|
+
identifiers: tuple[str, ...],
|
|
3197
|
+
*,
|
|
3198
|
+
visible_support_ids: set[str],
|
|
3199
|
+
benchmark_id: str,
|
|
3200
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
3201
|
+
require_canonical_pair = benchmark_id in {
|
|
3202
|
+
"2wiki.multihop",
|
|
3203
|
+
"hotpotqa.distractor",
|
|
3204
|
+
}
|
|
3205
|
+
valid: list[str] = []
|
|
3206
|
+
invalid: list[str] = []
|
|
3207
|
+
for identifier in identifiers:
|
|
3208
|
+
if identifier not in visible_support_ids or (
|
|
3209
|
+
require_canonical_pair and not _is_canonical_support_pair(identifier)
|
|
3210
|
+
):
|
|
3211
|
+
invalid.append(identifier)
|
|
3212
|
+
else:
|
|
3213
|
+
valid.append(identifier)
|
|
3214
|
+
return tuple(valid), tuple(invalid)
|
|
3215
|
+
|
|
3216
|
+
|
|
3217
|
+
def _is_canonical_support_pair(identifier: str) -> bool:
|
|
3218
|
+
try:
|
|
3219
|
+
value = json.loads(identifier)
|
|
3220
|
+
except json.JSONDecodeError:
|
|
3221
|
+
return False
|
|
3222
|
+
return bool(
|
|
3223
|
+
isinstance(value, list)
|
|
3224
|
+
and len(value) == 2
|
|
3225
|
+
and isinstance(value[0], str)
|
|
3226
|
+
and value[0].strip()
|
|
3227
|
+
and isinstance(value[1], int)
|
|
3228
|
+
and not isinstance(value[1], bool)
|
|
3229
|
+
and value[1] >= 0
|
|
3230
|
+
and canonical_json(value) == identifier
|
|
3231
|
+
)
|
|
3232
|
+
|
|
3233
|
+
|
|
3234
|
+
def _partition_two_wiki_evidence_ids(
|
|
3235
|
+
identifiers: tuple[str, ...],
|
|
3236
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
3237
|
+
valid: list[str] = []
|
|
3238
|
+
invalid: list[str] = []
|
|
3239
|
+
for identifier in identifiers:
|
|
3240
|
+
try:
|
|
3241
|
+
value = json.loads(identifier)
|
|
3242
|
+
except json.JSONDecodeError:
|
|
3243
|
+
invalid.append(identifier)
|
|
3244
|
+
continue
|
|
3245
|
+
if (
|
|
3246
|
+
not isinstance(value, list)
|
|
3247
|
+
or len(value) != 3
|
|
3248
|
+
or not all(isinstance(item, str) and item.strip() for item in value)
|
|
3249
|
+
or canonical_json(value) != identifier
|
|
3250
|
+
):
|
|
3251
|
+
invalid.append(identifier)
|
|
3252
|
+
continue
|
|
3253
|
+
valid.append(identifier)
|
|
3254
|
+
return tuple(valid), tuple(invalid)
|
|
3255
|
+
|
|
3256
|
+
|
|
3257
|
+
def _prediction_string_array(value: Any, *, label: str) -> tuple[str, ...]:
|
|
3258
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
3259
|
+
raise ValidationError(f"{label} must be an array")
|
|
3260
|
+
items = tuple(value)
|
|
3261
|
+
if any(not isinstance(item, str) or not item.strip() for item in items):
|
|
3262
|
+
raise ValidationError(f"{label} must contain non-empty strings")
|
|
3263
|
+
normalized = tuple(item.strip() for item in items)
|
|
3264
|
+
if len(normalized) != len(set(normalized)):
|
|
3265
|
+
raise ValidationError(f"{label} must not contain duplicates")
|
|
3266
|
+
return normalized
|
|
3267
|
+
|
|
3268
|
+
|
|
3269
|
+
def _prediction_string_array_keep_first(
|
|
3270
|
+
value: Any,
|
|
3271
|
+
*,
|
|
3272
|
+
label: str,
|
|
3273
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
3274
|
+
"""Normalize a model-produced set-like array and audit exact repeats.
|
|
3275
|
+
|
|
3276
|
+
Evidence-text scoring is set based, so an otherwise valid model response
|
|
3277
|
+
must not abort a run solely because it repeated the same visible span.
|
|
3278
|
+
The first occurrence is retained, subsequent exact trimmed repeats are
|
|
3279
|
+
returned for hash-only audit metadata, and malformed entries still fail
|
|
3280
|
+
closed.
|
|
3281
|
+
"""
|
|
3282
|
+
|
|
3283
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
3284
|
+
raise ValidationError(f"{label} must be an array")
|
|
3285
|
+
items = tuple(value)
|
|
3286
|
+
if any(not isinstance(item, str) or not item.strip() for item in items):
|
|
3287
|
+
raise ValidationError(f"{label} must contain non-empty strings")
|
|
3288
|
+
unique: list[str] = []
|
|
3289
|
+
duplicates: list[str] = []
|
|
3290
|
+
seen: set[str] = set()
|
|
3291
|
+
for item in items:
|
|
3292
|
+
normalized = item.strip()
|
|
3293
|
+
if normalized in seen:
|
|
3294
|
+
duplicates.append(normalized)
|
|
3295
|
+
continue
|
|
3296
|
+
seen.add(normalized)
|
|
3297
|
+
unique.append(normalized)
|
|
3298
|
+
return tuple(unique), tuple(duplicates)
|
|
3299
|
+
|
|
3300
|
+
|
|
3301
|
+
def _resolve_retriever_builder(
|
|
3302
|
+
spec: LockedRetrieverSpec,
|
|
3303
|
+
) -> ProductionRetrieverBuilderProtocol:
|
|
3304
|
+
candidate = resolve_driver_entrypoint(spec.entrypoint)
|
|
3305
|
+
if not callable(candidate):
|
|
3306
|
+
raise ArtifactError("retriever builder entrypoint must be a zero-argument factory")
|
|
3307
|
+
try:
|
|
3308
|
+
builder = candidate()
|
|
3309
|
+
except TypeError as exc:
|
|
3310
|
+
raise ArtifactError("retriever builder entrypoint must be a zero-argument factory") from exc
|
|
3311
|
+
if not isinstance(builder, ProductionRetrieverBuilderProtocol):
|
|
3312
|
+
raise ArtifactError("retriever builder does not implement the production protocol")
|
|
3313
|
+
if builder.builder_kind != "retriever":
|
|
3314
|
+
raise ArtifactError("nested production builder kind must be 'retriever'")
|
|
3315
|
+
observed = builder_fingerprint_sha256(builder)
|
|
3316
|
+
if observed != spec.fingerprint_sha256:
|
|
3317
|
+
raise ArtifactError("retriever builder fingerprint checksum mismatch")
|
|
3318
|
+
return builder
|
|
3319
|
+
|
|
3320
|
+
|
|
3321
|
+
def _fingerprint(value: Any, *, label: str) -> Mapping[str, Any]:
|
|
3322
|
+
fingerprint = getattr(value, "fingerprint", None)
|
|
3323
|
+
if not callable(fingerprint):
|
|
3324
|
+
raise ValidationError(f"{label} must expose fingerprint()")
|
|
3325
|
+
identity = fingerprint()
|
|
3326
|
+
if not isinstance(identity, Mapping) or not identity:
|
|
3327
|
+
raise ValidationError(f"{label} fingerprint must be a non-empty mapping")
|
|
3328
|
+
return _canonical_mapping(identity, label=f"{label} fingerprint")
|
|
3329
|
+
|
|
3330
|
+
|
|
3331
|
+
def _validate_product_hash(observed: str, expected: str, *, label: str) -> str:
|
|
3332
|
+
_require_sha256(observed, label=f"{label} observed SHA-256")
|
|
3333
|
+
_require_sha256(expected, label=f"{label} expected SHA-256")
|
|
3334
|
+
if observed != expected:
|
|
3335
|
+
raise ArtifactError(f"{label} checksum mismatch")
|
|
3336
|
+
return observed
|
|
3337
|
+
|
|
3338
|
+
|
|
3339
|
+
def _artifact_file_locks(value: Any, *, path: str) -> tuple[Mapping[str, Any], ...]:
|
|
3340
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
3341
|
+
raise ValidationError(f"{path} must be an array")
|
|
3342
|
+
locks: list[Mapping[str, Any]] = []
|
|
3343
|
+
names: set[str] = set()
|
|
3344
|
+
paths: set[Path] = set()
|
|
3345
|
+
for index, item in enumerate(value):
|
|
3346
|
+
data = _strict_mapping(
|
|
3347
|
+
item,
|
|
3348
|
+
path=f"{path}[{index}]",
|
|
3349
|
+
allowed={"name", "path", "sha256"},
|
|
3350
|
+
required={"name", "path", "sha256"},
|
|
3351
|
+
)
|
|
3352
|
+
name = _require_text(data["name"], label=f"{path}[{index}].name")
|
|
3353
|
+
artifact_path = _absolute_path(data["path"], label=f"{path}[{index}].path")
|
|
3354
|
+
digest = _sha256_text(data["sha256"], label=f"{path}[{index}].sha256")
|
|
3355
|
+
if name in names or artifact_path in paths:
|
|
3356
|
+
raise ValidationError(f"{path} contains duplicate names or paths")
|
|
3357
|
+
names.add(name)
|
|
3358
|
+
paths.add(artifact_path)
|
|
3359
|
+
locks.append(
|
|
3360
|
+
freeze_mapping(
|
|
3361
|
+
{
|
|
3362
|
+
"name": name,
|
|
3363
|
+
"path": artifact_path,
|
|
3364
|
+
"sha256": digest,
|
|
3365
|
+
}
|
|
3366
|
+
)
|
|
3367
|
+
)
|
|
3368
|
+
return tuple(locks)
|
|
3369
|
+
|
|
3370
|
+
|
|
3371
|
+
def _validate_transformers_artifact_layout(
|
|
3372
|
+
model_path: Path,
|
|
3373
|
+
artifacts: Sequence[Mapping[str, Any]],
|
|
3374
|
+
) -> None:
|
|
3375
|
+
"""Require artifact names to be exact paths within the local snapshot."""
|
|
3376
|
+
|
|
3377
|
+
for artifact in artifacts:
|
|
3378
|
+
artifact_path = Path(artifact["path"])
|
|
3379
|
+
try:
|
|
3380
|
+
relative = artifact_path.relative_to(model_path).as_posix()
|
|
3381
|
+
except ValueError as exc:
|
|
3382
|
+
raise ValidationError("Transformers artifact paths must be inside model_path") from exc
|
|
3383
|
+
if not relative or relative == ".":
|
|
3384
|
+
raise ValidationError("Transformers artifacts must identify files")
|
|
3385
|
+
if artifact["name"] != relative:
|
|
3386
|
+
raise ValidationError(
|
|
3387
|
+
"Transformers artifact names must equal their model-relative POSIX paths"
|
|
3388
|
+
)
|
|
3389
|
+
|
|
3390
|
+
|
|
3391
|
+
def _transformers_snapshot_sha256(
|
|
3392
|
+
artifacts: Sequence[Mapping[str, Any]],
|
|
3393
|
+
) -> str:
|
|
3394
|
+
return content_hash(
|
|
3395
|
+
{
|
|
3396
|
+
"protocol": "agentinputlab.transformers-snapshot-tree.v1",
|
|
3397
|
+
"files": tuple(
|
|
3398
|
+
{
|
|
3399
|
+
"path": artifact["name"],
|
|
3400
|
+
"sha256": artifact["sha256"],
|
|
3401
|
+
}
|
|
3402
|
+
for artifact in sorted(artifacts, key=lambda item: item["name"])
|
|
3403
|
+
),
|
|
3404
|
+
}
|
|
3405
|
+
)
|
|
3406
|
+
|
|
3407
|
+
|
|
3408
|
+
def _verify_transformers_snapshot(
|
|
3409
|
+
model_path: Path,
|
|
3410
|
+
artifacts: Sequence[Mapping[str, Any]],
|
|
3411
|
+
) -> None:
|
|
3412
|
+
"""Verify the complete, closed model tree and every declared file digest."""
|
|
3413
|
+
|
|
3414
|
+
_validate_transformers_artifact_layout(model_path, artifacts)
|
|
3415
|
+
try:
|
|
3416
|
+
root_metadata = model_path.lstat()
|
|
3417
|
+
except OSError as exc:
|
|
3418
|
+
raise ArtifactError(f"Transformers model snapshot is unavailable: {model_path}") from exc
|
|
3419
|
+
if stat.S_ISLNK(root_metadata.st_mode) or not stat.S_ISDIR(root_metadata.st_mode):
|
|
3420
|
+
raise ArtifactError(f"Transformers model_path is not a closed directory: {model_path}")
|
|
3421
|
+
|
|
3422
|
+
observed_paths: set[Path] = set()
|
|
3423
|
+
try:
|
|
3424
|
+
for candidate in model_path.rglob("*"):
|
|
3425
|
+
metadata = candidate.lstat()
|
|
3426
|
+
if stat.S_ISLNK(metadata.st_mode):
|
|
3427
|
+
raise ArtifactError(
|
|
3428
|
+
"Transformers model snapshot contains a symlink: "
|
|
3429
|
+
f"{candidate.relative_to(model_path)}"
|
|
3430
|
+
)
|
|
3431
|
+
if stat.S_ISDIR(metadata.st_mode):
|
|
3432
|
+
continue
|
|
3433
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
3434
|
+
raise ArtifactError(
|
|
3435
|
+
"Transformers model snapshot contains a non-regular entry: "
|
|
3436
|
+
f"{candidate.relative_to(model_path)}"
|
|
3437
|
+
)
|
|
3438
|
+
observed_paths.add(candidate.resolve())
|
|
3439
|
+
except ArtifactError:
|
|
3440
|
+
raise
|
|
3441
|
+
except OSError as exc:
|
|
3442
|
+
raise ArtifactError(
|
|
3443
|
+
f"Transformers model snapshot cannot be enumerated: {model_path}"
|
|
3444
|
+
) from exc
|
|
3445
|
+
|
|
3446
|
+
expected_paths = {Path(artifact["path"]) for artifact in artifacts}
|
|
3447
|
+
if observed_paths != expected_paths:
|
|
3448
|
+
missing = sorted(
|
|
3449
|
+
path.relative_to(model_path).as_posix() for path in observed_paths - expected_paths
|
|
3450
|
+
)
|
|
3451
|
+
stale = sorted(
|
|
3452
|
+
path.relative_to(model_path).as_posix() for path in expected_paths - observed_paths
|
|
3453
|
+
)
|
|
3454
|
+
raise ArtifactError(
|
|
3455
|
+
"Transformers artifact_files do not cover the complete model snapshot; "
|
|
3456
|
+
f"unlisted={missing[:5]}, absent={stale[:5]}"
|
|
3457
|
+
)
|
|
3458
|
+
for artifact in artifacts:
|
|
3459
|
+
_verify_checksum_file(
|
|
3460
|
+
artifact["path"],
|
|
3461
|
+
artifact["sha256"],
|
|
3462
|
+
label=f"Transformers artifact {artifact['name']!r}",
|
|
3463
|
+
)
|
|
3464
|
+
|
|
3465
|
+
|
|
3466
|
+
def _read_checksum_bytes(
|
|
3467
|
+
path: str | Path,
|
|
3468
|
+
expected_sha256: str,
|
|
3469
|
+
*,
|
|
3470
|
+
max_bytes: int | None,
|
|
3471
|
+
label: str,
|
|
3472
|
+
) -> bytes:
|
|
3473
|
+
path = Path(path)
|
|
3474
|
+
if not path.is_absolute():
|
|
3475
|
+
raise ArtifactError(f"{label} path must be absolute")
|
|
3476
|
+
_require_sha256(expected_sha256, label=f"{label} SHA-256")
|
|
3477
|
+
try:
|
|
3478
|
+
size = path.stat().st_size
|
|
3479
|
+
if not path.is_file():
|
|
3480
|
+
raise ArtifactError(f"{label} is not a regular file: {path}")
|
|
3481
|
+
if max_bytes is not None and size > max_bytes:
|
|
3482
|
+
raise ArtifactError(f"{label} exceeds the size limit: {path}")
|
|
3483
|
+
raw = path.read_bytes()
|
|
3484
|
+
except ArtifactError:
|
|
3485
|
+
raise
|
|
3486
|
+
except OSError as exc:
|
|
3487
|
+
raise ArtifactError(f"{label} is unavailable: {path}") from exc
|
|
3488
|
+
observed = hashlib.sha256(raw).hexdigest()
|
|
3489
|
+
if observed != expected_sha256:
|
|
3490
|
+
raise ArtifactError(f"{label} checksum mismatch: {path}")
|
|
3491
|
+
return raw
|
|
3492
|
+
|
|
3493
|
+
|
|
3494
|
+
def _strict_json_bytes(raw: bytes, *, label: str) -> Any:
|
|
3495
|
+
try:
|
|
3496
|
+
return json.loads(
|
|
3497
|
+
raw.decode("utf-8"),
|
|
3498
|
+
object_pairs_hook=_reject_duplicate_keys,
|
|
3499
|
+
parse_constant=lambda value: _reject_json_constant(value),
|
|
3500
|
+
)
|
|
3501
|
+
except (UnicodeError, json.JSONDecodeError) as exc:
|
|
3502
|
+
raise ArtifactError(f"{label} is not strict UTF-8 JSON") from exc
|
|
3503
|
+
|
|
3504
|
+
|
|
3505
|
+
def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
3506
|
+
output: dict[str, Any] = {}
|
|
3507
|
+
for key, value in pairs:
|
|
3508
|
+
if key in output:
|
|
3509
|
+
raise ValidationError(f"duplicate JSON key: {key}")
|
|
3510
|
+
output[key] = value
|
|
3511
|
+
return output
|
|
3512
|
+
|
|
3513
|
+
|
|
3514
|
+
def _reject_json_constant(value: str) -> None:
|
|
3515
|
+
raise ValidationError(f"non-finite JSON constant is forbidden: {value}")
|
|
3516
|
+
|
|
3517
|
+
|
|
3518
|
+
def _reject_embedded_secrets(value: Any, *, path: str) -> None:
|
|
3519
|
+
if isinstance(value, Mapping):
|
|
3520
|
+
for key, item in value.items():
|
|
3521
|
+
if not isinstance(key, str):
|
|
3522
|
+
raise ValidationError(f"{path} keys must be strings")
|
|
3523
|
+
child = f"{path}.{key}"
|
|
3524
|
+
if key == "require_api_key" or path.endswith(".extra_headers"):
|
|
3525
|
+
_reject_embedded_secrets(item, path=child)
|
|
3526
|
+
continue
|
|
3527
|
+
# OpenAI tool declarations use arbitrary domain field names as keys
|
|
3528
|
+
# below JSON Schema ``properties``. A field such as
|
|
3529
|
+
# ``new_password`` names an argument; it is not itself a credential.
|
|
3530
|
+
# Keep recursively scanning the property's schema so secret-like
|
|
3531
|
+
# defaults, examples, descriptions, and other literal values still
|
|
3532
|
+
# fail closed.
|
|
3533
|
+
if (
|
|
3534
|
+
path.startswith("backend parameters.tools.definitions[")
|
|
3535
|
+
and ".function.parameters" in path
|
|
3536
|
+
and path.endswith(".properties")
|
|
3537
|
+
):
|
|
3538
|
+
_reject_embedded_secrets(item, path=child)
|
|
3539
|
+
continue
|
|
3540
|
+
if _SENSITIVE_KEY.search(key):
|
|
3541
|
+
if not key.casefold().endswith("_env"):
|
|
3542
|
+
raise ValidationError(
|
|
3543
|
+
f"{child} looks secret-bearing; store only an environment-variable name"
|
|
3544
|
+
)
|
|
3545
|
+
_require_env_name(item, label=child)
|
|
3546
|
+
continue
|
|
3547
|
+
_reject_embedded_secrets(item, path=child)
|
|
3548
|
+
return
|
|
3549
|
+
if isinstance(value, (tuple, list)):
|
|
3550
|
+
for index, item in enumerate(value):
|
|
3551
|
+
_reject_embedded_secrets(item, path=f"{path}[{index}]")
|
|
3552
|
+
return
|
|
3553
|
+
if isinstance(value, str) and any(pattern.search(value) for pattern in _SECRET_VALUE_PATTERNS):
|
|
3554
|
+
raise ValidationError(
|
|
3555
|
+
f"{path} contains secret-like text; use environment-variable indirection"
|
|
3556
|
+
)
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
def _capabilities(value: Any, *, path: str) -> tuple[ModelCapability, ...]:
|
|
3560
|
+
names = _text_sequence(value, path=path, allow_empty=True)
|
|
3561
|
+
try:
|
|
3562
|
+
return tuple(sorted((ModelCapability(name) for name in names), key=lambda item: item.value))
|
|
3563
|
+
except ValueError as exc:
|
|
3564
|
+
raise ValidationError(f"{path} contains an unknown capability") from exc
|
|
3565
|
+
|
|
3566
|
+
|
|
3567
|
+
def _environment_names(value: Any, *, path: str) -> tuple[str, ...]:
|
|
3568
|
+
names = _text_sequence(value, path=path, allow_empty=True)
|
|
3569
|
+
for name in names:
|
|
3570
|
+
_require_env_name(name, label=path)
|
|
3571
|
+
return tuple(sorted(names))
|
|
3572
|
+
|
|
3573
|
+
|
|
3574
|
+
def _text_sequence(value: Any, *, path: str, allow_empty: bool) -> tuple[str, ...]:
|
|
3575
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
3576
|
+
raise ValidationError(f"{path} must be an array of strings")
|
|
3577
|
+
items = tuple(value)
|
|
3578
|
+
if not allow_empty and not items:
|
|
3579
|
+
raise ValidationError(f"{path} must not be empty")
|
|
3580
|
+
if any(not isinstance(item, str) or not item for item in items):
|
|
3581
|
+
raise ValidationError(f"{path} must contain non-empty strings")
|
|
3582
|
+
if len(set(items)) != len(items):
|
|
3583
|
+
raise ValidationError(f"{path} must not contain duplicates")
|
|
3584
|
+
return items
|
|
3585
|
+
|
|
3586
|
+
|
|
3587
|
+
def _strict_mapping(
|
|
3588
|
+
value: Any,
|
|
3589
|
+
*,
|
|
3590
|
+
path: str,
|
|
3591
|
+
allowed: set[str],
|
|
3592
|
+
required: set[str],
|
|
3593
|
+
) -> Mapping[str, Any]:
|
|
3594
|
+
if not isinstance(value, Mapping):
|
|
3595
|
+
raise ValidationError(f"{path} must be an object")
|
|
3596
|
+
if any(not isinstance(key, str) for key in value):
|
|
3597
|
+
raise ValidationError(f"{path} keys must be strings")
|
|
3598
|
+
unknown = set(value) - allowed
|
|
3599
|
+
missing = required - set(value)
|
|
3600
|
+
if unknown:
|
|
3601
|
+
raise ValidationError(f"{path} has unknown fields: {', '.join(sorted(unknown))}")
|
|
3602
|
+
if missing:
|
|
3603
|
+
raise ValidationError(f"{path} is missing fields: {', '.join(sorted(missing))}")
|
|
3604
|
+
return value
|
|
3605
|
+
|
|
3606
|
+
|
|
3607
|
+
def _mapping_or_empty(value: Any, *, path: str) -> Mapping[str, Any]:
|
|
3608
|
+
if value is None:
|
|
3609
|
+
return {}
|
|
3610
|
+
if not isinstance(value, Mapping):
|
|
3611
|
+
raise ValidationError(f"{path} must be an object")
|
|
3612
|
+
return value
|
|
3613
|
+
|
|
3614
|
+
|
|
3615
|
+
def _canonical_mapping(value: Mapping[str, Any], *, label: str) -> Mapping[str, Any]:
|
|
3616
|
+
try:
|
|
3617
|
+
return freeze_mapping(value)
|
|
3618
|
+
except (TypeError, ValueError) as exc:
|
|
3619
|
+
raise ValidationError(f"{label} must be canonical JSON") from exc
|
|
3620
|
+
|
|
3621
|
+
|
|
3622
|
+
def _require_schema_version(value: Any, *, path: str) -> None:
|
|
3623
|
+
if value != _SCHEMA_VERSION or isinstance(value, bool):
|
|
3624
|
+
raise ValidationError(f"{path} must equal {_SCHEMA_VERSION}")
|
|
3625
|
+
|
|
3626
|
+
|
|
3627
|
+
def _require_cell(value: str) -> None:
|
|
3628
|
+
if value not in _CELL_SPLITS:
|
|
3629
|
+
raise ValidationError(
|
|
3630
|
+
f"unsupported static production benchmark {value!r}; "
|
|
3631
|
+
f"available: {', '.join(sorted(_CELL_SPLITS))}"
|
|
3632
|
+
)
|
|
3633
|
+
|
|
3634
|
+
|
|
3635
|
+
def _require_retriever_cell(value: str) -> None:
|
|
3636
|
+
if value not in _RETRIEVER_ROUTE_CELLS:
|
|
3637
|
+
raise ValidationError(
|
|
3638
|
+
f"unsupported production retriever benchmark {value!r}; "
|
|
3639
|
+
f"available: {', '.join(sorted(_RETRIEVER_ROUTE_CELLS))}"
|
|
3640
|
+
)
|
|
3641
|
+
|
|
3642
|
+
|
|
3643
|
+
def _require_text(value: Any, *, label: str) -> str:
|
|
3644
|
+
if not isinstance(value, str) or not value.strip():
|
|
3645
|
+
raise ValidationError(f"{label} must be a non-empty string")
|
|
3646
|
+
return value
|
|
3647
|
+
|
|
3648
|
+
|
|
3649
|
+
def _verify_locked_file(path: Path, expected_sha256: str, *, label: str) -> str:
|
|
3650
|
+
if not path.is_file():
|
|
3651
|
+
raise ArtifactError(f"{label} is unavailable: {path}")
|
|
3652
|
+
observed = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
3653
|
+
if observed != expected_sha256:
|
|
3654
|
+
raise ArtifactError(f"{label} checksum mismatch")
|
|
3655
|
+
return observed
|
|
3656
|
+
|
|
3657
|
+
|
|
3658
|
+
def _absolute_path(value: Any, *, label: str) -> Path:
|
|
3659
|
+
text = _require_text(value, label=label)
|
|
3660
|
+
path = Path(text).expanduser()
|
|
3661
|
+
if not path.is_absolute():
|
|
3662
|
+
raise ValidationError(f"{label} must be an absolute path")
|
|
3663
|
+
return path.resolve()
|
|
3664
|
+
|
|
3665
|
+
|
|
3666
|
+
def _absolute_http_url(value: Any, *, label: str) -> str:
|
|
3667
|
+
text = _require_text(value, label=label)
|
|
3668
|
+
parsed = urllib.parse.urlparse(text)
|
|
3669
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
3670
|
+
raise ValidationError(f"{label} must be an absolute http(s) URL")
|
|
3671
|
+
if parsed.username or parsed.password or parsed.query or parsed.fragment:
|
|
3672
|
+
raise ValidationError(f"{label} must not contain credentials, query, or fragment")
|
|
3673
|
+
return text.rstrip("/")
|
|
3674
|
+
|
|
3675
|
+
|
|
3676
|
+
def _sha256_text(value: Any, *, label: str) -> str:
|
|
3677
|
+
text = _require_text(value, label=label)
|
|
3678
|
+
_require_sha256(text, label=label)
|
|
3679
|
+
return text
|
|
3680
|
+
|
|
3681
|
+
|
|
3682
|
+
def _require_sha256(value: Any, *, label: str) -> None:
|
|
3683
|
+
if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
|
|
3684
|
+
raise ValidationError(f"{label} must be a lowercase SHA-256")
|
|
3685
|
+
|
|
3686
|
+
|
|
3687
|
+
def _require_env_name(value: Any, *, label: str) -> str:
|
|
3688
|
+
if not isinstance(value, str) or _ENV_NAME.fullmatch(value) is None:
|
|
3689
|
+
raise ValidationError(f"{label} must be an environment-variable name")
|
|
3690
|
+
return value
|
|
3691
|
+
|
|
3692
|
+
|
|
3693
|
+
def _require_seed(value: Any) -> None:
|
|
3694
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
3695
|
+
raise ValidationError("seed must be a non-negative integer")
|
|
3696
|
+
|
|
3697
|
+
|
|
3698
|
+
def _positive_int(value: Any, *, label: str) -> int:
|
|
3699
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
|
3700
|
+
raise ValidationError(f"{label} must be a positive integer")
|
|
3701
|
+
return value
|
|
3702
|
+
|
|
3703
|
+
|
|
3704
|
+
def _positive_number(value: Any, *, label: str) -> float:
|
|
3705
|
+
number = _nonnegative_number(value, label=label)
|
|
3706
|
+
if number <= 0:
|
|
3707
|
+
raise ValidationError(f"{label} must be positive")
|
|
3708
|
+
return number
|
|
3709
|
+
|
|
3710
|
+
|
|
3711
|
+
def _nonnegative_number(value: Any, *, label: str) -> float:
|
|
3712
|
+
if (
|
|
3713
|
+
isinstance(value, bool)
|
|
3714
|
+
or not isinstance(value, (int, float))
|
|
3715
|
+
or not math.isfinite(value)
|
|
3716
|
+
or value < 0
|
|
3717
|
+
):
|
|
3718
|
+
raise ValidationError(f"{label} must be finite and non-negative")
|
|
3719
|
+
return float(value)
|
|
3720
|
+
|
|
3721
|
+
|
|
3722
|
+
__all__ = [
|
|
3723
|
+
"LockedRetrieverSpec",
|
|
3724
|
+
"PreparedCorpusRetrieverBuilder",
|
|
3725
|
+
"ProductionBackendBuilder",
|
|
3726
|
+
"ProductionDownstreamPredictor",
|
|
3727
|
+
"ProductionPredictorBuilder",
|
|
3728
|
+
"ProductionRetrieverBuildRequest",
|
|
3729
|
+
"ProductionRetrieverBuilderProtocol",
|
|
3730
|
+
"ProductionRetrieverRouter",
|
|
3731
|
+
"builder_fingerprint_sha256",
|
|
3732
|
+
"prepared_corpus_retriever_builder_factory",
|
|
3733
|
+
"production_backend_builder_factory",
|
|
3734
|
+
"production_predictor_builder_factory",
|
|
3735
|
+
"retriever_product_sha256",
|
|
3736
|
+
"validate_predictor_product_sha256",
|
|
3737
|
+
"validate_retriever_product_sha256",
|
|
3738
|
+
"validate_runtime_product_sha256",
|
|
3739
|
+
]
|