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
workers/ircot/driver.py
ADDED
|
@@ -0,0 +1,1373 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pinned JSONL driver for the original IRCoT controller loop."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import contextlib
|
|
7
|
+
import copy
|
|
8
|
+
import difflib
|
|
9
|
+
import hashlib
|
|
10
|
+
import importlib
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import types
|
|
19
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, TextIO
|
|
23
|
+
|
|
24
|
+
UPSTREAM_COMMIT = "3c1820f698eea5eeddb4fba3c56b64c961e063e4"
|
|
25
|
+
PROTOCOL_VERSION = "agentinputlab.ircot.v1"
|
|
26
|
+
MANIFEST_SCHEMA = "agentinputlab.ircot.compatibility.v1"
|
|
27
|
+
UPSTREAM_ENV = "AGENTINPUTLAB_UPSTREAM_ROOT"
|
|
28
|
+
PROFILE_ENV = "AGENTINPUTLAB_IRCOT_PROFILE"
|
|
29
|
+
DEFAULT_MANIFEST = Path(__file__).with_name("compatibility_manifest.json")
|
|
30
|
+
DEFAULT_PROFILE = "fixture.tiny.frozen.v1"
|
|
31
|
+
MAX_REQUEST_BYTES = 2 * 1024 * 1024
|
|
32
|
+
_REQUEST_FIELDS = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"request_id",
|
|
35
|
+
"state_hash",
|
|
36
|
+
"task_id",
|
|
37
|
+
"question",
|
|
38
|
+
"controls",
|
|
39
|
+
"generator_fingerprint",
|
|
40
|
+
"retriever_fingerprint",
|
|
41
|
+
"corpus_revision",
|
|
42
|
+
"index_revision",
|
|
43
|
+
"prompt_hash",
|
|
44
|
+
"upstream_commit",
|
|
45
|
+
"protocol_version",
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
_CONTROL_FIELDS = frozenset(
|
|
49
|
+
{
|
|
50
|
+
"per_step_top_k",
|
|
51
|
+
"max_steps",
|
|
52
|
+
"max_total_documents",
|
|
53
|
+
"max_output_tokens",
|
|
54
|
+
"deduplicate",
|
|
55
|
+
"reset_policy",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
_SECRET_KEY_PARTS = (
|
|
59
|
+
"api_key",
|
|
60
|
+
"apikey",
|
|
61
|
+
"authorization",
|
|
62
|
+
"credential",
|
|
63
|
+
"password",
|
|
64
|
+
"secret",
|
|
65
|
+
)
|
|
66
|
+
_SECRET_TOKEN_KEYS = frozenset(
|
|
67
|
+
{
|
|
68
|
+
"token",
|
|
69
|
+
"access_token",
|
|
70
|
+
"auth_token",
|
|
71
|
+
"bearer_token",
|
|
72
|
+
"refresh_token",
|
|
73
|
+
"session_token",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class DriverError(RuntimeError):
|
|
79
|
+
def __init__(self, code: str, message: str, *, pre_execution: bool = True) -> None:
|
|
80
|
+
super().__init__(message)
|
|
81
|
+
self.code = code
|
|
82
|
+
self.public_message = message
|
|
83
|
+
self.pre_execution = pre_execution
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True, slots=True)
|
|
87
|
+
class Controls:
|
|
88
|
+
per_step_top_k: int
|
|
89
|
+
max_steps: int
|
|
90
|
+
max_total_documents: int
|
|
91
|
+
max_output_tokens: int
|
|
92
|
+
deduplicate: bool
|
|
93
|
+
reset_policy: str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class Request:
|
|
98
|
+
request_id: str
|
|
99
|
+
state_hash: str
|
|
100
|
+
task_id: str
|
|
101
|
+
question: str
|
|
102
|
+
controls: Controls
|
|
103
|
+
generator_fingerprint: Mapping[str, Any]
|
|
104
|
+
retriever_fingerprint: Mapping[str, Any]
|
|
105
|
+
corpus_revision: str
|
|
106
|
+
index_revision: str
|
|
107
|
+
prompt_hash: str
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True, slots=True)
|
|
111
|
+
class Checkout:
|
|
112
|
+
root: Path
|
|
113
|
+
worker_root: Path
|
|
114
|
+
manifest: Mapping[str, Any]
|
|
115
|
+
manifest_sha256: str
|
|
116
|
+
profile_id: str
|
|
117
|
+
profile: Mapping[str, Any]
|
|
118
|
+
config_path: Path
|
|
119
|
+
prompt_path: Path
|
|
120
|
+
bindings_path: Path | None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(frozen=True, slots=True)
|
|
124
|
+
class Snapshot:
|
|
125
|
+
question: str
|
|
126
|
+
titles: tuple[str, ...]
|
|
127
|
+
paragraphs: tuple[str, ...]
|
|
128
|
+
generated_sentences: tuple[str, ...]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class ControllerEvent:
|
|
133
|
+
command: str
|
|
134
|
+
role: str
|
|
135
|
+
before: Snapshot
|
|
136
|
+
after: Snapshot
|
|
137
|
+
retrieval_rounds: tuple[Mapping[str, Any], ...]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class _BoundedCapture(io.TextIOBase):
|
|
141
|
+
def __init__(self, limit: int = 128 * 1024) -> None:
|
|
142
|
+
self.limit = limit
|
|
143
|
+
self.size = 0
|
|
144
|
+
self._digest = hashlib.sha256()
|
|
145
|
+
|
|
146
|
+
def writable(self) -> bool:
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
def write(self, value: str) -> int:
|
|
150
|
+
encoded = value.encode("utf-8", errors="replace")
|
|
151
|
+
self.size += len(encoded)
|
|
152
|
+
if self.size > self.limit:
|
|
153
|
+
raise DriverError(
|
|
154
|
+
"UPSTREAM_OUTPUT_LIMIT",
|
|
155
|
+
"upstream diagnostic output exceeded its limit",
|
|
156
|
+
pre_execution=False,
|
|
157
|
+
)
|
|
158
|
+
self._digest.update(encoded)
|
|
159
|
+
return len(value)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def hexdigest(self) -> str:
|
|
163
|
+
return self._digest.hexdigest()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class _FrozenResponse:
|
|
167
|
+
def __init__(self, payload: Mapping[str, Any]) -> None:
|
|
168
|
+
self._payload = copy.deepcopy(dict(payload))
|
|
169
|
+
self.ok = True
|
|
170
|
+
self.status_code = 200
|
|
171
|
+
|
|
172
|
+
def json(self) -> Mapping[str, Any]:
|
|
173
|
+
return copy.deepcopy(self._payload)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class _FrozenBindings:
|
|
177
|
+
def __init__(self, value: Mapping[str, Any], corpus_revision: str) -> None:
|
|
178
|
+
self.corpus_revision = corpus_revision
|
|
179
|
+
self.expected_question = _required_text(value, "question", "fixture bindings")
|
|
180
|
+
raw_generations = _mapping_sequence(
|
|
181
|
+
value.get("generator_outputs"),
|
|
182
|
+
"fixture generator_outputs",
|
|
183
|
+
)
|
|
184
|
+
self.generations = [dict(item) for item in raw_generations]
|
|
185
|
+
raw_rounds = _mapping_sequence(value.get("retrieval_rounds"), "fixture retrieval_rounds")
|
|
186
|
+
self.rounds = [dict(item) for item in raw_rounds]
|
|
187
|
+
self.generation_index = 0
|
|
188
|
+
self.retrieval_index = 0
|
|
189
|
+
self.input_tokens = 0
|
|
190
|
+
self.output_tokens = 0
|
|
191
|
+
|
|
192
|
+
def generator_class(self) -> type[Any]:
|
|
193
|
+
owner = self
|
|
194
|
+
|
|
195
|
+
class FrozenGenerator:
|
|
196
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
197
|
+
self.kwargs = dict(kwargs)
|
|
198
|
+
|
|
199
|
+
def generate_text_sequence(self, prompt: str) -> list[tuple[str, float]]:
|
|
200
|
+
if owner.generation_index >= len(owner.generations):
|
|
201
|
+
raise DriverError(
|
|
202
|
+
"FIXTURE_EXHAUSTED",
|
|
203
|
+
"frozen generator output sequence is exhausted",
|
|
204
|
+
pre_execution=False,
|
|
205
|
+
)
|
|
206
|
+
item = owner.generations[owner.generation_index]
|
|
207
|
+
owner.generation_index += 1
|
|
208
|
+
text = _required_text(item, "text", "fixture generation")
|
|
209
|
+
owner.input_tokens += _nonnegative_integer(
|
|
210
|
+
item.get("input_tokens"),
|
|
211
|
+
"fixture input_tokens",
|
|
212
|
+
)
|
|
213
|
+
owner.output_tokens += _nonnegative_integer(
|
|
214
|
+
item.get("output_tokens"),
|
|
215
|
+
"fixture output_tokens",
|
|
216
|
+
)
|
|
217
|
+
if not prompt.strip():
|
|
218
|
+
raise DriverError(
|
|
219
|
+
"FIXTURE_PROMPT_INVALID",
|
|
220
|
+
"upstream generator received an empty prompt",
|
|
221
|
+
pre_execution=False,
|
|
222
|
+
)
|
|
223
|
+
return [(text, 0.0)]
|
|
224
|
+
|
|
225
|
+
return FrozenGenerator
|
|
226
|
+
|
|
227
|
+
def safe_post_request(self, url: str, params: Mapping[str, Any]) -> _FrozenResponse:
|
|
228
|
+
del url
|
|
229
|
+
if self.retrieval_index >= len(self.rounds):
|
|
230
|
+
raise DriverError(
|
|
231
|
+
"FIXTURE_EXHAUSTED",
|
|
232
|
+
"frozen retrieval sequence is exhausted",
|
|
233
|
+
pre_execution=False,
|
|
234
|
+
)
|
|
235
|
+
item = self.rounds[self.retrieval_index]
|
|
236
|
+
self.retrieval_index += 1
|
|
237
|
+
expected_query = _required_text(item, "query", "fixture retrieval")
|
|
238
|
+
actual_query = str(params.get("query_text", "")).strip()
|
|
239
|
+
if actual_query != expected_query:
|
|
240
|
+
raise DriverError(
|
|
241
|
+
"FIXTURE_QUERY_MISMATCH",
|
|
242
|
+
"upstream retrieval query differs from frozen trajectory",
|
|
243
|
+
pre_execution=False,
|
|
244
|
+
)
|
|
245
|
+
documents = _mapping_sequence(item.get("documents"), "fixture documents")
|
|
246
|
+
retrieval = []
|
|
247
|
+
for document in documents:
|
|
248
|
+
retrieval.append(
|
|
249
|
+
{
|
|
250
|
+
**dict(document),
|
|
251
|
+
"corpus_name": self.corpus_revision,
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
return _FrozenResponse({"retrieval": retrieval})
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class _RetrievalCapture:
|
|
258
|
+
def __init__(self, delegate: Any) -> None:
|
|
259
|
+
self.delegate = delegate
|
|
260
|
+
self.rounds: list[Mapping[str, Any]] = []
|
|
261
|
+
|
|
262
|
+
def __call__(self, url: str, params: Mapping[str, Any]) -> Any:
|
|
263
|
+
response = self.delegate(url, params)
|
|
264
|
+
documents: list[Mapping[str, Any]] = []
|
|
265
|
+
try:
|
|
266
|
+
if response.ok:
|
|
267
|
+
payload = response.json()
|
|
268
|
+
raw = payload.get("retrieval", []) if isinstance(payload, Mapping) else []
|
|
269
|
+
documents = [dict(item) for item in raw if isinstance(item, Mapping)]
|
|
270
|
+
except Exception:
|
|
271
|
+
documents = []
|
|
272
|
+
self.rounds.append(
|
|
273
|
+
{
|
|
274
|
+
"query": str(params.get("query_text", "")),
|
|
275
|
+
"documents": documents,
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
return response
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class _TracingController:
|
|
282
|
+
def __init__(
|
|
283
|
+
self,
|
|
284
|
+
controller: Any,
|
|
285
|
+
role_by_command: Mapping[str, str],
|
|
286
|
+
retrieval_capture: _RetrievalCapture,
|
|
287
|
+
) -> None:
|
|
288
|
+
self._controller = controller
|
|
289
|
+
self._roles = dict(role_by_command)
|
|
290
|
+
self._capture = retrieval_capture
|
|
291
|
+
self.events: list[ControllerEvent] = []
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def start_state(self) -> Any:
|
|
295
|
+
return self._controller.start_state
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def end_state(self) -> Any:
|
|
299
|
+
return self._controller.end_state
|
|
300
|
+
|
|
301
|
+
def init_data(self, value: Mapping[str, Any]) -> Any:
|
|
302
|
+
return self._controller.init_data(value)
|
|
303
|
+
|
|
304
|
+
def execute(self, state: Any, debug: bool = False) -> list[Any]:
|
|
305
|
+
before = _snapshot(state.data)
|
|
306
|
+
capture_start = len(self._capture.rounds)
|
|
307
|
+
outputs = self._controller.execute(state, debug=debug)
|
|
308
|
+
if not isinstance(outputs, list) or len(outputs) != 1:
|
|
309
|
+
raise DriverError(
|
|
310
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
311
|
+
"IRCoT participant must return exactly one state",
|
|
312
|
+
pre_execution=False,
|
|
313
|
+
)
|
|
314
|
+
output = outputs[0]
|
|
315
|
+
command = state.next
|
|
316
|
+
if not isinstance(command, str) or command not in self._roles:
|
|
317
|
+
raise DriverError(
|
|
318
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
319
|
+
"IRCoT controller emitted an unknown command",
|
|
320
|
+
pre_execution=False,
|
|
321
|
+
)
|
|
322
|
+
self.events.append(
|
|
323
|
+
ControllerEvent(
|
|
324
|
+
command=command,
|
|
325
|
+
role=self._roles[command],
|
|
326
|
+
before=before,
|
|
327
|
+
after=_snapshot(output.data),
|
|
328
|
+
retrieval_rounds=tuple(self._capture.rounds[capture_start:]),
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
return outputs
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class IRCoTDriver:
|
|
335
|
+
def __init__(
|
|
336
|
+
self,
|
|
337
|
+
*,
|
|
338
|
+
manifest_path: Path = DEFAULT_MANIFEST,
|
|
339
|
+
profile_id: str | None = None,
|
|
340
|
+
environment: Mapping[str, str] | None = None,
|
|
341
|
+
isolated_process: bool = False,
|
|
342
|
+
) -> None:
|
|
343
|
+
self.manifest_path = Path(manifest_path)
|
|
344
|
+
self.environment = dict(os.environ if environment is None else environment)
|
|
345
|
+
self.profile_id = profile_id or self.environment.get(PROFILE_ENV, DEFAULT_PROFILE)
|
|
346
|
+
self.isolated_process = isolated_process
|
|
347
|
+
|
|
348
|
+
def identity(self) -> Mapping[str, Any]:
|
|
349
|
+
checkout = self._checkout()
|
|
350
|
+
api_status = self.smoke_upstream_api(checkout=checkout)
|
|
351
|
+
return {
|
|
352
|
+
"profile_id": checkout.profile_id,
|
|
353
|
+
"identity_kind": checkout.profile["identity_kind"],
|
|
354
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
355
|
+
"manifest_sha256": checkout.manifest_sha256,
|
|
356
|
+
"config_sha256": checkout.profile["config_sha256"],
|
|
357
|
+
"prompt_sha256": checkout.profile["prompt_sha256"],
|
|
358
|
+
"binding_attestation": checkout.profile["binding_attestation"],
|
|
359
|
+
"full_api_status": api_status["full_api_status"],
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def smoke_upstream_api(self, *, checkout: Checkout | None = None) -> Mapping[str, str]:
|
|
363
|
+
checkout = checkout or self._checkout()
|
|
364
|
+
with _upstream_import_scope(checkout.root, fixture_shims=False):
|
|
365
|
+
try:
|
|
366
|
+
model_search = importlib.import_module("commaqa.inference.model_search")
|
|
367
|
+
except ModuleNotFoundError as exc:
|
|
368
|
+
raise _dependency_error(exc) from exc
|
|
369
|
+
if not isinstance(getattr(model_search, "BestFirstDecomposer", None), type):
|
|
370
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "BestFirstDecomposer is unavailable")
|
|
371
|
+
full_status = "available"
|
|
372
|
+
try:
|
|
373
|
+
with _upstream_import_scope(checkout.root, fixture_shims=False):
|
|
374
|
+
module = importlib.import_module("commaqa.inference.ircot")
|
|
375
|
+
for symbol in (
|
|
376
|
+
"RetrieveAndResetParagraphsParticipant",
|
|
377
|
+
"StepByStepCOTGenParticipant",
|
|
378
|
+
"StepByStepExitControllerParticipant",
|
|
379
|
+
):
|
|
380
|
+
if not isinstance(getattr(module, symbol, None), type):
|
|
381
|
+
raise DriverError("UPSTREAM_API_MISMATCH", f"{symbol} is unavailable")
|
|
382
|
+
except DriverError:
|
|
383
|
+
raise
|
|
384
|
+
except ModuleNotFoundError as exc:
|
|
385
|
+
name = exc.name if isinstance(exc.name, str) else "optional_dependency"
|
|
386
|
+
full_status = f"dependency_missing:{name}"
|
|
387
|
+
return {
|
|
388
|
+
"core_symbol": "commaqa.inference.model_search.BestFirstDecomposer",
|
|
389
|
+
"full_api_status": full_status,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
def run(self, value: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
393
|
+
request = _parse_request(value)
|
|
394
|
+
checkout = self._checkout()
|
|
395
|
+
_attest_request(request, checkout, self.environment)
|
|
396
|
+
identity_kind = checkout.profile["identity_kind"]
|
|
397
|
+
if identity_kind == "upstream_native" and not self.isolated_process:
|
|
398
|
+
raise DriverError(
|
|
399
|
+
"ISOLATION_REQUIRED",
|
|
400
|
+
"upstream-native IRCoT must run through the JSONL worker process",
|
|
401
|
+
)
|
|
402
|
+
if identity_kind == "upstream_native":
|
|
403
|
+
raise DriverError(
|
|
404
|
+
"TOKEN_USAGE_OBSERVER_REQUIRED",
|
|
405
|
+
"historical native clients lack observed token usage; use the startup-bound controlled runtime",
|
|
406
|
+
)
|
|
407
|
+
config = _load_config(checkout, self.environment)
|
|
408
|
+
config = _patch_config(config, checkout, request.controls)
|
|
409
|
+
fixture_bindings = (
|
|
410
|
+
_load_fixture_bindings(checkout, request)
|
|
411
|
+
if identity_kind == "controlled_fixture"
|
|
412
|
+
else None
|
|
413
|
+
)
|
|
414
|
+
capture = _BoundedCapture()
|
|
415
|
+
fixture_shims = fixture_bindings is not None
|
|
416
|
+
|
|
417
|
+
with (
|
|
418
|
+
_working_directory(checkout.root),
|
|
419
|
+
_upstream_import_scope(checkout.root, fixture_shims=fixture_shims),
|
|
420
|
+
contextlib.redirect_stdout(capture),
|
|
421
|
+
):
|
|
422
|
+
try:
|
|
423
|
+
configurable = importlib.import_module("commaqa.inference.configurable_inference")
|
|
424
|
+
ircot_module = importlib.import_module("commaqa.inference.ircot")
|
|
425
|
+
except ModuleNotFoundError as exc:
|
|
426
|
+
raise _dependency_error(exc) from exc
|
|
427
|
+
if fixture_bindings is not None:
|
|
428
|
+
frozen_generator = fixture_bindings.generator_class()
|
|
429
|
+
ircot_module.GPT3Generator = frozen_generator
|
|
430
|
+
ircot_module.LLMClientGenerator = frozen_generator
|
|
431
|
+
retrieval_delegate = fixture_bindings.safe_post_request
|
|
432
|
+
else:
|
|
433
|
+
retrieval_delegate = ircot_module.safe_post_request
|
|
434
|
+
retrieval_capture = _RetrievalCapture(retrieval_delegate)
|
|
435
|
+
ircot_module.safe_post_request = retrieval_capture
|
|
436
|
+
builder = getattr(configurable, "build_decomposer_and_models", None)
|
|
437
|
+
if not callable(builder):
|
|
438
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "upstream builder is unavailable")
|
|
439
|
+
try:
|
|
440
|
+
decomposer, model_map = builder(copy.deepcopy(config))
|
|
441
|
+
except ModuleNotFoundError as exc:
|
|
442
|
+
raise _dependency_error(exc) from exc
|
|
443
|
+
except DriverError:
|
|
444
|
+
raise
|
|
445
|
+
except Exception as exc:
|
|
446
|
+
raise DriverError(
|
|
447
|
+
"UPSTREAM_BOOT_FAILED",
|
|
448
|
+
"upstream IRCoT participant construction failed",
|
|
449
|
+
) from exc
|
|
450
|
+
if not isinstance(model_map, Mapping) or not hasattr(decomposer, "controller"):
|
|
451
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "upstream builder result is invalid")
|
|
452
|
+
role_by_command = {key: item["name"] for key, item in config["models"].items()}
|
|
453
|
+
tracing = _TracingController(
|
|
454
|
+
decomposer.controller,
|
|
455
|
+
role_by_command,
|
|
456
|
+
retrieval_capture,
|
|
457
|
+
)
|
|
458
|
+
decomposer.controller = tracing
|
|
459
|
+
example = {
|
|
460
|
+
"qid": request.task_id or request.request_id,
|
|
461
|
+
"query": request.question,
|
|
462
|
+
"question": request.question,
|
|
463
|
+
"metadata": {
|
|
464
|
+
"pinned_title": None,
|
|
465
|
+
"pinned_para": None,
|
|
466
|
+
"pin_position": None,
|
|
467
|
+
},
|
|
468
|
+
"titles": [],
|
|
469
|
+
"paras": [],
|
|
470
|
+
}
|
|
471
|
+
try:
|
|
472
|
+
final_state, _ = decomposer.find_answer_decomp(example, debug=False)
|
|
473
|
+
except DriverError:
|
|
474
|
+
raise
|
|
475
|
+
except SystemExit as exc:
|
|
476
|
+
raise DriverError(
|
|
477
|
+
"UPSTREAM_ABORTED",
|
|
478
|
+
"upstream model client aborted execution",
|
|
479
|
+
pre_execution=False,
|
|
480
|
+
) from exc
|
|
481
|
+
except Exception as exc:
|
|
482
|
+
raise DriverError(
|
|
483
|
+
"UPSTREAM_EXECUTION_FAILED",
|
|
484
|
+
"upstream IRCoT execution failed",
|
|
485
|
+
pre_execution=False,
|
|
486
|
+
) from exc
|
|
487
|
+
result = _project_trajectory(
|
|
488
|
+
request,
|
|
489
|
+
checkout,
|
|
490
|
+
config,
|
|
491
|
+
tracing.events,
|
|
492
|
+
final_state,
|
|
493
|
+
ircot_module,
|
|
494
|
+
model_map,
|
|
495
|
+
fixture_bindings,
|
|
496
|
+
)
|
|
497
|
+
result["upstream_stdout_sha256"] = capture.hexdigest
|
|
498
|
+
return result
|
|
499
|
+
|
|
500
|
+
def safe_call(self, value: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
501
|
+
request_id = value.get("request_id", "") if isinstance(value, Mapping) else ""
|
|
502
|
+
try:
|
|
503
|
+
return self.run(value)
|
|
504
|
+
except DriverError as exc:
|
|
505
|
+
response: dict[str, Any] = {
|
|
506
|
+
"status": "error",
|
|
507
|
+
"request_id": request_id if isinstance(request_id, str) else "",
|
|
508
|
+
"failure_code": exc.code,
|
|
509
|
+
"message": exc.public_message,
|
|
510
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
511
|
+
}
|
|
512
|
+
if exc.pre_execution:
|
|
513
|
+
response["consumed"] = False
|
|
514
|
+
return response
|
|
515
|
+
|
|
516
|
+
def _checkout(self) -> Checkout:
|
|
517
|
+
root_value = self.environment.get(UPSTREAM_ENV, "")
|
|
518
|
+
if not root_value:
|
|
519
|
+
raise DriverError("UPSTREAM_ROOT_MISSING", f"{UPSTREAM_ENV} is required")
|
|
520
|
+
root = Path(root_value).expanduser().resolve()
|
|
521
|
+
if not root.is_dir():
|
|
522
|
+
raise DriverError("UPSTREAM_ROOT_INVALID", "IRCoT checkout is unavailable")
|
|
523
|
+
manifest, manifest_sha = _load_manifest(self.manifest_path)
|
|
524
|
+
_verify_checkout(root, manifest)
|
|
525
|
+
profiles = manifest["profiles"]
|
|
526
|
+
profile = profiles.get(self.profile_id) if isinstance(profiles, Mapping) else None
|
|
527
|
+
if not isinstance(profile, Mapping):
|
|
528
|
+
raise DriverError("PROFILE_NOT_ALLOWED", "IRCoT worker profile is not allowlisted")
|
|
529
|
+
if profile.get("identity_kind") not in {"controlled_fixture", "upstream_native"}:
|
|
530
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT profile identity kind is invalid")
|
|
531
|
+
worker_root = self.manifest_path.resolve().parent
|
|
532
|
+
config_path = _profile_file(root, worker_root, profile, "config")
|
|
533
|
+
prompt_path = _profile_file(root, worker_root, profile, "prompt")
|
|
534
|
+
bindings_path = (
|
|
535
|
+
_profile_file(root, worker_root, profile, "bindings")
|
|
536
|
+
if "bindings_path" in profile
|
|
537
|
+
else None
|
|
538
|
+
)
|
|
539
|
+
return Checkout(
|
|
540
|
+
root=root,
|
|
541
|
+
worker_root=worker_root,
|
|
542
|
+
manifest=manifest,
|
|
543
|
+
manifest_sha256=manifest_sha,
|
|
544
|
+
profile_id=self.profile_id,
|
|
545
|
+
profile=profile,
|
|
546
|
+
config_path=config_path,
|
|
547
|
+
prompt_path=prompt_path,
|
|
548
|
+
bindings_path=bindings_path,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _parse_request(value: Mapping[str, Any]) -> Request:
|
|
553
|
+
if not isinstance(value, Mapping) or set(value) != _REQUEST_FIELDS:
|
|
554
|
+
raise DriverError("INVALID_REQUEST", "IRCoT request fields do not match schema")
|
|
555
|
+
if value.get("upstream_commit") != UPSTREAM_COMMIT:
|
|
556
|
+
raise DriverError("REVISION_MISMATCH", "IRCoT request revision is not pinned")
|
|
557
|
+
if value.get("protocol_version") != PROTOCOL_VERSION:
|
|
558
|
+
raise DriverError("PROTOCOL_MISMATCH", "IRCoT request protocol is unsupported")
|
|
559
|
+
request_id = _bounded_text(value.get("request_id"), "request_id", 256)
|
|
560
|
+
state_hash = _bounded_text(value.get("state_hash"), "state_hash", 128)
|
|
561
|
+
task_id = value.get("task_id")
|
|
562
|
+
if not isinstance(task_id, str) or len(task_id) > 512:
|
|
563
|
+
raise DriverError("INVALID_REQUEST", "task_id must be bounded text")
|
|
564
|
+
question = _bounded_text(value.get("question"), "question", 200_000)
|
|
565
|
+
controls_value = value.get("controls")
|
|
566
|
+
if not isinstance(controls_value, Mapping) or set(controls_value) != _CONTROL_FIELDS:
|
|
567
|
+
raise DriverError("INVALID_REQUEST", "IRCoT controls do not match schema")
|
|
568
|
+
controls = Controls(
|
|
569
|
+
per_step_top_k=_positive_integer(controls_value["per_step_top_k"], "per_step_top_k", 100),
|
|
570
|
+
max_steps=_positive_integer(controls_value["max_steps"], "max_steps", 128),
|
|
571
|
+
max_total_documents=_positive_integer(
|
|
572
|
+
controls_value["max_total_documents"],
|
|
573
|
+
"max_total_documents",
|
|
574
|
+
10_000,
|
|
575
|
+
),
|
|
576
|
+
max_output_tokens=_positive_integer(
|
|
577
|
+
controls_value["max_output_tokens"],
|
|
578
|
+
"max_output_tokens",
|
|
579
|
+
10_000_000,
|
|
580
|
+
),
|
|
581
|
+
deduplicate=_boolean(controls_value["deduplicate"], "deduplicate"),
|
|
582
|
+
reset_policy=_enum_text(
|
|
583
|
+
controls_value["reset_policy"],
|
|
584
|
+
"reset_policy",
|
|
585
|
+
{"upstream_native", "never", "replace_each_step"},
|
|
586
|
+
),
|
|
587
|
+
)
|
|
588
|
+
if not controls.deduplicate:
|
|
589
|
+
raise DriverError(
|
|
590
|
+
"CONTROL_UNSUPPORTED",
|
|
591
|
+
"pinned upstream IRCoT always de-duplicates retrieved paragraphs",
|
|
592
|
+
)
|
|
593
|
+
generator = _canonical_mapping(value.get("generator_fingerprint"), "generator_fingerprint")
|
|
594
|
+
retriever = _canonical_mapping(value.get("retriever_fingerprint"), "retriever_fingerprint")
|
|
595
|
+
return Request(
|
|
596
|
+
request_id=request_id,
|
|
597
|
+
state_hash=state_hash,
|
|
598
|
+
task_id=task_id,
|
|
599
|
+
question=question,
|
|
600
|
+
controls=controls,
|
|
601
|
+
generator_fingerprint=generator,
|
|
602
|
+
retriever_fingerprint=retriever,
|
|
603
|
+
corpus_revision=_bounded_text(value.get("corpus_revision"), "corpus_revision", 256),
|
|
604
|
+
index_revision=_bounded_text(value.get("index_revision"), "index_revision", 256),
|
|
605
|
+
prompt_hash=_bounded_text(value.get("prompt_hash"), "prompt_hash", 128),
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _attest_request(request: Request, checkout: Checkout, environment: Mapping[str, str]) -> None:
|
|
610
|
+
profile = checkout.profile
|
|
611
|
+
if request.prompt_hash != profile["prompt_sha256"]:
|
|
612
|
+
raise DriverError("PROMPT_MISMATCH", "request prompt hash differs from pinned prompt")
|
|
613
|
+
if request.corpus_revision != profile.get("corpus_revision"):
|
|
614
|
+
raise DriverError("CORPUS_MISMATCH", "request corpus revision differs from worker profile")
|
|
615
|
+
expected_index = profile.get("index_revision")
|
|
616
|
+
if expected_index is None:
|
|
617
|
+
env_key = profile.get("index_revision_env")
|
|
618
|
+
expected_index = environment.get(env_key, "") if isinstance(env_key, str) else ""
|
|
619
|
+
if not expected_index or request.index_revision != expected_index:
|
|
620
|
+
raise DriverError("INDEX_MISMATCH", "request index revision is not attested")
|
|
621
|
+
if profile["identity_kind"] == "upstream_native":
|
|
622
|
+
generator_env = profile.get("generator_fingerprint_sha256_env")
|
|
623
|
+
retriever_env = profile.get("retriever_fingerprint_sha256_env")
|
|
624
|
+
expected_generator = (
|
|
625
|
+
environment.get(generator_env, "") if isinstance(generator_env, str) else ""
|
|
626
|
+
)
|
|
627
|
+
expected_retriever = (
|
|
628
|
+
environment.get(retriever_env, "") if isinstance(retriever_env, str) else ""
|
|
629
|
+
)
|
|
630
|
+
if (
|
|
631
|
+
not expected_generator
|
|
632
|
+
or _canonical_hash(request.generator_fingerprint) != expected_generator
|
|
633
|
+
):
|
|
634
|
+
raise DriverError("GENERATOR_IDENTITY_MISMATCH", "generator binding is not attested")
|
|
635
|
+
if (
|
|
636
|
+
not expected_retriever
|
|
637
|
+
or _canonical_hash(request.retriever_fingerprint) != expected_retriever
|
|
638
|
+
):
|
|
639
|
+
raise DriverError("RETRIEVER_IDENTITY_MISMATCH", "retriever binding is not attested")
|
|
640
|
+
required = profile.get("required_runtime_env", [])
|
|
641
|
+
if not isinstance(required, Sequence) or isinstance(required, (str, bytes)):
|
|
642
|
+
raise DriverError("MANIFEST_INVALID", "required runtime environment list is invalid")
|
|
643
|
+
if any(not isinstance(key, str) or not environment.get(key, "") for key in required):
|
|
644
|
+
raise DriverError("RUNTIME_BINDING_MISSING", "production model binding is unavailable")
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _load_config(checkout: Checkout, environment: Mapping[str, str]) -> Mapping[str, Any]:
|
|
648
|
+
if checkout.config_path.suffix == ".json":
|
|
649
|
+
try:
|
|
650
|
+
value = json.loads(checkout.config_path.read_text(encoding="utf-8"))
|
|
651
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
652
|
+
raise DriverError("CONFIG_INVALID", "pinned JSON config cannot be loaded") from exc
|
|
653
|
+
elif checkout.config_path.suffix == ".jsonnet":
|
|
654
|
+
try:
|
|
655
|
+
jsonnet = importlib.import_module("_jsonnet")
|
|
656
|
+
except ModuleNotFoundError as exc:
|
|
657
|
+
raise _dependency_error(exc) from exc
|
|
658
|
+
ext_var_spec = checkout.profile.get("config_ext_vars", {})
|
|
659
|
+
if not isinstance(ext_var_spec, Mapping):
|
|
660
|
+
raise DriverError("MANIFEST_INVALID", "config extVar specification is invalid")
|
|
661
|
+
ext_vars: dict[str, str] = {}
|
|
662
|
+
for ext_name, env_name in ext_var_spec.items():
|
|
663
|
+
if not isinstance(ext_name, str) or not isinstance(env_name, str):
|
|
664
|
+
raise DriverError("MANIFEST_INVALID", "config extVar names must be strings")
|
|
665
|
+
env_value = environment.get(env_name, "")
|
|
666
|
+
if not env_value:
|
|
667
|
+
raise DriverError(
|
|
668
|
+
"RUNTIME_BINDING_MISSING", "production retriever binding is unavailable"
|
|
669
|
+
)
|
|
670
|
+
ext_vars[ext_name] = env_value
|
|
671
|
+
try:
|
|
672
|
+
value = json.loads(jsonnet.evaluate_file(str(checkout.config_path), ext_vars=ext_vars))
|
|
673
|
+
except (RuntimeError, json.JSONDecodeError) as exc:
|
|
674
|
+
raise DriverError(
|
|
675
|
+
"CONFIG_INVALID", "pinned Jsonnet config cannot be evaluated"
|
|
676
|
+
) from exc
|
|
677
|
+
else:
|
|
678
|
+
raise DriverError("CONFIG_INVALID", "pinned config extension is unsupported")
|
|
679
|
+
if not isinstance(value, Mapping):
|
|
680
|
+
raise DriverError("CONFIG_INVALID", "pinned config root must be an object")
|
|
681
|
+
return value
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _patch_config(
|
|
685
|
+
config: Mapping[str, Any],
|
|
686
|
+
checkout: Checkout,
|
|
687
|
+
controls: Controls,
|
|
688
|
+
) -> Mapping[str, Any]:
|
|
689
|
+
patched = copy.deepcopy(dict(config))
|
|
690
|
+
models = patched.get("models")
|
|
691
|
+
if not isinstance(models, Mapping):
|
|
692
|
+
raise DriverError("CONFIG_INVALID", "IRCoT config requires models")
|
|
693
|
+
allowed = _string_set(checkout.manifest["allowed_model_names"], "allowed model names")
|
|
694
|
+
roles: set[str] = set()
|
|
695
|
+
per_call_output = max(1, controls.max_output_tokens // controls.max_steps)
|
|
696
|
+
for model in models.values():
|
|
697
|
+
if not isinstance(model, dict):
|
|
698
|
+
raise DriverError("CONFIG_INVALID", "IRCoT model config must be an object")
|
|
699
|
+
name = model.get("name")
|
|
700
|
+
if not isinstance(name, str) or name not in allowed:
|
|
701
|
+
raise DriverError("UNSAFE_CONFIG", "IRCoT config contains a non-allowlisted model")
|
|
702
|
+
roles.add(name)
|
|
703
|
+
if name == "retrieve_and_reset_paragraphs":
|
|
704
|
+
model["retrieval_count"] = controls.per_step_top_k
|
|
705
|
+
model["global_max_num_paras"] = controls.max_total_documents
|
|
706
|
+
model["cumulate_titles"] = controls.reset_policy != "replace_each_step"
|
|
707
|
+
elif name == "step_by_step_cot_gen":
|
|
708
|
+
model["max_num_sentences"] = controls.max_steps
|
|
709
|
+
if "max_tokens" in model or model.get("gen_model") == "gpt3":
|
|
710
|
+
model["max_tokens"] = min(
|
|
711
|
+
int(model.get("max_tokens", per_call_output)), per_call_output
|
|
712
|
+
)
|
|
713
|
+
if "max_length" in model or model.get("gen_model") == "llm_api":
|
|
714
|
+
model["max_length"] = min(
|
|
715
|
+
int(model.get("max_length", per_call_output)), per_call_output
|
|
716
|
+
)
|
|
717
|
+
prompt_file = model.get("prompt_file")
|
|
718
|
+
if checkout.profile["identity_kind"] == "controlled_fixture":
|
|
719
|
+
if prompt_file != "__AGENTINPUTLAB_LOCKED_PROMPT__":
|
|
720
|
+
raise DriverError("CONFIG_INVALID", "fixture prompt marker is missing")
|
|
721
|
+
model["prompt_file"] = str(checkout.prompt_path)
|
|
722
|
+
else:
|
|
723
|
+
resolved = _safe_file(checkout.root, prompt_file)
|
|
724
|
+
if resolved != checkout.prompt_path:
|
|
725
|
+
raise DriverError("PROMPT_MISMATCH", "config uses an unpinned prompt")
|
|
726
|
+
elif name == "step_by_step_exit_controller":
|
|
727
|
+
model["max_num_sentences"] = controls.max_steps
|
|
728
|
+
model["global_max_num_paras"] = controls.max_total_documents
|
|
729
|
+
required_roles = {
|
|
730
|
+
"retrieve_and_reset_paragraphs",
|
|
731
|
+
"step_by_step_cot_gen",
|
|
732
|
+
"step_by_step_exit_controller",
|
|
733
|
+
}
|
|
734
|
+
if roles != required_roles:
|
|
735
|
+
raise DriverError(
|
|
736
|
+
"CONFIG_INVALID", "IRCoT profile must contain the complete three-stage loop"
|
|
737
|
+
)
|
|
738
|
+
return patched
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def _project_trajectory(
|
|
742
|
+
request: Request,
|
|
743
|
+
checkout: Checkout,
|
|
744
|
+
config: Mapping[str, Any],
|
|
745
|
+
events: Sequence[ControllerEvent],
|
|
746
|
+
final_state: Any,
|
|
747
|
+
ircot_module: Any,
|
|
748
|
+
model_map: Mapping[str, Any],
|
|
749
|
+
fixture_bindings: _FrozenBindings | None,
|
|
750
|
+
) -> dict[str, Any]:
|
|
751
|
+
retrieval_events = [event for event in events if event.role == "retrieve_and_reset_paragraphs"]
|
|
752
|
+
if not retrieval_events:
|
|
753
|
+
raise DriverError(
|
|
754
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
755
|
+
"upstream IRCoT trajectory contains no retrieval",
|
|
756
|
+
pre_execution=False,
|
|
757
|
+
)
|
|
758
|
+
if len(retrieval_events) - 1 > request.controls.max_steps:
|
|
759
|
+
raise DriverError(
|
|
760
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
761
|
+
"upstream IRCoT trajectory exceeds max_steps",
|
|
762
|
+
pre_execution=False,
|
|
763
|
+
)
|
|
764
|
+
document_records: dict[str, dict[str, Any]] = {}
|
|
765
|
+
pair_to_id: dict[tuple[str, str], str] = {}
|
|
766
|
+
seen: set[str] = set()
|
|
767
|
+
steps: list[dict[str, Any]] = []
|
|
768
|
+
active_ids: tuple[str, ...] = ()
|
|
769
|
+
initial_ids: tuple[str, ...] = ()
|
|
770
|
+
|
|
771
|
+
for retrieval_index, event in enumerate(retrieval_events):
|
|
772
|
+
raw_hits = [
|
|
773
|
+
hit
|
|
774
|
+
for round_value in event.retrieval_rounds
|
|
775
|
+
for hit in _mapping_sequence(round_value.get("documents", []), "retrieval documents")
|
|
776
|
+
]
|
|
777
|
+
hit_by_pair: dict[tuple[str, str], Mapping[str, Any]] = {}
|
|
778
|
+
for hit in raw_hits:
|
|
779
|
+
title = str(hit.get("title", ""))
|
|
780
|
+
paragraph = str(hit.get("paragraph_text", ""))
|
|
781
|
+
if title and paragraph:
|
|
782
|
+
hit_by_pair[(title, paragraph)] = hit
|
|
783
|
+
current_ids: list[str] = []
|
|
784
|
+
for title, paragraph in zip(
|
|
785
|
+
event.after.titles,
|
|
786
|
+
event.after.paragraphs,
|
|
787
|
+
strict=True,
|
|
788
|
+
):
|
|
789
|
+
pair = (title, paragraph)
|
|
790
|
+
hit = hit_by_pair.get(pair, {})
|
|
791
|
+
document_id = pair_to_id.get(pair)
|
|
792
|
+
if document_id is None:
|
|
793
|
+
raw_id = hit.get("id") if isinstance(hit, Mapping) else None
|
|
794
|
+
document_id = (
|
|
795
|
+
raw_id
|
|
796
|
+
if isinstance(raw_id, str) and raw_id
|
|
797
|
+
else str(ircot_module.get_pid_for_title_paragraph_text(title, paragraph))
|
|
798
|
+
)
|
|
799
|
+
pair_to_id[pair] = document_id
|
|
800
|
+
score_value = hit.get("score") if isinstance(hit, Mapping) else None
|
|
801
|
+
score = _optional_finite_number(score_value, "document score")
|
|
802
|
+
if document_id not in document_records or hit:
|
|
803
|
+
document_records[document_id] = {
|
|
804
|
+
"document_id": document_id,
|
|
805
|
+
"text": paragraph,
|
|
806
|
+
"title": title,
|
|
807
|
+
"corpus_revision": request.corpus_revision,
|
|
808
|
+
"score": score,
|
|
809
|
+
"metadata": {
|
|
810
|
+
"worker_profile": checkout.profile_id,
|
|
811
|
+
"worker_identity_kind": checkout.profile["identity_kind"],
|
|
812
|
+
},
|
|
813
|
+
}
|
|
814
|
+
current_ids.append(document_id)
|
|
815
|
+
active_ids = tuple(dict.fromkeys(current_ids))
|
|
816
|
+
raw_retrieved: list[str] = []
|
|
817
|
+
for pair in hit_by_pair:
|
|
818
|
+
if pair in pair_to_id and pair_to_id[pair] in active_ids:
|
|
819
|
+
raw_retrieved.append(pair_to_id[pair])
|
|
820
|
+
before_pairs = set(zip(event.before.titles, event.before.paragraphs, strict=True))
|
|
821
|
+
new_active = [
|
|
822
|
+
pair_to_id[pair]
|
|
823
|
+
for pair in pair_to_id
|
|
824
|
+
if pair not in before_pairs and pair_to_id[pair] in active_ids
|
|
825
|
+
]
|
|
826
|
+
for document_id in new_active:
|
|
827
|
+
if document_id not in raw_retrieved:
|
|
828
|
+
raw_retrieved.append(document_id)
|
|
829
|
+
retrieved_ids = tuple(dict.fromkeys(raw_retrieved))
|
|
830
|
+
if retrieval_index == 0:
|
|
831
|
+
initial_ids = active_ids
|
|
832
|
+
seen.update(initial_ids)
|
|
833
|
+
continue
|
|
834
|
+
generated = tuple(
|
|
835
|
+
str(item)
|
|
836
|
+
for item in ircot_module.remove_reasoning_sentences(
|
|
837
|
+
list(event.before.generated_sentences)
|
|
838
|
+
)
|
|
839
|
+
if str(item).strip()
|
|
840
|
+
)
|
|
841
|
+
latest = generated[-1].strip() if generated else request.question.strip()
|
|
842
|
+
cot_step = " ".join(generated).strip() or latest
|
|
843
|
+
new_ids = tuple(document_id for document_id in retrieved_ids if document_id not in seen)
|
|
844
|
+
seen.update(new_ids)
|
|
845
|
+
reset_applied = request.controls.reset_policy == "replace_each_step"
|
|
846
|
+
steps.append(
|
|
847
|
+
{
|
|
848
|
+
"index": len(steps),
|
|
849
|
+
"cot_step": cot_step,
|
|
850
|
+
"latest_cot_sentence": latest,
|
|
851
|
+
"retrieval_query": latest,
|
|
852
|
+
"retrieved_document_ids": list(retrieved_ids),
|
|
853
|
+
"new_document_ids": list(new_ids),
|
|
854
|
+
"active_document_ids": list(active_ids),
|
|
855
|
+
"reset_applied": reset_applied,
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
final_generated = (
|
|
859
|
+
_snapshot(final_state.data).generated_sentences if final_state is not None else ()
|
|
860
|
+
)
|
|
861
|
+
exit_model = next(
|
|
862
|
+
model
|
|
863
|
+
for model in config["models"].values()
|
|
864
|
+
if model["name"] == "step_by_step_exit_controller"
|
|
865
|
+
)
|
|
866
|
+
regex = re.compile(str(exit_model.get("answer_extractor_regex", ".* answer is:? (.*)")))
|
|
867
|
+
answer = ""
|
|
868
|
+
if final_generated:
|
|
869
|
+
match = regex.match(final_generated[-1])
|
|
870
|
+
if match:
|
|
871
|
+
answer = match.group(1)
|
|
872
|
+
if exit_model.get("answer_extractor_remove_last_fullstop") and answer.endswith("."):
|
|
873
|
+
answer = answer[:-1]
|
|
874
|
+
if answer:
|
|
875
|
+
exit_reason = "answer_detected"
|
|
876
|
+
elif final_state is None:
|
|
877
|
+
exit_reason = "upstream"
|
|
878
|
+
elif len(steps) == request.controls.max_steps:
|
|
879
|
+
exit_reason = "max_steps"
|
|
880
|
+
else:
|
|
881
|
+
exit_reason = "controller"
|
|
882
|
+
model_calls = _model_calls(model_map)
|
|
883
|
+
if model_calls < 1:
|
|
884
|
+
raise DriverError(
|
|
885
|
+
"USAGE_ACCOUNTING_UNAVAILABLE",
|
|
886
|
+
"upstream IRCoT reported no model calls",
|
|
887
|
+
pre_execution=False,
|
|
888
|
+
)
|
|
889
|
+
input_tokens = fixture_bindings.input_tokens if fixture_bindings is not None else 0
|
|
890
|
+
output_tokens = fixture_bindings.output_tokens if fixture_bindings is not None else 0
|
|
891
|
+
if output_tokens > request.controls.max_output_tokens:
|
|
892
|
+
raise DriverError(
|
|
893
|
+
"OUTPUT_BUDGET_EXCEEDED",
|
|
894
|
+
"upstream IRCoT exceeded max_output_tokens",
|
|
895
|
+
pre_execution=False,
|
|
896
|
+
)
|
|
897
|
+
return {
|
|
898
|
+
"status": "ok",
|
|
899
|
+
"request_id": request.request_id,
|
|
900
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
901
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
902
|
+
"initial_query": request.question,
|
|
903
|
+
"initial_document_ids": list(initial_ids),
|
|
904
|
+
"steps": steps,
|
|
905
|
+
"documents": list(document_records.values()),
|
|
906
|
+
"active_document_ids": list(active_ids),
|
|
907
|
+
"exit_reason": exit_reason,
|
|
908
|
+
"answer": answer,
|
|
909
|
+
"usage": {
|
|
910
|
+
"retrieval_calls": 1 + len(steps),
|
|
911
|
+
"model_calls": model_calls,
|
|
912
|
+
"unpriced_model_calls": model_calls,
|
|
913
|
+
"input_tokens": input_tokens,
|
|
914
|
+
"output_tokens": output_tokens,
|
|
915
|
+
},
|
|
916
|
+
"worker_profile_id": checkout.profile_id,
|
|
917
|
+
"worker_identity_kind": checkout.profile["identity_kind"],
|
|
918
|
+
"binding_attestation": checkout.profile["binding_attestation"],
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def _model_calls(model_map: Mapping[str, Any]) -> int:
|
|
923
|
+
total = 0
|
|
924
|
+
for participant in model_map.values():
|
|
925
|
+
reporter = getattr(participant, "return_model_calls", None)
|
|
926
|
+
if not callable(reporter):
|
|
927
|
+
continue
|
|
928
|
+
counters = reporter()
|
|
929
|
+
if not isinstance(counters, Mapping):
|
|
930
|
+
raise DriverError("USAGE_ACCOUNTING_UNAVAILABLE", "participant counters are invalid")
|
|
931
|
+
for name, amount in counters.items():
|
|
932
|
+
if isinstance(amount, bool) or not isinstance(amount, int) or amount < 0:
|
|
933
|
+
raise DriverError("USAGE_ACCOUNTING_UNAVAILABLE", "participant counter is invalid")
|
|
934
|
+
if name in {"step_by_step_cot", "llm_qa"}:
|
|
935
|
+
total += amount
|
|
936
|
+
return total
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _snapshot(data: Any) -> Snapshot:
|
|
940
|
+
try:
|
|
941
|
+
titles = tuple(str(item) for item in data.get("titles", []))
|
|
942
|
+
paragraphs = tuple(str(item) for item in data.get("paras", []))
|
|
943
|
+
generated = tuple(str(item) for item in data.get("generated_sentences", []))
|
|
944
|
+
question = str(data.get("question", ""))
|
|
945
|
+
except (AttributeError, TypeError, ValueError) as exc:
|
|
946
|
+
raise DriverError(
|
|
947
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
948
|
+
"upstream IRCoT state cannot be inspected",
|
|
949
|
+
pre_execution=False,
|
|
950
|
+
) from exc
|
|
951
|
+
if len(titles) != len(paragraphs):
|
|
952
|
+
raise DriverError(
|
|
953
|
+
"UPSTREAM_OUTPUT_INVALID",
|
|
954
|
+
"upstream titles and paragraphs are unbalanced",
|
|
955
|
+
pre_execution=False,
|
|
956
|
+
)
|
|
957
|
+
return Snapshot(question, titles, paragraphs, generated)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def _load_fixture_bindings(checkout: Checkout, request: Request) -> _FrozenBindings:
|
|
961
|
+
if checkout.bindings_path is None:
|
|
962
|
+
raise DriverError("MANIFEST_INVALID", "fixture profile lacks frozen bindings")
|
|
963
|
+
try:
|
|
964
|
+
value = json.loads(checkout.bindings_path.read_text(encoding="utf-8"))
|
|
965
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
966
|
+
raise DriverError("FIXTURE_INVALID", "frozen bindings cannot be loaded") from exc
|
|
967
|
+
if not isinstance(value, Mapping):
|
|
968
|
+
raise DriverError("FIXTURE_INVALID", "frozen bindings root must be an object")
|
|
969
|
+
bindings = _FrozenBindings(value, request.corpus_revision)
|
|
970
|
+
if request.question != bindings.expected_question:
|
|
971
|
+
raise DriverError("FIXTURE_QUESTION_MISMATCH", "question is outside frozen fixture")
|
|
972
|
+
return bindings
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def _load_manifest(path: Path) -> tuple[Mapping[str, Any], str]:
|
|
976
|
+
try:
|
|
977
|
+
raw = path.read_bytes()
|
|
978
|
+
value = json.loads(raw)
|
|
979
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
980
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT manifest cannot be loaded") from exc
|
|
981
|
+
if not isinstance(value, Mapping) or value.get("schema_version") != MANIFEST_SCHEMA:
|
|
982
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT manifest schema is invalid")
|
|
983
|
+
upstream = value.get("upstream")
|
|
984
|
+
if not isinstance(upstream, Mapping) or upstream.get("commit") != UPSTREAM_COMMIT:
|
|
985
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT manifest revision is invalid")
|
|
986
|
+
for key in ("source_files", "allowed_model_names", "profiles", "patches"):
|
|
987
|
+
if key not in value:
|
|
988
|
+
raise DriverError("MANIFEST_INVALID", f"IRCoT manifest lacks {key}")
|
|
989
|
+
return value, _sha256_bytes(raw)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def _verify_checkout(root: Path, manifest: Mapping[str, Any]) -> None:
|
|
993
|
+
if _read_git_head(root) != UPSTREAM_COMMIT:
|
|
994
|
+
raise DriverError("REVISION_MISMATCH", "IRCoT checkout is at the wrong commit")
|
|
995
|
+
upstream = manifest["upstream"]
|
|
996
|
+
if upstream.get("license_spdx") != "Apache-2.0":
|
|
997
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT license declaration is invalid")
|
|
998
|
+
if _sha256_file(root / "LICENSE") != upstream.get("license_sha256"):
|
|
999
|
+
raise DriverError("LICENSE_MISMATCH", "IRCoT Apache-2.0 license does not match pin")
|
|
1000
|
+
sources = manifest["source_files"]
|
|
1001
|
+
if not isinstance(sources, Mapping) or not sources:
|
|
1002
|
+
raise DriverError("MANIFEST_INVALID", "IRCoT source pins are invalid")
|
|
1003
|
+
for relative, expected in sources.items():
|
|
1004
|
+
if not isinstance(expected, str) or _sha256_file(_safe_file(root, relative)) != expected:
|
|
1005
|
+
raise DriverError("SOURCE_MISMATCH", "IRCoT source does not match manifest")
|
|
1006
|
+
if upstream.get("require_clean_checkout") is True:
|
|
1007
|
+
_require_clean_checkout(root)
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def _profile_file(
|
|
1011
|
+
upstream_root: Path,
|
|
1012
|
+
worker_root: Path,
|
|
1013
|
+
profile: Mapping[str, Any],
|
|
1014
|
+
prefix: str,
|
|
1015
|
+
) -> Path:
|
|
1016
|
+
scope = profile.get(f"{prefix}_scope")
|
|
1017
|
+
relative = profile.get(f"{prefix}_path")
|
|
1018
|
+
expected = profile.get(f"{prefix}_sha256")
|
|
1019
|
+
root = upstream_root if scope == "upstream" else worker_root if scope == "worker" else None
|
|
1020
|
+
if root is None or not isinstance(expected, str):
|
|
1021
|
+
raise DriverError("MANIFEST_INVALID", f"{prefix} artifact declaration is invalid")
|
|
1022
|
+
path = _safe_file(root, relative)
|
|
1023
|
+
if _sha256_file(path) != expected:
|
|
1024
|
+
raise DriverError(f"{prefix.upper()}_MISMATCH", f"pinned {prefix} checksum differs")
|
|
1025
|
+
return path
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@contextlib.contextmanager
|
|
1029
|
+
def _working_directory(path: Path) -> Iterator[None]:
|
|
1030
|
+
previous = Path.cwd()
|
|
1031
|
+
os.chdir(path)
|
|
1032
|
+
try:
|
|
1033
|
+
yield
|
|
1034
|
+
finally:
|
|
1035
|
+
os.chdir(previous)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
@contextlib.contextmanager
|
|
1039
|
+
def _upstream_import_scope(root: Path, *, fixture_shims: bool) -> Iterator[None]:
|
|
1040
|
+
existing = {name for name in sys.modules if name == "commaqa" or name.startswith("commaqa.")}
|
|
1041
|
+
if existing:
|
|
1042
|
+
raise DriverError("IMPORT_STATE_UNSAFE", "commaqa modules were already loaded")
|
|
1043
|
+
shims = _fixture_shims() if fixture_shims else {}
|
|
1044
|
+
prior_modules = {name: sys.modules.get(name) for name in shims}
|
|
1045
|
+
for name, module in shims.items():
|
|
1046
|
+
sys.modules[name] = module
|
|
1047
|
+
sys.path.insert(0, str(root))
|
|
1048
|
+
importlib.invalidate_caches()
|
|
1049
|
+
try:
|
|
1050
|
+
yield
|
|
1051
|
+
finally:
|
|
1052
|
+
sys.path.remove(str(root))
|
|
1053
|
+
for name in tuple(sys.modules):
|
|
1054
|
+
if name == "commaqa" or name.startswith("commaqa."):
|
|
1055
|
+
del sys.modules[name]
|
|
1056
|
+
for name, previous in prior_modules.items():
|
|
1057
|
+
if previous is None:
|
|
1058
|
+
sys.modules.pop(name, None)
|
|
1059
|
+
else:
|
|
1060
|
+
sys.modules[name] = previous
|
|
1061
|
+
importlib.invalidate_caches()
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
def _fixture_shims() -> Mapping[str, types.ModuleType]:
|
|
1065
|
+
diskcache = types.ModuleType("diskcache")
|
|
1066
|
+
|
|
1067
|
+
class Cache:
|
|
1068
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
1069
|
+
del args, kwargs
|
|
1070
|
+
|
|
1071
|
+
def memoize(self, *args: Any, **kwargs: Any) -> Any:
|
|
1072
|
+
del args, kwargs
|
|
1073
|
+
|
|
1074
|
+
def decorate(function: Any) -> Any:
|
|
1075
|
+
return function
|
|
1076
|
+
|
|
1077
|
+
return decorate
|
|
1078
|
+
|
|
1079
|
+
diskcache.Cache = Cache
|
|
1080
|
+
rapidfuzz = types.ModuleType("rapidfuzz")
|
|
1081
|
+
fuzz = types.ModuleType("rapidfuzz.fuzz")
|
|
1082
|
+
|
|
1083
|
+
def ratio(left: str, right: str) -> float:
|
|
1084
|
+
return 100.0 * difflib.SequenceMatcher(None, left, right).ratio()
|
|
1085
|
+
|
|
1086
|
+
fuzz.ratio = ratio
|
|
1087
|
+
fuzz.token_sort_ratio = lambda left, right: ratio(
|
|
1088
|
+
" ".join(sorted(left.split())),
|
|
1089
|
+
" ".join(sorted(right.split())),
|
|
1090
|
+
)
|
|
1091
|
+
rapidfuzz.fuzz = fuzz
|
|
1092
|
+
ftfy = types.ModuleType("ftfy")
|
|
1093
|
+
ftfy.fix_text = lambda value: value
|
|
1094
|
+
dateutil = types.ModuleType("dateutil")
|
|
1095
|
+
dateutil_parser = types.ModuleType("dateutil.parser")
|
|
1096
|
+
|
|
1097
|
+
def unsupported_date_parse(*args: Any, **kwargs: Any) -> Any:
|
|
1098
|
+
del args, kwargs
|
|
1099
|
+
raise ValueError("date parsing is unavailable in the frozen IRCoT fixture")
|
|
1100
|
+
|
|
1101
|
+
dateutil_parser.parse = unsupported_date_parse
|
|
1102
|
+
dateutil.parser = dateutil_parser
|
|
1103
|
+
jsonnet = types.ModuleType("_jsonnet")
|
|
1104
|
+
jsonnet.evaluate_file = lambda *args, **kwargs: (_ for _ in ()).throw(
|
|
1105
|
+
RuntimeError("Jsonnet is unavailable in the frozen fixture")
|
|
1106
|
+
)
|
|
1107
|
+
nltk = types.ModuleType("nltk")
|
|
1108
|
+
nltk_corpus = types.ModuleType("nltk.corpus")
|
|
1109
|
+
nltk_stem = types.ModuleType("nltk.stem")
|
|
1110
|
+
nltk_porter = types.ModuleType("nltk.stem.porter")
|
|
1111
|
+
|
|
1112
|
+
class Stopwords:
|
|
1113
|
+
@staticmethod
|
|
1114
|
+
def words(language: str) -> list[str]:
|
|
1115
|
+
del language
|
|
1116
|
+
return []
|
|
1117
|
+
|
|
1118
|
+
class PorterStemmer:
|
|
1119
|
+
@staticmethod
|
|
1120
|
+
def stem(token: str) -> str:
|
|
1121
|
+
return token
|
|
1122
|
+
|
|
1123
|
+
nltk.word_tokenize = lambda value: value.split()
|
|
1124
|
+
nltk_corpus.stopwords = Stopwords()
|
|
1125
|
+
nltk_porter.PorterStemmer = PorterStemmer
|
|
1126
|
+
nltk.corpus = nltk_corpus
|
|
1127
|
+
nltk.stem = nltk_stem
|
|
1128
|
+
nltk_stem.porter = nltk_porter
|
|
1129
|
+
spacy = types.ModuleType("spacy")
|
|
1130
|
+
|
|
1131
|
+
class Sentence:
|
|
1132
|
+
def __init__(self, text: str) -> None:
|
|
1133
|
+
self.text = text
|
|
1134
|
+
self.text_with_ws = text
|
|
1135
|
+
|
|
1136
|
+
class Document:
|
|
1137
|
+
def __init__(self, text: str) -> None:
|
|
1138
|
+
self.sents = [Sentence(text)] if text else []
|
|
1139
|
+
|
|
1140
|
+
spacy.load = lambda name: lambda text: Document(text)
|
|
1141
|
+
return {
|
|
1142
|
+
"diskcache": diskcache,
|
|
1143
|
+
"rapidfuzz": rapidfuzz,
|
|
1144
|
+
"rapidfuzz.fuzz": fuzz,
|
|
1145
|
+
"ftfy": ftfy,
|
|
1146
|
+
"dateutil": dateutil,
|
|
1147
|
+
"dateutil.parser": dateutil_parser,
|
|
1148
|
+
"_jsonnet": jsonnet,
|
|
1149
|
+
"nltk": nltk,
|
|
1150
|
+
"nltk.corpus": nltk_corpus,
|
|
1151
|
+
"nltk.stem": nltk_stem,
|
|
1152
|
+
"nltk.stem.porter": nltk_porter,
|
|
1153
|
+
"spacy": spacy,
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def _read_git_head(root: Path) -> str:
|
|
1158
|
+
try:
|
|
1159
|
+
head = (root / ".git" / "HEAD").read_text(encoding="utf-8").strip()
|
|
1160
|
+
if head.startswith("ref: "):
|
|
1161
|
+
head = (
|
|
1162
|
+
_safe_file(root / ".git", head.removeprefix("ref: "))
|
|
1163
|
+
.read_text(encoding="utf-8")
|
|
1164
|
+
.strip()
|
|
1165
|
+
)
|
|
1166
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
1167
|
+
raise DriverError("REVISION_MISMATCH", "IRCoT checkout HEAD is unavailable") from exc
|
|
1168
|
+
if len(head) != 40 or any(character not in "0123456789abcdef" for character in head):
|
|
1169
|
+
raise DriverError("REVISION_MISMATCH", "IRCoT checkout HEAD is invalid")
|
|
1170
|
+
return head
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def _require_clean_checkout(root: Path) -> None:
|
|
1174
|
+
try:
|
|
1175
|
+
result = subprocess.run(
|
|
1176
|
+
(
|
|
1177
|
+
"git",
|
|
1178
|
+
"-C",
|
|
1179
|
+
str(root),
|
|
1180
|
+
"status",
|
|
1181
|
+
"--porcelain=v1",
|
|
1182
|
+
"--untracked-files=all",
|
|
1183
|
+
),
|
|
1184
|
+
check=False,
|
|
1185
|
+
capture_output=True,
|
|
1186
|
+
text=True,
|
|
1187
|
+
timeout=10,
|
|
1188
|
+
env={"PATH": os.environ.get("PATH", "")},
|
|
1189
|
+
)
|
|
1190
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
1191
|
+
raise DriverError("GIT_VERIFICATION_FAILED", "IRCoT git check is unavailable") from exc
|
|
1192
|
+
if result.returncode != 0:
|
|
1193
|
+
raise DriverError("GIT_VERIFICATION_FAILED", "IRCoT git check failed")
|
|
1194
|
+
if result.stdout.strip():
|
|
1195
|
+
raise DriverError("DIRTY_CHECKOUT", "IRCoT checkout contains uncommitted files")
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def _safe_file(root: Path, relative: Any) -> Path:
|
|
1199
|
+
if not isinstance(relative, str) or not relative or "\\" in relative or "\x00" in relative:
|
|
1200
|
+
raise DriverError("PATH_INVALID", "manifest path is invalid")
|
|
1201
|
+
candidate = (root.resolve() / relative).resolve()
|
|
1202
|
+
try:
|
|
1203
|
+
candidate.relative_to(root.resolve())
|
|
1204
|
+
except ValueError as exc:
|
|
1205
|
+
raise DriverError("PATH_INVALID", "manifest path escapes its artifact root") from exc
|
|
1206
|
+
if not candidate.is_file():
|
|
1207
|
+
raise DriverError("PATH_INVALID", "manifest path is not a file")
|
|
1208
|
+
return candidate
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def _canonical_mapping(value: Any, label: str) -> Mapping[str, Any]:
|
|
1212
|
+
if not isinstance(value, Mapping):
|
|
1213
|
+
raise DriverError("INVALID_REQUEST", f"{label} must be a mapping")
|
|
1214
|
+
_reject_secret_fields(value)
|
|
1215
|
+
try:
|
|
1216
|
+
encoded = _canonical_json(value)
|
|
1217
|
+
except (TypeError, ValueError) as exc:
|
|
1218
|
+
raise DriverError("INVALID_REQUEST", f"{label} must be canonical JSON") from exc
|
|
1219
|
+
if len(encoded.encode("utf-8")) > 128 * 1024:
|
|
1220
|
+
raise DriverError("INVALID_REQUEST", f"{label} exceeds size limit")
|
|
1221
|
+
return json.loads(encoded)
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def _reject_secret_fields(value: Mapping[str, Any]) -> None:
|
|
1225
|
+
pending: list[Any] = [value]
|
|
1226
|
+
while pending:
|
|
1227
|
+
current = pending.pop()
|
|
1228
|
+
if isinstance(current, Mapping):
|
|
1229
|
+
for key, item in current.items():
|
|
1230
|
+
if not isinstance(key, str):
|
|
1231
|
+
raise DriverError("INVALID_REQUEST", "fingerprint keys must be strings")
|
|
1232
|
+
normalized = key.lower().replace("-", "_")
|
|
1233
|
+
if (
|
|
1234
|
+
any(part in normalized for part in _SECRET_KEY_PARTS)
|
|
1235
|
+
or normalized in _SECRET_TOKEN_KEYS
|
|
1236
|
+
):
|
|
1237
|
+
raise DriverError("SECRET_REJECTED", "fingerprints must not contain secrets")
|
|
1238
|
+
pending.append(item)
|
|
1239
|
+
elif isinstance(current, Sequence) and not isinstance(current, (str, bytes, bytearray)):
|
|
1240
|
+
pending.extend(current)
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def _mapping_sequence(value: Any, label: str) -> tuple[Mapping[str, Any], ...]:
|
|
1244
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
1245
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", f"{label} must be a sequence")
|
|
1246
|
+
result = tuple(value)
|
|
1247
|
+
if not all(isinstance(item, Mapping) for item in result):
|
|
1248
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", f"{label} must contain mappings")
|
|
1249
|
+
return result # type: ignore[return-value]
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def _required_text(value: Mapping[str, Any], key: str, label: str) -> str:
|
|
1253
|
+
item = value.get(key)
|
|
1254
|
+
if not isinstance(item, str) or not item.strip():
|
|
1255
|
+
raise DriverError("FIXTURE_INVALID", f"{label}.{key} must be text")
|
|
1256
|
+
return item
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
def _bounded_text(value: Any, label: str, limit: int) -> str:
|
|
1260
|
+
if not isinstance(value, str) or not value.strip() or len(value) > limit or "\x00" in value:
|
|
1261
|
+
raise DriverError("INVALID_REQUEST", f"{label} must be bounded non-empty text")
|
|
1262
|
+
return value
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
def _positive_integer(value: Any, label: str, maximum: int) -> int:
|
|
1266
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= maximum:
|
|
1267
|
+
raise DriverError("INVALID_REQUEST", f"{label} must be a bounded positive integer")
|
|
1268
|
+
return value
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def _nonnegative_integer(value: Any, label: str) -> int:
|
|
1272
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
1273
|
+
raise DriverError("FIXTURE_INVALID", f"{label} must be non-negative")
|
|
1274
|
+
return value
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
def _optional_finite_number(value: Any, label: str) -> float | None:
|
|
1278
|
+
if value is None:
|
|
1279
|
+
return None
|
|
1280
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
1281
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", f"{label} must be numeric")
|
|
1282
|
+
result = float(value)
|
|
1283
|
+
if not math.isfinite(result):
|
|
1284
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", f"{label} must be finite")
|
|
1285
|
+
return result
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
def _boolean(value: Any, label: str) -> bool:
|
|
1289
|
+
if not isinstance(value, bool):
|
|
1290
|
+
raise DriverError("INVALID_REQUEST", f"{label} must be boolean")
|
|
1291
|
+
return value
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _enum_text(value: Any, label: str, allowed: set[str]) -> str:
|
|
1295
|
+
if not isinstance(value, str) or value not in allowed:
|
|
1296
|
+
raise DriverError("INVALID_REQUEST", f"{label} is unsupported")
|
|
1297
|
+
return value
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def _string_set(value: Any, label: str) -> frozenset[str]:
|
|
1301
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
1302
|
+
raise DriverError("MANIFEST_INVALID", f"{label} must be a sequence")
|
|
1303
|
+
result = tuple(value)
|
|
1304
|
+
if not all(isinstance(item, str) and item for item in result):
|
|
1305
|
+
raise DriverError("MANIFEST_INVALID", f"{label} must contain strings")
|
|
1306
|
+
return frozenset(result)
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
def _dependency_error(error: ModuleNotFoundError) -> DriverError:
|
|
1310
|
+
name = error.name if isinstance(error.name, str) else "optional_dependency"
|
|
1311
|
+
return DriverError("DEPENDENCY_MISSING", f"required upstream dependency is missing: {name}")
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
def _canonical_hash(value: Any) -> str:
|
|
1315
|
+
return _sha256_bytes(_canonical_json(value).encode("utf-8"))
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def _canonical_json(value: Any) -> str:
|
|
1319
|
+
return json.dumps(
|
|
1320
|
+
value,
|
|
1321
|
+
ensure_ascii=False,
|
|
1322
|
+
sort_keys=True,
|
|
1323
|
+
separators=(",", ":"),
|
|
1324
|
+
allow_nan=False,
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
def _sha256_file(path: Path) -> str:
|
|
1329
|
+
try:
|
|
1330
|
+
return _sha256_bytes(path.read_bytes())
|
|
1331
|
+
except OSError as exc:
|
|
1332
|
+
raise DriverError("ARTIFACT_MISSING", "pinned IRCoT artifact is unavailable") from exc
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
def _sha256_bytes(value: bytes) -> str:
|
|
1336
|
+
return hashlib.sha256(value).hexdigest()
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
def serve_jsonl(driver: IRCoTDriver, source: TextIO, sink: TextIO) -> int:
|
|
1340
|
+
for line in source:
|
|
1341
|
+
if len(line.encode("utf-8")) > MAX_REQUEST_BYTES:
|
|
1342
|
+
response: Mapping[str, Any] = {
|
|
1343
|
+
"status": "error",
|
|
1344
|
+
"failure_code": "REQUEST_TOO_LARGE",
|
|
1345
|
+
"message": "IRCoT request exceeds byte limit",
|
|
1346
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
1347
|
+
"consumed": False,
|
|
1348
|
+
}
|
|
1349
|
+
else:
|
|
1350
|
+
try:
|
|
1351
|
+
value = json.loads(line)
|
|
1352
|
+
except json.JSONDecodeError:
|
|
1353
|
+
response = {
|
|
1354
|
+
"status": "error",
|
|
1355
|
+
"failure_code": "INVALID_JSON",
|
|
1356
|
+
"message": "IRCoT request is not valid JSON",
|
|
1357
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
1358
|
+
"consumed": False,
|
|
1359
|
+
}
|
|
1360
|
+
else:
|
|
1361
|
+
response = driver.safe_call(value)
|
|
1362
|
+
sink.write(_canonical_json(response) + "\n")
|
|
1363
|
+
sink.flush()
|
|
1364
|
+
return 0
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
def main() -> int:
|
|
1368
|
+
driver = IRCoTDriver(isolated_process=True)
|
|
1369
|
+
return serve_jsonl(driver, sys.stdin, sys.stdout)
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
if __name__ == "__main__":
|
|
1373
|
+
raise SystemExit(main())
|