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,1459 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pinned, user-mounted ReFormeR inference worker.
|
|
3
|
+
|
|
4
|
+
No upstream source is vendored. The worker verifies a caller-mounted checkout,
|
|
5
|
+
loads ``Reformulator`` from that checkout, and directly calls the official
|
|
6
|
+
``select_best_pattern`` and ``apply_pattern`` methods. AgentInputLab only
|
|
7
|
+
supplies a bounded OpenAI-compatible model seam and materializes the documented
|
|
8
|
+
paper/repository output-semantics divergence.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import ast
|
|
15
|
+
import hashlib
|
|
16
|
+
import importlib.util
|
|
17
|
+
import json
|
|
18
|
+
import math
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import types
|
|
24
|
+
import urllib.error
|
|
25
|
+
import urllib.parse
|
|
26
|
+
import urllib.request
|
|
27
|
+
from collections.abc import Mapping, Sequence
|
|
28
|
+
from contextlib import redirect_stdout
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path, PurePosixPath
|
|
31
|
+
from types import SimpleNamespace
|
|
32
|
+
from typing import Any, Protocol, TextIO
|
|
33
|
+
|
|
34
|
+
UPSTREAM_COMMIT = "72e52450a922bc0051b5b39c3a6307186a555137"
|
|
35
|
+
METHOD_ID = "reformer"
|
|
36
|
+
PROTOCOL_VERSION = "agentinputlab.reformer.v2"
|
|
37
|
+
ADAPTER_REVISION = "unique-library-name-reference-and-failure-usage.v2"
|
|
38
|
+
MANIFEST_SCHEMA = "agentinputlab.reformer.compatibility.v1"
|
|
39
|
+
DEFAULT_MANIFEST = Path(__file__).with_name("compatibility_manifest.json")
|
|
40
|
+
DEFAULT_PATTERN_PATH = "reformer/patterns/extracted_patterns.json"
|
|
41
|
+
PROMPT_PATH = "reformer/prompts.json"
|
|
42
|
+
CORE_PATH = "reformer/core/reformer.py"
|
|
43
|
+
PROMPT_MANAGER_PATH = "reformer/prompt_manager.py"
|
|
44
|
+
INDUCTION_PATH = "reformer/patterns/iterative_pattern_extraction.py"
|
|
45
|
+
MAX_REQUEST_BYTES = 4 * 1024 * 1024
|
|
46
|
+
MAX_RESPONSE_BYTES = 4 * 1024 * 1024
|
|
47
|
+
MAX_CONTEXT_BLOCKS = 256
|
|
48
|
+
MAX_TEXT_CHARS = 1_000_000
|
|
49
|
+
_HASH_RE = re.compile(r"[0-9a-f]{64}")
|
|
50
|
+
_COMMIT_RE = re.compile(r"[0-9a-f]{40}")
|
|
51
|
+
_REF_RE = re.compile(r"refs/[A-Za-z0-9._/-]+")
|
|
52
|
+
_ENV_NAME_RE = re.compile(r"[A-Z_][A-Z0-9_]*")
|
|
53
|
+
_ROOT_FIELDS = frozenset(
|
|
54
|
+
{
|
|
55
|
+
"method_id",
|
|
56
|
+
"release_id",
|
|
57
|
+
"upstream_commit",
|
|
58
|
+
"state_hash",
|
|
59
|
+
"payload",
|
|
60
|
+
"dependency_fingerprints",
|
|
61
|
+
"minimum_reservation",
|
|
62
|
+
"protocol_version",
|
|
63
|
+
"request_id",
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
_PAYLOAD_FIELDS = frozenset(
|
|
67
|
+
{
|
|
68
|
+
"task_id",
|
|
69
|
+
"query",
|
|
70
|
+
"context",
|
|
71
|
+
"top_document_count",
|
|
72
|
+
"pattern_artifact_hash",
|
|
73
|
+
"prompt_artifact_hash",
|
|
74
|
+
"semantics",
|
|
75
|
+
"pattern_mode",
|
|
76
|
+
"manual_pattern_id",
|
|
77
|
+
"source_license_acknowledged",
|
|
78
|
+
"controls",
|
|
79
|
+
"seed",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
_CONTROL_FIELDS = frozenset({"max_model_calls", "max_tokens", "max_cost_usd"})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DriverError(RuntimeError):
|
|
86
|
+
"""Expected, secret-free worker failure."""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
code: str,
|
|
91
|
+
message: str,
|
|
92
|
+
*,
|
|
93
|
+
consumed: bool = False,
|
|
94
|
+
usage: Mapping[str, int | float] | None = None,
|
|
95
|
+
provider_usage: Mapping[str, int] | None = None,
|
|
96
|
+
diagnostics: Mapping[str, Any] | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
super().__init__(message)
|
|
99
|
+
self.code = code
|
|
100
|
+
self.consumed = consumed
|
|
101
|
+
self.usage = dict(usage) if usage is not None else None
|
|
102
|
+
self.provider_usage = dict(provider_usage) if provider_usage is not None else None
|
|
103
|
+
self.diagnostics = dict(diagnostics or {})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True, slots=True)
|
|
107
|
+
class CompatibilityPolicy:
|
|
108
|
+
commit: str
|
|
109
|
+
license_status: str
|
|
110
|
+
license_file_present: bool
|
|
111
|
+
license_evidence_path: str
|
|
112
|
+
license_evidence_sha256: str
|
|
113
|
+
require_clean_checkout: bool
|
|
114
|
+
source_hashes: Mapping[str, str]
|
|
115
|
+
profiles: Mapping[str, Mapping[str, str]]
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_mapping(cls, value: Mapping[str, Any]) -> CompatibilityPolicy:
|
|
119
|
+
if value.get("schema_version") != MANIFEST_SCHEMA:
|
|
120
|
+
raise DriverError("INVALID_MANIFEST", "ReFormeR manifest schema mismatch")
|
|
121
|
+
upstream = _mapping(value.get("upstream"), "manifest upstream")
|
|
122
|
+
source_files = _mapping(value.get("source_files"), "manifest source_files")
|
|
123
|
+
profiles = _mapping(value.get("profiles"), "manifest profiles")
|
|
124
|
+
if upstream.get("commit") != UPSTREAM_COMMIT:
|
|
125
|
+
raise DriverError("INVALID_MANIFEST", "manifest commit is not approved")
|
|
126
|
+
if upstream.get("license_spdx") != "NOASSERTION":
|
|
127
|
+
raise DriverError("INVALID_MANIFEST", "ReFormeR license must remain NOASSERTION")
|
|
128
|
+
if upstream.get("license_file_present") is not False:
|
|
129
|
+
raise DriverError("INVALID_MANIFEST", "manifest must record the absent license file")
|
|
130
|
+
clean = upstream.get("require_clean_checkout")
|
|
131
|
+
if not isinstance(clean, bool):
|
|
132
|
+
raise DriverError("INVALID_MANIFEST", "clean-checkout flag must be boolean")
|
|
133
|
+
hashes: dict[str, str] = {}
|
|
134
|
+
for raw_path, raw_hash in source_files.items():
|
|
135
|
+
path = _safe_relative_path(raw_path, "manifest source path")
|
|
136
|
+
hashes[path] = _hash(raw_hash, f"manifest hash for {path}")
|
|
137
|
+
required = {
|
|
138
|
+
"README.md",
|
|
139
|
+
CORE_PATH,
|
|
140
|
+
PROMPT_MANAGER_PATH,
|
|
141
|
+
PROMPT_PATH,
|
|
142
|
+
DEFAULT_PATTERN_PATH,
|
|
143
|
+
INDUCTION_PATH,
|
|
144
|
+
}
|
|
145
|
+
if not required <= set(hashes):
|
|
146
|
+
raise DriverError("INVALID_MANIFEST", "manifest omits required upstream sources")
|
|
147
|
+
normalized_profiles: dict[str, Mapping[str, str]] = {}
|
|
148
|
+
expected = {
|
|
149
|
+
"rewrite.reformer.repo_native_r_only": ("r_only", "automatic"),
|
|
150
|
+
"rewrite.reformer.paper_spec_q_concat_r": ("q_concat_r", "automatic"),
|
|
151
|
+
"rewrite.reformer.controlled_r_only": ("r_only", "manual"),
|
|
152
|
+
"rewrite.reformer.controlled_q_concat_r": ("q_concat_r", "manual"),
|
|
153
|
+
}
|
|
154
|
+
if set(profiles) != set(expected):
|
|
155
|
+
raise DriverError("INVALID_MANIFEST", "manifest must define exactly four profiles")
|
|
156
|
+
for release_id, identity in expected.items():
|
|
157
|
+
item = _mapping(profiles[release_id], f"manifest profile {release_id}")
|
|
158
|
+
_exact_keys(item, {"semantics", "pattern_mode"}, "manifest profile")
|
|
159
|
+
if (item["semantics"], item["pattern_mode"]) != identity:
|
|
160
|
+
raise DriverError("INVALID_MANIFEST", "manifest profile identity mismatch")
|
|
161
|
+
normalized_profiles[release_id] = dict(item)
|
|
162
|
+
evidence = _safe_relative_path(
|
|
163
|
+
upstream.get("license_evidence_path"), "license evidence path"
|
|
164
|
+
)
|
|
165
|
+
return cls(
|
|
166
|
+
commit=UPSTREAM_COMMIT,
|
|
167
|
+
license_status="NOASSERTION",
|
|
168
|
+
license_file_present=False,
|
|
169
|
+
license_evidence_path=evidence,
|
|
170
|
+
license_evidence_sha256=_hash(
|
|
171
|
+
upstream.get("license_evidence_sha256"), "license evidence hash"
|
|
172
|
+
),
|
|
173
|
+
require_clean_checkout=clean,
|
|
174
|
+
source_hashes=hashes,
|
|
175
|
+
profiles=normalized_profiles,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _fingerprint_model(value: Mapping[str, Any]) -> str | None:
|
|
180
|
+
"""Resolve model identity from native or AgentInputLab backend fingerprints."""
|
|
181
|
+
|
|
182
|
+
for key in ("model", "model_id"):
|
|
183
|
+
candidate = value.get(key)
|
|
184
|
+
if isinstance(candidate, str) and candidate.strip():
|
|
185
|
+
return candidate
|
|
186
|
+
nested = value.get("backend")
|
|
187
|
+
if isinstance(nested, Mapping):
|
|
188
|
+
return _fingerprint_model(nested)
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@dataclass(frozen=True, slots=True)
|
|
193
|
+
class BackendConfig:
|
|
194
|
+
base_url: str
|
|
195
|
+
api_key_env: str
|
|
196
|
+
model: str
|
|
197
|
+
fingerprint: Mapping[str, Any]
|
|
198
|
+
extra_body: Mapping[str, Any]
|
|
199
|
+
timeout_seconds: float
|
|
200
|
+
max_input_tokens_per_call: int
|
|
201
|
+
max_output_tokens: int
|
|
202
|
+
temperature: float
|
|
203
|
+
input_cost_per_million: float
|
|
204
|
+
output_cost_per_million: float
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def from_json(cls, raw: str) -> BackendConfig:
|
|
208
|
+
value = _mapping(_loads_strict_json(raw), "backend configuration")
|
|
209
|
+
_exact_keys(
|
|
210
|
+
value,
|
|
211
|
+
{
|
|
212
|
+
"base_url",
|
|
213
|
+
"api_key_env",
|
|
214
|
+
"model",
|
|
215
|
+
"fingerprint",
|
|
216
|
+
"timeout_seconds",
|
|
217
|
+
"extra_body",
|
|
218
|
+
"max_input_tokens_per_call",
|
|
219
|
+
"max_output_tokens",
|
|
220
|
+
"temperature",
|
|
221
|
+
"input_cost_per_million",
|
|
222
|
+
"output_cost_per_million",
|
|
223
|
+
},
|
|
224
|
+
"backend configuration",
|
|
225
|
+
)
|
|
226
|
+
model = _text(value["model"], "backend model")
|
|
227
|
+
fingerprint = dict(_mapping(value["fingerprint"], "backend fingerprint"))
|
|
228
|
+
if not fingerprint or _fingerprint_model(fingerprint) != model:
|
|
229
|
+
raise DriverError(
|
|
230
|
+
"INVALID_BACKEND_CONFIG", "backend fingerprint must bind the exact model"
|
|
231
|
+
)
|
|
232
|
+
extra_body = dict(_mapping(value["extra_body"], "backend extra_body"))
|
|
233
|
+
reserved = {"model", "messages", "temperature", "seed", "max_tokens"}
|
|
234
|
+
if reserved & set(extra_body):
|
|
235
|
+
raise DriverError(
|
|
236
|
+
"INVALID_BACKEND_CONFIG", "backend extra_body overrides reserved fields"
|
|
237
|
+
)
|
|
238
|
+
_canonical_json(extra_body)
|
|
239
|
+
api_key_env = _text(value["api_key_env"], "backend api_key_env")
|
|
240
|
+
if _ENV_NAME_RE.fullmatch(api_key_env) is None:
|
|
241
|
+
raise DriverError("INVALID_BACKEND_CONFIG", "api_key_env is invalid")
|
|
242
|
+
temperature = _nonnegative_float(value["temperature"], "backend temperature")
|
|
243
|
+
if temperature > 2:
|
|
244
|
+
raise DriverError("INVALID_BACKEND_CONFIG", "backend temperature exceeds 2")
|
|
245
|
+
return cls(
|
|
246
|
+
base_url=_validate_base_url(_text(value["base_url"], "backend base_url")),
|
|
247
|
+
api_key_env=api_key_env,
|
|
248
|
+
model=model,
|
|
249
|
+
fingerprint=fingerprint,
|
|
250
|
+
extra_body=extra_body,
|
|
251
|
+
timeout_seconds=_positive_float(value["timeout_seconds"], "backend timeout"),
|
|
252
|
+
max_input_tokens_per_call=_positive_int(
|
|
253
|
+
value["max_input_tokens_per_call"],
|
|
254
|
+
"backend max_input_tokens_per_call",
|
|
255
|
+
),
|
|
256
|
+
max_output_tokens=_positive_int(
|
|
257
|
+
value["max_output_tokens"], "backend max_output_tokens"
|
|
258
|
+
),
|
|
259
|
+
temperature=temperature,
|
|
260
|
+
input_cost_per_million=_nonnegative_float(
|
|
261
|
+
value["input_cost_per_million"], "backend input price"
|
|
262
|
+
),
|
|
263
|
+
output_cost_per_million=_nonnegative_float(
|
|
264
|
+
value["output_cost_per_million"], "backend output price"
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@dataclass(frozen=True, slots=True)
|
|
270
|
+
class CompletionResult:
|
|
271
|
+
text: str
|
|
272
|
+
input_tokens: int
|
|
273
|
+
output_tokens: int
|
|
274
|
+
finish_reason: str | None = None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class CompletionBackend(Protocol):
|
|
278
|
+
def fingerprint(self) -> Mapping[str, Any]: ...
|
|
279
|
+
|
|
280
|
+
def complete(
|
|
281
|
+
self,
|
|
282
|
+
*,
|
|
283
|
+
messages: Sequence[Mapping[str, str]],
|
|
284
|
+
seed: int,
|
|
285
|
+
temperature: float,
|
|
286
|
+
max_output_tokens: int,
|
|
287
|
+
) -> CompletionResult: ...
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class OpenAICompatibleBackend:
|
|
291
|
+
def __init__(self, config: BackendConfig) -> None:
|
|
292
|
+
self.config = config
|
|
293
|
+
|
|
294
|
+
def fingerprint(self) -> Mapping[str, Any]:
|
|
295
|
+
return self.config.fingerprint
|
|
296
|
+
|
|
297
|
+
def complete(
|
|
298
|
+
self,
|
|
299
|
+
*,
|
|
300
|
+
messages: Sequence[Mapping[str, str]],
|
|
301
|
+
seed: int,
|
|
302
|
+
temperature: float,
|
|
303
|
+
max_output_tokens: int,
|
|
304
|
+
) -> CompletionResult:
|
|
305
|
+
key = os.environ.get(self.config.api_key_env)
|
|
306
|
+
if not key:
|
|
307
|
+
raise DriverError("BACKEND_CREDENTIAL_UNAVAILABLE", "backend credential unavailable")
|
|
308
|
+
body = {
|
|
309
|
+
"model": self.config.model,
|
|
310
|
+
"messages": list(messages),
|
|
311
|
+
"temperature": temperature,
|
|
312
|
+
"seed": seed,
|
|
313
|
+
"max_tokens": max_output_tokens,
|
|
314
|
+
}
|
|
315
|
+
body.update(self.config.extra_body)
|
|
316
|
+
request = urllib.request.Request(
|
|
317
|
+
f"{self.config.base_url}/chat/completions",
|
|
318
|
+
data=_canonical_json(body).encode("utf-8"),
|
|
319
|
+
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
|
320
|
+
method="POST",
|
|
321
|
+
)
|
|
322
|
+
try:
|
|
323
|
+
with urllib.request.urlopen(request, timeout=self.config.timeout_seconds) as response:
|
|
324
|
+
raw = response.read(MAX_RESPONSE_BYTES + 1)
|
|
325
|
+
except (OSError, urllib.error.HTTPError, urllib.error.URLError) as exc:
|
|
326
|
+
raise DriverError(
|
|
327
|
+
"BACKEND_CALL_FAILED",
|
|
328
|
+
"backend completion request failed",
|
|
329
|
+
consumed=True,
|
|
330
|
+
) from exc
|
|
331
|
+
if len(raw) > MAX_RESPONSE_BYTES:
|
|
332
|
+
raise DriverError(
|
|
333
|
+
"BACKEND_RESPONSE_TOO_LARGE",
|
|
334
|
+
"backend response exceeded limit",
|
|
335
|
+
consumed=True,
|
|
336
|
+
)
|
|
337
|
+
provider_usage = None
|
|
338
|
+
diagnostics: dict[str, Any] = {"response_sha256": _sha256_bytes(raw)}
|
|
339
|
+
try:
|
|
340
|
+
root = _mapping(_loads_strict_json(raw.decode("utf-8")), "backend response")
|
|
341
|
+
usage = _mapping(root.get("usage"), "backend usage")
|
|
342
|
+
provider_usage = {
|
|
343
|
+
"input_tokens": _nonnegative_int(usage.get("prompt_tokens"), "prompt_tokens"),
|
|
344
|
+
"output_tokens": _nonnegative_int(usage.get("completion_tokens"), "completion_tokens"),
|
|
345
|
+
}
|
|
346
|
+
if root.get("model") not in {None, self.config.model}:
|
|
347
|
+
raise DriverError("BACKEND_IDENTITY_MISMATCH", "provider returned another model")
|
|
348
|
+
choices = root.get("choices")
|
|
349
|
+
if (
|
|
350
|
+
not isinstance(choices, Sequence)
|
|
351
|
+
or isinstance(choices, (str, bytes))
|
|
352
|
+
or len(choices) != 1
|
|
353
|
+
):
|
|
354
|
+
raise DriverError("BACKEND_OUTPUT_INVALID", "backend must return one choice")
|
|
355
|
+
choice = _mapping(choices[0], "backend choice")
|
|
356
|
+
finish_reason = choice.get("finish_reason")
|
|
357
|
+
if isinstance(finish_reason, str) and finish_reason in {
|
|
358
|
+
"stop", "length", "content_filter", "tool_calls"
|
|
359
|
+
}:
|
|
360
|
+
diagnostics["finish_reason"] = finish_reason
|
|
361
|
+
message = _mapping(choice.get("message"), "message")
|
|
362
|
+
text = message.get("content")
|
|
363
|
+
diagnostics["content_type"] = type(text).__name__
|
|
364
|
+
if text is None or (isinstance(text, str) and not text.strip()):
|
|
365
|
+
diagnostics["failure_kind"] = (
|
|
366
|
+
"empty_final_at_output_limit" if finish_reason == "length" else "empty_final"
|
|
367
|
+
)
|
|
368
|
+
raise DriverError("BACKEND_OUTPUT_INVALID", "backend returned no final text")
|
|
369
|
+
if not isinstance(text, str):
|
|
370
|
+
raise DriverError("BACKEND_OUTPUT_INVALID", "backend content must be text")
|
|
371
|
+
return CompletionResult(
|
|
372
|
+
text=text,
|
|
373
|
+
input_tokens=provider_usage["input_tokens"],
|
|
374
|
+
output_tokens=provider_usage["output_tokens"],
|
|
375
|
+
finish_reason=finish_reason if isinstance(finish_reason, str) else None,
|
|
376
|
+
)
|
|
377
|
+
except (DriverError, UnicodeDecodeError) as exc:
|
|
378
|
+
if isinstance(exc, DriverError):
|
|
379
|
+
exc.consumed = True
|
|
380
|
+
exc.provider_usage = provider_usage
|
|
381
|
+
exc.diagnostics.update(diagnostics)
|
|
382
|
+
raise
|
|
383
|
+
raise DriverError(
|
|
384
|
+
"BACKEND_OUTPUT_INVALID",
|
|
385
|
+
"backend response must be UTF-8 JSON",
|
|
386
|
+
consumed=True,
|
|
387
|
+
) from exc
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@dataclass(frozen=True, slots=True)
|
|
391
|
+
class Controls:
|
|
392
|
+
max_model_calls: int
|
|
393
|
+
max_tokens: int
|
|
394
|
+
max_cost_usd: float
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@dataclass(frozen=True, slots=True)
|
|
398
|
+
class ValidatedRequest:
|
|
399
|
+
request_id: str
|
|
400
|
+
release_id: str
|
|
401
|
+
query: str
|
|
402
|
+
documents: tuple[Mapping[str, str], ...]
|
|
403
|
+
pattern_hash: str
|
|
404
|
+
semantics: str
|
|
405
|
+
pattern_mode: str
|
|
406
|
+
manual_pattern_id: str
|
|
407
|
+
controls: Controls
|
|
408
|
+
seed: int
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class UsageTracker:
|
|
412
|
+
def __init__(self, controls: Controls, config: BackendConfig) -> None:
|
|
413
|
+
self.controls = controls
|
|
414
|
+
self.config = config
|
|
415
|
+
self.model_calls = 0
|
|
416
|
+
self.input_tokens = 0
|
|
417
|
+
self.output_tokens = 0
|
|
418
|
+
self.cost_usd = 0.0
|
|
419
|
+
self.stages: list[dict[str, Any]] = []
|
|
420
|
+
|
|
421
|
+
def complete(
|
|
422
|
+
self,
|
|
423
|
+
backend: CompletionBackend,
|
|
424
|
+
*,
|
|
425
|
+
stage: str,
|
|
426
|
+
messages: Sequence[Mapping[str, str]],
|
|
427
|
+
seed: int,
|
|
428
|
+
) -> str:
|
|
429
|
+
if self.model_calls >= self.controls.max_model_calls:
|
|
430
|
+
raise DriverError("MODEL_CALL_BUDGET_EXCEEDED", "ReFormeR max_model_calls exhausted")
|
|
431
|
+
token_ceiling = self.config.max_input_tokens_per_call + self.config.max_output_tokens
|
|
432
|
+
remaining_tokens = self.controls.max_tokens - self.input_tokens - self.output_tokens
|
|
433
|
+
if token_ceiling > remaining_tokens:
|
|
434
|
+
raise DriverError(
|
|
435
|
+
"TOKEN_BUDGET_EXCEEDED",
|
|
436
|
+
"ReFormeR attested input/output ceiling exceeds the remaining token budget",
|
|
437
|
+
)
|
|
438
|
+
call_cost_ceiling = (
|
|
439
|
+
self.config.max_input_tokens_per_call * self.config.input_cost_per_million
|
|
440
|
+
+ self.config.max_output_tokens * self.config.output_cost_per_million
|
|
441
|
+
) / 1_000_000
|
|
442
|
+
if self.cost_usd + call_cost_ceiling > self.controls.max_cost_usd:
|
|
443
|
+
raise DriverError(
|
|
444
|
+
"COST_BUDGET_EXCEEDED",
|
|
445
|
+
"ReFormeR attested provider-price ceiling exceeds the remaining cost budget",
|
|
446
|
+
)
|
|
447
|
+
self.model_calls += 1
|
|
448
|
+
try:
|
|
449
|
+
result = backend.complete(
|
|
450
|
+
messages=messages,
|
|
451
|
+
seed=seed,
|
|
452
|
+
temperature=self.config.temperature,
|
|
453
|
+
max_output_tokens=self.config.max_output_tokens,
|
|
454
|
+
)
|
|
455
|
+
except DriverError as exc:
|
|
456
|
+
if exc.code == "BACKEND_CREDENTIAL_UNAVAILABLE" and not exc.consumed:
|
|
457
|
+
self.model_calls -= 1
|
|
458
|
+
else:
|
|
459
|
+
exc.consumed = True
|
|
460
|
+
if exc.provider_usage is not None:
|
|
461
|
+
self._add_usage(
|
|
462
|
+
exc.provider_usage["input_tokens"], exc.provider_usage["output_tokens"]
|
|
463
|
+
)
|
|
464
|
+
self.stages.append(
|
|
465
|
+
{
|
|
466
|
+
"index": len(self.stages),
|
|
467
|
+
"stage": stage,
|
|
468
|
+
"seed": seed,
|
|
469
|
+
"failure_code": exc.code,
|
|
470
|
+
**exc.diagnostics,
|
|
471
|
+
"provider_usage": exc.provider_usage,
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
exc.usage = self.as_dict()
|
|
475
|
+
raise
|
|
476
|
+
if not isinstance(result, CompletionResult):
|
|
477
|
+
raise DriverError(
|
|
478
|
+
"BACKEND_OUTPUT_INVALID",
|
|
479
|
+
"backend returned invalid completion",
|
|
480
|
+
consumed=True,
|
|
481
|
+
usage=self.as_dict(),
|
|
482
|
+
)
|
|
483
|
+
input_tokens = _nonnegative_int(result.input_tokens, "completion input_tokens")
|
|
484
|
+
output_tokens = _nonnegative_int(result.output_tokens, "completion output_tokens")
|
|
485
|
+
self._add_usage(input_tokens, output_tokens)
|
|
486
|
+
if (
|
|
487
|
+
input_tokens > self.config.max_input_tokens_per_call
|
|
488
|
+
or output_tokens > self.config.max_output_tokens
|
|
489
|
+
):
|
|
490
|
+
raise DriverError(
|
|
491
|
+
"BACKEND_USAGE_ATTESTATION_EXCEEDED",
|
|
492
|
+
"ReFormeR provider usage exceeded the startup token attestation",
|
|
493
|
+
consumed=True,
|
|
494
|
+
usage=self.as_dict(),
|
|
495
|
+
)
|
|
496
|
+
if self.input_tokens + self.output_tokens > self.controls.max_tokens:
|
|
497
|
+
raise DriverError(
|
|
498
|
+
"TOKEN_BUDGET_EXCEEDED",
|
|
499
|
+
"ReFormeR max_tokens exceeded",
|
|
500
|
+
consumed=True,
|
|
501
|
+
usage=self.as_dict(),
|
|
502
|
+
)
|
|
503
|
+
if self.cost_usd > self.controls.max_cost_usd:
|
|
504
|
+
raise DriverError(
|
|
505
|
+
"COST_BUDGET_EXCEEDED",
|
|
506
|
+
"ReFormeR max_cost_usd exceeded",
|
|
507
|
+
consumed=True,
|
|
508
|
+
usage=self.as_dict(),
|
|
509
|
+
)
|
|
510
|
+
if not isinstance(result.text, str):
|
|
511
|
+
raise DriverError(
|
|
512
|
+
"BACKEND_OUTPUT_INVALID",
|
|
513
|
+
"backend content must be text",
|
|
514
|
+
consumed=True,
|
|
515
|
+
usage=self.as_dict(),
|
|
516
|
+
)
|
|
517
|
+
self.stages.append(
|
|
518
|
+
{
|
|
519
|
+
"index": len(self.stages),
|
|
520
|
+
"stage": stage,
|
|
521
|
+
"seed": seed,
|
|
522
|
+
"input_tokens": result.input_tokens,
|
|
523
|
+
"output_tokens": result.output_tokens,
|
|
524
|
+
"response_sha256": _sha256_bytes(result.text.encode("utf-8")),
|
|
525
|
+
"finish_reason": result.finish_reason,
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
if not result.text.strip():
|
|
529
|
+
raise DriverError(
|
|
530
|
+
"BACKEND_OUTPUT_INVALID",
|
|
531
|
+
"backend returned no final text",
|
|
532
|
+
consumed=True,
|
|
533
|
+
usage=self.as_dict(),
|
|
534
|
+
)
|
|
535
|
+
return result.text
|
|
536
|
+
|
|
537
|
+
def _add_usage(self, input_tokens: int, output_tokens: int) -> None:
|
|
538
|
+
self.input_tokens += input_tokens
|
|
539
|
+
self.output_tokens += output_tokens
|
|
540
|
+
self.cost_usd += (
|
|
541
|
+
input_tokens * self.config.input_cost_per_million
|
|
542
|
+
+ output_tokens * self.config.output_cost_per_million
|
|
543
|
+
) / 1_000_000
|
|
544
|
+
|
|
545
|
+
def as_dict(self) -> dict[str, int | float]:
|
|
546
|
+
return {
|
|
547
|
+
"model_calls": self.model_calls,
|
|
548
|
+
"retrieval_calls": 0,
|
|
549
|
+
"input_tokens": self.input_tokens,
|
|
550
|
+
"output_tokens": self.output_tokens,
|
|
551
|
+
"cost_usd": self.cost_usd,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class _MessageTokenizer:
|
|
556
|
+
"""Preserve official messages while replacing only the vLLM serialization seam."""
|
|
557
|
+
|
|
558
|
+
def apply_chat_template(self, messages, *, tokenize, add_generation_prompt):
|
|
559
|
+
if tokenize is not False or add_generation_prompt is not True:
|
|
560
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official chat-template call changed")
|
|
561
|
+
if not isinstance(messages, Sequence) or isinstance(messages, (str, bytes)):
|
|
562
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official messages are invalid")
|
|
563
|
+
normalized: list[dict[str, str]] = []
|
|
564
|
+
for message in messages:
|
|
565
|
+
item = _mapping(message, "official prompt message")
|
|
566
|
+
if set(item) != {"role", "content"}:
|
|
567
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official message shape changed")
|
|
568
|
+
normalized.append(
|
|
569
|
+
{
|
|
570
|
+
"role": _text(item["role"], "official message role"),
|
|
571
|
+
"content": _text(item["content"], "official message content"),
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
return normalized
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class _OfficialLLMShim:
|
|
578
|
+
def __init__(self, backend: CompletionBackend, tracker: UsageTracker, seed: int) -> None:
|
|
579
|
+
self.backend = backend
|
|
580
|
+
self.tracker = tracker
|
|
581
|
+
self.seed = seed
|
|
582
|
+
self.stage = ""
|
|
583
|
+
self.fatal_error: DriverError | None = None
|
|
584
|
+
self.raw_text: dict[str, str] = {}
|
|
585
|
+
|
|
586
|
+
def generate(self, *, prompts, sampling_params):
|
|
587
|
+
expected_sampling = {
|
|
588
|
+
"max_tokens": self.tracker.config.max_output_tokens,
|
|
589
|
+
"temperature": self.tracker.config.temperature,
|
|
590
|
+
}
|
|
591
|
+
try:
|
|
592
|
+
observed_sampling = vars(sampling_params)
|
|
593
|
+
except TypeError as exc:
|
|
594
|
+
raise DriverError(
|
|
595
|
+
"UPSTREAM_API_MISMATCH", "official sampling parameters changed"
|
|
596
|
+
) from exc
|
|
597
|
+
if observed_sampling != expected_sampling:
|
|
598
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official sampling parameters changed")
|
|
599
|
+
if self.stage not in {"pattern_selection", "pattern_application"}:
|
|
600
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official call occurred outside a stage")
|
|
601
|
+
if not isinstance(prompts, Sequence) or len(prompts) != 1:
|
|
602
|
+
raise DriverError("UPSTREAM_API_MISMATCH", "official generate call shape changed")
|
|
603
|
+
messages = prompts[0]
|
|
604
|
+
offset = 0 if self.stage == "pattern_selection" else 1
|
|
605
|
+
try:
|
|
606
|
+
text = self.tracker.complete(
|
|
607
|
+
self.backend,
|
|
608
|
+
stage=self.stage,
|
|
609
|
+
messages=messages,
|
|
610
|
+
seed=self.seed + offset,
|
|
611
|
+
)
|
|
612
|
+
except DriverError as exc:
|
|
613
|
+
self.fatal_error = exc
|
|
614
|
+
raise
|
|
615
|
+
self.raw_text[self.stage] = text
|
|
616
|
+
return [SimpleNamespace(outputs=[SimpleNamespace(text=text)])]
|
|
617
|
+
|
|
618
|
+
def raise_fatal(self) -> None:
|
|
619
|
+
if self.fatal_error is not None:
|
|
620
|
+
raise self.fatal_error
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class _BlockedNativeVLLMSymbol:
|
|
624
|
+
"""Fail closed if pinned source escapes the audited remote-model seam.
|
|
625
|
+
|
|
626
|
+
The pinned ReFormeR module imports ``LLM`` and ``SamplingParams`` at module
|
|
627
|
+
load time, even though AgentInputLab bypasses its constructor and injects
|
|
628
|
+
``_OfficialLLMShim`` plus an explicit sampling-parameter object. Requiring
|
|
629
|
+
the full vLLM package in every short-lived worker would therefore add an
|
|
630
|
+
unused accelerator runtime and make the worker environment differ from the
|
|
631
|
+
declared OpenAI-compatible dependency. These symbols satisfy only the
|
|
632
|
+
source-locked import. Any future upstream path that tries to instantiate
|
|
633
|
+
either native symbol fails instead of silently loading another model.
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
637
|
+
del args, kwargs
|
|
638
|
+
raise DriverError(
|
|
639
|
+
"UNDECLARED_NATIVE_MODEL_LOAD",
|
|
640
|
+
"pinned ReFormeR attempted to bypass the audited remote-model seam",
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
class ReFormeRRuntime:
|
|
645
|
+
def __init__(
|
|
646
|
+
self,
|
|
647
|
+
checkout: Path,
|
|
648
|
+
backend_config: BackendConfig,
|
|
649
|
+
backend: CompletionBackend,
|
|
650
|
+
*,
|
|
651
|
+
pattern_path: Path | None = None,
|
|
652
|
+
source_license_acknowledged: bool = False,
|
|
653
|
+
policy: CompatibilityPolicy | None = None,
|
|
654
|
+
) -> None:
|
|
655
|
+
if source_license_acknowledged is not True:
|
|
656
|
+
raise DriverError(
|
|
657
|
+
"SOURCE_LICENSE_NOT_ACKNOWLEDGED",
|
|
658
|
+
"ReFormeR source is NOASSERTION and requires operator acknowledgement",
|
|
659
|
+
)
|
|
660
|
+
self.policy = policy or load_compatibility_policy()
|
|
661
|
+
self.checkout = verify_checkout(checkout, self.policy)
|
|
662
|
+
self.backend_config = backend_config
|
|
663
|
+
self.backend = backend
|
|
664
|
+
if _canonical_json(backend.fingerprint()) != _canonical_json(backend_config.fingerprint):
|
|
665
|
+
raise DriverError("BACKEND_IDENTITY_MISMATCH", "backend differs from startup config")
|
|
666
|
+
selected_path = pattern_path or (self.checkout / DEFAULT_PATTERN_PATH)
|
|
667
|
+
self.pattern_path = selected_path.expanduser().resolve(strict=True)
|
|
668
|
+
if not self.pattern_path.is_file() or self.pattern_path.is_symlink():
|
|
669
|
+
raise DriverError("PATTERN_ARTIFACT_UNAVAILABLE", "pattern artifact is unavailable")
|
|
670
|
+
self.pattern_hash = _sha256_file(self.pattern_path)
|
|
671
|
+
self.patterns = _load_patterns(self.pattern_path)
|
|
672
|
+
self.prompt_hash = _sha256_file(self.checkout / PROMPT_PATH)
|
|
673
|
+
self.reformulator_class, self.prompt_manager_class = _load_official_runtime(self.checkout)
|
|
674
|
+
|
|
675
|
+
def run(self, value: Mapping[str, Any]) -> dict[str, Any]:
|
|
676
|
+
request = validate_request(
|
|
677
|
+
value,
|
|
678
|
+
self.policy,
|
|
679
|
+
self.backend_config,
|
|
680
|
+
pattern_hash=self.pattern_hash,
|
|
681
|
+
prompt_hash=self.prompt_hash,
|
|
682
|
+
)
|
|
683
|
+
tracker = UsageTracker(request.controls, self.backend_config)
|
|
684
|
+
try:
|
|
685
|
+
return self._run_validated(request, tracker)
|
|
686
|
+
except DriverError as exc:
|
|
687
|
+
# Parsing or library validation can fail after a paid model call.
|
|
688
|
+
# Preserve that cost just as for transport-level failures.
|
|
689
|
+
if tracker.model_calls:
|
|
690
|
+
exc.consumed = True
|
|
691
|
+
exc.usage = tracker.as_dict()
|
|
692
|
+
exc.diagnostics.update(
|
|
693
|
+
{"adapter_revision": ADAPTER_REVISION, "model_trace": tracker.stages}
|
|
694
|
+
)
|
|
695
|
+
raise
|
|
696
|
+
|
|
697
|
+
def _run_validated(
|
|
698
|
+
self, request: ValidatedRequest, tracker: UsageTracker
|
|
699
|
+
) -> dict[str, Any]:
|
|
700
|
+
llm = _OfficialLLMShim(self.backend, tracker, request.seed)
|
|
701
|
+
reformulator = self.reformulator_class.__new__(self.reformulator_class)
|
|
702
|
+
reformulator.patterns = self.patterns
|
|
703
|
+
reformulator.prompt_manager = self.prompt_manager_class(str(self.checkout / PROMPT_PATH))
|
|
704
|
+
reformulator.tokenizer = _MessageTokenizer()
|
|
705
|
+
reformulator.llm = llm
|
|
706
|
+
reformulator.sampling_params = SimpleNamespace(
|
|
707
|
+
max_tokens=self.backend_config.max_output_tokens,
|
|
708
|
+
temperature=self.backend_config.temperature,
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
if request.pattern_mode == "manual":
|
|
712
|
+
selected = _pattern_by_id(self.patterns, request.manual_pattern_id)
|
|
713
|
+
parse_status = "manual"
|
|
714
|
+
fallback_id = ""
|
|
715
|
+
elif not request.documents:
|
|
716
|
+
selected = reformulator.select_best_pattern(request.query, [])
|
|
717
|
+
llm.raise_fatal()
|
|
718
|
+
parse_status = "no_documents_first_pattern"
|
|
719
|
+
fallback_id = _pattern_id(selected)
|
|
720
|
+
else:
|
|
721
|
+
llm.stage = "pattern_selection"
|
|
722
|
+
selected = reformulator.select_best_pattern(
|
|
723
|
+
request.query,
|
|
724
|
+
[document["text"] for document in request.documents],
|
|
725
|
+
)
|
|
726
|
+
llm.raise_fatal()
|
|
727
|
+
raw_selection = llm.raw_text.get("pattern_selection", "")
|
|
728
|
+
parsed = _try_json(raw_selection)
|
|
729
|
+
if isinstance(parsed, Mapping):
|
|
730
|
+
parse_status = "valid_json"
|
|
731
|
+
fallback_id = ""
|
|
732
|
+
else:
|
|
733
|
+
parse_status = "first_pattern_fallback"
|
|
734
|
+
fallback_id = _pattern_id(self.patterns[0])
|
|
735
|
+
raw_selected_hash = _canonical_hash(selected)
|
|
736
|
+
name_reference = isinstance(selected, Mapping) and set(selected) == {"pattern_name"}
|
|
737
|
+
selected = _require_library_pattern(selected, self.patterns)
|
|
738
|
+
selection_match = (
|
|
739
|
+
"unique_name_reference" if name_reference else
|
|
740
|
+
"exact" if raw_selected_hash == _canonical_hash(selected) else "application_fields"
|
|
741
|
+
)
|
|
742
|
+
llm.stage = "pattern_application"
|
|
743
|
+
reformulation = reformulator.apply_pattern(request.query, selected)
|
|
744
|
+
llm.raise_fatal()
|
|
745
|
+
if not isinstance(reformulation, str) or not reformulation.strip():
|
|
746
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", "official application returned empty text")
|
|
747
|
+
pattern_id = _pattern_id(selected)
|
|
748
|
+
pattern_json = _canonical_json(selected)
|
|
749
|
+
return {
|
|
750
|
+
"status": "ok",
|
|
751
|
+
"request_id": request.request_id,
|
|
752
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
753
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
754
|
+
"payload": {
|
|
755
|
+
"adapter_revision": ADAPTER_REVISION,
|
|
756
|
+
"selection_library_match": selection_match,
|
|
757
|
+
"raw_selected_pattern_hash": raw_selected_hash,
|
|
758
|
+
"pattern_library_hash": self.pattern_hash,
|
|
759
|
+
"prompt_artifact_hash": self.prompt_hash,
|
|
760
|
+
"selected_pattern_id": pattern_id,
|
|
761
|
+
"selected_pattern": _text(
|
|
762
|
+
selected.get("transformation_rule"), "selected transformation_rule"
|
|
763
|
+
),
|
|
764
|
+
"selected_pattern_hash": _sha256_bytes(pattern_json.encode("utf-8")),
|
|
765
|
+
"selected_examples_hash": _canonical_hash(selected.get("examples", [])),
|
|
766
|
+
"reformulation": reformulation.strip(),
|
|
767
|
+
"top_document_ids": [document["document_id"] for document in request.documents],
|
|
768
|
+
"retrieval_state_hash": _canonical_hash(request.documents),
|
|
769
|
+
"selection_parse_status": parse_status,
|
|
770
|
+
"fallback_first_pattern_id": fallback_id,
|
|
771
|
+
"semantics": request.semantics,
|
|
772
|
+
"pattern_mode": request.pattern_mode,
|
|
773
|
+
"manual_pattern_id": request.manual_pattern_id,
|
|
774
|
+
"official_calls": (
|
|
775
|
+
["Reformulator.apply_pattern"]
|
|
776
|
+
if request.pattern_mode == "manual"
|
|
777
|
+
else [
|
|
778
|
+
"Reformulator.select_best_pattern",
|
|
779
|
+
"Reformulator.apply_pattern",
|
|
780
|
+
]
|
|
781
|
+
if request.documents
|
|
782
|
+
else [
|
|
783
|
+
"Reformulator.select_best_pattern:no_documents",
|
|
784
|
+
"Reformulator.apply_pattern",
|
|
785
|
+
]
|
|
786
|
+
),
|
|
787
|
+
"model_fingerprint": dict(self.backend_config.fingerprint),
|
|
788
|
+
"model_trace": tracker.stages,
|
|
789
|
+
"source_license": "NOASSERTION",
|
|
790
|
+
"source_license_acknowledged": True,
|
|
791
|
+
"vendored_upstream_source": False,
|
|
792
|
+
"paper_result_parity_claimed": False,
|
|
793
|
+
},
|
|
794
|
+
"usage": tracker.as_dict(),
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def load_compatibility_policy(path: Path = DEFAULT_MANIFEST) -> CompatibilityPolicy:
|
|
799
|
+
try:
|
|
800
|
+
value = _loads_strict_json(path.read_text(encoding="utf-8"))
|
|
801
|
+
except OSError as exc:
|
|
802
|
+
raise DriverError("MANIFEST_UNAVAILABLE", "compatibility manifest unavailable") from exc
|
|
803
|
+
return CompatibilityPolicy.from_mapping(_mapping(value, "compatibility manifest"))
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def verify_checkout(checkout: Path, policy: CompatibilityPolicy) -> Path:
|
|
807
|
+
try:
|
|
808
|
+
root = checkout.expanduser().resolve(strict=True)
|
|
809
|
+
except OSError as exc:
|
|
810
|
+
raise DriverError("CHECKOUT_UNAVAILABLE", "ReFormeR checkout unavailable") from exc
|
|
811
|
+
if not root.is_dir():
|
|
812
|
+
raise DriverError("CHECKOUT_UNAVAILABLE", "checkout must be a directory")
|
|
813
|
+
if read_checkout_head(root) != policy.commit:
|
|
814
|
+
raise DriverError("UPSTREAM_REVISION_MISMATCH", "checkout is not at pinned commit")
|
|
815
|
+
license_candidates = tuple(
|
|
816
|
+
path
|
|
817
|
+
for pattern in ("LICENSE*", "COPYING*", "NOTICE*")
|
|
818
|
+
for path in root.glob(pattern)
|
|
819
|
+
if path.is_file()
|
|
820
|
+
)
|
|
821
|
+
if license_candidates:
|
|
822
|
+
raise DriverError(
|
|
823
|
+
"LICENSE_STATE_MISMATCH",
|
|
824
|
+
"pinned ReFormeR revision is expected to have no license file",
|
|
825
|
+
)
|
|
826
|
+
evidence = _checkout_file(root, policy.license_evidence_path)
|
|
827
|
+
if _sha256_file(evidence) != policy.license_evidence_sha256:
|
|
828
|
+
raise DriverError("LICENSE_EVIDENCE_MISMATCH", "README license evidence hash mismatch")
|
|
829
|
+
if "License-MIT" not in evidence.read_text(encoding="utf-8", errors="strict"):
|
|
830
|
+
raise DriverError("LICENSE_EVIDENCE_MISMATCH", "README MIT badge evidence is absent")
|
|
831
|
+
for relative, expected in policy.source_hashes.items():
|
|
832
|
+
source = _checkout_file(root, relative)
|
|
833
|
+
if _sha256_file(source) != expected:
|
|
834
|
+
raise DriverError("UPSTREAM_SOURCE_MISMATCH", f"source hash mismatch: {relative}")
|
|
835
|
+
audit_source_symbols(root, policy)
|
|
836
|
+
if policy.require_clean_checkout:
|
|
837
|
+
_require_clean_checkout(root)
|
|
838
|
+
return root
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def audit_source_symbols(root: Path, policy: CompatibilityPolicy) -> dict[str, tuple[str, ...]]:
|
|
842
|
+
del policy
|
|
843
|
+
expected = {
|
|
844
|
+
CORE_PATH: {
|
|
845
|
+
"Reformulator": (
|
|
846
|
+
"select_best_pattern",
|
|
847
|
+
"apply_pattern",
|
|
848
|
+
"reformulate_query",
|
|
849
|
+
)
|
|
850
|
+
},
|
|
851
|
+
INDUCTION_PATH: {
|
|
852
|
+
"IterativePatternExtractor": (
|
|
853
|
+
"extract_patterns_from_batch",
|
|
854
|
+
"run_iterative_extraction",
|
|
855
|
+
)
|
|
856
|
+
},
|
|
857
|
+
}
|
|
858
|
+
found: dict[str, tuple[str, ...]] = {}
|
|
859
|
+
for relative, classes in expected.items():
|
|
860
|
+
try:
|
|
861
|
+
tree = ast.parse(_checkout_file(root, relative).read_text(encoding="utf-8"))
|
|
862
|
+
except (OSError, SyntaxError, UnicodeError) as exc:
|
|
863
|
+
raise DriverError("UPSTREAM_SOURCE_INVALID", f"cannot parse {relative}") from exc
|
|
864
|
+
definitions = {
|
|
865
|
+
node.name: {
|
|
866
|
+
child.name
|
|
867
|
+
for child in node.body
|
|
868
|
+
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
869
|
+
}
|
|
870
|
+
for node in tree.body
|
|
871
|
+
if isinstance(node, ast.ClassDef)
|
|
872
|
+
}
|
|
873
|
+
for class_name, methods in classes.items():
|
|
874
|
+
if class_name not in definitions or not set(methods) <= definitions[class_name]:
|
|
875
|
+
raise DriverError(
|
|
876
|
+
"UPSTREAM_SYMBOL_MISMATCH", f"official symbols changed: {relative}"
|
|
877
|
+
)
|
|
878
|
+
found[class_name] = methods
|
|
879
|
+
return found
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def validate_request(
|
|
883
|
+
value: Mapping[str, Any],
|
|
884
|
+
policy: CompatibilityPolicy,
|
|
885
|
+
config: BackendConfig,
|
|
886
|
+
*,
|
|
887
|
+
pattern_hash: str,
|
|
888
|
+
prompt_hash: str,
|
|
889
|
+
) -> ValidatedRequest:
|
|
890
|
+
_exact_keys(value, set(_ROOT_FIELDS), "worker request")
|
|
891
|
+
if value["method_id"] != METHOD_ID:
|
|
892
|
+
raise DriverError("PROFILE_IDENTITY_MISMATCH", "request targets another method")
|
|
893
|
+
release_id = _text(value["release_id"], "release_id")
|
|
894
|
+
profile = policy.profiles.get(release_id)
|
|
895
|
+
if profile is None:
|
|
896
|
+
raise DriverError("PROFILE_IDENTITY_MISMATCH", "unknown ReFormeR profile")
|
|
897
|
+
if value["upstream_commit"] != UPSTREAM_COMMIT:
|
|
898
|
+
raise DriverError("UPSTREAM_REVISION_MISMATCH", "request revision mismatch")
|
|
899
|
+
if value["protocol_version"] != PROTOCOL_VERSION:
|
|
900
|
+
raise DriverError("PROTOCOL_MISMATCH", "request protocol mismatch")
|
|
901
|
+
request_id = _hash(value["request_id"], "request_id")
|
|
902
|
+
_hash(value["state_hash"], "state_hash")
|
|
903
|
+
identity = {key: value[key] for key in _ROOT_FIELDS if key != "request_id"}
|
|
904
|
+
if request_id != _canonical_hash(identity):
|
|
905
|
+
raise DriverError("REQUEST_ID_MISMATCH", "request_id is invalid")
|
|
906
|
+
payload = _mapping(value["payload"], "worker payload")
|
|
907
|
+
_exact_keys(payload, set(_PAYLOAD_FIELDS), "worker payload")
|
|
908
|
+
if payload["source_license_acknowledged"] is not True:
|
|
909
|
+
raise DriverError("SOURCE_LICENSE_NOT_ACKNOWLEDGED", "per-request license gate failed")
|
|
910
|
+
semantics = _text(payload["semantics"], "semantics")
|
|
911
|
+
pattern_mode = _text(payload["pattern_mode"], "pattern_mode")
|
|
912
|
+
if semantics != profile["semantics"] or pattern_mode != profile["pattern_mode"]:
|
|
913
|
+
raise DriverError("PROFILE_IDENTITY_MISMATCH", "request profile axes mismatch")
|
|
914
|
+
requested_pattern_hash = _hash(payload["pattern_artifact_hash"], "pattern hash")
|
|
915
|
+
if requested_pattern_hash != pattern_hash:
|
|
916
|
+
raise DriverError("PATTERN_IDENTITY_MISMATCH", "pattern artifact hash mismatch")
|
|
917
|
+
if payload["prompt_artifact_hash"] != prompt_hash:
|
|
918
|
+
raise DriverError("PROMPT_IDENTITY_MISMATCH", "prompt artifact hash mismatch")
|
|
919
|
+
dependencies = _mapping(value["dependency_fingerprints"], "dependencies")
|
|
920
|
+
if set(dependencies) != {"backend", "retriever"}:
|
|
921
|
+
raise DriverError("DEPENDENCY_IDENTITY_MISMATCH", "dependency fields mismatch")
|
|
922
|
+
backend_fp = _mapping(dependencies["backend"], "backend fingerprint")
|
|
923
|
+
if _canonical_json(backend_fp) != _canonical_json(config.fingerprint):
|
|
924
|
+
raise DriverError("BACKEND_IDENTITY_MISMATCH", "request model differs from startup")
|
|
925
|
+
retriever_fp = _mapping(dependencies["retriever"], "retriever fingerprint")
|
|
926
|
+
if pattern_mode == "automatic" and not retriever_fp:
|
|
927
|
+
raise DriverError("DEPENDENCY_IDENTITY_MISMATCH", "automatic profile requires retriever")
|
|
928
|
+
manual_pattern_id = payload["manual_pattern_id"]
|
|
929
|
+
if not isinstance(manual_pattern_id, str):
|
|
930
|
+
raise DriverError("INVALID_SCHEMA", "manual_pattern_id must be text")
|
|
931
|
+
if pattern_mode == "manual" and not manual_pattern_id.strip():
|
|
932
|
+
raise DriverError("INVALID_SCHEMA", "manual profile requires pattern ID")
|
|
933
|
+
if pattern_mode == "automatic" and manual_pattern_id:
|
|
934
|
+
raise DriverError("INVALID_SCHEMA", "automatic profile cannot set manual pattern")
|
|
935
|
+
top_count = _positive_int(payload["top_document_count"], "top_document_count")
|
|
936
|
+
documents = _validate_documents(payload["context"], top_count)
|
|
937
|
+
expected_calls = 2 if pattern_mode == "automatic" and documents else 1
|
|
938
|
+
reservation = _mapping(value["minimum_reservation"], "minimum_reservation")
|
|
939
|
+
if reservation != {"model_calls": expected_calls}:
|
|
940
|
+
raise DriverError("INVALID_RESERVATION", "model-call reservation mismatches trajectory")
|
|
941
|
+
controls = _validate_controls(payload["controls"])
|
|
942
|
+
if controls.max_model_calls < expected_calls:
|
|
943
|
+
raise DriverError("MODEL_CALL_BUDGET_EXCEEDED", "profile cannot fit max_model_calls")
|
|
944
|
+
query = _text(payload["query"], "query")
|
|
945
|
+
if len(query) > MAX_TEXT_CHARS:
|
|
946
|
+
raise DriverError("REQUEST_TOO_LARGE", "query exceeds limit")
|
|
947
|
+
task_id = payload["task_id"]
|
|
948
|
+
if not isinstance(task_id, str):
|
|
949
|
+
raise DriverError("INVALID_SCHEMA", "task_id must be text")
|
|
950
|
+
return ValidatedRequest(
|
|
951
|
+
request_id=request_id,
|
|
952
|
+
release_id=release_id,
|
|
953
|
+
query=query,
|
|
954
|
+
documents=documents,
|
|
955
|
+
pattern_hash=requested_pattern_hash,
|
|
956
|
+
semantics=semantics,
|
|
957
|
+
pattern_mode=pattern_mode,
|
|
958
|
+
manual_pattern_id=manual_pattern_id,
|
|
959
|
+
controls=controls,
|
|
960
|
+
seed=_nonnegative_int(payload["seed"], "seed"),
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _validate_documents(value: Any, count: int) -> tuple[Mapping[str, str], ...]:
|
|
965
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
966
|
+
raise DriverError("INVALID_SCHEMA", "context must be a sequence")
|
|
967
|
+
if len(value) > MAX_CONTEXT_BLOCKS:
|
|
968
|
+
raise DriverError("REQUEST_TOO_LARGE", "too many context blocks")
|
|
969
|
+
documents: list[Mapping[str, str]] = []
|
|
970
|
+
seen: set[str] = set()
|
|
971
|
+
for index, raw in enumerate(value[:count]):
|
|
972
|
+
item = _mapping(raw, f"context[{index}]")
|
|
973
|
+
_exact_keys(item, {"text", "block_id", "source", "score", "metadata"}, "context block")
|
|
974
|
+
text = _text(item["text"], f"context[{index}] text")
|
|
975
|
+
document_id = _text(item["block_id"], f"context[{index}] block_id")
|
|
976
|
+
if document_id in seen:
|
|
977
|
+
raise DriverError("INVALID_SCHEMA", "context document IDs must be unique")
|
|
978
|
+
seen.add(document_id)
|
|
979
|
+
_canonical_json(item["metadata"])
|
|
980
|
+
score = item["score"]
|
|
981
|
+
if score is not None:
|
|
982
|
+
_finite_float(score, f"context[{index}] score")
|
|
983
|
+
documents.append({"document_id": document_id, "text": text})
|
|
984
|
+
if len(_canonical_json(documents)) > MAX_TEXT_CHARS:
|
|
985
|
+
raise DriverError("REQUEST_TOO_LARGE", "context text exceeds limit")
|
|
986
|
+
return tuple(documents)
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def _validate_controls(value: Any) -> Controls:
|
|
990
|
+
item = _mapping(value, "controls")
|
|
991
|
+
_exact_keys(item, set(_CONTROL_FIELDS), "controls")
|
|
992
|
+
return Controls(
|
|
993
|
+
max_model_calls=_positive_int(item["max_model_calls"], "max_model_calls"),
|
|
994
|
+
max_tokens=_positive_int(item["max_tokens"], "max_tokens"),
|
|
995
|
+
max_cost_usd=_nonnegative_float(item["max_cost_usd"], "max_cost_usd"),
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def _load_patterns(path: Path) -> tuple[Mapping[str, Any], ...]:
|
|
1000
|
+
try:
|
|
1001
|
+
value = _loads_strict_json(path.read_text(encoding="utf-8"))
|
|
1002
|
+
except OSError as exc:
|
|
1003
|
+
raise DriverError("PATTERN_ARTIFACT_UNAVAILABLE", "cannot read pattern artifact") from exc
|
|
1004
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)) or not value:
|
|
1005
|
+
raise DriverError("PATTERN_ARTIFACT_INVALID", "pattern artifact must be non-empty list")
|
|
1006
|
+
patterns: list[Mapping[str, Any]] = []
|
|
1007
|
+
ids: set[str] = set()
|
|
1008
|
+
for index, raw in enumerate(value):
|
|
1009
|
+
item = _mapping(raw, f"pattern[{index}]")
|
|
1010
|
+
_exact_keys(
|
|
1011
|
+
item,
|
|
1012
|
+
{"pattern_name", "description", "transformation_rule", "examples"},
|
|
1013
|
+
"pattern",
|
|
1014
|
+
)
|
|
1015
|
+
pattern_id = _text(item["pattern_name"], "pattern_name")
|
|
1016
|
+
if pattern_id in ids:
|
|
1017
|
+
raise DriverError("PATTERN_ARTIFACT_INVALID", "pattern names must be unique")
|
|
1018
|
+
ids.add(pattern_id)
|
|
1019
|
+
_text(item["description"], "pattern description")
|
|
1020
|
+
_text(item["transformation_rule"], "pattern transformation_rule")
|
|
1021
|
+
examples = item["examples"]
|
|
1022
|
+
if not isinstance(examples, Sequence) or isinstance(examples, (str, bytes)):
|
|
1023
|
+
raise DriverError("PATTERN_ARTIFACT_INVALID", "pattern examples must be a sequence")
|
|
1024
|
+
for pair in examples:
|
|
1025
|
+
if (
|
|
1026
|
+
not isinstance(pair, Sequence)
|
|
1027
|
+
or isinstance(pair, (str, bytes))
|
|
1028
|
+
or len(pair) != 2
|
|
1029
|
+
or not all(isinstance(part, str) for part in pair)
|
|
1030
|
+
):
|
|
1031
|
+
raise DriverError("PATTERN_ARTIFACT_INVALID", "pattern example must be text pair")
|
|
1032
|
+
patterns.append(dict(item))
|
|
1033
|
+
return tuple(patterns)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _load_official_runtime(root: Path) -> tuple[type[Any], type[Any]]:
|
|
1037
|
+
namespace = f"_agentinputlab_reformer_{hashlib.sha256(str(root).encode()).hexdigest()[:12]}"
|
|
1038
|
+
reformer_package = f"{namespace}.reformer"
|
|
1039
|
+
core_package = f"{reformer_package}.core"
|
|
1040
|
+
for name, path in (
|
|
1041
|
+
(namespace, root),
|
|
1042
|
+
(reformer_package, root / "reformer"),
|
|
1043
|
+
(core_package, root / "reformer" / "core"),
|
|
1044
|
+
):
|
|
1045
|
+
module = types.ModuleType(name)
|
|
1046
|
+
module.__path__ = [str(path)]
|
|
1047
|
+
module.__package__ = name
|
|
1048
|
+
sys.modules[name] = module
|
|
1049
|
+
previous_dont_write_bytecode = sys.dont_write_bytecode
|
|
1050
|
+
missing = object()
|
|
1051
|
+
previous_vllm = sys.modules.get("vllm", missing)
|
|
1052
|
+
vllm_shim = types.ModuleType("vllm")
|
|
1053
|
+
vllm_shim.LLM = _BlockedNativeVLLMSymbol
|
|
1054
|
+
vllm_shim.SamplingParams = _BlockedNativeVLLMSymbol
|
|
1055
|
+
sys.modules["vllm"] = vllm_shim
|
|
1056
|
+
sys.dont_write_bytecode = True
|
|
1057
|
+
try:
|
|
1058
|
+
prompt_module = _load_module(
|
|
1059
|
+
f"{reformer_package}.prompt_manager", root / PROMPT_MANAGER_PATH
|
|
1060
|
+
)
|
|
1061
|
+
core_module = _load_module(f"{core_package}.reformer", root / CORE_PATH)
|
|
1062
|
+
except ModuleNotFoundError as exc:
|
|
1063
|
+
raise DriverError(
|
|
1064
|
+
"UPSTREAM_DEPENDENCY_UNAVAILABLE",
|
|
1065
|
+
f"pinned ReFormeR dependency unavailable: {exc.name or 'unknown'}",
|
|
1066
|
+
) from exc
|
|
1067
|
+
except (ImportError, OSError, SyntaxError) as exc:
|
|
1068
|
+
raise DriverError("UPSTREAM_IMPORT_FAILED", "pinned ReFormeR import failed") from exc
|
|
1069
|
+
finally:
|
|
1070
|
+
sys.dont_write_bytecode = previous_dont_write_bytecode
|
|
1071
|
+
if previous_vllm is missing:
|
|
1072
|
+
sys.modules.pop("vllm", None)
|
|
1073
|
+
else:
|
|
1074
|
+
sys.modules["vllm"] = previous_vllm
|
|
1075
|
+
reformulator = getattr(core_module, "Reformulator", None)
|
|
1076
|
+
prompt_manager = getattr(prompt_module, "PromptManager", None)
|
|
1077
|
+
if not isinstance(reformulator, type) or not isinstance(prompt_manager, type):
|
|
1078
|
+
raise DriverError("UPSTREAM_SYMBOL_MISMATCH", "official runtime classes are missing")
|
|
1079
|
+
return reformulator, prompt_manager
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _load_module(name: str, path: Path):
|
|
1083
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
1084
|
+
if spec is None or spec.loader is None:
|
|
1085
|
+
raise DriverError("UPSTREAM_IMPORT_FAILED", "cannot create upstream module spec")
|
|
1086
|
+
module = importlib.util.module_from_spec(spec)
|
|
1087
|
+
sys.modules[name] = module
|
|
1088
|
+
spec.loader.exec_module(module)
|
|
1089
|
+
return module
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
def _pattern_by_id(patterns: Sequence[Mapping[str, Any]], pattern_id: str):
|
|
1093
|
+
for pattern in patterns:
|
|
1094
|
+
if pattern.get("pattern_name") == pattern_id:
|
|
1095
|
+
return pattern
|
|
1096
|
+
raise DriverError("MANUAL_PATTERN_NOT_FOUND", "manual pattern ID is not in pattern set")
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
def _require_library_pattern(selected: Any, patterns: Sequence[Mapping[str, Any]]):
|
|
1100
|
+
if not isinstance(selected, Mapping):
|
|
1101
|
+
raise DriverError("UPSTREAM_OUTPUT_INVALID", "official selector returned no pattern")
|
|
1102
|
+
same_name = [p for p in patterns if p.get("pattern_name") == selected.get("pattern_name")]
|
|
1103
|
+
# A complete name-only JSON object references the pinned library, not a new
|
|
1104
|
+
# generated rule. Never fuzzy-match, resolve ambiguity or ignore conflicting
|
|
1105
|
+
# operative fields. This adapter revision records the resolution explicitly.
|
|
1106
|
+
if set(selected) == {"pattern_name"} and len(same_name) == 1:
|
|
1107
|
+
return same_name[0]
|
|
1108
|
+
# The pinned apply_pattern reads only transformation_rule and examples.
|
|
1109
|
+
# Description/annotation differences must not reject an otherwise identical
|
|
1110
|
+
# application. Objects supplying rules/examples must match them exactly.
|
|
1111
|
+
matches = [
|
|
1112
|
+
pattern
|
|
1113
|
+
for pattern in patterns
|
|
1114
|
+
if selected.get("pattern_name") == pattern.get("pattern_name")
|
|
1115
|
+
and selected.get("transformation_rule") == pattern.get("transformation_rule")
|
|
1116
|
+
and _canonical_json(selected.get("examples", []))
|
|
1117
|
+
== _canonical_json(pattern.get("examples", []))
|
|
1118
|
+
]
|
|
1119
|
+
if len(matches) == 1:
|
|
1120
|
+
return matches[0]
|
|
1121
|
+
raise DriverError(
|
|
1122
|
+
"PATTERN_SELECTION_OUTSIDE_LIBRARY",
|
|
1123
|
+
"selector returned unknown pattern",
|
|
1124
|
+
diagnostics={
|
|
1125
|
+
"pattern_selection": {
|
|
1126
|
+
"same_name_candidates": len(same_name),
|
|
1127
|
+
"rule_matches_named_pattern": any(
|
|
1128
|
+
p.get("transformation_rule") == selected.get("transformation_rule")
|
|
1129
|
+
for p in same_name
|
|
1130
|
+
),
|
|
1131
|
+
"examples_match_named_pattern": any(
|
|
1132
|
+
_canonical_json(p.get("examples", []))
|
|
1133
|
+
== _canonical_json(selected.get("examples", []))
|
|
1134
|
+
for p in same_name
|
|
1135
|
+
),
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def _pattern_id(pattern: Mapping[str, Any]) -> str:
|
|
1142
|
+
return _text(pattern.get("pattern_name"), "selected pattern_name")
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _try_json(value: str) -> Any:
|
|
1146
|
+
try:
|
|
1147
|
+
return _loads_strict_json(value)
|
|
1148
|
+
except DriverError:
|
|
1149
|
+
return None
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def read_checkout_head(root: Path) -> str:
|
|
1153
|
+
git_entry = root / ".git"
|
|
1154
|
+
if git_entry.is_dir():
|
|
1155
|
+
git_dir = git_entry.resolve(strict=True)
|
|
1156
|
+
elif git_entry.is_file():
|
|
1157
|
+
raw = git_entry.read_text(encoding="utf-8").strip()
|
|
1158
|
+
if not raw.startswith("gitdir: "):
|
|
1159
|
+
raise DriverError("GIT_METADATA_INVALID", "checkout .git file malformed")
|
|
1160
|
+
candidate = Path(raw.removeprefix("gitdir: "))
|
|
1161
|
+
git_dir = (
|
|
1162
|
+
(root / candidate).resolve(strict=True)
|
|
1163
|
+
if not candidate.is_absolute()
|
|
1164
|
+
else candidate.resolve(strict=True)
|
|
1165
|
+
)
|
|
1166
|
+
else:
|
|
1167
|
+
raise DriverError("GIT_METADATA_INVALID", "checkout lacks Git metadata")
|
|
1168
|
+
head = (git_dir / "HEAD").read_text(encoding="utf-8").strip()
|
|
1169
|
+
if _COMMIT_RE.fullmatch(head):
|
|
1170
|
+
return head
|
|
1171
|
+
if not head.startswith("ref: "):
|
|
1172
|
+
raise DriverError("GIT_METADATA_INVALID", "checkout HEAD malformed")
|
|
1173
|
+
ref = head.removeprefix("ref: ")
|
|
1174
|
+
if _REF_RE.fullmatch(ref) is None or ".." in PurePosixPath(ref).parts:
|
|
1175
|
+
raise DriverError("GIT_METADATA_INVALID", "checkout HEAD reference unsafe")
|
|
1176
|
+
for base in (git_dir, _common_git_dir(git_dir)):
|
|
1177
|
+
loose = _safe_child(base, ref)
|
|
1178
|
+
if loose.is_file():
|
|
1179
|
+
value = loose.read_text(encoding="utf-8").strip()
|
|
1180
|
+
if _COMMIT_RE.fullmatch(value):
|
|
1181
|
+
return value
|
|
1182
|
+
packed = base / "packed-refs"
|
|
1183
|
+
if packed.is_file():
|
|
1184
|
+
for line in packed.read_text(encoding="utf-8").splitlines():
|
|
1185
|
+
commit, separator, name = line.partition(" ")
|
|
1186
|
+
if separator and name == ref and _COMMIT_RE.fullmatch(commit):
|
|
1187
|
+
return commit
|
|
1188
|
+
raise DriverError("GIT_METADATA_INVALID", "checkout HEAD commit unavailable")
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def _common_git_dir(git_dir: Path) -> Path:
|
|
1192
|
+
marker = git_dir / "commondir"
|
|
1193
|
+
if not marker.is_file():
|
|
1194
|
+
return git_dir
|
|
1195
|
+
candidate = Path(marker.read_text(encoding="utf-8").strip())
|
|
1196
|
+
return (
|
|
1197
|
+
(git_dir / candidate).resolve(strict=True)
|
|
1198
|
+
if not candidate.is_absolute()
|
|
1199
|
+
else candidate.resolve(strict=True)
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def _require_clean_checkout(root: Path) -> None:
|
|
1204
|
+
environment = {
|
|
1205
|
+
"PATH": os.environ.get("PATH", ""),
|
|
1206
|
+
"LC_ALL": "C",
|
|
1207
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
1208
|
+
"GIT_CONFIG_GLOBAL": os.devnull,
|
|
1209
|
+
"GIT_OPTIONAL_LOCKS": "0",
|
|
1210
|
+
}
|
|
1211
|
+
try:
|
|
1212
|
+
result = subprocess.run(
|
|
1213
|
+
(
|
|
1214
|
+
"git",
|
|
1215
|
+
"-c",
|
|
1216
|
+
"core.fsmonitor=false",
|
|
1217
|
+
"-C",
|
|
1218
|
+
str(root),
|
|
1219
|
+
"status",
|
|
1220
|
+
"--porcelain=v1",
|
|
1221
|
+
"--untracked-files=all",
|
|
1222
|
+
),
|
|
1223
|
+
capture_output=True,
|
|
1224
|
+
check=False,
|
|
1225
|
+
text=True,
|
|
1226
|
+
encoding="utf-8",
|
|
1227
|
+
errors="replace",
|
|
1228
|
+
env=environment,
|
|
1229
|
+
timeout=15,
|
|
1230
|
+
)
|
|
1231
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
1232
|
+
raise DriverError("GIT_STATUS_FAILED", "clean-checkout check failed") from exc
|
|
1233
|
+
if result.returncode != 0:
|
|
1234
|
+
raise DriverError("GIT_STATUS_FAILED", "clean-checkout check failed")
|
|
1235
|
+
if result.stdout:
|
|
1236
|
+
raise DriverError("DIRTY_CHECKOUT", "checkout contains changes")
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def serve(runtime: ReFormeRRuntime, input_stream: TextIO, output_stream: TextIO) -> None:
|
|
1240
|
+
for raw_line in input_stream:
|
|
1241
|
+
if not raw_line.strip():
|
|
1242
|
+
continue
|
|
1243
|
+
request_id = ""
|
|
1244
|
+
try:
|
|
1245
|
+
if len(raw_line.encode("utf-8")) > MAX_REQUEST_BYTES:
|
|
1246
|
+
raise DriverError("REQUEST_TOO_LARGE", "request exceeds byte limit")
|
|
1247
|
+
value = _mapping(_loads_strict_json(raw_line), "JSONL request")
|
|
1248
|
+
if isinstance(value.get("request_id"), str):
|
|
1249
|
+
request_id = value["request_id"][:128]
|
|
1250
|
+
with redirect_stdout(sys.stderr):
|
|
1251
|
+
response = runtime.run(value)
|
|
1252
|
+
except DriverError as exc:
|
|
1253
|
+
response = _failure_response(request_id, exc)
|
|
1254
|
+
except Exception as exc: # pragma: no cover
|
|
1255
|
+
response = _failure_response(
|
|
1256
|
+
request_id,
|
|
1257
|
+
DriverError("INTERNAL_WORKER_ERROR", f"worker failed with {type(exc).__name__}"),
|
|
1258
|
+
)
|
|
1259
|
+
output_stream.write(_canonical_json(response) + "\n")
|
|
1260
|
+
output_stream.flush()
|
|
1261
|
+
|
|
1262
|
+
|
|
1263
|
+
def _failure_response(request_id: str, error: DriverError) -> dict[str, Any]:
|
|
1264
|
+
response: dict[str, Any] = {
|
|
1265
|
+
"status": "error",
|
|
1266
|
+
"request_id": request_id,
|
|
1267
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
1268
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
1269
|
+
"failure_code": error.code,
|
|
1270
|
+
"message": str(error),
|
|
1271
|
+
"consumed": error.consumed,
|
|
1272
|
+
}
|
|
1273
|
+
if error.usage is not None:
|
|
1274
|
+
response["usage"] = dict(error.usage)
|
|
1275
|
+
if error.diagnostics:
|
|
1276
|
+
response["diagnostics"] = dict(error.diagnostics)
|
|
1277
|
+
return response
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
def _checkout_file(root: Path, relative: str) -> Path:
|
|
1281
|
+
path = _safe_child(root, relative)
|
|
1282
|
+
if not path.is_file() or path.is_symlink():
|
|
1283
|
+
raise DriverError("UPSTREAM_FILE_UNAVAILABLE", f"pinned file unavailable: {relative}")
|
|
1284
|
+
return path
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def _safe_child(root: Path, relative: str) -> Path:
|
|
1288
|
+
root = root.resolve(strict=True)
|
|
1289
|
+
candidate = (root / relative).resolve(strict=False)
|
|
1290
|
+
if candidate != root and root not in candidate.parents:
|
|
1291
|
+
raise DriverError("UNSAFE_PATH", "path escapes fixed root")
|
|
1292
|
+
return candidate
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
def _safe_relative_path(value: Any, label: str) -> str:
|
|
1296
|
+
if not isinstance(value, str) or not value or "\\" in value:
|
|
1297
|
+
raise DriverError("INVALID_MANIFEST", f"{label} invalid")
|
|
1298
|
+
path = PurePosixPath(value)
|
|
1299
|
+
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
|
1300
|
+
raise DriverError("INVALID_MANIFEST", f"{label} unsafe")
|
|
1301
|
+
return path.as_posix()
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
def _sha256_file(path: Path) -> str:
|
|
1305
|
+
digest = hashlib.sha256()
|
|
1306
|
+
with path.open("rb") as handle:
|
|
1307
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
1308
|
+
digest.update(chunk)
|
|
1309
|
+
return digest.hexdigest()
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
def _sha256_bytes(value: bytes) -> str:
|
|
1313
|
+
return hashlib.sha256(value).hexdigest()
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def _canonical_hash(value: Any) -> str:
|
|
1317
|
+
return _sha256_bytes(_canonical_json(value).encode("utf-8"))
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def _canonical_json(value: Any) -> str:
|
|
1321
|
+
try:
|
|
1322
|
+
return json.dumps(
|
|
1323
|
+
value,
|
|
1324
|
+
ensure_ascii=False,
|
|
1325
|
+
sort_keys=True,
|
|
1326
|
+
separators=(",", ":"),
|
|
1327
|
+
allow_nan=False,
|
|
1328
|
+
)
|
|
1329
|
+
except (TypeError, ValueError) as exc:
|
|
1330
|
+
raise DriverError("NON_CANONICAL_JSON", "value is not canonical JSON") from exc
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def _loads_strict_json(raw: str) -> Any:
|
|
1334
|
+
def reject_constant(value: str) -> None:
|
|
1335
|
+
raise ValueError(f"non-finite constant: {value}")
|
|
1336
|
+
|
|
1337
|
+
def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
1338
|
+
result: dict[str, Any] = {}
|
|
1339
|
+
for key, value in pairs:
|
|
1340
|
+
if key in result:
|
|
1341
|
+
raise ValueError(f"duplicate key: {key}")
|
|
1342
|
+
result[key] = value
|
|
1343
|
+
return result
|
|
1344
|
+
|
|
1345
|
+
try:
|
|
1346
|
+
return json.loads(raw, parse_constant=reject_constant, object_pairs_hook=reject_duplicates)
|
|
1347
|
+
except (TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
1348
|
+
raise DriverError("INVALID_JSON", "input is not strict JSON") from exc
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def _mapping(value: Any, label: str) -> Mapping[str, Any]:
|
|
1352
|
+
if not isinstance(value, Mapping) or any(not isinstance(key, str) for key in value):
|
|
1353
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be string-keyed mapping")
|
|
1354
|
+
return value
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def _exact_keys(value: Mapping[str, Any], expected: set[str], label: str) -> None:
|
|
1358
|
+
if set(value) != expected:
|
|
1359
|
+
raise DriverError("INVALID_SCHEMA", f"{label} fields do not match protocol")
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
def _text(value: Any, label: str) -> str:
|
|
1363
|
+
if not isinstance(value, str) or not value.strip():
|
|
1364
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be non-empty text")
|
|
1365
|
+
return value
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def _hash(value: Any, label: str) -> str:
|
|
1369
|
+
if not isinstance(value, str) or _HASH_RE.fullmatch(value) is None:
|
|
1370
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be SHA-256")
|
|
1371
|
+
return value
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def _positive_int(value: Any, label: str) -> int:
|
|
1375
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
|
1376
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be positive integer")
|
|
1377
|
+
return value
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
def _nonnegative_int(value: Any, label: str) -> int:
|
|
1381
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
1382
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be non-negative integer")
|
|
1383
|
+
return value
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
def _finite_float(value: Any, label: str) -> float:
|
|
1387
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
1388
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be numeric")
|
|
1389
|
+
result = float(value)
|
|
1390
|
+
if not math.isfinite(result):
|
|
1391
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be finite")
|
|
1392
|
+
return result
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
def _nonnegative_float(value: Any, label: str) -> float:
|
|
1396
|
+
result = _finite_float(value, label)
|
|
1397
|
+
if result < 0:
|
|
1398
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be non-negative")
|
|
1399
|
+
return result
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
def _positive_float(value: Any, label: str) -> float:
|
|
1403
|
+
result = _nonnegative_float(value, label)
|
|
1404
|
+
if result <= 0:
|
|
1405
|
+
raise DriverError("INVALID_SCHEMA", f"{label} must be positive")
|
|
1406
|
+
return result
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def _validate_base_url(value: str) -> str:
|
|
1410
|
+
parsed = urllib.parse.urlparse(value)
|
|
1411
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
1412
|
+
raise DriverError("INVALID_BACKEND_CONFIG", "base_url must be HTTP(S)")
|
|
1413
|
+
if parsed.username or parsed.password or parsed.query or parsed.fragment:
|
|
1414
|
+
raise DriverError("INVALID_BACKEND_CONFIG", "base_url contains forbidden components")
|
|
1415
|
+
return value.rstrip("/")
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
1419
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
1420
|
+
parser.add_argument("--checkout", type=Path, required=True)
|
|
1421
|
+
parser.add_argument("--patterns", type=Path)
|
|
1422
|
+
parser.add_argument("--compatibility-manifest", type=Path, default=DEFAULT_MANIFEST)
|
|
1423
|
+
parser.add_argument("--backend-config-json")
|
|
1424
|
+
parser.add_argument("--acknowledge-noassertion-license", action="store_true")
|
|
1425
|
+
parser.add_argument("--verify-only", action="store_true")
|
|
1426
|
+
args = parser.parse_args(argv)
|
|
1427
|
+
policy = load_compatibility_policy(args.compatibility_manifest)
|
|
1428
|
+
if args.verify_only:
|
|
1429
|
+
root = verify_checkout(args.checkout, policy)
|
|
1430
|
+
print(
|
|
1431
|
+
_canonical_json(
|
|
1432
|
+
{
|
|
1433
|
+
"status": "ok",
|
|
1434
|
+
"upstream_commit": UPSTREAM_COMMIT,
|
|
1435
|
+
"source_license": "NOASSERTION",
|
|
1436
|
+
"symbols": audit_source_symbols(root, policy),
|
|
1437
|
+
}
|
|
1438
|
+
)
|
|
1439
|
+
)
|
|
1440
|
+
return 0
|
|
1441
|
+
if not args.backend_config_json:
|
|
1442
|
+
parser.error("--backend-config-json is required unless --verify-only is used")
|
|
1443
|
+
config = BackendConfig.from_json(args.backend_config_json)
|
|
1444
|
+
sys.dont_write_bytecode = True
|
|
1445
|
+
with redirect_stdout(sys.stderr):
|
|
1446
|
+
runtime = ReFormeRRuntime(
|
|
1447
|
+
args.checkout,
|
|
1448
|
+
config,
|
|
1449
|
+
OpenAICompatibleBackend(config),
|
|
1450
|
+
pattern_path=args.patterns,
|
|
1451
|
+
source_license_acknowledged=args.acknowledge_noassertion_license,
|
|
1452
|
+
policy=policy,
|
|
1453
|
+
)
|
|
1454
|
+
serve(runtime, sys.stdin, sys.stdout)
|
|
1455
|
+
return 0
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
if __name__ == "__main__":
|
|
1459
|
+
raise SystemExit(main())
|