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,678 @@
|
|
|
1
|
+
"""Bounded, secret-minimizing subprocess execution for worker protocols."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
import signal
|
|
9
|
+
import subprocess
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from contextlib import suppress
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from agentinputlab.contracts.errors import ExternalDependencyError, ValidationError
|
|
18
|
+
from agentinputlab.contracts.serialization import freeze_mapping
|
|
19
|
+
|
|
20
|
+
_INHERITED_ENVIRONMENT_KEYS = (
|
|
21
|
+
"LANG",
|
|
22
|
+
"LC_ALL",
|
|
23
|
+
"PATH",
|
|
24
|
+
"SYSTEMROOT",
|
|
25
|
+
"TEMP",
|
|
26
|
+
"TMP",
|
|
27
|
+
"TMPDIR",
|
|
28
|
+
"WINDIR",
|
|
29
|
+
)
|
|
30
|
+
_RESOURCE_SAMPLE_INTERVAL_SECONDS = 0.02
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _unavailable_measurement_identity(
|
|
34
|
+
reason: str = "runner_did_not_report",
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
return {
|
|
37
|
+
"process_tree": {
|
|
38
|
+
"status": "unavailable",
|
|
39
|
+
"reason": reason,
|
|
40
|
+
},
|
|
41
|
+
"accelerator": {
|
|
42
|
+
"status": "unavailable",
|
|
43
|
+
"reason": reason,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class ProcessResourceObservation:
|
|
50
|
+
"""Best-effort measurements from exactly one spawned process tree.
|
|
51
|
+
|
|
52
|
+
A missing value is not a measured zero. ``measurement_identity`` records
|
|
53
|
+
why each optional sampler was available or unavailable.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
cpu_seconds: float | None = None
|
|
57
|
+
peak_rss_bytes: int | None = None
|
|
58
|
+
accelerator_seconds: float | None = None
|
|
59
|
+
accelerator_peak_memory_bytes: int | None = None
|
|
60
|
+
measurement_identity: Mapping[str, Any] = field(
|
|
61
|
+
default_factory=_unavailable_measurement_identity
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def __post_init__(self) -> None:
|
|
65
|
+
for name in ("cpu_seconds", "accelerator_seconds"):
|
|
66
|
+
value = getattr(self, name)
|
|
67
|
+
if value is not None and (
|
|
68
|
+
isinstance(value, bool)
|
|
69
|
+
or not isinstance(value, (int, float))
|
|
70
|
+
or not math.isfinite(value)
|
|
71
|
+
or value < 0
|
|
72
|
+
):
|
|
73
|
+
raise ValidationError(f"{name} must be finite and non-negative when provided")
|
|
74
|
+
if value is not None:
|
|
75
|
+
object.__setattr__(self, name, float(value))
|
|
76
|
+
for name in ("peak_rss_bytes", "accelerator_peak_memory_bytes"):
|
|
77
|
+
value = getattr(self, name)
|
|
78
|
+
if value is not None and (
|
|
79
|
+
isinstance(value, bool) or not isinstance(value, int) or value < 0
|
|
80
|
+
):
|
|
81
|
+
raise ValidationError(f"{name} must be a non-negative integer when provided")
|
|
82
|
+
identity = self.measurement_identity or _unavailable_measurement_identity()
|
|
83
|
+
process_identity = identity.get("process_tree")
|
|
84
|
+
accelerator_identity = identity.get("accelerator")
|
|
85
|
+
if not isinstance(process_identity, Mapping) or not isinstance(
|
|
86
|
+
accelerator_identity,
|
|
87
|
+
Mapping,
|
|
88
|
+
):
|
|
89
|
+
raise ValidationError(
|
|
90
|
+
"measurement_identity requires process_tree and accelerator objects"
|
|
91
|
+
)
|
|
92
|
+
if process_identity.get("status") not in {"available", "unavailable"}:
|
|
93
|
+
raise ValidationError("process-tree measurement status is invalid")
|
|
94
|
+
if accelerator_identity.get("status") not in {"available", "unavailable"}:
|
|
95
|
+
raise ValidationError("accelerator measurement status is invalid")
|
|
96
|
+
if (
|
|
97
|
+
self.cpu_seconds is not None or self.peak_rss_bytes is not None
|
|
98
|
+
) and process_identity.get("status") == "unavailable":
|
|
99
|
+
raise ValidationError(
|
|
100
|
+
"unavailable process-tree measurements cannot contain measured values"
|
|
101
|
+
)
|
|
102
|
+
if (
|
|
103
|
+
self.accelerator_seconds is not None or self.accelerator_peak_memory_bytes is not None
|
|
104
|
+
) and accelerator_identity.get("status") == "unavailable":
|
|
105
|
+
raise ValidationError(
|
|
106
|
+
"unavailable accelerator measurements cannot contain measured values"
|
|
107
|
+
)
|
|
108
|
+
object.__setattr__(self, "measurement_identity", freeze_mapping(identity))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True, slots=True)
|
|
112
|
+
class BoundedProcessResult:
|
|
113
|
+
returncode: int
|
|
114
|
+
stdout: str
|
|
115
|
+
stderr: str
|
|
116
|
+
cpu_seconds: float | None = None
|
|
117
|
+
peak_rss_bytes: int | None = None
|
|
118
|
+
accelerator_seconds: float | None = None
|
|
119
|
+
accelerator_peak_memory_bytes: int | None = None
|
|
120
|
+
measurement_identity: Mapping[str, Any] = field(
|
|
121
|
+
default_factory=_unavailable_measurement_identity
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def __post_init__(self) -> None:
|
|
125
|
+
identity = self.measurement_identity
|
|
126
|
+
if not identity or identity == _unavailable_measurement_identity():
|
|
127
|
+
identity = _reported_measurement_identity(
|
|
128
|
+
has_process_measurement=(
|
|
129
|
+
self.cpu_seconds is not None or self.peak_rss_bytes is not None
|
|
130
|
+
),
|
|
131
|
+
has_accelerator_measurement=(
|
|
132
|
+
self.accelerator_seconds is not None
|
|
133
|
+
or self.accelerator_peak_memory_bytes is not None
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
observation = ProcessResourceObservation(
|
|
137
|
+
cpu_seconds=self.cpu_seconds,
|
|
138
|
+
peak_rss_bytes=self.peak_rss_bytes,
|
|
139
|
+
accelerator_seconds=self.accelerator_seconds,
|
|
140
|
+
accelerator_peak_memory_bytes=self.accelerator_peak_memory_bytes,
|
|
141
|
+
measurement_identity=identity,
|
|
142
|
+
)
|
|
143
|
+
object.__setattr__(self, "cpu_seconds", observation.cpu_seconds)
|
|
144
|
+
object.__setattr__(self, "peak_rss_bytes", observation.peak_rss_bytes)
|
|
145
|
+
object.__setattr__(self, "accelerator_seconds", observation.accelerator_seconds)
|
|
146
|
+
object.__setattr__(
|
|
147
|
+
self,
|
|
148
|
+
"accelerator_peak_memory_bytes",
|
|
149
|
+
observation.accelerator_peak_memory_bytes,
|
|
150
|
+
)
|
|
151
|
+
object.__setattr__(
|
|
152
|
+
self,
|
|
153
|
+
"measurement_identity",
|
|
154
|
+
observation.measurement_identity,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def resource_observation(self) -> ProcessResourceObservation:
|
|
159
|
+
return ProcessResourceObservation(
|
|
160
|
+
cpu_seconds=self.cpu_seconds,
|
|
161
|
+
peak_rss_bytes=self.peak_rss_bytes,
|
|
162
|
+
accelerator_seconds=self.accelerator_seconds,
|
|
163
|
+
accelerator_peak_memory_bytes=self.accelerator_peak_memory_bytes,
|
|
164
|
+
measurement_identity=self.measurement_identity,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class BoundedProcessError(ExternalDependencyError):
|
|
169
|
+
"""A bounded transport failure with any observation collected before exit."""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
message: str,
|
|
174
|
+
*,
|
|
175
|
+
resource_observation: ProcessResourceObservation | None = None,
|
|
176
|
+
) -> None:
|
|
177
|
+
super().__init__(message)
|
|
178
|
+
self.resource_observation = resource_observation or ProcessResourceObservation()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class BoundedProcessTimeoutError(BoundedProcessError):
|
|
182
|
+
"""A bounded process exceeded its hard wall-clock deadline."""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class BoundedProcessStartupError(BoundedProcessError):
|
|
186
|
+
"""The requested executable could not be started."""
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class BoundedProcessProtocolError(BoundedProcessError):
|
|
190
|
+
"""The child process violated the bounded pipe transport."""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class BoundedProcessOutputLimitError(BoundedProcessError):
|
|
194
|
+
"""A bounded process exceeded a configured pipe byte limit."""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class _ProcessTreeMonitor:
|
|
198
|
+
"""Poll a spawned process tree with optional psutil and NVML dependencies."""
|
|
199
|
+
|
|
200
|
+
def __init__(self, pid: int) -> None:
|
|
201
|
+
self._pid = pid
|
|
202
|
+
self._stop = threading.Event()
|
|
203
|
+
self._thread: threading.Thread | None = None
|
|
204
|
+
self._psutil, psutil_reason = _optional_module("psutil")
|
|
205
|
+
self._known_processes: dict[int, Any] = {}
|
|
206
|
+
self._latest_cpu_by_pid: dict[int, float] = {}
|
|
207
|
+
self._peak_rss_bytes = 0
|
|
208
|
+
self._cpu_samples = 0
|
|
209
|
+
self._rss_samples = 0
|
|
210
|
+
self._process_errors = 0
|
|
211
|
+
self._psutil_reason = psutil_reason
|
|
212
|
+
if self._psutil is not None:
|
|
213
|
+
try:
|
|
214
|
+
root = self._psutil.Process(pid)
|
|
215
|
+
self._known_processes[int(root.pid)] = root
|
|
216
|
+
except Exception as exc: # optional telemetry must not break execution
|
|
217
|
+
self._psutil_reason = f"initialization_failed:{type(exc).__name__}"
|
|
218
|
+
self._psutil = None
|
|
219
|
+
|
|
220
|
+
self._nvml, nvml_reason = _optional_module("pynvml")
|
|
221
|
+
self._nvml_reason = nvml_reason
|
|
222
|
+
self._nvml_handles: tuple[Any, ...] = ()
|
|
223
|
+
self._nvml_initialized = False
|
|
224
|
+
self._nvml_failed = False
|
|
225
|
+
self._accelerator_samples = 0
|
|
226
|
+
self._accelerator_seconds = 0.0
|
|
227
|
+
self._accelerator_peak_memory_bytes = 0
|
|
228
|
+
self._last_accelerator_sample_at: float | None = None
|
|
229
|
+
self._last_active_device_count = 0
|
|
230
|
+
if self._psutil is None and self._nvml is not None:
|
|
231
|
+
self._nvml_reason = "psutil_required_for_process_attribution"
|
|
232
|
+
self._nvml = None
|
|
233
|
+
if self._nvml is not None:
|
|
234
|
+
try:
|
|
235
|
+
self._nvml.nvmlInit()
|
|
236
|
+
self._nvml_initialized = True
|
|
237
|
+
count = int(self._nvml.nvmlDeviceGetCount())
|
|
238
|
+
self._nvml_handles = tuple(
|
|
239
|
+
self._nvml.nvmlDeviceGetHandleByIndex(index) for index in range(count)
|
|
240
|
+
)
|
|
241
|
+
except Exception as exc: # driver/library absence is an expected optional state
|
|
242
|
+
self._nvml_reason = f"initialization_failed:{type(exc).__name__}"
|
|
243
|
+
if self._nvml_initialized:
|
|
244
|
+
with suppress(Exception):
|
|
245
|
+
self._nvml.nvmlShutdown()
|
|
246
|
+
self._nvml_initialized = False
|
|
247
|
+
self._nvml = None
|
|
248
|
+
self._nvml_handles = ()
|
|
249
|
+
|
|
250
|
+
def start(self) -> None:
|
|
251
|
+
if self._psutil is None:
|
|
252
|
+
return
|
|
253
|
+
self._sample()
|
|
254
|
+
self._thread = threading.Thread(
|
|
255
|
+
target=self._sample_loop,
|
|
256
|
+
name=f"agentinputlab-resource-monitor-{self._pid}",
|
|
257
|
+
daemon=True,
|
|
258
|
+
)
|
|
259
|
+
self._thread.start()
|
|
260
|
+
|
|
261
|
+
def stop(self) -> ProcessResourceObservation:
|
|
262
|
+
self._stop.set()
|
|
263
|
+
if self._thread is not None:
|
|
264
|
+
self._thread.join(timeout=max(1.0, _RESOURCE_SAMPLE_INTERVAL_SECONDS * 5))
|
|
265
|
+
if self._psutil is not None:
|
|
266
|
+
self._sample()
|
|
267
|
+
if self._nvml_initialized:
|
|
268
|
+
try:
|
|
269
|
+
self._nvml.nvmlShutdown()
|
|
270
|
+
except Exception as exc:
|
|
271
|
+
self._nvml_reason = f"shutdown_failed:{type(exc).__name__}"
|
|
272
|
+
self._nvml = None
|
|
273
|
+
finally:
|
|
274
|
+
self._nvml_initialized = False
|
|
275
|
+
|
|
276
|
+
process_available = self._psutil is not None
|
|
277
|
+
cpu_available = process_available and self._cpu_samples > 0
|
|
278
|
+
rss_available = process_available and self._rss_samples > 0
|
|
279
|
+
accelerator_available = (
|
|
280
|
+
self._nvml is not None and not self._nvml_failed and self._accelerator_samples > 0
|
|
281
|
+
)
|
|
282
|
+
cpu_reason = None if cpu_available else (self._psutil_reason or "no_successful_samples")
|
|
283
|
+
rss_reason = None if rss_available else (self._psutil_reason or "no_successful_samples")
|
|
284
|
+
accelerator_reason = (
|
|
285
|
+
None if accelerator_available else (self._nvml_reason or "no_successful_samples")
|
|
286
|
+
)
|
|
287
|
+
process_identity = {
|
|
288
|
+
"status": "available" if process_available else "unavailable",
|
|
289
|
+
"sampler": "psutil.process_tree.v1",
|
|
290
|
+
"scope": "spawned_process_tree",
|
|
291
|
+
"dependency_version": _module_version(self._psutil),
|
|
292
|
+
"sample_interval_seconds": _RESOURCE_SAMPLE_INTERVAL_SECONDS,
|
|
293
|
+
"samples": max(self._cpu_samples, self._rss_samples),
|
|
294
|
+
"sampling_errors": self._process_errors,
|
|
295
|
+
"cpu_seconds": {
|
|
296
|
+
"status": "available" if cpu_available else "unavailable",
|
|
297
|
+
"reason": cpu_reason,
|
|
298
|
+
},
|
|
299
|
+
"peak_rss_bytes": {
|
|
300
|
+
"status": "available" if rss_available else "unavailable",
|
|
301
|
+
"reason": rss_reason,
|
|
302
|
+
"semantics": "maximum sampled sum of resident bytes across the process tree",
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
accelerator_identity = {
|
|
306
|
+
"status": "available" if accelerator_available else "unavailable",
|
|
307
|
+
"sampler": "pynvml.process_tree_allocation.v1",
|
|
308
|
+
"scope": "spawned_process_tree",
|
|
309
|
+
"dependency_version": _module_version(self._nvml),
|
|
310
|
+
"reason": accelerator_reason,
|
|
311
|
+
"samples": self._accelerator_samples,
|
|
312
|
+
"seconds_semantics": "allocated device-seconds for matched process-tree PIDs",
|
|
313
|
+
"peak_memory_semantics": (
|
|
314
|
+
"maximum sampled sum of NVML process memory across matched devices"
|
|
315
|
+
),
|
|
316
|
+
}
|
|
317
|
+
return ProcessResourceObservation(
|
|
318
|
+
cpu_seconds=(sum(self._latest_cpu_by_pid.values()) if cpu_available else None),
|
|
319
|
+
peak_rss_bytes=self._peak_rss_bytes if rss_available else None,
|
|
320
|
+
accelerator_seconds=(self._accelerator_seconds if accelerator_available else None),
|
|
321
|
+
accelerator_peak_memory_bytes=(
|
|
322
|
+
self._accelerator_peak_memory_bytes if accelerator_available else None
|
|
323
|
+
),
|
|
324
|
+
measurement_identity={
|
|
325
|
+
"process_tree": process_identity,
|
|
326
|
+
"accelerator": accelerator_identity,
|
|
327
|
+
},
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
def _sample_loop(self) -> None:
|
|
331
|
+
while not self._stop.wait(_RESOURCE_SAMPLE_INTERVAL_SECONDS):
|
|
332
|
+
self._sample()
|
|
333
|
+
|
|
334
|
+
def _sample(self) -> None:
|
|
335
|
+
if self._psutil is None:
|
|
336
|
+
return
|
|
337
|
+
processes = list(self._known_processes.values())
|
|
338
|
+
for process in tuple(processes):
|
|
339
|
+
try:
|
|
340
|
+
children = process.children(recursive=True)
|
|
341
|
+
except Exception:
|
|
342
|
+
self._process_errors += 1
|
|
343
|
+
continue
|
|
344
|
+
for child in children:
|
|
345
|
+
pid = int(child.pid)
|
|
346
|
+
if pid not in self._known_processes:
|
|
347
|
+
self._known_processes[pid] = child
|
|
348
|
+
processes.append(child)
|
|
349
|
+
|
|
350
|
+
rss_total = 0
|
|
351
|
+
rss_observed = False
|
|
352
|
+
cpu_observed = False
|
|
353
|
+
active_pids: set[int] = set()
|
|
354
|
+
for process in processes:
|
|
355
|
+
pid = int(process.pid)
|
|
356
|
+
try:
|
|
357
|
+
cpu = process.cpu_times()
|
|
358
|
+
cpu_seconds = float(cpu.user) + float(cpu.system)
|
|
359
|
+
self._latest_cpu_by_pid[pid] = max(
|
|
360
|
+
cpu_seconds,
|
|
361
|
+
self._latest_cpu_by_pid.get(pid, 0.0),
|
|
362
|
+
)
|
|
363
|
+
cpu_observed = True
|
|
364
|
+
active_pids.add(pid)
|
|
365
|
+
except Exception:
|
|
366
|
+
self._process_errors += 1
|
|
367
|
+
try:
|
|
368
|
+
rss_total += int(process.memory_info().rss)
|
|
369
|
+
rss_observed = True
|
|
370
|
+
active_pids.add(pid)
|
|
371
|
+
except Exception:
|
|
372
|
+
self._process_errors += 1
|
|
373
|
+
if cpu_observed:
|
|
374
|
+
self._cpu_samples += 1
|
|
375
|
+
if rss_observed:
|
|
376
|
+
self._rss_samples += 1
|
|
377
|
+
self._peak_rss_bytes = max(self._peak_rss_bytes, rss_total)
|
|
378
|
+
self._sample_accelerators(active_pids)
|
|
379
|
+
|
|
380
|
+
def _sample_accelerators(self, active_pids: set[int]) -> None:
|
|
381
|
+
if self._nvml is None or self._nvml_failed:
|
|
382
|
+
return
|
|
383
|
+
now = time.monotonic()
|
|
384
|
+
if self._last_accelerator_sample_at is not None:
|
|
385
|
+
elapsed = max(0.0, now - self._last_accelerator_sample_at)
|
|
386
|
+
self._accelerator_seconds += elapsed * self._last_active_device_count
|
|
387
|
+
try:
|
|
388
|
+
active_devices = 0
|
|
389
|
+
memory_total = 0
|
|
390
|
+
for handle in self._nvml_handles:
|
|
391
|
+
matched = False
|
|
392
|
+
for process in _nvml_processes(self._nvml, handle):
|
|
393
|
+
try:
|
|
394
|
+
pid = int(process.pid)
|
|
395
|
+
except (AttributeError, TypeError, ValueError):
|
|
396
|
+
continue
|
|
397
|
+
if pid not in active_pids:
|
|
398
|
+
continue
|
|
399
|
+
matched = True
|
|
400
|
+
used_memory = getattr(process, "usedGpuMemory", None)
|
|
401
|
+
if (
|
|
402
|
+
isinstance(used_memory, int)
|
|
403
|
+
and not isinstance(used_memory, bool)
|
|
404
|
+
and 0 <= used_memory < (1 << 63)
|
|
405
|
+
):
|
|
406
|
+
memory_total += used_memory
|
|
407
|
+
if matched:
|
|
408
|
+
active_devices += 1
|
|
409
|
+
self._last_accelerator_sample_at = now
|
|
410
|
+
self._last_active_device_count = active_devices
|
|
411
|
+
self._accelerator_peak_memory_bytes = max(
|
|
412
|
+
self._accelerator_peak_memory_bytes,
|
|
413
|
+
memory_total,
|
|
414
|
+
)
|
|
415
|
+
self._accelerator_samples += 1
|
|
416
|
+
except Exception as exc:
|
|
417
|
+
self._nvml_reason = f"sampling_failed:{type(exc).__name__}"
|
|
418
|
+
self._nvml_failed = True
|
|
419
|
+
self._last_active_device_count = 0
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _optional_module(name: str) -> tuple[Any | None, str | None]:
|
|
423
|
+
try:
|
|
424
|
+
return importlib.import_module(name), None
|
|
425
|
+
except ModuleNotFoundError:
|
|
426
|
+
return None, "dependency_missing"
|
|
427
|
+
except Exception as exc:
|
|
428
|
+
return None, f"import_failed:{type(exc).__name__}"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _reported_measurement_identity(
|
|
432
|
+
*,
|
|
433
|
+
has_process_measurement: bool,
|
|
434
|
+
has_accelerator_measurement: bool,
|
|
435
|
+
) -> dict[str, Any]:
|
|
436
|
+
return {
|
|
437
|
+
"process_tree": {
|
|
438
|
+
"status": "available" if has_process_measurement else "unavailable",
|
|
439
|
+
"reason": None if has_process_measurement else "runner_did_not_report",
|
|
440
|
+
"sampler": "injected_process_runner.v1",
|
|
441
|
+
"scope": "supervised_workload",
|
|
442
|
+
},
|
|
443
|
+
"accelerator": {
|
|
444
|
+
"status": "available" if has_accelerator_measurement else "unavailable",
|
|
445
|
+
"reason": None if has_accelerator_measurement else "runner_did_not_report",
|
|
446
|
+
"sampler": "injected_process_runner.v1",
|
|
447
|
+
"scope": "supervised_workload",
|
|
448
|
+
},
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _module_version(module: Any | None) -> str | None:
|
|
453
|
+
if module is None:
|
|
454
|
+
return None
|
|
455
|
+
value = getattr(module, "__version__", None)
|
|
456
|
+
return value if isinstance(value, str) and value else None
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _nvml_processes(module: Any, handle: Any) -> tuple[Any, ...]:
|
|
460
|
+
processes: dict[int, Any] = {}
|
|
461
|
+
for prefix in ("Compute", "Graphics"):
|
|
462
|
+
getter = None
|
|
463
|
+
for suffix in ("_v3", "_v2", ""):
|
|
464
|
+
candidate = getattr(
|
|
465
|
+
module,
|
|
466
|
+
f"nvmlDeviceGet{prefix}RunningProcesses{suffix}",
|
|
467
|
+
None,
|
|
468
|
+
)
|
|
469
|
+
if callable(candidate):
|
|
470
|
+
getter = candidate
|
|
471
|
+
break
|
|
472
|
+
if getter is None:
|
|
473
|
+
continue
|
|
474
|
+
try:
|
|
475
|
+
observed = getter(handle)
|
|
476
|
+
except Exception as exc:
|
|
477
|
+
not_supported = getattr(module, "NVMLError_NotSupported", ())
|
|
478
|
+
if isinstance(not_supported, type) and isinstance(exc, not_supported):
|
|
479
|
+
continue
|
|
480
|
+
raise
|
|
481
|
+
for process in observed or ():
|
|
482
|
+
pid = getattr(process, "pid", None)
|
|
483
|
+
if isinstance(pid, int) and not isinstance(pid, bool):
|
|
484
|
+
processes[pid] = process
|
|
485
|
+
return tuple(processes.values())
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def minimal_worker_environment(
|
|
489
|
+
overrides: Mapping[str, str] | None = None,
|
|
490
|
+
) -> dict[str, str]:
|
|
491
|
+
"""Keep process bootstrap variables, but never inherit credentials."""
|
|
492
|
+
|
|
493
|
+
environment = {key: os.environ[key] for key in _INHERITED_ENVIRONMENT_KEYS if key in os.environ}
|
|
494
|
+
environment["PYTHONIOENCODING"] = "utf-8"
|
|
495
|
+
for key, value in (overrides or {}).items():
|
|
496
|
+
if not isinstance(key, str) or not isinstance(value, str):
|
|
497
|
+
raise ValidationError("worker environment keys and values must be strings")
|
|
498
|
+
if "\x00" in key or "\x00" in value or "=" in key:
|
|
499
|
+
raise ValidationError("worker environment contains an invalid key or value")
|
|
500
|
+
environment[key] = value
|
|
501
|
+
return environment
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def run_bounded_process(
|
|
505
|
+
argv: Sequence[str],
|
|
506
|
+
*,
|
|
507
|
+
input_text: str,
|
|
508
|
+
timeout_seconds: float,
|
|
509
|
+
max_input_bytes: int,
|
|
510
|
+
max_stdout_bytes: int,
|
|
511
|
+
max_stderr_bytes: int,
|
|
512
|
+
cwd: str | None,
|
|
513
|
+
environment: Mapping[str, str] | None,
|
|
514
|
+
process_name: str,
|
|
515
|
+
) -> BoundedProcessResult:
|
|
516
|
+
"""Stream all pipes with hard in-memory bounds and a wall-clock timeout."""
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
isinstance(timeout_seconds, bool)
|
|
520
|
+
or not isinstance(timeout_seconds, (int, float))
|
|
521
|
+
or not math.isfinite(timeout_seconds)
|
|
522
|
+
or timeout_seconds <= 0
|
|
523
|
+
):
|
|
524
|
+
raise ValidationError("worker timeout_seconds must be finite and positive")
|
|
525
|
+
deadline = time.monotonic() + float(timeout_seconds)
|
|
526
|
+
encoded_input = input_text.encode("utf-8")
|
|
527
|
+
if len(encoded_input) > max_input_bytes:
|
|
528
|
+
raise BoundedProcessOutputLimitError(f"{process_name} request exceeds its input limit")
|
|
529
|
+
if min(max_input_bytes, max_stdout_bytes, max_stderr_bytes) < 1:
|
|
530
|
+
raise ValidationError("worker byte limits must be positive")
|
|
531
|
+
try:
|
|
532
|
+
process = subprocess.Popen(
|
|
533
|
+
tuple(argv),
|
|
534
|
+
stdin=subprocess.PIPE,
|
|
535
|
+
stdout=subprocess.PIPE,
|
|
536
|
+
stderr=subprocess.PIPE,
|
|
537
|
+
shell=False,
|
|
538
|
+
cwd=cwd,
|
|
539
|
+
env=minimal_worker_environment(environment),
|
|
540
|
+
start_new_session=os.name == "posix",
|
|
541
|
+
)
|
|
542
|
+
except FileNotFoundError as exc:
|
|
543
|
+
raise BoundedProcessStartupError(
|
|
544
|
+
f"{process_name} executable is unavailable: {argv[0]}"
|
|
545
|
+
) from exc
|
|
546
|
+
except OSError as exc:
|
|
547
|
+
raise BoundedProcessStartupError(f"{process_name} could not be started") from exc
|
|
548
|
+
|
|
549
|
+
def kill_process_tree() -> None:
|
|
550
|
+
try:
|
|
551
|
+
if os.name == "posix":
|
|
552
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
553
|
+
elif process.poll() is None:
|
|
554
|
+
process.kill()
|
|
555
|
+
except ProcessLookupError:
|
|
556
|
+
pass
|
|
557
|
+
|
|
558
|
+
if process.stdin is None or process.stdout is None or process.stderr is None:
|
|
559
|
+
kill_process_tree()
|
|
560
|
+
raise ExternalDependencyError(f"{process_name} did not expose worker pipes")
|
|
561
|
+
|
|
562
|
+
monitor = _ProcessTreeMonitor(process.pid)
|
|
563
|
+
monitor.start()
|
|
564
|
+
stdout = bytearray()
|
|
565
|
+
stderr = bytearray()
|
|
566
|
+
overflows: list[str] = []
|
|
567
|
+
writer_errors: list[BaseException] = []
|
|
568
|
+
lock = threading.Lock()
|
|
569
|
+
|
|
570
|
+
def read_pipe(stream, output: bytearray, limit: int, label: str) -> None:
|
|
571
|
+
try:
|
|
572
|
+
while True:
|
|
573
|
+
remaining = limit + 1 - len(output)
|
|
574
|
+
chunk = stream.read(max(1, min(64 * 1024, remaining)))
|
|
575
|
+
if not chunk:
|
|
576
|
+
return
|
|
577
|
+
output.extend(chunk)
|
|
578
|
+
if len(output) > limit:
|
|
579
|
+
with lock:
|
|
580
|
+
overflows.append(label)
|
|
581
|
+
kill_process_tree()
|
|
582
|
+
return
|
|
583
|
+
finally:
|
|
584
|
+
stream.close()
|
|
585
|
+
|
|
586
|
+
def write_input() -> None:
|
|
587
|
+
try:
|
|
588
|
+
process.stdin.write(encoded_input)
|
|
589
|
+
process.stdin.flush()
|
|
590
|
+
except BrokenPipeError:
|
|
591
|
+
pass
|
|
592
|
+
except BaseException as exc: # captured and re-raised on the caller thread
|
|
593
|
+
writer_errors.append(exc)
|
|
594
|
+
kill_process_tree()
|
|
595
|
+
finally:
|
|
596
|
+
# The worker may exit between ``flush`` and ``close``. On Python
|
|
597
|
+
# 3.13 the buffered close performs a final flush and can surface
|
|
598
|
+
# the same expected EPIPE even when the explicit flush above was
|
|
599
|
+
# already handled. Keep that shutdown race on the writer thread
|
|
600
|
+
# from leaking as an unhandled-thread warning.
|
|
601
|
+
with suppress(BrokenPipeError):
|
|
602
|
+
process.stdin.close()
|
|
603
|
+
|
|
604
|
+
threads = (
|
|
605
|
+
threading.Thread(
|
|
606
|
+
target=read_pipe,
|
|
607
|
+
args=(process.stdout, stdout, max_stdout_bytes, "stdout"),
|
|
608
|
+
daemon=True,
|
|
609
|
+
),
|
|
610
|
+
threading.Thread(
|
|
611
|
+
target=read_pipe,
|
|
612
|
+
args=(process.stderr, stderr, max_stderr_bytes, "stderr"),
|
|
613
|
+
daemon=True,
|
|
614
|
+
),
|
|
615
|
+
threading.Thread(target=write_input, daemon=True),
|
|
616
|
+
)
|
|
617
|
+
for thread in threads:
|
|
618
|
+
thread.start()
|
|
619
|
+
|
|
620
|
+
timed_out = False
|
|
621
|
+
try:
|
|
622
|
+
remaining = deadline - time.monotonic()
|
|
623
|
+
if remaining <= 0:
|
|
624
|
+
process.wait(timeout=0)
|
|
625
|
+
else:
|
|
626
|
+
process.wait(timeout=remaining)
|
|
627
|
+
except subprocess.TimeoutExpired:
|
|
628
|
+
timed_out = True
|
|
629
|
+
kill_process_tree()
|
|
630
|
+
process.wait()
|
|
631
|
+
for thread in threads:
|
|
632
|
+
thread.join(timeout=1.0)
|
|
633
|
+
pipes_open = any(thread.is_alive() for thread in threads)
|
|
634
|
+
if pipes_open:
|
|
635
|
+
kill_process_tree()
|
|
636
|
+
for thread in threads:
|
|
637
|
+
thread.join(timeout=0.5)
|
|
638
|
+
observation = monitor.stop()
|
|
639
|
+
|
|
640
|
+
if timed_out:
|
|
641
|
+
raise BoundedProcessTimeoutError(
|
|
642
|
+
f"{process_name} exceeded {timeout_seconds}s",
|
|
643
|
+
resource_observation=observation,
|
|
644
|
+
)
|
|
645
|
+
if pipes_open:
|
|
646
|
+
raise BoundedProcessProtocolError(
|
|
647
|
+
f"{process_name} left worker pipes open",
|
|
648
|
+
resource_observation=observation,
|
|
649
|
+
)
|
|
650
|
+
if writer_errors:
|
|
651
|
+
raise BoundedProcessProtocolError(
|
|
652
|
+
f"{process_name} request pipe failed",
|
|
653
|
+
resource_observation=observation,
|
|
654
|
+
) from writer_errors[0]
|
|
655
|
+
if overflows:
|
|
656
|
+
streams = ",".join(sorted(set(overflows)))
|
|
657
|
+
raise BoundedProcessOutputLimitError(
|
|
658
|
+
f"{process_name} exceeded its {streams} limit",
|
|
659
|
+
resource_observation=observation,
|
|
660
|
+
)
|
|
661
|
+
try:
|
|
662
|
+
stdout_text = bytes(stdout).decode("utf-8")
|
|
663
|
+
except UnicodeDecodeError as exc:
|
|
664
|
+
raise BoundedProcessProtocolError(
|
|
665
|
+
f"{process_name} emitted non-UTF-8 stdout",
|
|
666
|
+
resource_observation=observation,
|
|
667
|
+
) from exc
|
|
668
|
+
stderr_text = bytes(stderr).decode("utf-8", errors="replace")
|
|
669
|
+
return BoundedProcessResult(
|
|
670
|
+
returncode=process.returncode,
|
|
671
|
+
stdout=stdout_text,
|
|
672
|
+
stderr=stderr_text,
|
|
673
|
+
cpu_seconds=observation.cpu_seconds,
|
|
674
|
+
peak_rss_bytes=observation.peak_rss_bytes,
|
|
675
|
+
accelerator_seconds=observation.accelerator_seconds,
|
|
676
|
+
accelerator_peak_memory_bytes=(observation.accelerator_peak_memory_bytes),
|
|
677
|
+
measurement_identity=observation.measurement_identity,
|
|
678
|
+
)
|