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,1514 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pinned, fail-closed JSONL worker for the official LLMCompiler runtime.
|
|
3
|
+
|
|
4
|
+
The worker deliberately supports frozen model and tool replays only. It uses
|
|
5
|
+
the official Planner, TaskFetchingUnit, LLMCompiler join, and replan loop; the
|
|
6
|
+
local code supplies transport verification, bounded tracing, and side-effect
|
|
7
|
+
policy rather than a clean-room implementation of the algorithm.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import asyncio
|
|
14
|
+
import contextlib
|
|
15
|
+
import hashlib
|
|
16
|
+
import importlib
|
|
17
|
+
import importlib.metadata
|
|
18
|
+
import inspect
|
|
19
|
+
import io
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from collections.abc import Mapping, Sequence
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from types import ModuleType, SimpleNamespace
|
|
30
|
+
from typing import Any
|
|
31
|
+
from uuid import uuid4
|
|
32
|
+
|
|
33
|
+
UPSTREAM_COMMIT = "a00c9d35507507da70e8c637eee64efc8c1857ae"
|
|
34
|
+
PROTOCOL_VERSION = "agentinputlab.llm-compiler.v1"
|
|
35
|
+
COMPATIBILITY_MANIFEST_SHA256 = "4b22356ddd587b0fac952511ab53dcf1ae0007b9645e02cd6375122a41ddedd6"
|
|
36
|
+
_MANIFEST_PATH = Path(__file__).with_name("compatibility_manifest.json")
|
|
37
|
+
_MAX_MANIFEST_BYTES = 8 * 1024 * 1024
|
|
38
|
+
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
39
|
+
_DISTRIBUTION_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
40
|
+
_TOOL_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
41
|
+
_RELEASE_MODES = {
|
|
42
|
+
"plan.llm_compiler.planner_component": "planner_component",
|
|
43
|
+
"plan.llm_compiler.full": "full",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WorkerFailure(RuntimeError):
|
|
48
|
+
"""One sanitized, typed failure returned over the JSONL boundary."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
code: str,
|
|
53
|
+
message: str,
|
|
54
|
+
*,
|
|
55
|
+
consumed: bool = False,
|
|
56
|
+
usage: Mapping[str, int] | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
super().__init__(message)
|
|
59
|
+
self.code = code
|
|
60
|
+
self.public_message = message
|
|
61
|
+
self.consumed = consumed
|
|
62
|
+
self.usage = dict(usage) if usage is not None else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _DiscardTextIO(io.TextIOBase):
|
|
66
|
+
def write(self, value: str) -> int:
|
|
67
|
+
return len(value)
|
|
68
|
+
|
|
69
|
+
def flush(self) -> None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, slots=True)
|
|
74
|
+
class Controls:
|
|
75
|
+
max_tasks: int
|
|
76
|
+
max_replans: int
|
|
77
|
+
concurrency: int
|
|
78
|
+
timeout_seconds: float
|
|
79
|
+
streaming: bool
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class ValidatedRequest:
|
|
84
|
+
request_id: str
|
|
85
|
+
release_id: str
|
|
86
|
+
execution_mode: str
|
|
87
|
+
query: str
|
|
88
|
+
seed: int
|
|
89
|
+
prompt_hash: str
|
|
90
|
+
tool_specs: tuple[Mapping[str, Any], ...]
|
|
91
|
+
controls: Controls
|
|
92
|
+
backend_fingerprint: Mapping[str, Any]
|
|
93
|
+
tools_fingerprint: Mapping[str, Any]
|
|
94
|
+
tool_argument_order: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True, slots=True)
|
|
98
|
+
class LoadedUpstream:
|
|
99
|
+
planner: ModuleType
|
|
100
|
+
task_fetching: ModuleType
|
|
101
|
+
compiler: ModuleType
|
|
102
|
+
tools: ModuleType
|
|
103
|
+
logger: ModuleType
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _sha256_bytes(value: bytes) -> str:
|
|
107
|
+
return hashlib.sha256(value).hexdigest()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _sha256_text(value: str) -> str:
|
|
111
|
+
return _sha256_bytes(value.encode("utf-8"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _canonical_json(value: Any) -> str:
|
|
115
|
+
return json.dumps(
|
|
116
|
+
value,
|
|
117
|
+
ensure_ascii=False,
|
|
118
|
+
sort_keys=True,
|
|
119
|
+
separators=(",", ":"),
|
|
120
|
+
allow_nan=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _canonical_hash(value: Any) -> str:
|
|
125
|
+
return _sha256_text(_canonical_json(value))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _read_bytes(path: Path, *, label: str, limit: int = _MAX_MANIFEST_BYTES) -> bytes:
|
|
129
|
+
if path.is_symlink() or not path.is_file():
|
|
130
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} must be a regular non-symlink file")
|
|
131
|
+
try:
|
|
132
|
+
size = path.stat().st_size
|
|
133
|
+
except OSError as exc:
|
|
134
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} is unreadable") from exc
|
|
135
|
+
if size > limit:
|
|
136
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} exceeds the byte limit")
|
|
137
|
+
try:
|
|
138
|
+
return path.read_bytes()
|
|
139
|
+
except OSError as exc:
|
|
140
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} is unreadable") from exc
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _load_json(path: Path, *, label: str) -> tuple[Mapping[str, Any], str]:
|
|
144
|
+
raw = _read_bytes(path, label=label)
|
|
145
|
+
try:
|
|
146
|
+
value = json.loads(raw)
|
|
147
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
148
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} is not valid UTF-8 JSON") from exc
|
|
149
|
+
if not isinstance(value, Mapping):
|
|
150
|
+
raise WorkerFailure("MANIFEST_INVALID", f"{label} must contain a JSON object")
|
|
151
|
+
return value, _sha256_bytes(raw)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _expect_keys(value: Mapping[str, Any], expected: set[str], *, label: str) -> None:
|
|
155
|
+
if set(value) != expected:
|
|
156
|
+
raise WorkerFailure("SCHEMA_INVALID", f"{label} contains unsupported or missing fields")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _required_text(value: Mapping[str, Any], key: str, *, label: str) -> str:
|
|
160
|
+
result = value.get(key)
|
|
161
|
+
if not isinstance(result, str) or not result.strip():
|
|
162
|
+
raise WorkerFailure("SCHEMA_INVALID", f"{label}.{key} must be non-empty text")
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _nonnegative_int(value: Any, *, label: str) -> int:
|
|
167
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
168
|
+
raise WorkerFailure("SCHEMA_INVALID", f"{label} must be a non-negative integer")
|
|
169
|
+
return value
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _positive_int(value: Any, *, label: str) -> int:
|
|
173
|
+
result = _nonnegative_int(value, label=label)
|
|
174
|
+
if result < 1:
|
|
175
|
+
raise WorkerFailure("SCHEMA_INVALID", f"{label} must be positive")
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _verify_static_manifests() -> Mapping[str, Any]:
|
|
180
|
+
manifest, digest = _load_json(_MANIFEST_PATH, label="LLMCompiler compatibility manifest")
|
|
181
|
+
if digest != COMPATIBILITY_MANIFEST_SHA256:
|
|
182
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "compatibility manifest hash mismatch")
|
|
183
|
+
_expect_keys(
|
|
184
|
+
manifest,
|
|
185
|
+
{
|
|
186
|
+
"schema_version",
|
|
187
|
+
"upstream",
|
|
188
|
+
"source_files",
|
|
189
|
+
"legacy_dependencies",
|
|
190
|
+
"patch_manifest",
|
|
191
|
+
"execution_policy",
|
|
192
|
+
},
|
|
193
|
+
label="compatibility manifest",
|
|
194
|
+
)
|
|
195
|
+
if manifest["schema_version"] != "agentinputlab.llmcompiler.compatibility.v1":
|
|
196
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "compatibility schema mismatch")
|
|
197
|
+
upstream = manifest["upstream"]
|
|
198
|
+
if not isinstance(upstream, Mapping) or upstream.get("commit") != UPSTREAM_COMMIT:
|
|
199
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "pinned upstream identity mismatch")
|
|
200
|
+
patch = manifest["patch_manifest"]
|
|
201
|
+
if not isinstance(patch, Mapping) or set(patch) != {"path", "sha256"}:
|
|
202
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "patch manifest declaration is invalid")
|
|
203
|
+
if patch["path"] != "patch_manifest.json" or not _HEX64.fullmatch(str(patch["sha256"])):
|
|
204
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "patch manifest identity is invalid")
|
|
205
|
+
patch_path = _MANIFEST_PATH.parent / str(patch["path"])
|
|
206
|
+
if (
|
|
207
|
+
_sha256_bytes(_read_bytes(patch_path, label="LLMCompiler patch manifest"))
|
|
208
|
+
!= patch["sha256"]
|
|
209
|
+
):
|
|
210
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "patch manifest hash mismatch")
|
|
211
|
+
source_files = manifest["source_files"]
|
|
212
|
+
legacy = manifest["legacy_dependencies"]
|
|
213
|
+
if not isinstance(source_files, Mapping):
|
|
214
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "source hash manifest is invalid")
|
|
215
|
+
if not isinstance(legacy, Mapping) or set(legacy) != {
|
|
216
|
+
"requirements_sha256",
|
|
217
|
+
"required_versions",
|
|
218
|
+
}:
|
|
219
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "legacy dependency declaration is invalid")
|
|
220
|
+
requirements_sha = legacy["requirements_sha256"]
|
|
221
|
+
if (
|
|
222
|
+
not isinstance(requirements_sha, str)
|
|
223
|
+
or not _HEX64.fullmatch(requirements_sha)
|
|
224
|
+
or requirements_sha != source_files.get("requirements.txt")
|
|
225
|
+
):
|
|
226
|
+
raise WorkerFailure(
|
|
227
|
+
"WORKER_INTEGRITY_FAILURE",
|
|
228
|
+
"legacy dependency requirements hash is invalid",
|
|
229
|
+
)
|
|
230
|
+
versions = legacy["required_versions"]
|
|
231
|
+
if not isinstance(versions, Mapping) or not versions:
|
|
232
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "required dependency versions are empty")
|
|
233
|
+
for distribution_name, version in versions.items():
|
|
234
|
+
if (
|
|
235
|
+
not isinstance(distribution_name, str)
|
|
236
|
+
or not _DISTRIBUTION_NAME.fullmatch(distribution_name)
|
|
237
|
+
or not isinstance(version, str)
|
|
238
|
+
or not version.strip()
|
|
239
|
+
):
|
|
240
|
+
raise WorkerFailure(
|
|
241
|
+
"WORKER_INTEGRITY_FAILURE",
|
|
242
|
+
"required dependency version entry is invalid",
|
|
243
|
+
)
|
|
244
|
+
return manifest
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _verify_required_dependencies(manifest: Mapping[str, Any]) -> None:
|
|
248
|
+
"""Verify every manifest-pinned distribution and its imported package origin."""
|
|
249
|
+
|
|
250
|
+
legacy = manifest["legacy_dependencies"]
|
|
251
|
+
assert isinstance(legacy, Mapping) # established by _verify_static_manifests
|
|
252
|
+
required_versions = legacy["required_versions"]
|
|
253
|
+
assert isinstance(required_versions, Mapping)
|
|
254
|
+
for distribution_name, expected_version in required_versions.items():
|
|
255
|
+
assert isinstance(distribution_name, str)
|
|
256
|
+
assert isinstance(expected_version, str)
|
|
257
|
+
import_name = distribution_name.replace("-", "_")
|
|
258
|
+
requirement = f"{distribution_name}=={expected_version}"
|
|
259
|
+
try:
|
|
260
|
+
distribution = importlib.metadata.distribution(distribution_name)
|
|
261
|
+
except importlib.metadata.PackageNotFoundError as exc:
|
|
262
|
+
raise WorkerFailure(
|
|
263
|
+
"LEGACY_DEPENDENCY_MISSING",
|
|
264
|
+
f"pinned LLMCompiler dependency is unavailable: {requirement}",
|
|
265
|
+
) from exc
|
|
266
|
+
if distribution.version != expected_version:
|
|
267
|
+
raise WorkerFailure(
|
|
268
|
+
"LEGACY_DEPENDENCY_VERSION_MISMATCH",
|
|
269
|
+
f"pinned LLMCompiler requires {requirement}",
|
|
270
|
+
)
|
|
271
|
+
try:
|
|
272
|
+
module = importlib.import_module(import_name)
|
|
273
|
+
except ModuleNotFoundError as exc:
|
|
274
|
+
raise WorkerFailure(
|
|
275
|
+
"LEGACY_DEPENDENCY_MISSING",
|
|
276
|
+
f"pinned LLMCompiler dependency cannot be imported: {requirement}",
|
|
277
|
+
) from exc
|
|
278
|
+
except Exception as exc:
|
|
279
|
+
raise WorkerFailure(
|
|
280
|
+
"LEGACY_DEPENDENCY_IMPORT_FAILURE",
|
|
281
|
+
f"pinned LLMCompiler dependency import failed: {requirement}",
|
|
282
|
+
) from exc
|
|
283
|
+
module_file = getattr(module, "__file__", None)
|
|
284
|
+
try:
|
|
285
|
+
distribution_package = Path(distribution.locate_file(import_name)).resolve()
|
|
286
|
+
module_path = Path(module_file).resolve() if isinstance(module_file, str) else None
|
|
287
|
+
if module_path is None:
|
|
288
|
+
raise ValueError
|
|
289
|
+
module_path.relative_to(distribution_package)
|
|
290
|
+
except (OSError, TypeError, ValueError) as exc:
|
|
291
|
+
raise WorkerFailure(
|
|
292
|
+
"LEGACY_DEPENDENCY_ORIGIN_MISMATCH",
|
|
293
|
+
f"pinned LLMCompiler dependency escaped its distribution: {distribution_name}",
|
|
294
|
+
) from exc
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _git(checkout: Path, *arguments: str) -> str:
|
|
298
|
+
environment = {
|
|
299
|
+
"PATH": os.environ.get("PATH", ""),
|
|
300
|
+
"LANG": "C",
|
|
301
|
+
"LC_ALL": "C",
|
|
302
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
303
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
304
|
+
}
|
|
305
|
+
try:
|
|
306
|
+
result = subprocess.run(
|
|
307
|
+
("git", "-C", str(checkout), *arguments),
|
|
308
|
+
check=False,
|
|
309
|
+
capture_output=True,
|
|
310
|
+
text=True,
|
|
311
|
+
timeout=10,
|
|
312
|
+
env=environment,
|
|
313
|
+
)
|
|
314
|
+
except FileNotFoundError as exc:
|
|
315
|
+
raise WorkerFailure("GIT_UNAVAILABLE", "git is required to verify the checkout") from exc
|
|
316
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
317
|
+
raise WorkerFailure("CHECKOUT_VERIFICATION_FAILED", "git verification failed") from exc
|
|
318
|
+
if result.returncode != 0:
|
|
319
|
+
raise WorkerFailure("CHECKOUT_VERIFICATION_FAILED", "git verification failed")
|
|
320
|
+
return result.stdout.strip()
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def verify_checkout(checkout: Path) -> Mapping[str, Any]:
|
|
324
|
+
"""Verify commit, cleanliness, license, source hashes, and patch attestation."""
|
|
325
|
+
|
|
326
|
+
manifest = _verify_static_manifests()
|
|
327
|
+
checkout = checkout.resolve()
|
|
328
|
+
if not checkout.is_dir():
|
|
329
|
+
raise WorkerFailure("CHECKOUT_MISSING", "the pinned LLMCompiler checkout is missing")
|
|
330
|
+
if _git(checkout, "rev-parse", "HEAD") != UPSTREAM_COMMIT:
|
|
331
|
+
raise WorkerFailure("CHECKOUT_REVISION_MISMATCH", "LLMCompiler checkout revision mismatch")
|
|
332
|
+
if _git(checkout, "status", "--porcelain=v1", "--untracked-files=all"):
|
|
333
|
+
raise WorkerFailure("CHECKOUT_DIRTY", "LLMCompiler checkout must be clean")
|
|
334
|
+
upstream = manifest["upstream"]
|
|
335
|
+
license_path = checkout / "LICENSE"
|
|
336
|
+
expected_license = str(upstream["license_sha256"])
|
|
337
|
+
if _sha256_bytes(_read_bytes(license_path, label="upstream LICENSE")) != expected_license:
|
|
338
|
+
raise WorkerFailure("LICENSE_MISMATCH", "LLMCompiler MIT license hash mismatch")
|
|
339
|
+
source_files = manifest["source_files"]
|
|
340
|
+
if not isinstance(source_files, Mapping) or not source_files:
|
|
341
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "source hash manifest is empty")
|
|
342
|
+
for relative, expected in source_files.items():
|
|
343
|
+
if not isinstance(relative, str) or not _HEX64.fullmatch(str(expected)):
|
|
344
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "source hash entry is invalid")
|
|
345
|
+
candidate = (checkout / relative).resolve()
|
|
346
|
+
try:
|
|
347
|
+
candidate.relative_to(checkout)
|
|
348
|
+
except ValueError as exc:
|
|
349
|
+
raise WorkerFailure("WORKER_INTEGRITY_FAILURE", "source path escapes checkout") from exc
|
|
350
|
+
actual = _sha256_bytes(_read_bytes(candidate, label=f"upstream source {relative}"))
|
|
351
|
+
if actual != expected:
|
|
352
|
+
raise WorkerFailure("SOURCE_MISMATCH", f"pinned upstream source mismatch: {relative}")
|
|
353
|
+
return manifest
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _validate_controls(value: Any) -> Controls:
|
|
357
|
+
if not isinstance(value, Mapping):
|
|
358
|
+
raise WorkerFailure("SCHEMA_INVALID", "payload.controls must be an object")
|
|
359
|
+
_expect_keys(
|
|
360
|
+
value,
|
|
361
|
+
{"max_tasks", "max_replans", "concurrency", "timeout_seconds", "streaming"},
|
|
362
|
+
label="payload.controls",
|
|
363
|
+
)
|
|
364
|
+
timeout = value["timeout_seconds"]
|
|
365
|
+
if (
|
|
366
|
+
isinstance(timeout, bool)
|
|
367
|
+
or not isinstance(timeout, (int, float))
|
|
368
|
+
or not math.isfinite(float(timeout))
|
|
369
|
+
or float(timeout) <= 0
|
|
370
|
+
):
|
|
371
|
+
raise WorkerFailure("SCHEMA_INVALID", "controls.timeout_seconds must be positive")
|
|
372
|
+
streaming = value["streaming"]
|
|
373
|
+
if not isinstance(streaming, bool):
|
|
374
|
+
raise WorkerFailure("SCHEMA_INVALID", "controls.streaming must be boolean")
|
|
375
|
+
return Controls(
|
|
376
|
+
max_tasks=_positive_int(value["max_tasks"], label="controls.max_tasks"),
|
|
377
|
+
max_replans=_nonnegative_int(value["max_replans"], label="controls.max_replans"),
|
|
378
|
+
concurrency=_positive_int(value["concurrency"], label="controls.concurrency"),
|
|
379
|
+
timeout_seconds=float(timeout),
|
|
380
|
+
streaming=streaming,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _validate_tool_specs(value: Any) -> tuple[Mapping[str, Any], ...]:
|
|
385
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence) or not value:
|
|
386
|
+
raise WorkerFailure("SCHEMA_INVALID", "payload.tool_specs must be a non-empty list")
|
|
387
|
+
result: list[Mapping[str, Any]] = []
|
|
388
|
+
names: set[str] = set()
|
|
389
|
+
for index, item in enumerate(value):
|
|
390
|
+
if not isinstance(item, Mapping):
|
|
391
|
+
raise WorkerFailure("SCHEMA_INVALID", f"tool_specs[{index}] must be an object")
|
|
392
|
+
_expect_keys(
|
|
393
|
+
item,
|
|
394
|
+
{"name", "description", "input_schema", "read_only", "network"},
|
|
395
|
+
label=f"tool_specs[{index}]",
|
|
396
|
+
)
|
|
397
|
+
name = _required_text(item, "name", label=f"tool_specs[{index}]")
|
|
398
|
+
if not _TOOL_NAME.fullmatch(name) or name == "join" or name in names:
|
|
399
|
+
raise WorkerFailure("TOOL_POLICY_DENIED", "tool names must be unique safe identifiers")
|
|
400
|
+
if not isinstance(item["description"], str) or not item["description"].strip():
|
|
401
|
+
raise WorkerFailure("SCHEMA_INVALID", "tool description must be non-empty")
|
|
402
|
+
if not isinstance(item["input_schema"], Mapping):
|
|
403
|
+
raise WorkerFailure("SCHEMA_INVALID", "tool input_schema must be an object")
|
|
404
|
+
if item["read_only"] is not True or item["network"] is not False:
|
|
405
|
+
raise WorkerFailure(
|
|
406
|
+
"TOOL_POLICY_DENIED",
|
|
407
|
+
"LLMCompiler worker accepts only read-only, no-network tools",
|
|
408
|
+
)
|
|
409
|
+
try:
|
|
410
|
+
_canonical_json(item["input_schema"])
|
|
411
|
+
except (TypeError, ValueError) as exc:
|
|
412
|
+
raise WorkerFailure("SCHEMA_INVALID", "tool schema is not canonical JSON") from exc
|
|
413
|
+
names.add(name)
|
|
414
|
+
result.append(dict(item))
|
|
415
|
+
return tuple(result)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def validate_request(value: Any) -> ValidatedRequest:
|
|
419
|
+
if not isinstance(value, Mapping):
|
|
420
|
+
raise WorkerFailure("SCHEMA_INVALID", "worker request must be a JSON object")
|
|
421
|
+
_expect_keys(
|
|
422
|
+
value,
|
|
423
|
+
{
|
|
424
|
+
"method_id",
|
|
425
|
+
"release_id",
|
|
426
|
+
"upstream_commit",
|
|
427
|
+
"state_hash",
|
|
428
|
+
"payload",
|
|
429
|
+
"dependency_fingerprints",
|
|
430
|
+
"minimum_reservation",
|
|
431
|
+
"protocol_version",
|
|
432
|
+
"request_id",
|
|
433
|
+
},
|
|
434
|
+
label="request",
|
|
435
|
+
)
|
|
436
|
+
request_id = _required_text(value, "request_id", label="request")
|
|
437
|
+
if not _HEX64.fullmatch(request_id):
|
|
438
|
+
raise WorkerFailure("REQUEST_ID_MISMATCH", "request_id must be a SHA-256 digest")
|
|
439
|
+
identity = {key: value[key] for key in value if key != "request_id"}
|
|
440
|
+
try:
|
|
441
|
+
expected_request_id = _canonical_hash(identity)
|
|
442
|
+
except (TypeError, ValueError) as exc:
|
|
443
|
+
raise WorkerFailure("SCHEMA_INVALID", "request is not canonical JSON") from exc
|
|
444
|
+
if request_id != expected_request_id:
|
|
445
|
+
raise WorkerFailure("REQUEST_ID_MISMATCH", "request_id content hash mismatch")
|
|
446
|
+
if value["method_id"] != "llm_compiler":
|
|
447
|
+
raise WorkerFailure("IDENTITY_MISMATCH", "worker accepts only llm_compiler")
|
|
448
|
+
release_id = _required_text(value, "release_id", label="request")
|
|
449
|
+
if release_id not in _RELEASE_MODES:
|
|
450
|
+
raise WorkerFailure("IDENTITY_MISMATCH", "unsupported LLMCompiler release")
|
|
451
|
+
if value["upstream_commit"] != UPSTREAM_COMMIT:
|
|
452
|
+
raise WorkerFailure("IDENTITY_MISMATCH", "upstream commit mismatch")
|
|
453
|
+
if value["protocol_version"] != PROTOCOL_VERSION:
|
|
454
|
+
raise WorkerFailure("IDENTITY_MISMATCH", "protocol version mismatch")
|
|
455
|
+
_required_text(value, "state_hash", label="request")
|
|
456
|
+
payload = value["payload"]
|
|
457
|
+
if not isinstance(payload, Mapping):
|
|
458
|
+
raise WorkerFailure("SCHEMA_INVALID", "request.payload must be an object")
|
|
459
|
+
expected_payload = {
|
|
460
|
+
"task_id",
|
|
461
|
+
"query",
|
|
462
|
+
"instruction",
|
|
463
|
+
"messages",
|
|
464
|
+
"tool_specs",
|
|
465
|
+
"controls",
|
|
466
|
+
"prompt_hash",
|
|
467
|
+
"execution_mode",
|
|
468
|
+
"seed",
|
|
469
|
+
}
|
|
470
|
+
if (not expected_payload <= set(payload)
|
|
471
|
+
or set(payload) - expected_payload - {"execution_budget", "tool_argument_order"}):
|
|
472
|
+
raise WorkerFailure("SCHEMA_INVALID", "payload contains unsupported or missing fields")
|
|
473
|
+
query = _required_text(payload, "query", label="payload")
|
|
474
|
+
if payload["instruction"] != "" or payload["messages"] != []:
|
|
475
|
+
raise WorkerFailure(
|
|
476
|
+
"UNSUPPORTED_CONVERSATION_STATE",
|
|
477
|
+
"official LLMCompiler accepts a single query without instruction/history",
|
|
478
|
+
)
|
|
479
|
+
mode = _required_text(payload, "execution_mode", label="payload")
|
|
480
|
+
if mode != _RELEASE_MODES[release_id]:
|
|
481
|
+
raise WorkerFailure("IDENTITY_MISMATCH", "release and execution_mode disagree")
|
|
482
|
+
seed = _nonnegative_int(payload["seed"], label="payload.seed")
|
|
483
|
+
dependencies = value["dependency_fingerprints"]
|
|
484
|
+
if not isinstance(dependencies, Mapping) or set(dependencies) != {"backend", "tools"}:
|
|
485
|
+
raise WorkerFailure("SCHEMA_INVALID", "dependency fingerprints must bind backend/tools")
|
|
486
|
+
backend = dependencies["backend"]
|
|
487
|
+
tools = dependencies["tools"]
|
|
488
|
+
if not isinstance(backend, Mapping) or not isinstance(tools, Mapping):
|
|
489
|
+
raise WorkerFailure("SCHEMA_INVALID", "dependency fingerprints must be objects")
|
|
490
|
+
reservation = value["minimum_reservation"]
|
|
491
|
+
if (
|
|
492
|
+
not isinstance(reservation, Mapping)
|
|
493
|
+
or _nonnegative_int(reservation.get("model_calls"), label="minimum_reservation.model_calls")
|
|
494
|
+
< 1
|
|
495
|
+
):
|
|
496
|
+
raise WorkerFailure("SCHEMA_INVALID", "worker requires a model-call reservation")
|
|
497
|
+
specs = _validate_tool_specs(payload["tool_specs"])
|
|
498
|
+
order = payload.get("tool_argument_order", {})
|
|
499
|
+
by_name = {spec["name"]: spec for spec in specs}
|
|
500
|
+
if (not isinstance(order, Mapping) or not set(order) <= set(by_name)
|
|
501
|
+
or any(not isinstance(keys, (list, tuple)) or not all(isinstance(k, str) for k in keys)
|
|
502
|
+
or len(set(keys)) != len(keys)
|
|
503
|
+
or set(keys) != set(by_name[name]["input_schema"].get("properties", {}))
|
|
504
|
+
for name, keys in order.items())):
|
|
505
|
+
raise WorkerFailure("SCHEMA_INVALID", "tool_argument_order must bind exact tool parameter names")
|
|
506
|
+
return ValidatedRequest(
|
|
507
|
+
request_id=request_id,
|
|
508
|
+
release_id=release_id,
|
|
509
|
+
execution_mode=mode,
|
|
510
|
+
query=query,
|
|
511
|
+
seed=seed,
|
|
512
|
+
prompt_hash=_required_text(payload, "prompt_hash", label="payload"),
|
|
513
|
+
tool_specs=specs,
|
|
514
|
+
controls=_validate_controls(payload["controls"]),
|
|
515
|
+
backend_fingerprint=dict(backend),
|
|
516
|
+
tools_fingerprint=dict(tools),
|
|
517
|
+
tool_argument_order={name: tuple(keys) for name, keys in order.items()},
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _fingerprint_sha(value: Mapping[str, Any], key: str, *, label: str) -> str:
|
|
522
|
+
result = value.get(key)
|
|
523
|
+
if not isinstance(result, str) or not _HEX64.fullmatch(result):
|
|
524
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", f"{label} must bind {key}")
|
|
525
|
+
return result
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@dataclass(frozen=True, slots=True)
|
|
529
|
+
class ReplayEntry:
|
|
530
|
+
channel: str
|
|
531
|
+
prompt_sha256: str
|
|
532
|
+
response: str
|
|
533
|
+
input_tokens: int
|
|
534
|
+
output_tokens: int
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@dataclass(frozen=True, slots=True)
|
|
538
|
+
class ReplayBundle:
|
|
539
|
+
prompts: Mapping[str, Any]
|
|
540
|
+
entries: tuple[ReplayEntry, ...]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def load_model_replay(path: Path, request: ValidatedRequest) -> ReplayBundle:
|
|
544
|
+
value, digest = _load_json(path, label="LLMCompiler model replay")
|
|
545
|
+
if digest != _fingerprint_sha(
|
|
546
|
+
request.backend_fingerprint,
|
|
547
|
+
"replay_manifest_sha256",
|
|
548
|
+
label="backend fingerprint",
|
|
549
|
+
):
|
|
550
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "model replay manifest hash mismatch")
|
|
551
|
+
_expect_keys(
|
|
552
|
+
value,
|
|
553
|
+
{"schema_version", "upstream_commit", "seed", "prompts", "prompt_hash", "responses"},
|
|
554
|
+
label="model replay",
|
|
555
|
+
)
|
|
556
|
+
if (
|
|
557
|
+
value["schema_version"] != "agentinputlab.llmcompiler.model-replay.v1"
|
|
558
|
+
or value["upstream_commit"] != UPSTREAM_COMMIT
|
|
559
|
+
or value["seed"] != request.seed
|
|
560
|
+
):
|
|
561
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "model replay identity mismatch")
|
|
562
|
+
prompts = value["prompts"]
|
|
563
|
+
if not isinstance(prompts, Mapping):
|
|
564
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay prompts must be an object")
|
|
565
|
+
_expect_keys(
|
|
566
|
+
prompts,
|
|
567
|
+
{
|
|
568
|
+
"planner_example_prompt",
|
|
569
|
+
"planner_example_prompt_replan",
|
|
570
|
+
"planner_stop",
|
|
571
|
+
"joinner_prompt",
|
|
572
|
+
"joinner_prompt_final",
|
|
573
|
+
},
|
|
574
|
+
label="model replay prompts",
|
|
575
|
+
)
|
|
576
|
+
if not all(
|
|
577
|
+
isinstance(prompts[key], str)
|
|
578
|
+
for key in (
|
|
579
|
+
"planner_example_prompt",
|
|
580
|
+
"planner_example_prompt_replan",
|
|
581
|
+
"joinner_prompt",
|
|
582
|
+
"joinner_prompt_final",
|
|
583
|
+
)
|
|
584
|
+
):
|
|
585
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay prompt fields must be text")
|
|
586
|
+
stop = prompts["planner_stop"]
|
|
587
|
+
if (
|
|
588
|
+
isinstance(stop, (str, bytes))
|
|
589
|
+
or not isinstance(stop, Sequence)
|
|
590
|
+
or not all(isinstance(item, str) and item for item in stop)
|
|
591
|
+
):
|
|
592
|
+
raise WorkerFailure("MANIFEST_INVALID", "planner_stop must be a list of strings")
|
|
593
|
+
computed_prompt_hash = _canonical_hash(prompts)
|
|
594
|
+
if value["prompt_hash"] != computed_prompt_hash or request.prompt_hash != computed_prompt_hash:
|
|
595
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "prompt bundle hash mismatch")
|
|
596
|
+
responses = value["responses"]
|
|
597
|
+
if isinstance(responses, (str, bytes)) or not isinstance(responses, Sequence) or not responses:
|
|
598
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay responses must be non-empty")
|
|
599
|
+
entries: list[ReplayEntry] = []
|
|
600
|
+
for index, item in enumerate(responses):
|
|
601
|
+
if not isinstance(item, Mapping):
|
|
602
|
+
raise WorkerFailure("MANIFEST_INVALID", f"model replay response {index} is invalid")
|
|
603
|
+
_expect_keys(
|
|
604
|
+
item,
|
|
605
|
+
{
|
|
606
|
+
"channel",
|
|
607
|
+
"prompt_sha256",
|
|
608
|
+
"response",
|
|
609
|
+
"response_sha256",
|
|
610
|
+
"input_tokens",
|
|
611
|
+
"output_tokens",
|
|
612
|
+
},
|
|
613
|
+
label=f"model replay response {index}",
|
|
614
|
+
)
|
|
615
|
+
channel = item["channel"]
|
|
616
|
+
response = item["response"]
|
|
617
|
+
if channel not in {"planner", "join"} or not isinstance(response, str) or not response:
|
|
618
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay channel/response is invalid")
|
|
619
|
+
if not _HEX64.fullmatch(str(item["prompt_sha256"])):
|
|
620
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay prompt hash is invalid")
|
|
621
|
+
if item["response_sha256"] != _sha256_text(response):
|
|
622
|
+
raise WorkerFailure("MANIFEST_INVALID", "model replay response hash mismatch")
|
|
623
|
+
entries.append(
|
|
624
|
+
ReplayEntry(
|
|
625
|
+
channel=channel,
|
|
626
|
+
prompt_sha256=str(item["prompt_sha256"]),
|
|
627
|
+
response=response,
|
|
628
|
+
input_tokens=_nonnegative_int(
|
|
629
|
+
item["input_tokens"], label="model replay input_tokens"
|
|
630
|
+
),
|
|
631
|
+
output_tokens=_nonnegative_int(
|
|
632
|
+
item["output_tokens"], label="model replay output_tokens"
|
|
633
|
+
),
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
return ReplayBundle(prompts=dict(prompts), entries=tuple(entries))
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@dataclass(slots=True)
|
|
640
|
+
class ToolReplay:
|
|
641
|
+
cases: Mapping[str, Any]
|
|
642
|
+
calls: int = 0
|
|
643
|
+
|
|
644
|
+
async def invoke(self, name: str, arguments: Sequence[Any]) -> Any:
|
|
645
|
+
self.calls += 1
|
|
646
|
+
try:
|
|
647
|
+
key = _canonical_hash(list(arguments))
|
|
648
|
+
except (TypeError, ValueError) as exc:
|
|
649
|
+
raise WorkerFailure(
|
|
650
|
+
"TOOL_ARGUMENT_DENIED",
|
|
651
|
+
"tool arguments are not canonical JSON",
|
|
652
|
+
consumed=True,
|
|
653
|
+
) from exc
|
|
654
|
+
compound = f"{name}:{key}"
|
|
655
|
+
if compound not in self.cases:
|
|
656
|
+
raise WorkerFailure(
|
|
657
|
+
"TOOL_REPLAY_MISS",
|
|
658
|
+
"tool arguments are absent from the frozen allowlist",
|
|
659
|
+
consumed=True,
|
|
660
|
+
)
|
|
661
|
+
return self.cases[compound]
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def load_tool_replay(path: Path, request: ValidatedRequest) -> ToolReplay:
|
|
665
|
+
value, digest = _load_json(path, label="LLMCompiler tool replay")
|
|
666
|
+
if digest != _fingerprint_sha(
|
|
667
|
+
request.tools_fingerprint,
|
|
668
|
+
"tool_manifest_sha256",
|
|
669
|
+
label="tools fingerprint",
|
|
670
|
+
):
|
|
671
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "tool replay manifest hash mismatch")
|
|
672
|
+
_expect_keys(value, {"schema_version", "upstream_commit", "tools"}, label="tool replay")
|
|
673
|
+
if (
|
|
674
|
+
value["schema_version"] != "agentinputlab.llmcompiler.tool-replay.v1"
|
|
675
|
+
or value["upstream_commit"] != UPSTREAM_COMMIT
|
|
676
|
+
):
|
|
677
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "tool replay identity mismatch")
|
|
678
|
+
tools = value["tools"]
|
|
679
|
+
if isinstance(tools, (str, bytes)) or not isinstance(tools, Sequence):
|
|
680
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay tools must be a list")
|
|
681
|
+
by_name: dict[str, Mapping[str, Any]] = {}
|
|
682
|
+
cases: dict[str, Any] = {}
|
|
683
|
+
for index, item in enumerate(tools):
|
|
684
|
+
if not isinstance(item, Mapping):
|
|
685
|
+
raise WorkerFailure("MANIFEST_INVALID", f"tool replay item {index} is invalid")
|
|
686
|
+
_expect_keys(
|
|
687
|
+
item,
|
|
688
|
+
{
|
|
689
|
+
"name",
|
|
690
|
+
"description",
|
|
691
|
+
"input_schema",
|
|
692
|
+
"read_only",
|
|
693
|
+
"network",
|
|
694
|
+
"implementation",
|
|
695
|
+
"cases",
|
|
696
|
+
},
|
|
697
|
+
label=f"tool replay item {index}",
|
|
698
|
+
)
|
|
699
|
+
name = _required_text(item, "name", label=f"tool replay item {index}")
|
|
700
|
+
if name in by_name or not _TOOL_NAME.fullmatch(name) or name == "join":
|
|
701
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay names are invalid or duplicated")
|
|
702
|
+
if (
|
|
703
|
+
item["read_only"] is not True
|
|
704
|
+
or item["network"] is not False
|
|
705
|
+
or item["implementation"] != "replay_table"
|
|
706
|
+
or not isinstance(item["input_schema"], Mapping)
|
|
707
|
+
):
|
|
708
|
+
raise WorkerFailure("TOOL_POLICY_DENIED", "tool replay violates no-side-effect policy")
|
|
709
|
+
rows = item["cases"]
|
|
710
|
+
if isinstance(rows, (str, bytes)) or not isinstance(rows, Sequence):
|
|
711
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay cases must be a list")
|
|
712
|
+
for row in rows:
|
|
713
|
+
if not isinstance(row, Mapping) or set(row) != {"arguments", "output"}:
|
|
714
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay case is invalid")
|
|
715
|
+
arguments = row["arguments"]
|
|
716
|
+
if isinstance(arguments, (str, bytes)) or not isinstance(arguments, Sequence):
|
|
717
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay arguments must be a list")
|
|
718
|
+
try:
|
|
719
|
+
key = f"{name}:{_canonical_hash(list(arguments))}"
|
|
720
|
+
_canonical_json(row["output"])
|
|
721
|
+
except (TypeError, ValueError) as exc:
|
|
722
|
+
raise WorkerFailure(
|
|
723
|
+
"MANIFEST_INVALID", "tool replay case is not canonical"
|
|
724
|
+
) from exc
|
|
725
|
+
if key in cases:
|
|
726
|
+
raise WorkerFailure("MANIFEST_INVALID", "tool replay contains duplicate cases")
|
|
727
|
+
cases[key] = row["output"]
|
|
728
|
+
by_name[name] = item
|
|
729
|
+
requested_names = {str(item["name"]) for item in request.tool_specs}
|
|
730
|
+
if not requested_names <= set(by_name):
|
|
731
|
+
raise WorkerFailure(
|
|
732
|
+
"TOOL_POLICY_DENIED", "request contains a tool absent from the manifest"
|
|
733
|
+
)
|
|
734
|
+
for spec in request.tool_specs:
|
|
735
|
+
manifest_spec = by_name[str(spec["name"])]
|
|
736
|
+
comparable = {
|
|
737
|
+
key: manifest_spec[key]
|
|
738
|
+
for key in ("name", "description", "input_schema", "read_only", "network")
|
|
739
|
+
}
|
|
740
|
+
if _canonical_json(comparable) != _canonical_json(spec):
|
|
741
|
+
raise WorkerFailure("DEPENDENCY_MISMATCH", "tool schema differs from frozen manifest")
|
|
742
|
+
return ToolReplay(cases=cases)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class _ReplayModel:
|
|
746
|
+
def __init__(self, entries: Sequence[ReplayEntry]) -> None:
|
|
747
|
+
self._entries = list(entries)
|
|
748
|
+
self._cursor = 0
|
|
749
|
+
self.consumed: list[ReplayEntry] = []
|
|
750
|
+
self.planner_responses: list[str] = []
|
|
751
|
+
|
|
752
|
+
def usage(self, *, tool_calls: int = 0) -> dict[str, int]:
|
|
753
|
+
return {
|
|
754
|
+
"model_calls": len(self.consumed),
|
|
755
|
+
"retrieval_calls": 0,
|
|
756
|
+
"tool_calls": tool_calls,
|
|
757
|
+
"input_tokens": sum(item.input_tokens for item in self.consumed),
|
|
758
|
+
"output_tokens": sum(item.output_tokens for item in self.consumed),
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
def _consume(self, channel: str, prompt: str) -> ReplayEntry:
|
|
762
|
+
if self._cursor >= len(self._entries):
|
|
763
|
+
raise WorkerFailure(
|
|
764
|
+
"MODEL_REPLAY_EXHAUSTED",
|
|
765
|
+
"model replay has no response for this call",
|
|
766
|
+
consumed=bool(self.consumed),
|
|
767
|
+
usage=self.usage(),
|
|
768
|
+
)
|
|
769
|
+
entry = self._entries[self._cursor]
|
|
770
|
+
if entry.channel != channel or entry.prompt_sha256 != _sha256_text(prompt):
|
|
771
|
+
raise WorkerFailure(
|
|
772
|
+
"MODEL_PROMPT_MISMATCH",
|
|
773
|
+
"model replay prompt/channel mismatch",
|
|
774
|
+
consumed=bool(self.consumed),
|
|
775
|
+
usage=self.usage(),
|
|
776
|
+
)
|
|
777
|
+
self._cursor += 1
|
|
778
|
+
self.consumed.append(entry)
|
|
779
|
+
if channel == "planner":
|
|
780
|
+
self.planner_responses.append(entry.response)
|
|
781
|
+
return entry
|
|
782
|
+
|
|
783
|
+
@staticmethod
|
|
784
|
+
async def _notify(callbacks: Any, response: str) -> None:
|
|
785
|
+
if callbacks is None:
|
|
786
|
+
return
|
|
787
|
+
items = callbacks if isinstance(callbacks, Sequence) else (callbacks,)
|
|
788
|
+
run_id = uuid4()
|
|
789
|
+
for token in response:
|
|
790
|
+
for callback in items:
|
|
791
|
+
method = getattr(callback, "on_llm_new_token", None)
|
|
792
|
+
if callable(method):
|
|
793
|
+
pending = method(token, run_id=run_id)
|
|
794
|
+
if inspect.isawaitable(pending):
|
|
795
|
+
await pending
|
|
796
|
+
for callback in items:
|
|
797
|
+
method = getattr(callback, "on_llm_end", None)
|
|
798
|
+
if callable(method):
|
|
799
|
+
pending = method(None, run_id=run_id)
|
|
800
|
+
if inspect.isawaitable(pending):
|
|
801
|
+
await pending
|
|
802
|
+
|
|
803
|
+
async def apredict(
|
|
804
|
+
self,
|
|
805
|
+
prompt: str,
|
|
806
|
+
*,
|
|
807
|
+
callbacks: Any = None,
|
|
808
|
+
stop: Any = None,
|
|
809
|
+
**_: Any,
|
|
810
|
+
) -> str:
|
|
811
|
+
del stop
|
|
812
|
+
entry = self._consume("planner", prompt)
|
|
813
|
+
await self._notify(callbacks, entry.response)
|
|
814
|
+
return entry.response
|
|
815
|
+
|
|
816
|
+
async def agenerate_prompt(
|
|
817
|
+
self,
|
|
818
|
+
prompts: Sequence[Any],
|
|
819
|
+
stop: Any = None,
|
|
820
|
+
callbacks: Any = None,
|
|
821
|
+
**_: Any,
|
|
822
|
+
) -> Any:
|
|
823
|
+
del stop, callbacks
|
|
824
|
+
if len(prompts) != 1:
|
|
825
|
+
raise WorkerFailure("MODEL_REPLAY_INVALID_CALL", "join requires exactly one prompt")
|
|
826
|
+
value = prompts[0]
|
|
827
|
+
if hasattr(value, "text"):
|
|
828
|
+
prompt = value.text
|
|
829
|
+
elif callable(getattr(value, "to_string", None)):
|
|
830
|
+
prompt = value.to_string()
|
|
831
|
+
else:
|
|
832
|
+
prompt = str(value)
|
|
833
|
+
entry = self._consume("join", prompt)
|
|
834
|
+
generation = SimpleNamespace(text=entry.response)
|
|
835
|
+
return SimpleNamespace(generations=[[generation]])
|
|
836
|
+
|
|
837
|
+
def assert_exhausted(self) -> None:
|
|
838
|
+
if self._cursor != len(self._entries):
|
|
839
|
+
raise WorkerFailure(
|
|
840
|
+
"MODEL_REPLAY_UNUSED",
|
|
841
|
+
"model replay contains unused responses",
|
|
842
|
+
consumed=bool(self.consumed),
|
|
843
|
+
usage=self.usage(),
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def _module_in_checkout(module: ModuleType, checkout: Path) -> bool:
|
|
848
|
+
location = getattr(module, "__file__", None)
|
|
849
|
+
if not isinstance(location, str):
|
|
850
|
+
return False
|
|
851
|
+
try:
|
|
852
|
+
Path(location).resolve().relative_to(checkout.resolve())
|
|
853
|
+
except ValueError:
|
|
854
|
+
return False
|
|
855
|
+
return True
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def import_upstream(checkout: Path) -> LoadedUpstream:
|
|
859
|
+
"""Import only fixed module names after checkout verification."""
|
|
860
|
+
|
|
861
|
+
checkout = checkout.resolve()
|
|
862
|
+
manifest = _verify_static_manifests()
|
|
863
|
+
_verify_required_dependencies(manifest)
|
|
864
|
+
sys.path.insert(0, str(checkout))
|
|
865
|
+
discarded = _DiscardTextIO()
|
|
866
|
+
try:
|
|
867
|
+
with contextlib.redirect_stdout(discarded):
|
|
868
|
+
task_module = importlib.import_module("src.llm_compiler.task_fetching_unit")
|
|
869
|
+
tools_module = importlib.import_module("src.tools.base")
|
|
870
|
+
planner_module = importlib.import_module("src.llm_compiler.planner")
|
|
871
|
+
compiler_module = importlib.import_module("src.llm_compiler.llm_compiler")
|
|
872
|
+
logger_module = importlib.import_module("src.utils.logger_utils")
|
|
873
|
+
except ModuleNotFoundError as exc:
|
|
874
|
+
missing = str(exc.name or "legacy dependency").split(".")[0]
|
|
875
|
+
raise WorkerFailure(
|
|
876
|
+
"LEGACY_DEPENDENCY_MISSING",
|
|
877
|
+
f"pinned LLMCompiler dependency is unavailable: {missing}",
|
|
878
|
+
) from exc
|
|
879
|
+
except Exception as exc:
|
|
880
|
+
raise WorkerFailure(
|
|
881
|
+
"UPSTREAM_IMPORT_FAILURE",
|
|
882
|
+
f"pinned LLMCompiler import failed: {type(exc).__name__}",
|
|
883
|
+
) from exc
|
|
884
|
+
modules = (task_module, tools_module, planner_module, compiler_module, logger_module)
|
|
885
|
+
if not all(_module_in_checkout(module, checkout) for module in modules):
|
|
886
|
+
raise WorkerFailure("UPSTREAM_IMPORT_FAILURE", "upstream module escaped pinned checkout")
|
|
887
|
+
enable_logging = getattr(logger_module, "enable_logging", None)
|
|
888
|
+
if callable(enable_logging):
|
|
889
|
+
enable_logging(False)
|
|
890
|
+
return LoadedUpstream(
|
|
891
|
+
planner=planner_module,
|
|
892
|
+
task_fetching=task_module,
|
|
893
|
+
compiler=compiler_module,
|
|
894
|
+
tools=tools_module,
|
|
895
|
+
logger=logger_module,
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _patch_model_type_gates(upstream: LoadedUpstream) -> None:
|
|
900
|
+
class _NeverChatModel:
|
|
901
|
+
pass
|
|
902
|
+
|
|
903
|
+
upstream.planner.BaseChatModel = _NeverChatModel
|
|
904
|
+
upstream.planner.BaseLLM = _ReplayModel
|
|
905
|
+
upstream.compiler.BaseChatModel = _NeverChatModel
|
|
906
|
+
upstream.compiler.BaseLLM = _ReplayModel
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def _build_tools(
|
|
910
|
+
upstream: LoadedUpstream,
|
|
911
|
+
specs: Sequence[Mapping[str, Any]],
|
|
912
|
+
replay: ToolReplay,
|
|
913
|
+
) -> list[Any]:
|
|
914
|
+
tool_type = getattr(upstream.tools, "Tool", None)
|
|
915
|
+
if tool_type is None:
|
|
916
|
+
raise WorkerFailure("UPSTREAM_API_MISMATCH", "upstream Tool is unavailable")
|
|
917
|
+
result: list[Any] = []
|
|
918
|
+
for spec in specs:
|
|
919
|
+
name = str(spec["name"])
|
|
920
|
+
|
|
921
|
+
async def invoke(*arguments: Any, _name: str = name) -> Any:
|
|
922
|
+
return await replay.invoke(_name, arguments)
|
|
923
|
+
|
|
924
|
+
try:
|
|
925
|
+
result.append(tool_type(name=name, func=invoke, description=str(spec["description"])))
|
|
926
|
+
except Exception as exc:
|
|
927
|
+
raise WorkerFailure(
|
|
928
|
+
"UPSTREAM_API_MISMATCH",
|
|
929
|
+
f"upstream Tool construction failed: {type(exc).__name__}",
|
|
930
|
+
) from exc
|
|
931
|
+
return result
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
@dataclass(slots=True)
|
|
935
|
+
class TraceRecorder:
|
|
936
|
+
allowed_tools: frozenset[str]
|
|
937
|
+
max_tasks: int
|
|
938
|
+
concurrency: int
|
|
939
|
+
argument_order: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
|
|
940
|
+
tasks: dict[int, Any] = field(default_factory=dict)
|
|
941
|
+
scheduled_ids: set[int] = field(default_factory=set)
|
|
942
|
+
dependency_graph: dict[int, set[int]] = field(default_factory=dict)
|
|
943
|
+
events: list[dict[str, Any]] = field(default_factory=list)
|
|
944
|
+
failure: WorkerFailure | None = None
|
|
945
|
+
active_tasks: set[asyncio.Task[Any]] = field(default_factory=set)
|
|
946
|
+
planner_tasks: set[asyncio.Task[Any]] = field(default_factory=set)
|
|
947
|
+
|
|
948
|
+
def register_batch(self, tasks: Mapping[Any, Any]) -> None:
|
|
949
|
+
"""Validate one planner batch atomically before the scheduler can see it."""
|
|
950
|
+
|
|
951
|
+
if not isinstance(tasks, Mapping):
|
|
952
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "upstream task batch is malformed")
|
|
953
|
+
normalized: list[tuple[int, Any, bool, set[int]]] = []
|
|
954
|
+
batch_ids: set[int] = set()
|
|
955
|
+
executable_count = 0
|
|
956
|
+
for key, task in tasks.items():
|
|
957
|
+
task_id = getattr(task, "idx", None)
|
|
958
|
+
name = getattr(task, "name", None)
|
|
959
|
+
dependencies = getattr(task, "dependencies", None)
|
|
960
|
+
is_join = bool(getattr(task, "is_join", False))
|
|
961
|
+
if (
|
|
962
|
+
isinstance(task_id, bool)
|
|
963
|
+
or not isinstance(task_id, int)
|
|
964
|
+
or task_id < 1
|
|
965
|
+
or isinstance(key, bool)
|
|
966
|
+
or not isinstance(key, int)
|
|
967
|
+
or key != task_id
|
|
968
|
+
or not isinstance(name, str)
|
|
969
|
+
or (is_join and name != "join")
|
|
970
|
+
or (not is_join and name not in self.allowed_tools)
|
|
971
|
+
or isinstance(dependencies, (str, bytes, Mapping))
|
|
972
|
+
or not isinstance(dependencies, (Sequence, set, frozenset))
|
|
973
|
+
):
|
|
974
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "upstream task is malformed")
|
|
975
|
+
dependency_tuple = tuple(dependencies)
|
|
976
|
+
if any(
|
|
977
|
+
isinstance(dependency, bool) or not isinstance(dependency, int) or dependency < 1
|
|
978
|
+
for dependency in dependency_tuple
|
|
979
|
+
):
|
|
980
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "upstream dependency is malformed")
|
|
981
|
+
dependency_ids = set(dependency_tuple)
|
|
982
|
+
if len(dependency_ids) != len(dependency_tuple) or task_id in dependency_ids:
|
|
983
|
+
raise WorkerFailure(
|
|
984
|
+
"UPSTREAM_PLAN_INVALID",
|
|
985
|
+
"upstream task has duplicate or self dependencies",
|
|
986
|
+
)
|
|
987
|
+
if task_id in self.scheduled_ids or task_id in batch_ids:
|
|
988
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "upstream task IDs are duplicated")
|
|
989
|
+
batch_ids.add(task_id)
|
|
990
|
+
executable_count += int(not is_join)
|
|
991
|
+
normalized.append((task_id, task, is_join, dependency_ids))
|
|
992
|
+
|
|
993
|
+
if len(self.tasks) + executable_count > self.max_tasks:
|
|
994
|
+
raise WorkerFailure("TASK_LIMIT_EXCEEDED", "upstream plan exceeded max_tasks")
|
|
995
|
+
known_ids = self.scheduled_ids | batch_ids
|
|
996
|
+
if any(not dependencies <= known_ids for _, _, _, dependencies in normalized):
|
|
997
|
+
raise WorkerFailure(
|
|
998
|
+
"UPSTREAM_PLAN_INVALID",
|
|
999
|
+
"upstream task references an unknown dependency",
|
|
1000
|
+
)
|
|
1001
|
+
candidate_graph = {
|
|
1002
|
+
node: set(dependencies) for node, dependencies in self.dependency_graph.items()
|
|
1003
|
+
}
|
|
1004
|
+
candidate_graph.update(
|
|
1005
|
+
{task_id: set(dependencies) for task_id, _, _, dependencies in normalized}
|
|
1006
|
+
)
|
|
1007
|
+
visiting: set[int] = set()
|
|
1008
|
+
visited: set[int] = set()
|
|
1009
|
+
|
|
1010
|
+
def visit(node: int) -> None:
|
|
1011
|
+
if node in visiting:
|
|
1012
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "upstream task graph has a cycle")
|
|
1013
|
+
if node in visited:
|
|
1014
|
+
return
|
|
1015
|
+
visiting.add(node)
|
|
1016
|
+
for dependency in candidate_graph[node]:
|
|
1017
|
+
visit(dependency)
|
|
1018
|
+
visiting.remove(node)
|
|
1019
|
+
visited.add(node)
|
|
1020
|
+
|
|
1021
|
+
for task_id in batch_ids:
|
|
1022
|
+
visit(task_id)
|
|
1023
|
+
|
|
1024
|
+
self.scheduled_ids.update(batch_ids)
|
|
1025
|
+
self.dependency_graph = candidate_graph
|
|
1026
|
+
for task_id, task, is_join, _ in normalized:
|
|
1027
|
+
if not is_join:
|
|
1028
|
+
self.tasks[task_id] = task
|
|
1029
|
+
self.emit(task_id, "planned")
|
|
1030
|
+
|
|
1031
|
+
def emit(self, task_id: int, state: str) -> None:
|
|
1032
|
+
self.events.append({"index": len(self.events), "task_id": task_id, "state": state})
|
|
1033
|
+
|
|
1034
|
+
def fail(self, task_id: int, exc: BaseException) -> None:
|
|
1035
|
+
if self.failure is None:
|
|
1036
|
+
self.failure = (
|
|
1037
|
+
exc
|
|
1038
|
+
if isinstance(exc, WorkerFailure)
|
|
1039
|
+
else WorkerFailure(
|
|
1040
|
+
"TOOL_EXECUTION_FAILURE",
|
|
1041
|
+
f"allowlisted tool failed: {type(exc).__name__}",
|
|
1042
|
+
consumed=True,
|
|
1043
|
+
)
|
|
1044
|
+
)
|
|
1045
|
+
self.emit(task_id, "failed")
|
|
1046
|
+
|
|
1047
|
+
def fail_planner(self, exc: BaseException) -> WorkerFailure:
|
|
1048
|
+
if self.failure is None:
|
|
1049
|
+
self.failure = (
|
|
1050
|
+
exc
|
|
1051
|
+
if isinstance(exc, WorkerFailure)
|
|
1052
|
+
else WorkerFailure(
|
|
1053
|
+
"PLANNER_STREAM_FAILURE",
|
|
1054
|
+
f"upstream streaming planner failed: {type(exc).__name__}",
|
|
1055
|
+
)
|
|
1056
|
+
)
|
|
1057
|
+
return self.failure
|
|
1058
|
+
|
|
1059
|
+
def task_rows(self, specs: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
1060
|
+
properties = {
|
|
1061
|
+
str(spec["name"]): self.argument_order.get(str(spec["name"]), tuple(spec["input_schema"].get("properties", {}).keys()))
|
|
1062
|
+
if isinstance(spec["input_schema"].get("properties", {}), Mapping)
|
|
1063
|
+
else ()
|
|
1064
|
+
for spec in specs
|
|
1065
|
+
}
|
|
1066
|
+
rows: list[dict[str, Any]] = []
|
|
1067
|
+
for task_id in sorted(self.tasks):
|
|
1068
|
+
task = self.tasks[task_id]
|
|
1069
|
+
arguments = list(getattr(task, "args", ()))
|
|
1070
|
+
names = properties.get(str(task.name), ())
|
|
1071
|
+
if len(arguments) == 1 and isinstance(arguments[0], Mapping):
|
|
1072
|
+
mapped_arguments = dict(arguments[0])
|
|
1073
|
+
elif len(names) == len(arguments) or (str(task.name) in self.argument_order and len(arguments) <= len(names)):
|
|
1074
|
+
mapped_arguments = dict(zip(names[:len(arguments)], arguments, strict=True))
|
|
1075
|
+
else:
|
|
1076
|
+
mapped_arguments = {"__args__": arguments}
|
|
1077
|
+
try:
|
|
1078
|
+
_canonical_json(mapped_arguments)
|
|
1079
|
+
except (TypeError, ValueError) as exc:
|
|
1080
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "task arguments are not JSON") from exc
|
|
1081
|
+
rows.append(
|
|
1082
|
+
{
|
|
1083
|
+
"task_id": task_id,
|
|
1084
|
+
"tool_name": str(task.name),
|
|
1085
|
+
"arguments": mapped_arguments,
|
|
1086
|
+
"dependencies": sorted(getattr(task, "dependencies", ())),
|
|
1087
|
+
}
|
|
1088
|
+
)
|
|
1089
|
+
if not rows:
|
|
1090
|
+
raise WorkerFailure(
|
|
1091
|
+
"EMPTY_EXECUTABLE_PLAN", "plan contains only join/no executable task"
|
|
1092
|
+
)
|
|
1093
|
+
return rows
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def _traced_task_fetching_unit(base: type[Any], recorder: TraceRecorder) -> type[Any]:
|
|
1097
|
+
# Upstream creates a new TFU for each replan, but its parser uses global task
|
|
1098
|
+
# IDs (and join depends on every preceding ID). Make completed observations
|
|
1099
|
+
# available for dependency lookup without scheduling or iterating them again.
|
|
1100
|
+
# These maps belong to one _run_full invocation, never to another request.
|
|
1101
|
+
completed_tasks: dict[int, Any] = {}
|
|
1102
|
+
completed_events: dict[int, asyncio.Event] = {}
|
|
1103
|
+
|
|
1104
|
+
class RoundTasks(dict[int, Any]):
|
|
1105
|
+
def __missing__(self, task_id: int) -> Any:
|
|
1106
|
+
return completed_tasks[task_id]
|
|
1107
|
+
|
|
1108
|
+
class RoundEvents(dict[int, asyncio.Event]):
|
|
1109
|
+
def __missing__(self, task_id: int) -> asyncio.Event:
|
|
1110
|
+
return completed_events[task_id]
|
|
1111
|
+
|
|
1112
|
+
class TracedTaskFetchingUnit(base):
|
|
1113
|
+
def __init__(self) -> None:
|
|
1114
|
+
super().__init__()
|
|
1115
|
+
self.tasks = RoundTasks(self.tasks)
|
|
1116
|
+
self.tasks_done = RoundEvents(self.tasks_done)
|
|
1117
|
+
self._agentinputlab_semaphore = asyncio.Semaphore(recorder.concurrency)
|
|
1118
|
+
|
|
1119
|
+
def set_tasks(self, tasks: Mapping[Any, Any]) -> None:
|
|
1120
|
+
recorder.register_batch(tasks)
|
|
1121
|
+
super().set_tasks(tasks)
|
|
1122
|
+
|
|
1123
|
+
async def _run_task(self, task: Any) -> None:
|
|
1124
|
+
current = asyncio.current_task()
|
|
1125
|
+
if current is not None:
|
|
1126
|
+
recorder.active_tasks.add(current)
|
|
1127
|
+
if bool(getattr(task, "is_join", False)):
|
|
1128
|
+
await super()._run_task(task)
|
|
1129
|
+
completed_tasks[task.idx] = task
|
|
1130
|
+
completed_events[task.idx] = self.tasks_done[task.idx]
|
|
1131
|
+
return
|
|
1132
|
+
task_id = int(task.idx)
|
|
1133
|
+
recorder.emit(task_id, "ready")
|
|
1134
|
+
async with self._agentinputlab_semaphore:
|
|
1135
|
+
if recorder.failure is not None:
|
|
1136
|
+
return
|
|
1137
|
+
recorder.emit(task_id, "running")
|
|
1138
|
+
try:
|
|
1139
|
+
await super()._run_task(task)
|
|
1140
|
+
except asyncio.CancelledError:
|
|
1141
|
+
raise
|
|
1142
|
+
except BaseException as exc:
|
|
1143
|
+
recorder.fail(task_id, exc)
|
|
1144
|
+
for event in tuple(self.tasks_done.values()):
|
|
1145
|
+
event.set()
|
|
1146
|
+
self.remaining_tasks.clear()
|
|
1147
|
+
return
|
|
1148
|
+
recorder.emit(task_id, "completed")
|
|
1149
|
+
completed_tasks[task_id] = task
|
|
1150
|
+
completed_events[task_id] = self.tasks_done[task_id]
|
|
1151
|
+
|
|
1152
|
+
TracedTaskFetchingUnit.__name__ = "AgentInputLabTracedTaskFetchingUnit"
|
|
1153
|
+
return TracedTaskFetchingUnit
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
async def _cancel_and_await(tasks: Sequence[asyncio.Task[Any]] | set[asyncio.Task[Any]]) -> None:
|
|
1157
|
+
current = asyncio.current_task()
|
|
1158
|
+
selected = tuple(dict.fromkeys(task for task in tasks if task is not current))
|
|
1159
|
+
for task in selected:
|
|
1160
|
+
if not task.done():
|
|
1161
|
+
task.cancel()
|
|
1162
|
+
if selected:
|
|
1163
|
+
await asyncio.gather(*selected, return_exceptions=True)
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
async def _settle_planner_tasks(recorder: TraceRecorder) -> None:
|
|
1167
|
+
tasks = tuple(task for task in recorder.planner_tasks if task is not asyncio.current_task())
|
|
1168
|
+
if not tasks:
|
|
1169
|
+
return
|
|
1170
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
1171
|
+
for result in results:
|
|
1172
|
+
if isinstance(result, asyncio.CancelledError):
|
|
1173
|
+
recorder.fail_planner(
|
|
1174
|
+
WorkerFailure("PLANNER_STREAM_FAILURE", "upstream streaming planner was cancelled")
|
|
1175
|
+
)
|
|
1176
|
+
elif isinstance(result, BaseException):
|
|
1177
|
+
recorder.fail_planner(result)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
def _planner(
|
|
1181
|
+
upstream: LoadedUpstream,
|
|
1182
|
+
model: _ReplayModel,
|
|
1183
|
+
prompts: Mapping[str, Any],
|
|
1184
|
+
tools: Sequence[Any],
|
|
1185
|
+
) -> Any:
|
|
1186
|
+
planner_type = getattr(upstream.planner, "Planner", None)
|
|
1187
|
+
if planner_type is None:
|
|
1188
|
+
raise WorkerFailure("UPSTREAM_API_MISMATCH", "upstream Planner is unavailable")
|
|
1189
|
+
return planner_type(
|
|
1190
|
+
llm=model,
|
|
1191
|
+
example_prompt=prompts["planner_example_prompt"],
|
|
1192
|
+
example_prompt_replan=prompts["planner_example_prompt_replan"],
|
|
1193
|
+
tools=tools,
|
|
1194
|
+
stop=list(prompts["planner_stop"]),
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
async def _run_planner_component(
|
|
1199
|
+
request: ValidatedRequest,
|
|
1200
|
+
upstream: LoadedUpstream,
|
|
1201
|
+
model: _ReplayModel,
|
|
1202
|
+
prompts: Mapping[str, Any],
|
|
1203
|
+
tools: Sequence[Any],
|
|
1204
|
+
) -> tuple[list[Any], list[dict[str, Any]], int, str]:
|
|
1205
|
+
planner = _planner(upstream, model, prompts, tools)
|
|
1206
|
+
inputs = {"input": request.query}
|
|
1207
|
+
recorder = TraceRecorder(
|
|
1208
|
+
allowed_tools=frozenset(str(spec["name"]) for spec in request.tool_specs),
|
|
1209
|
+
max_tasks=request.controls.max_tasks,
|
|
1210
|
+
concurrency=request.controls.concurrency,
|
|
1211
|
+
argument_order=request.tool_argument_order,
|
|
1212
|
+
)
|
|
1213
|
+
if request.controls.streaming:
|
|
1214
|
+
queue: asyncio.Queue[Any] = asyncio.Queue()
|
|
1215
|
+
|
|
1216
|
+
async def produce() -> None:
|
|
1217
|
+
try:
|
|
1218
|
+
await planner.aplan(inputs=inputs, task_queue=queue, is_replan=False)
|
|
1219
|
+
except asyncio.CancelledError:
|
|
1220
|
+
raise
|
|
1221
|
+
except WorkerFailure:
|
|
1222
|
+
raise
|
|
1223
|
+
except Exception as exc:
|
|
1224
|
+
raise WorkerFailure(
|
|
1225
|
+
"PLANNER_STREAM_FAILURE",
|
|
1226
|
+
f"upstream streaming planner failed: {type(exc).__name__}",
|
|
1227
|
+
) from exc
|
|
1228
|
+
finally:
|
|
1229
|
+
queue.put_nowait(None)
|
|
1230
|
+
|
|
1231
|
+
planner_task = asyncio.create_task(produce())
|
|
1232
|
+
recorder.planner_tasks.add(planner_task)
|
|
1233
|
+
try:
|
|
1234
|
+
while True:
|
|
1235
|
+
item = await queue.get()
|
|
1236
|
+
if item is None:
|
|
1237
|
+
break
|
|
1238
|
+
task_id = getattr(item, "idx", None)
|
|
1239
|
+
recorder.register_batch({task_id: item})
|
|
1240
|
+
await _settle_planner_tasks(recorder)
|
|
1241
|
+
if recorder.failure is not None:
|
|
1242
|
+
raise recorder.failure
|
|
1243
|
+
finally:
|
|
1244
|
+
await _cancel_and_await(recorder.planner_tasks)
|
|
1245
|
+
else:
|
|
1246
|
+
planned = await planner.plan(inputs=inputs, is_replan=False)
|
|
1247
|
+
if not isinstance(planned, Mapping):
|
|
1248
|
+
raise WorkerFailure("UPSTREAM_PLAN_INVALID", "Planner.plan did not return a task map")
|
|
1249
|
+
recorder.register_batch(planned)
|
|
1250
|
+
rows = recorder.task_rows(request.tool_specs)
|
|
1251
|
+
return rows, [], 0, ""
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
async def _run_full(
|
|
1255
|
+
request: ValidatedRequest,
|
|
1256
|
+
upstream: LoadedUpstream,
|
|
1257
|
+
model: _ReplayModel,
|
|
1258
|
+
prompts: Mapping[str, Any],
|
|
1259
|
+
tools: Sequence[Any],
|
|
1260
|
+
) -> tuple[list[Any], list[dict[str, Any]], int, str]:
|
|
1261
|
+
compiler_type = getattr(upstream.compiler, "LLMCompiler", None)
|
|
1262
|
+
base_tfu = getattr(upstream.task_fetching, "TaskFetchingUnit", None)
|
|
1263
|
+
if compiler_type is None or base_tfu is None:
|
|
1264
|
+
raise WorkerFailure("UPSTREAM_API_MISMATCH", "upstream full runtime is unavailable")
|
|
1265
|
+
recorder = TraceRecorder(
|
|
1266
|
+
allowed_tools=frozenset(str(spec["name"]) for spec in request.tool_specs),
|
|
1267
|
+
max_tasks=request.controls.max_tasks,
|
|
1268
|
+
concurrency=request.controls.concurrency,
|
|
1269
|
+
argument_order=request.tool_argument_order,
|
|
1270
|
+
)
|
|
1271
|
+
traced_tfu = _traced_task_fetching_unit(base_tfu, recorder)
|
|
1272
|
+
original_tfu = getattr(upstream.compiler, "TaskFetchingUnit", None)
|
|
1273
|
+
upstream.compiler.TaskFetchingUnit = traced_tfu
|
|
1274
|
+
try:
|
|
1275
|
+
compiler = compiler_type(
|
|
1276
|
+
tools=tools,
|
|
1277
|
+
planner_llm=model,
|
|
1278
|
+
planner_example_prompt=prompts["planner_example_prompt"],
|
|
1279
|
+
planner_example_prompt_replan=prompts["planner_example_prompt_replan"],
|
|
1280
|
+
planner_stop=list(prompts["planner_stop"]),
|
|
1281
|
+
planner_stream=request.controls.streaming,
|
|
1282
|
+
agent_llm=model,
|
|
1283
|
+
joinner_prompt=prompts["joinner_prompt"],
|
|
1284
|
+
joinner_prompt_final=prompts["joinner_prompt_final"],
|
|
1285
|
+
max_replans=request.controls.max_replans + 1,
|
|
1286
|
+
benchmark=False,
|
|
1287
|
+
)
|
|
1288
|
+
original_aplan = compiler.planner.aplan
|
|
1289
|
+
|
|
1290
|
+
async def guarded_aplan(*args: Any, **kwargs: Any) -> Any:
|
|
1291
|
+
current = asyncio.current_task()
|
|
1292
|
+
if current is not None:
|
|
1293
|
+
recorder.planner_tasks.add(current)
|
|
1294
|
+
task_queue = kwargs.get("task_queue")
|
|
1295
|
+
if task_queue is None and len(args) > 1:
|
|
1296
|
+
task_queue = args[1]
|
|
1297
|
+
try:
|
|
1298
|
+
return await original_aplan(*args, **kwargs)
|
|
1299
|
+
except asyncio.CancelledError:
|
|
1300
|
+
raise
|
|
1301
|
+
except BaseException as exc:
|
|
1302
|
+
raise recorder.fail_planner(exc) from exc
|
|
1303
|
+
finally:
|
|
1304
|
+
put_nowait = getattr(task_queue, "put_nowait", None)
|
|
1305
|
+
if callable(put_nowait):
|
|
1306
|
+
put_nowait(None)
|
|
1307
|
+
|
|
1308
|
+
compiler.planner.aplan = guarded_aplan
|
|
1309
|
+
original_join = compiler.join
|
|
1310
|
+
|
|
1311
|
+
async def guarded_join(*args: Any, **kwargs: Any) -> Any:
|
|
1312
|
+
await _settle_planner_tasks(recorder)
|
|
1313
|
+
if recorder.failure is not None:
|
|
1314
|
+
raise recorder.failure
|
|
1315
|
+
return await original_join(*args, **kwargs)
|
|
1316
|
+
|
|
1317
|
+
object.__setattr__(compiler, "join", guarded_join)
|
|
1318
|
+
result = await compiler._acall({"input": request.query})
|
|
1319
|
+
finally:
|
|
1320
|
+
upstream.compiler.TaskFetchingUnit = original_tfu
|
|
1321
|
+
await _cancel_and_await(recorder.planner_tasks | recorder.active_tasks)
|
|
1322
|
+
if recorder.failure is not None:
|
|
1323
|
+
raise recorder.failure
|
|
1324
|
+
if not isinstance(result, Mapping) or not isinstance(result.get("output"), str):
|
|
1325
|
+
raise WorkerFailure("UPSTREAM_OUTPUT_INVALID", "LLMCompiler returned an invalid output")
|
|
1326
|
+
answer = result["output"].strip()
|
|
1327
|
+
if not answer:
|
|
1328
|
+
raise WorkerFailure("UPSTREAM_OUTPUT_INVALID", "LLMCompiler returned an empty answer")
|
|
1329
|
+
replans = max(0, len(model.planner_responses) - 1)
|
|
1330
|
+
if replans > request.controls.max_replans:
|
|
1331
|
+
raise WorkerFailure("REPLAN_LIMIT_EXCEEDED", "LLMCompiler exceeded max_replans")
|
|
1332
|
+
return recorder.task_rows(request.tool_specs), recorder.events, replans, answer
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
async def execute_request(
|
|
1336
|
+
request: ValidatedRequest,
|
|
1337
|
+
upstream: LoadedUpstream,
|
|
1338
|
+
model_bundle: ReplayBundle,
|
|
1339
|
+
tool_replay: ToolReplay,
|
|
1340
|
+
*,
|
|
1341
|
+
runtime_model: _ReplayModel | None = None,
|
|
1342
|
+
) -> Mapping[str, Any]:
|
|
1343
|
+
runtime: dict[str, _ReplayModel] = {}
|
|
1344
|
+
|
|
1345
|
+
async def run() -> Mapping[str, Any]:
|
|
1346
|
+
_patch_model_type_gates(upstream)
|
|
1347
|
+
model = runtime_model if runtime_model is not None else _ReplayModel(model_bundle.entries)
|
|
1348
|
+
runtime["model"] = model
|
|
1349
|
+
tools = _build_tools(upstream, request.tool_specs, tool_replay)
|
|
1350
|
+
discarded = _DiscardTextIO()
|
|
1351
|
+
try:
|
|
1352
|
+
with contextlib.redirect_stdout(discarded):
|
|
1353
|
+
if request.execution_mode == "planner_component":
|
|
1354
|
+
tasks, events, replans, final_answer = await _run_planner_component(
|
|
1355
|
+
request, upstream, model, model_bundle.prompts, tools
|
|
1356
|
+
)
|
|
1357
|
+
termination = "planner_complete"
|
|
1358
|
+
else:
|
|
1359
|
+
tasks, events, replans, final_answer = await _run_full(
|
|
1360
|
+
request, upstream, model, model_bundle.prompts, tools
|
|
1361
|
+
)
|
|
1362
|
+
termination = "finish"
|
|
1363
|
+
model.assert_exhausted()
|
|
1364
|
+
except WorkerFailure as exc:
|
|
1365
|
+
if model.consumed or tool_replay.calls:
|
|
1366
|
+
exc.usage = model.usage(tool_calls=tool_replay.calls)
|
|
1367
|
+
exc.consumed = True
|
|
1368
|
+
raise
|
|
1369
|
+
except Exception as exc:
|
|
1370
|
+
raise WorkerFailure(
|
|
1371
|
+
"UPSTREAM_RUNTIME_FAILURE",
|
|
1372
|
+
f"pinned LLMCompiler runtime failed: {type(exc).__name__}",
|
|
1373
|
+
consumed=bool(model.consumed or tool_replay.calls),
|
|
1374
|
+
usage=model.usage(tool_calls=tool_replay.calls),
|
|
1375
|
+
) from exc
|
|
1376
|
+
raw_plan_hash = _canonical_hash(model.planner_responses)
|
|
1377
|
+
return {
|
|
1378
|
+
"status": "ok",
|
|
1379
|
+
"request_id": request.request_id,
|
|
1380
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
1381
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
1382
|
+
"payload": {
|
|
1383
|
+
"tasks": tasks,
|
|
1384
|
+
"scheduler_events": events,
|
|
1385
|
+
"replans": replans,
|
|
1386
|
+
"streaming": request.controls.streaming,
|
|
1387
|
+
"raw_plan_hash": raw_plan_hash,
|
|
1388
|
+
"termination": termination,
|
|
1389
|
+
"final_answer": final_answer,
|
|
1390
|
+
},
|
|
1391
|
+
"usage": model.usage(tool_calls=tool_replay.calls),
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
try:
|
|
1395
|
+
return await asyncio.wait_for(run(), timeout=request.controls.timeout_seconds)
|
|
1396
|
+
# Python 3.10 uses a distinct asyncio timeout exception; 3.11+ aliases it.
|
|
1397
|
+
except asyncio.TimeoutError as exc:
|
|
1398
|
+
model = runtime.get("model")
|
|
1399
|
+
usage = (
|
|
1400
|
+
model.usage(tool_calls=tool_replay.calls)
|
|
1401
|
+
if model is not None
|
|
1402
|
+
else {
|
|
1403
|
+
"model_calls": 0,
|
|
1404
|
+
"retrieval_calls": 0,
|
|
1405
|
+
"tool_calls": tool_replay.calls,
|
|
1406
|
+
"input_tokens": 0,
|
|
1407
|
+
"output_tokens": 0,
|
|
1408
|
+
}
|
|
1409
|
+
)
|
|
1410
|
+
raise WorkerFailure(
|
|
1411
|
+
"RUN_TIMEOUT",
|
|
1412
|
+
"LLMCompiler exceeded controls.timeout_seconds",
|
|
1413
|
+
consumed=bool(usage["model_calls"] or usage["tool_calls"]),
|
|
1414
|
+
usage=usage,
|
|
1415
|
+
) from exc
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
def _failure_response(exc: WorkerFailure, *, request_id: str = "") -> Mapping[str, Any]:
|
|
1419
|
+
response: dict[str, Any] = {
|
|
1420
|
+
"status": "error",
|
|
1421
|
+
"failure_code": exc.code,
|
|
1422
|
+
"message": exc.public_message,
|
|
1423
|
+
"consumed": exc.consumed,
|
|
1424
|
+
}
|
|
1425
|
+
if request_id:
|
|
1426
|
+
response["request_id"] = request_id
|
|
1427
|
+
if exc.usage is not None:
|
|
1428
|
+
response["usage"] = exc.usage
|
|
1429
|
+
return response
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def process_request(
|
|
1433
|
+
raw_request: Any,
|
|
1434
|
+
*,
|
|
1435
|
+
checkout: Path,
|
|
1436
|
+
model_replay_path: Path,
|
|
1437
|
+
tool_replay_path: Path,
|
|
1438
|
+
upstream: LoadedUpstream | None = None,
|
|
1439
|
+
) -> Mapping[str, Any]:
|
|
1440
|
+
verify_checkout(checkout)
|
|
1441
|
+
request = validate_request(raw_request)
|
|
1442
|
+
model_bundle = load_model_replay(model_replay_path, request)
|
|
1443
|
+
tool_replay = load_tool_replay(tool_replay_path, request)
|
|
1444
|
+
loaded = upstream if upstream is not None else import_upstream(checkout)
|
|
1445
|
+
return asyncio.run(execute_request(request, loaded, model_bundle, tool_replay))
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
def _read_request_line() -> Any:
|
|
1449
|
+
raw = sys.stdin.buffer.readline(_MAX_MANIFEST_BYTES + 1)
|
|
1450
|
+
if len(raw) > _MAX_MANIFEST_BYTES:
|
|
1451
|
+
raise WorkerFailure("REQUEST_TOO_LARGE", "worker request exceeds the byte limit")
|
|
1452
|
+
if not raw:
|
|
1453
|
+
raise WorkerFailure("SCHEMA_INVALID", "worker requires one JSON line")
|
|
1454
|
+
if sys.stdin.buffer.read(1):
|
|
1455
|
+
raise WorkerFailure("SCHEMA_INVALID", "worker accepts exactly one JSON line")
|
|
1456
|
+
try:
|
|
1457
|
+
return json.loads(raw)
|
|
1458
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
1459
|
+
raise WorkerFailure("SCHEMA_INVALID", "worker request is not valid UTF-8 JSON") from exc
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
def _parser() -> argparse.ArgumentParser:
|
|
1463
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
1464
|
+
parser.add_argument("--checkout", type=Path, required=True)
|
|
1465
|
+
parser.add_argument("--model-replay", type=Path)
|
|
1466
|
+
parser.add_argument("--tool-replay", type=Path)
|
|
1467
|
+
parser.add_argument("--smoke-import", action="store_true")
|
|
1468
|
+
return parser
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
1472
|
+
args = _parser().parse_args(argv)
|
|
1473
|
+
request_id = ""
|
|
1474
|
+
try:
|
|
1475
|
+
verify_checkout(args.checkout)
|
|
1476
|
+
if args.smoke_import:
|
|
1477
|
+
import_upstream(args.checkout)
|
|
1478
|
+
response: Mapping[str, Any] = {
|
|
1479
|
+
"status": "ok",
|
|
1480
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
1481
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
1482
|
+
"smoke_import": True,
|
|
1483
|
+
}
|
|
1484
|
+
else:
|
|
1485
|
+
if args.model_replay is None or args.tool_replay is None:
|
|
1486
|
+
raise WorkerFailure(
|
|
1487
|
+
"CONFIGURATION_MISSING",
|
|
1488
|
+
"normal execution requires --model-replay and --tool-replay",
|
|
1489
|
+
)
|
|
1490
|
+
raw_request = _read_request_line()
|
|
1491
|
+
if isinstance(raw_request, Mapping) and isinstance(raw_request.get("request_id"), str):
|
|
1492
|
+
request_id = str(raw_request["request_id"])
|
|
1493
|
+
request = validate_request(raw_request)
|
|
1494
|
+
model_bundle = load_model_replay(args.model_replay, request)
|
|
1495
|
+
tool_replay = load_tool_replay(args.tool_replay, request)
|
|
1496
|
+
upstream = import_upstream(args.checkout)
|
|
1497
|
+
response = asyncio.run(execute_request(request, upstream, model_bundle, tool_replay))
|
|
1498
|
+
except WorkerFailure as exc:
|
|
1499
|
+
response = _failure_response(exc, request_id=request_id)
|
|
1500
|
+
except Exception as exc: # pragma: no cover - last-resort JSONL containment
|
|
1501
|
+
response = _failure_response(
|
|
1502
|
+
WorkerFailure(
|
|
1503
|
+
"WORKER_INTERNAL_FAILURE",
|
|
1504
|
+
f"worker failed closed: {type(exc).__name__}",
|
|
1505
|
+
),
|
|
1506
|
+
request_id=request_id,
|
|
1507
|
+
)
|
|
1508
|
+
sys.stdout.write(_canonical_json(response) + "\n")
|
|
1509
|
+
sys.stdout.flush()
|
|
1510
|
+
return 0 if response.get("status") == "ok" else 2
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
if __name__ == "__main__":
|
|
1514
|
+
raise SystemExit(main())
|