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,1321 @@
|
|
|
1
|
+
"""Production provider for the pinned ToolSandbox official runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import random
|
|
9
|
+
import subprocess
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from agentinputlab.contracts.serialization import to_jsonable
|
|
15
|
+
|
|
16
|
+
from ..protocol import (
|
|
17
|
+
OfficialArenaError,
|
|
18
|
+
ProviderArmResult,
|
|
19
|
+
content_hash,
|
|
20
|
+
intervention_usage,
|
|
21
|
+
materialized_state,
|
|
22
|
+
require_materialized_budget,
|
|
23
|
+
resolve_shared_budget_config,
|
|
24
|
+
validate_dependency_hashes,
|
|
25
|
+
visible_shared_budget,
|
|
26
|
+
)
|
|
27
|
+
from ._message_compat import (
|
|
28
|
+
coalesce_chat_system_messages,
|
|
29
|
+
requires_qwen38_initial_system_message,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _EvaluatedAgentOutputInvalid(RuntimeError):
|
|
34
|
+
"""A recognized malformed model generation from the evaluated agent."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, error: Exception) -> None:
|
|
37
|
+
super().__init__(f"{type(error).__name__}: evaluated agent output is invalid")
|
|
38
|
+
self.error = error
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _model_output_invalid_result(
|
|
42
|
+
*,
|
|
43
|
+
replay_record: Mapping[str, Any],
|
|
44
|
+
role: str,
|
|
45
|
+
seed: int,
|
|
46
|
+
work_dir: Path,
|
|
47
|
+
error: Exception,
|
|
48
|
+
measured_usage: Mapping[str, int | float],
|
|
49
|
+
compatibility_repairs: list[Mapping[str, Any]],
|
|
50
|
+
budget_enforcements: list[Mapping[str, int]],
|
|
51
|
+
user_simulator_usage: Mapping[str, int | float],
|
|
52
|
+
) -> ProviderArmResult:
|
|
53
|
+
"""Materialize a malformed evaluated-agent response as an official zero score."""
|
|
54
|
+
|
|
55
|
+
trace = {
|
|
56
|
+
"provider": "toolsandbox",
|
|
57
|
+
"source_revision": ToolSandboxProvider.source_revision,
|
|
58
|
+
"scenario_name": replay_record["protected"]["scenario_name"],
|
|
59
|
+
"role": role,
|
|
60
|
+
"seed": seed,
|
|
61
|
+
"termination_reason": "generated_agent_response_invalid",
|
|
62
|
+
"generation_controls": {
|
|
63
|
+
"hard_stops": 1,
|
|
64
|
+
"failure_type": type(error).__name__,
|
|
65
|
+
"failed_generation_scope": "evaluated_agent",
|
|
66
|
+
},
|
|
67
|
+
"compatibility_repairs": list(compatibility_repairs),
|
|
68
|
+
"budget_enforcements": list(budget_enforcements),
|
|
69
|
+
"user_simulator_usage": dict(user_simulator_usage),
|
|
70
|
+
"score_provenance": "paper_model_output_invalid_zero_action",
|
|
71
|
+
}
|
|
72
|
+
work_dir.joinpath("model-output-invalid.json").write_text(
|
|
73
|
+
json.dumps(trace, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
74
|
+
encoding="utf-8",
|
|
75
|
+
)
|
|
76
|
+
return ProviderArmResult(
|
|
77
|
+
metrics={
|
|
78
|
+
"official_similarity": 0.0,
|
|
79
|
+
"milestone_similarity": 0.0,
|
|
80
|
+
"minefield_similarity": 0.0,
|
|
81
|
+
"turn_count": 0.0,
|
|
82
|
+
"adapter_compatibility_repairs": float(len(compatibility_repairs)),
|
|
83
|
+
"adapter_hard_stops": 1.0,
|
|
84
|
+
},
|
|
85
|
+
trace=trace,
|
|
86
|
+
usage=dict(measured_usage),
|
|
87
|
+
runtime_instance_id=content_hash(
|
|
88
|
+
{
|
|
89
|
+
"provider": "toolsandbox",
|
|
90
|
+
"checkpoint_id": replay_record["checkpoint_id"],
|
|
91
|
+
"role": role,
|
|
92
|
+
"seed": seed,
|
|
93
|
+
"termination_reason": trace["termination_reason"],
|
|
94
|
+
"work_dir": str(work_dir),
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ToolSandboxProvider:
|
|
101
|
+
"""Capture, restore, execute, and officially score ToolSandbox scenarios."""
|
|
102
|
+
|
|
103
|
+
benchmark_id = "toolsandbox.common_support"
|
|
104
|
+
source_revision = "165848b9a78cead7ca7fe7c89c688b58e6501219"
|
|
105
|
+
scorer_id = "toolsandbox.official_similarity_v1"
|
|
106
|
+
scorer_revision = "a81814306ce7aa229079fc530f6d026b423542cecc342a7677ac8dc3dd167481"
|
|
107
|
+
|
|
108
|
+
_CONFIG_FIELDS = frozenset(
|
|
109
|
+
{
|
|
110
|
+
"scenario_names",
|
|
111
|
+
"limit",
|
|
112
|
+
"preferred_tool_backend",
|
|
113
|
+
"agent_model",
|
|
114
|
+
"agent_base_url",
|
|
115
|
+
"agent_key_env",
|
|
116
|
+
"user_model",
|
|
117
|
+
"user_base_url",
|
|
118
|
+
"user_key_env",
|
|
119
|
+
"locked_dependency_hashes",
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
_OPTIONAL_CONFIG_FIELDS = frozenset({"budget_override"})
|
|
123
|
+
|
|
124
|
+
def __init__(self, *, history_transform: Any = None) -> None:
|
|
125
|
+
if history_transform is not None and not all(
|
|
126
|
+
callable(getattr(history_transform, name, None)) for name in ("transform", "fingerprint")
|
|
127
|
+
):
|
|
128
|
+
raise OfficialArenaError("ToolSandbox history transform requires transform/fingerprint")
|
|
129
|
+
self.history_transform = history_transform
|
|
130
|
+
|
|
131
|
+
def doctor(self, config: Mapping[str, Any]) -> tuple[str, ...]:
|
|
132
|
+
try:
|
|
133
|
+
self._validate_config(config)
|
|
134
|
+
head = subprocess.check_output(
|
|
135
|
+
["git", "rev-parse", "HEAD"],
|
|
136
|
+
text=True,
|
|
137
|
+
stderr=subprocess.DEVNULL,
|
|
138
|
+
).strip()
|
|
139
|
+
if head != self.source_revision:
|
|
140
|
+
raise OfficialArenaError("ToolSandbox checkout revision differs")
|
|
141
|
+
from tool_sandbox.common.scenario import Scenario
|
|
142
|
+
from tool_sandbox.roles.execution_environment import ExecutionEnvironment
|
|
143
|
+
from tool_sandbox.scenarios import named_scenarios
|
|
144
|
+
|
|
145
|
+
del ExecutionEnvironment, Scenario, named_scenarios
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
return (f"{type(exc).__name__}: {exc}",)
|
|
148
|
+
return ()
|
|
149
|
+
|
|
150
|
+
def capture(
|
|
151
|
+
self,
|
|
152
|
+
config: Mapping[str, Any],
|
|
153
|
+
*,
|
|
154
|
+
output_root: Path,
|
|
155
|
+
) -> tuple[Mapping[str, Any], ...]:
|
|
156
|
+
del output_root
|
|
157
|
+
parsed = self._validate_config(config)
|
|
158
|
+
scenarios = self._scenarios(parsed)
|
|
159
|
+
rows: list[Mapping[str, Any]] = []
|
|
160
|
+
for scenario_name, scenario in scenarios.items():
|
|
161
|
+
serialized_context = scenario.starting_context.to_dict(serialize_console=False)
|
|
162
|
+
sandbox_rows = _sandbox_rows(serialized_context)
|
|
163
|
+
query = _opening_user_query(sandbox_rows)
|
|
164
|
+
instruction = _agent_system_instruction(sandbox_rows)
|
|
165
|
+
available_tools = _agent_available_tools(scenario.starting_context)
|
|
166
|
+
tools = list(available_tools.keys())
|
|
167
|
+
from tool_sandbox.common.tool_conversion import convert_to_openai_tools
|
|
168
|
+
|
|
169
|
+
tool_schemas = convert_to_openai_tools(dict(available_tools))
|
|
170
|
+
if not tool_schemas or any(not isinstance(item, Mapping) for item in tool_schemas):
|
|
171
|
+
raise OfficialArenaError("ToolSandbox tool schemas are unavailable")
|
|
172
|
+
visible_state = {
|
|
173
|
+
"query": query,
|
|
174
|
+
"task_id": scenario_name,
|
|
175
|
+
"instruction": instruction,
|
|
176
|
+
"context": [
|
|
177
|
+
{
|
|
178
|
+
"text": json.dumps(
|
|
179
|
+
{
|
|
180
|
+
"available_tools": tools,
|
|
181
|
+
"tool_schemas": tool_schemas,
|
|
182
|
+
"categories": [str(item) for item in scenario.categories],
|
|
183
|
+
},
|
|
184
|
+
ensure_ascii=False,
|
|
185
|
+
sort_keys=True,
|
|
186
|
+
),
|
|
187
|
+
"block_id": "official_tool_catalog",
|
|
188
|
+
"source": "toolsandbox.starting_context",
|
|
189
|
+
"score": None,
|
|
190
|
+
"metadata": {"scenario_name": scenario_name},
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
"messages": [{"role": "user", "content": query, "name": None}],
|
|
194
|
+
"tool_names": tools,
|
|
195
|
+
"metadata": {
|
|
196
|
+
"benchmark_id": self.benchmark_id,
|
|
197
|
+
"checkpoint_phase": "turn_zero_before_agent_response",
|
|
198
|
+
"clarification_roundtrip_available": True,
|
|
199
|
+
"state_restore_available": True,
|
|
200
|
+
"scenario_categories": [str(item) for item in scenario.categories],
|
|
201
|
+
},
|
|
202
|
+
"budget": visible_shared_budget(
|
|
203
|
+
parsed,
|
|
204
|
+
{
|
|
205
|
+
"max_model_calls": scenario.max_messages,
|
|
206
|
+
"max_retrieval_calls": scenario.max_messages,
|
|
207
|
+
"max_tool_calls": scenario.max_messages,
|
|
208
|
+
"max_output_tokens": 65536,
|
|
209
|
+
"max_context_tokens": None,
|
|
210
|
+
"max_cost_usd": None,
|
|
211
|
+
},
|
|
212
|
+
),
|
|
213
|
+
}
|
|
214
|
+
rows.append(
|
|
215
|
+
{
|
|
216
|
+
"checkpoint_id": scenario_name,
|
|
217
|
+
"group_key": scenario_name,
|
|
218
|
+
"visible_state": visible_state,
|
|
219
|
+
"eligible_families": [
|
|
220
|
+
"reformulate",
|
|
221
|
+
"decompose",
|
|
222
|
+
"acquire",
|
|
223
|
+
"clarify",
|
|
224
|
+
"curate",
|
|
225
|
+
],
|
|
226
|
+
"protected": {
|
|
227
|
+
"scenario_name": scenario_name,
|
|
228
|
+
"starting_context": serialized_context,
|
|
229
|
+
"starting_context_hash": content_hash(serialized_context),
|
|
230
|
+
"locked_dependency_hashes": parsed["locked_dependency_hashes"],
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
if not rows:
|
|
235
|
+
raise OfficialArenaError("ToolSandbox capture found no scenarios")
|
|
236
|
+
return tuple(rows)
|
|
237
|
+
|
|
238
|
+
def run_arm(
|
|
239
|
+
self,
|
|
240
|
+
config: Mapping[str, Any],
|
|
241
|
+
*,
|
|
242
|
+
replay_record: Mapping[str, Any],
|
|
243
|
+
arm_payload: Mapping[str, Any],
|
|
244
|
+
seed: int,
|
|
245
|
+
role: str,
|
|
246
|
+
work_dir: Path,
|
|
247
|
+
) -> ProviderArmResult:
|
|
248
|
+
parsed = self._validate_config(config)
|
|
249
|
+
protected = replay_record["protected"]
|
|
250
|
+
state = materialized_state(arm_payload)
|
|
251
|
+
budget = require_materialized_budget(state, parsed)
|
|
252
|
+
optimizer_usage = intervention_usage(arm_payload)
|
|
253
|
+
budget_override = parsed.get("budget_override")
|
|
254
|
+
scenario_name = protected["scenario_name"]
|
|
255
|
+
scenarios = self._scenarios(
|
|
256
|
+
{
|
|
257
|
+
**parsed,
|
|
258
|
+
"scenario_names": [scenario_name],
|
|
259
|
+
"limit": 1,
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
try:
|
|
263
|
+
scenario = scenarios[scenario_name]
|
|
264
|
+
except KeyError as exc:
|
|
265
|
+
raise OfficialArenaError(
|
|
266
|
+
"ToolSandbox protected scenario identity is unavailable"
|
|
267
|
+
) from exc
|
|
268
|
+
|
|
269
|
+
from tool_sandbox.common.execution_context import ExecutionContext, RoleType
|
|
270
|
+
from tool_sandbox.roles.execution_environment import ExecutionEnvironment
|
|
271
|
+
|
|
272
|
+
serialized_context = protected["starting_context"]
|
|
273
|
+
if content_hash(serialized_context) != protected["starting_context_hash"]:
|
|
274
|
+
raise OfficialArenaError("ToolSandbox protected starting state changed")
|
|
275
|
+
scenario.starting_context = ExecutionContext.from_dict(dict(serialized_context))
|
|
276
|
+
agent_budget, simulator_budget = _role_budgets(
|
|
277
|
+
scenario_max_messages=scenario.max_messages,
|
|
278
|
+
budget=budget,
|
|
279
|
+
optimizer_usage=optimizer_usage,
|
|
280
|
+
budget_override=budget_override,
|
|
281
|
+
)
|
|
282
|
+
agent = _intervened_agent(state, parsed, seed=seed, budget=agent_budget,
|
|
283
|
+
history_transform=self.history_transform)
|
|
284
|
+
user = _tracked_user(parsed, seed=seed + 1_000_000, budget=simulator_budget)
|
|
285
|
+
try:
|
|
286
|
+
result = scenario.play_and_evaluate(
|
|
287
|
+
roles={
|
|
288
|
+
RoleType.USER: user,
|
|
289
|
+
RoleType.EXECUTION_ENVIRONMENT: ExecutionEnvironment(),
|
|
290
|
+
RoleType.AGENT: agent,
|
|
291
|
+
},
|
|
292
|
+
output_directory=work_dir,
|
|
293
|
+
scenario_name=scenario_name,
|
|
294
|
+
)
|
|
295
|
+
except _EvaluatedAgentOutputInvalid as failure:
|
|
296
|
+
return _model_output_invalid_result(
|
|
297
|
+
replay_record=replay_record,
|
|
298
|
+
role=role,
|
|
299
|
+
seed=seed,
|
|
300
|
+
work_dir=work_dir,
|
|
301
|
+
error=failure.error,
|
|
302
|
+
measured_usage=agent.usage,
|
|
303
|
+
compatibility_repairs=[
|
|
304
|
+
*agent.compatibility_repairs,
|
|
305
|
+
*user.compatibility_repairs,
|
|
306
|
+
],
|
|
307
|
+
budget_enforcements=agent.budget_enforcements,
|
|
308
|
+
user_simulator_usage=user.usage,
|
|
309
|
+
)
|
|
310
|
+
evaluation = result.evaluation_result
|
|
311
|
+
ending = result.ending_context.to_dict(serialize_console=False)
|
|
312
|
+
sandbox_rows = _sandbox_rows(ending)
|
|
313
|
+
tool_calls = sum(
|
|
314
|
+
1
|
|
315
|
+
for row in sandbox_rows
|
|
316
|
+
if _role_name(row.get("sender")) == "AGENT"
|
|
317
|
+
and _role_name(row.get("recipient")) == "EXECUTION_ENVIRONMENT"
|
|
318
|
+
)
|
|
319
|
+
# The simulated user is benchmark infrastructure, not the evaluated
|
|
320
|
+
# agent. Its calls must neither consume the intervention/agent budget
|
|
321
|
+
# nor enter paper usage and cost accounting. Keep its usage in the
|
|
322
|
+
# protected provider trace so the execution remains auditable.
|
|
323
|
+
usage = dict(agent.usage)
|
|
324
|
+
usage["tool_calls"] = tool_calls
|
|
325
|
+
trace = {
|
|
326
|
+
"provider": "toolsandbox",
|
|
327
|
+
"live_history_transform": (
|
|
328
|
+
to_jsonable(self.history_transform.fingerprint()) if self.history_transform is not None else None
|
|
329
|
+
),
|
|
330
|
+
"live_history_records": (
|
|
331
|
+
to_jsonable(list(getattr(self.history_transform, "records", ()))) if self.history_transform is not None else []
|
|
332
|
+
),
|
|
333
|
+
"compatibility_repairs": [
|
|
334
|
+
*agent.compatibility_repairs,
|
|
335
|
+
*user.compatibility_repairs,
|
|
336
|
+
],
|
|
337
|
+
"source_revision": self.source_revision,
|
|
338
|
+
"scenario_name": scenario_name,
|
|
339
|
+
"role": role,
|
|
340
|
+
"seed": seed,
|
|
341
|
+
"generation_controls": {
|
|
342
|
+
"temperature": 0.0,
|
|
343
|
+
"seed_policy": "role_base_seed_plus_call_index",
|
|
344
|
+
"agent_base_seed": seed,
|
|
345
|
+
"user_base_seed": seed + 1_000_000,
|
|
346
|
+
"enable_thinking": False,
|
|
347
|
+
"max_agent_model_calls": agent_budget["max_model_calls"],
|
|
348
|
+
"max_agent_tool_calls": agent_budget["max_tool_calls"],
|
|
349
|
+
"user_simulator_budget_separate": True,
|
|
350
|
+
"user_simulator_model_calls": simulator_budget["model_calls"],
|
|
351
|
+
"user_simulator_output_tokens": simulator_budget["output_tokens"],
|
|
352
|
+
"user_simulator_hard_stops": simulator_budget["hard_stops"],
|
|
353
|
+
"hard_stops": agent_budget["hard_stops"],
|
|
354
|
+
},
|
|
355
|
+
"budget_enforcements": list(agent.budget_enforcements),
|
|
356
|
+
"user_simulator_usage": dict(user.usage),
|
|
357
|
+
"ending_context": ending,
|
|
358
|
+
"evaluation": {
|
|
359
|
+
"similarity": evaluation.similarity,
|
|
360
|
+
"milestone_similarity": evaluation.milestone_similarity,
|
|
361
|
+
"minefield_similarity": evaluation.minefield_similarity,
|
|
362
|
+
"turn_count": evaluation.turn_count,
|
|
363
|
+
"milestone_mapping": dict(evaluation.milestone_mapping),
|
|
364
|
+
"minefield_mapping": dict(evaluation.minefield_mapping),
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
work_dir.joinpath("official_result.json").write_text(
|
|
368
|
+
json.dumps(trace, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
369
|
+
encoding="utf-8",
|
|
370
|
+
)
|
|
371
|
+
return ProviderArmResult(
|
|
372
|
+
metrics={
|
|
373
|
+
"official_similarity": float(evaluation.similarity),
|
|
374
|
+
"milestone_similarity": float(evaluation.milestone_similarity),
|
|
375
|
+
"minefield_similarity": float(evaluation.minefield_similarity),
|
|
376
|
+
"turn_count": float(evaluation.turn_count),
|
|
377
|
+
"adapter_compatibility_repairs": float(
|
|
378
|
+
len(agent.compatibility_repairs) + len(user.compatibility_repairs)
|
|
379
|
+
),
|
|
380
|
+
"adapter_hard_stops": float(agent_budget["hard_stops"]),
|
|
381
|
+
},
|
|
382
|
+
trace=trace,
|
|
383
|
+
usage=usage,
|
|
384
|
+
runtime_instance_id=content_hash(
|
|
385
|
+
{
|
|
386
|
+
"provider": "toolsandbox",
|
|
387
|
+
"checkpoint_id": replay_record["checkpoint_id"],
|
|
388
|
+
"role": role,
|
|
389
|
+
"seed": seed,
|
|
390
|
+
"work_dir": str(work_dir),
|
|
391
|
+
}
|
|
392
|
+
),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def _scenarios(self, config: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
396
|
+
from tool_sandbox.common.tool_discovery import ToolBackend
|
|
397
|
+
from tool_sandbox.scenarios import named_scenarios
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
backend = ToolBackend(config["preferred_tool_backend"])
|
|
401
|
+
except ValueError as exc:
|
|
402
|
+
try:
|
|
403
|
+
backend = ToolBackend[config["preferred_tool_backend"]]
|
|
404
|
+
except KeyError:
|
|
405
|
+
raise OfficialArenaError(
|
|
406
|
+
"ToolSandbox preferred_tool_backend is unsupported"
|
|
407
|
+
) from exc
|
|
408
|
+
all_scenarios = _deterministic_scenarios(
|
|
409
|
+
lambda: named_scenarios(preferred_tool_backend=backend),
|
|
410
|
+
seed_material={
|
|
411
|
+
"source_revision": self.source_revision,
|
|
412
|
+
"preferred_tool_backend": config["preferred_tool_backend"],
|
|
413
|
+
},
|
|
414
|
+
)
|
|
415
|
+
requested = config["scenario_names"]
|
|
416
|
+
names = list(all_scenarios) if requested is None else list(requested)
|
|
417
|
+
missing = set(names).difference(all_scenarios)
|
|
418
|
+
if missing:
|
|
419
|
+
raise OfficialArenaError(
|
|
420
|
+
"ToolSandbox scenarios are unavailable: " + ", ".join(sorted(missing))
|
|
421
|
+
)
|
|
422
|
+
return {name: all_scenarios[name] for name in names[: config["limit"]]}
|
|
423
|
+
|
|
424
|
+
def _validate_config(self, config: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
425
|
+
config = resolve_shared_budget_config(config)
|
|
426
|
+
if not isinstance(config, Mapping) or not (
|
|
427
|
+
set(config) >= self._CONFIG_FIELDS
|
|
428
|
+
and set(config) <= self._CONFIG_FIELDS | self._OPTIONAL_CONFIG_FIELDS
|
|
429
|
+
):
|
|
430
|
+
raise OfficialArenaError("ToolSandbox provider config fields are invalid")
|
|
431
|
+
names = config["scenario_names"]
|
|
432
|
+
if names is not None and (
|
|
433
|
+
isinstance(names, (str, bytes))
|
|
434
|
+
or not isinstance(names, list)
|
|
435
|
+
or not names
|
|
436
|
+
or any(not isinstance(item, str) or not item for item in names)
|
|
437
|
+
):
|
|
438
|
+
raise OfficialArenaError("ToolSandbox scenario_names is invalid")
|
|
439
|
+
limit = config["limit"]
|
|
440
|
+
if isinstance(limit, bool) or not isinstance(limit, int) or limit < 1:
|
|
441
|
+
raise OfficialArenaError("ToolSandbox limit must be positive")
|
|
442
|
+
for name in (
|
|
443
|
+
"preferred_tool_backend",
|
|
444
|
+
"agent_model",
|
|
445
|
+
"agent_base_url",
|
|
446
|
+
"agent_key_env",
|
|
447
|
+
"user_model",
|
|
448
|
+
"user_base_url",
|
|
449
|
+
"user_key_env",
|
|
450
|
+
):
|
|
451
|
+
value = config[name]
|
|
452
|
+
if not isinstance(value, str) or not value.strip():
|
|
453
|
+
raise OfficialArenaError(f"ToolSandbox {name} is invalid")
|
|
454
|
+
for name in ("agent_key_env", "user_key_env"):
|
|
455
|
+
environment_name = config[name]
|
|
456
|
+
if not environment_name.replace("_", "").isalnum():
|
|
457
|
+
raise OfficialArenaError(f"ToolSandbox {name} is invalid")
|
|
458
|
+
if not os.environ.get(environment_name):
|
|
459
|
+
raise OfficialArenaError(f"ToolSandbox environment {environment_name} is missing")
|
|
460
|
+
validate_dependency_hashes(
|
|
461
|
+
config["locked_dependency_hashes"],
|
|
462
|
+
label="ToolSandbox",
|
|
463
|
+
)
|
|
464
|
+
return config
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _role_budgets(
|
|
468
|
+
*,
|
|
469
|
+
scenario_max_messages: int,
|
|
470
|
+
budget: Mapping[str, Any],
|
|
471
|
+
optimizer_usage: Mapping[str, Any],
|
|
472
|
+
budget_override: Mapping[str, Any] | None,
|
|
473
|
+
) -> tuple[dict[str, int], dict[str, int]]:
|
|
474
|
+
"""Separate the evaluated agent budget from benchmark-simulator usage."""
|
|
475
|
+
|
|
476
|
+
per_call = int(
|
|
477
|
+
budget_override["max_output_tokens_per_call"]
|
|
478
|
+
if isinstance(budget_override, Mapping)
|
|
479
|
+
else 512
|
|
480
|
+
)
|
|
481
|
+
agent = {
|
|
482
|
+
"max_model_calls": max(
|
|
483
|
+
0,
|
|
484
|
+
min(
|
|
485
|
+
scenario_max_messages,
|
|
486
|
+
int(budget["max_model_calls"]) - int(optimizer_usage.get("model_calls", 0)),
|
|
487
|
+
),
|
|
488
|
+
),
|
|
489
|
+
"model_calls": 0,
|
|
490
|
+
"max_tool_calls": max(
|
|
491
|
+
0,
|
|
492
|
+
int(budget["max_tool_calls"]) - int(optimizer_usage.get("tool_calls", 0)),
|
|
493
|
+
),
|
|
494
|
+
"tool_calls": 0,
|
|
495
|
+
"max_output_tokens": max(
|
|
496
|
+
0,
|
|
497
|
+
int(budget["max_output_tokens"]) - int(optimizer_usage.get("output_tokens", 0)),
|
|
498
|
+
),
|
|
499
|
+
"output_tokens": 0,
|
|
500
|
+
"max_output_tokens_per_call": per_call,
|
|
501
|
+
"hard_stops": 0,
|
|
502
|
+
}
|
|
503
|
+
simulator = {
|
|
504
|
+
"max_model_calls": scenario_max_messages,
|
|
505
|
+
"model_calls": 0,
|
|
506
|
+
"max_output_tokens": scenario_max_messages * per_call,
|
|
507
|
+
"output_tokens": 0,
|
|
508
|
+
"max_output_tokens_per_call": per_call,
|
|
509
|
+
"hard_stops": 0,
|
|
510
|
+
}
|
|
511
|
+
return agent, simulator
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _sandbox_rows(serialized_context: Mapping[str, Any]) -> list[Mapping[str, Any]]:
|
|
515
|
+
databases = serialized_context.get("_dbs")
|
|
516
|
+
if not isinstance(databases, Mapping):
|
|
517
|
+
raise OfficialArenaError("ToolSandbox serialized context has no databases")
|
|
518
|
+
rows = databases.get("SANDBOX")
|
|
519
|
+
if not isinstance(rows, list):
|
|
520
|
+
raise OfficialArenaError("ToolSandbox serialized context has no SANDBOX rows")
|
|
521
|
+
return [row for row in rows if isinstance(row, Mapping)]
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _role_name(value: Any) -> str:
|
|
525
|
+
text = str(value)
|
|
526
|
+
return text.rsplit(".", 1)[-1].upper()
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _opening_user_query(rows: list[Mapping[str, Any]]) -> str:
|
|
530
|
+
# ToolSandbox prepends user-simulator few-shot conversations to the live
|
|
531
|
+
# scenario. The actual turn-zero task is the final USER -> AGENT message,
|
|
532
|
+
# not the first demonstration message.
|
|
533
|
+
for row in reversed(rows):
|
|
534
|
+
if (
|
|
535
|
+
_role_name(row.get("sender")) == "USER"
|
|
536
|
+
and _role_name(row.get("recipient")) == "AGENT"
|
|
537
|
+
and isinstance(row.get("content"), str)
|
|
538
|
+
and row["content"].strip()
|
|
539
|
+
):
|
|
540
|
+
return row["content"]
|
|
541
|
+
raise OfficialArenaError("ToolSandbox scenario has no visible opening user query")
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _agent_system_instruction(rows: list[Mapping[str, Any]]) -> str:
|
|
545
|
+
values = [
|
|
546
|
+
row["content"]
|
|
547
|
+
for row in rows
|
|
548
|
+
if _role_name(row.get("sender")) == "SYSTEM"
|
|
549
|
+
and _role_name(row.get("recipient")) == "AGENT"
|
|
550
|
+
and isinstance(row.get("content"), str)
|
|
551
|
+
and row["content"].strip()
|
|
552
|
+
]
|
|
553
|
+
return "\n\n".join(values)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _intervened_agent(
|
|
557
|
+
state: Mapping[str, Any],
|
|
558
|
+
config: Mapping[str, Any],
|
|
559
|
+
*,
|
|
560
|
+
seed: int,
|
|
561
|
+
budget: dict[str, int],
|
|
562
|
+
history_transform: Any = None,
|
|
563
|
+
) -> Any:
|
|
564
|
+
from openai import OpenAI
|
|
565
|
+
from tool_sandbox.common.execution_context import RoleType
|
|
566
|
+
from tool_sandbox.common.message_conversion import Message
|
|
567
|
+
from tool_sandbox.roles.openai_api_agent import OpenAIAPIAgent
|
|
568
|
+
|
|
569
|
+
class IntervenedAgent(OpenAIAPIAgent):
|
|
570
|
+
model_name = config["agent_model"]
|
|
571
|
+
|
|
572
|
+
def __init__(self) -> None:
|
|
573
|
+
# The pinned upstream constructor only creates an unused public
|
|
574
|
+
# OpenAI client. Construct the bound client directly so local-only
|
|
575
|
+
# execution never requires an unrelated OPENAI_API_KEY.
|
|
576
|
+
self.openai_client = OpenAI(
|
|
577
|
+
api_key=os.environ[config["agent_key_env"]],
|
|
578
|
+
base_url=config["agent_base_url"],
|
|
579
|
+
max_retries=0,
|
|
580
|
+
)
|
|
581
|
+
self.usage = _empty_usage()
|
|
582
|
+
self._first_response = True
|
|
583
|
+
self._first_inference = True
|
|
584
|
+
self._last_response: Any = None
|
|
585
|
+
self._last_advertised_tool_names: set[str] = set()
|
|
586
|
+
self.compatibility_repairs: list[dict[str, Any]] = []
|
|
587
|
+
self.budget_enforcements: list[dict[str, int]] = []
|
|
588
|
+
self._base_seed = seed
|
|
589
|
+
self._inference_calls = 0
|
|
590
|
+
|
|
591
|
+
def respond(self, ending_index: int | None = None) -> None:
|
|
592
|
+
if (
|
|
593
|
+
budget["model_calls"] >= budget["max_model_calls"]
|
|
594
|
+
or budget["tool_calls"] >= budget["max_tool_calls"]
|
|
595
|
+
or budget["output_tokens"] >= budget["max_output_tokens"]
|
|
596
|
+
):
|
|
597
|
+
if not budget["hard_stops"]:
|
|
598
|
+
budget["hard_stops"] = 1
|
|
599
|
+
self.add_messages(
|
|
600
|
+
[
|
|
601
|
+
Message(
|
|
602
|
+
sender=RoleType.AGENT,
|
|
603
|
+
recipient=RoleType.USER,
|
|
604
|
+
content="I could not complete the task within the run budget.",
|
|
605
|
+
conversation_active=False,
|
|
606
|
+
)
|
|
607
|
+
]
|
|
608
|
+
)
|
|
609
|
+
return
|
|
610
|
+
metadata = state.get("metadata", {})
|
|
611
|
+
clarification = (
|
|
612
|
+
metadata.get("clarification_request") if isinstance(metadata, Mapping) else None
|
|
613
|
+
)
|
|
614
|
+
if self._first_response and isinstance(clarification, str) and clarification.strip():
|
|
615
|
+
self._first_response = False
|
|
616
|
+
self.add_messages(
|
|
617
|
+
[
|
|
618
|
+
Message(
|
|
619
|
+
sender=RoleType.AGENT,
|
|
620
|
+
recipient=RoleType.USER,
|
|
621
|
+
content=clarification,
|
|
622
|
+
)
|
|
623
|
+
]
|
|
624
|
+
)
|
|
625
|
+
return
|
|
626
|
+
self._first_response = False
|
|
627
|
+
self._last_response = None
|
|
628
|
+
self._last_advertised_tool_names = set()
|
|
629
|
+
try:
|
|
630
|
+
super().respond(ending_index=ending_index)
|
|
631
|
+
except AssertionError as error:
|
|
632
|
+
if not _is_empty_length_response(self._last_response):
|
|
633
|
+
if _is_generated_agent_content_invalid(self._last_response):
|
|
634
|
+
raise _EvaluatedAgentOutputInvalid(error) from error
|
|
635
|
+
raise
|
|
636
|
+
budget["hard_stops"] = 1
|
|
637
|
+
replacement = "I could not complete the task within the run budget."
|
|
638
|
+
self.compatibility_repairs.append(
|
|
639
|
+
{
|
|
640
|
+
"kind": "empty_length_response_to_agent_budget_hard_stop",
|
|
641
|
+
"replacement": replacement,
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
self.add_messages(
|
|
645
|
+
[
|
|
646
|
+
Message(
|
|
647
|
+
sender=RoleType.AGENT,
|
|
648
|
+
recipient=RoleType.USER,
|
|
649
|
+
content=replacement,
|
|
650
|
+
conversation_active=False,
|
|
651
|
+
)
|
|
652
|
+
]
|
|
653
|
+
)
|
|
654
|
+
except KeyError as error:
|
|
655
|
+
repair = _repair_agent_end_conversation(
|
|
656
|
+
self._last_response,
|
|
657
|
+
advertised_tool_names=self._last_advertised_tool_names,
|
|
658
|
+
)
|
|
659
|
+
if repair is None:
|
|
660
|
+
if _has_unadvertised_agent_tool_call(
|
|
661
|
+
self._last_response,
|
|
662
|
+
advertised_tool_names=self._last_advertised_tool_names,
|
|
663
|
+
):
|
|
664
|
+
raise _EvaluatedAgentOutputInvalid(error) from error
|
|
665
|
+
raise
|
|
666
|
+
self.compatibility_repairs.append(
|
|
667
|
+
{
|
|
668
|
+
"kind": "unadvertised_agent_end_conversation_to_user_message",
|
|
669
|
+
"replacement": repair,
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
self.add_messages(
|
|
673
|
+
[
|
|
674
|
+
Message(
|
|
675
|
+
sender=RoleType.AGENT,
|
|
676
|
+
recipient=RoleType.USER,
|
|
677
|
+
content=repair,
|
|
678
|
+
)
|
|
679
|
+
]
|
|
680
|
+
)
|
|
681
|
+
except json.JSONDecodeError as error:
|
|
682
|
+
raise _EvaluatedAgentOutputInvalid(error) from error
|
|
683
|
+
except Exception as error:
|
|
684
|
+
if not _is_gpt_oss_harmony_parse_failure(error, model_name=self.model_name):
|
|
685
|
+
raise
|
|
686
|
+
# The local vLLM server completed generation but its Harmony
|
|
687
|
+
# response parser rejected malformed channel/header tokens.
|
|
688
|
+
# This is evaluated model output, so preserve the cell as the
|
|
689
|
+
# same official zero-action outcome used for other recognized
|
|
690
|
+
# malformed generations instead of losing the observation.
|
|
691
|
+
budget["model_calls"] += 1
|
|
692
|
+
self.usage["model_calls"] += 1
|
|
693
|
+
self.usage["unpriced_model_calls"] += 1
|
|
694
|
+
self.compatibility_repairs.append(
|
|
695
|
+
{
|
|
696
|
+
"kind": "gpt_oss_harmony_parse_failure_to_agent_invalid_output",
|
|
697
|
+
}
|
|
698
|
+
)
|
|
699
|
+
raise _EvaluatedAgentOutputInvalid(error) from None
|
|
700
|
+
|
|
701
|
+
def model_inference(self, openai_messages: Any, openai_tools: Any) -> Any:
|
|
702
|
+
messages = [dict(item) for item in openai_messages]
|
|
703
|
+
if history_transform is not None:
|
|
704
|
+
transformed = history_transform.transform(messages, state=state, remaining_budget={
|
|
705
|
+
"max_model_calls": budget["max_model_calls"] - budget["model_calls"],
|
|
706
|
+
"max_output_tokens": budget["max_output_tokens"] - budget["output_tokens"],
|
|
707
|
+
})
|
|
708
|
+
# Native rendering inserts its own system preamble below;
|
|
709
|
+
# never mutate the callback's private cross-turn history.
|
|
710
|
+
messages = [dict(message) for message in transformed["messages"]]
|
|
711
|
+
usage = transformed["usage"]
|
|
712
|
+
for key in ("model_calls", "output_tokens"):
|
|
713
|
+
if type(usage.get(key, 0)) is not int or usage.get(key, 0) < 0:
|
|
714
|
+
raise OfficialArenaError("ToolSandbox history transform usage is invalid")
|
|
715
|
+
budget[key] += usage.get(key, 0)
|
|
716
|
+
if (budget["model_calls"] >= budget["max_model_calls"] or
|
|
717
|
+
budget["output_tokens"] >= budget["max_output_tokens"]):
|
|
718
|
+
raise OfficialArenaError("history transform failed to reserve the downstream budget")
|
|
719
|
+
self.usage = _merge_tracked_usage(self.usage, usage)
|
|
720
|
+
additions = [state.get("instruction", "")]
|
|
721
|
+
additions.extend(
|
|
722
|
+
block.get("text", "")
|
|
723
|
+
for block in state.get("context", [])
|
|
724
|
+
if isinstance(block, Mapping)
|
|
725
|
+
)
|
|
726
|
+
optimized_context = "\n\n".join(
|
|
727
|
+
item for item in additions if isinstance(item, str) and item
|
|
728
|
+
)
|
|
729
|
+
messages.insert(
|
|
730
|
+
0,
|
|
731
|
+
{
|
|
732
|
+
"role": "system",
|
|
733
|
+
"content": (
|
|
734
|
+
"<agentinputlab_visible_input>\n"
|
|
735
|
+
f"Optimized query: {state['query']}\n\n"
|
|
736
|
+
f"{optimized_context}\n"
|
|
737
|
+
"The end_conversation tool is reserved for the simulated user. "
|
|
738
|
+
"Never call it as the agent; when the task is complete, reply "
|
|
739
|
+
"to the user in natural language.\n"
|
|
740
|
+
"</agentinputlab_visible_input>"
|
|
741
|
+
),
|
|
742
|
+
},
|
|
743
|
+
)
|
|
744
|
+
metadata = state.get("metadata", {})
|
|
745
|
+
clarification = (
|
|
746
|
+
metadata.get("clarification_request") if isinstance(metadata, Mapping) else None
|
|
747
|
+
)
|
|
748
|
+
if self._first_inference and not clarification:
|
|
749
|
+
for message in reversed(messages):
|
|
750
|
+
if message.get("role") == "user":
|
|
751
|
+
message["content"] = state["query"]
|
|
752
|
+
break
|
|
753
|
+
self._first_inference = False
|
|
754
|
+
if requires_qwen38_initial_system_message(self.model_name):
|
|
755
|
+
messages = coalesce_chat_system_messages(messages)
|
|
756
|
+
call_seed = self._base_seed + self._inference_calls
|
|
757
|
+
self._inference_calls += 1
|
|
758
|
+
output_cap = min(
|
|
759
|
+
budget["max_output_tokens_per_call"],
|
|
760
|
+
budget["max_output_tokens"] - budget["output_tokens"],
|
|
761
|
+
)
|
|
762
|
+
if output_cap < 1:
|
|
763
|
+
raise OfficialArenaError("ToolSandbox shared output budget is exhausted")
|
|
764
|
+
response = self.openai_client.chat.completions.create(
|
|
765
|
+
model=self.model_name,
|
|
766
|
+
messages=messages,
|
|
767
|
+
tools=openai_tools,
|
|
768
|
+
temperature=0.0,
|
|
769
|
+
seed=call_seed,
|
|
770
|
+
max_tokens=output_cap,
|
|
771
|
+
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
|
772
|
+
)
|
|
773
|
+
budget["model_calls"] += 1
|
|
774
|
+
self._last_response = response
|
|
775
|
+
self._last_advertised_tool_names = _openai_tool_names(openai_tools)
|
|
776
|
+
enforcement = _enforce_agent_tool_budget(response, budget=budget)
|
|
777
|
+
if enforcement is not None:
|
|
778
|
+
self.budget_enforcements.append(enforcement)
|
|
779
|
+
self.compatibility_repairs.extend(_normalize_tool_call_ids(response))
|
|
780
|
+
_record_openai_usage(self.usage, response)
|
|
781
|
+
reported = getattr(response, "usage", None)
|
|
782
|
+
budget["output_tokens"] += int(getattr(reported, "completion_tokens", 0) or 0)
|
|
783
|
+
return response
|
|
784
|
+
|
|
785
|
+
return IntervenedAgent()
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _normalize_tool_call_ids(response: Any) -> list[dict[str, str]]:
|
|
789
|
+
"""Encode opaque call IDs for upstream's Python-variable representation.
|
|
790
|
+
|
|
791
|
+
The pinned converter interpolates IDs into ``<id>_parameters`` and
|
|
792
|
+
``<id>_response``. OpenAI-compatible IDs may contain hyphens or punctuation;
|
|
793
|
+
those are valid correlation IDs, not valid Python identifiers. Change only
|
|
794
|
+
that representation before upstream stores both the call and its result,
|
|
795
|
+
so subsequent tool-result references use the same alias. Keep names,
|
|
796
|
+
arguments, order, call count, text, usage and all generation controls intact.
|
|
797
|
+
Raw HTTP receipts remain unchanged; every alias is recorded separately.
|
|
798
|
+
"""
|
|
799
|
+
choices = getattr(response, "choices", None)
|
|
800
|
+
if not isinstance(choices, (list, tuple)) or not choices:
|
|
801
|
+
return []
|
|
802
|
+
message = getattr(choices[0], "message", None)
|
|
803
|
+
calls = getattr(message, "tool_calls", None)
|
|
804
|
+
if not isinstance(calls, (list, tuple)) or not calls:
|
|
805
|
+
return []
|
|
806
|
+
reserved = {
|
|
807
|
+
call.id for call in calls
|
|
808
|
+
if isinstance(getattr(call, "id", None), str) and call.id.isidentifier()
|
|
809
|
+
}
|
|
810
|
+
aliases: dict[str, str] = {}
|
|
811
|
+
repairs: list[dict[str, str]] = []
|
|
812
|
+
for call in calls:
|
|
813
|
+
original = getattr(call, "id", None)
|
|
814
|
+
# Missing/non-string IDs remain upstream validation errors. Do not
|
|
815
|
+
# synthesize a missing model field or repair duplicate logical calls.
|
|
816
|
+
if not isinstance(original, str) or not original or original.isidentifier():
|
|
817
|
+
continue
|
|
818
|
+
if original not in aliases:
|
|
819
|
+
base = "ail_call_" + hashlib.sha256(original.encode()).hexdigest()[:32]
|
|
820
|
+
alias = base
|
|
821
|
+
suffix = 0
|
|
822
|
+
while alias in reserved:
|
|
823
|
+
suffix += 1
|
|
824
|
+
alias = f"{base}_{suffix}"
|
|
825
|
+
aliases[original] = alias
|
|
826
|
+
reserved.add(alias)
|
|
827
|
+
repairs.append({
|
|
828
|
+
"kind": "tool_call_id_to_python_identifier",
|
|
829
|
+
"original_id": original,
|
|
830
|
+
"normalized_id": alias,
|
|
831
|
+
})
|
|
832
|
+
call.id = aliases[original]
|
|
833
|
+
return repairs
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def _enforce_agent_tool_budget(
|
|
837
|
+
response: Any,
|
|
838
|
+
*,
|
|
839
|
+
budget: dict[str, int],
|
|
840
|
+
) -> dict[str, int] | None:
|
|
841
|
+
"""Execute at most the remaining agent tool calls from one model response.
|
|
842
|
+
|
|
843
|
+
ToolSandbox permits parallel tool calls, so limiting model turns alone does
|
|
844
|
+
not enforce ``max_tool_calls``. Truncation happens before the upstream role
|
|
845
|
+
materializes calls into executable messages; the partial trajectory is then
|
|
846
|
+
scored as a shared-budget hard stop instead of being discarded after replay.
|
|
847
|
+
"""
|
|
848
|
+
|
|
849
|
+
choices = getattr(response, "choices", None)
|
|
850
|
+
if not isinstance(choices, (list, tuple)) or not choices:
|
|
851
|
+
return None
|
|
852
|
+
message = getattr(choices[0], "message", None)
|
|
853
|
+
calls = getattr(message, "tool_calls", None)
|
|
854
|
+
if not isinstance(calls, (list, tuple)) or not calls:
|
|
855
|
+
return None
|
|
856
|
+
remaining = max(0, budget["max_tool_calls"] - budget["tool_calls"])
|
|
857
|
+
kept = min(len(calls), remaining)
|
|
858
|
+
budget["tool_calls"] += kept
|
|
859
|
+
if kept == len(calls):
|
|
860
|
+
return None
|
|
861
|
+
# ``respond`` prevents inference when no tool budget remains, so a generated
|
|
862
|
+
# tool response always retains at least one call here. Preserve upstream call
|
|
863
|
+
# order and remove only the suffix that cannot be executed fairly.
|
|
864
|
+
message.tool_calls = list(calls[:kept])
|
|
865
|
+
budget["hard_stops"] = 1
|
|
866
|
+
return {
|
|
867
|
+
"generated_tool_calls": len(calls),
|
|
868
|
+
"executed_tool_calls": kept,
|
|
869
|
+
"discarded_tool_calls": len(calls) - kept,
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _openai_tool_names(openai_tools: Any) -> set[str]:
|
|
874
|
+
if isinstance(openai_tools, (str, bytes)) or not isinstance(openai_tools, (list, tuple)):
|
|
875
|
+
return set()
|
|
876
|
+
names: set[str] = set()
|
|
877
|
+
for tool in openai_tools:
|
|
878
|
+
if not isinstance(tool, Mapping):
|
|
879
|
+
continue
|
|
880
|
+
function = tool.get("function")
|
|
881
|
+
if isinstance(function, Mapping):
|
|
882
|
+
name = function.get("name")
|
|
883
|
+
if isinstance(name, str) and name:
|
|
884
|
+
names.add(name)
|
|
885
|
+
return names
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _repair_agent_end_conversation(
|
|
889
|
+
response: Any,
|
|
890
|
+
*,
|
|
891
|
+
advertised_tool_names: set[str],
|
|
892
|
+
) -> str | None:
|
|
893
|
+
"""Turn one known model-protocol error into an auditable user-facing turn."""
|
|
894
|
+
choices = getattr(response, "choices", None)
|
|
895
|
+
if not isinstance(choices, (list, tuple)) or not choices:
|
|
896
|
+
return None
|
|
897
|
+
message = getattr(choices[0], "message", None)
|
|
898
|
+
calls = getattr(message, "tool_calls", None)
|
|
899
|
+
if not isinstance(calls, (list, tuple)) or len(calls) != 1:
|
|
900
|
+
return None
|
|
901
|
+
function = getattr(calls[0], "function", None)
|
|
902
|
+
name = getattr(function, "name", None)
|
|
903
|
+
if name != "end_conversation" or name in advertised_tool_names:
|
|
904
|
+
return None
|
|
905
|
+
content = getattr(message, "content", None)
|
|
906
|
+
if isinstance(content, str) and content.strip():
|
|
907
|
+
return content.strip()
|
|
908
|
+
return "I've completed the requested step."
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def _repair_user_end_conversation(
|
|
912
|
+
response: Any,
|
|
913
|
+
*,
|
|
914
|
+
tools_were_given: bool,
|
|
915
|
+
) -> str | None:
|
|
916
|
+
"""Recover the one valid ToolSandbox user terminator from a strict client mismatch."""
|
|
917
|
+
if tools_were_given:
|
|
918
|
+
return None
|
|
919
|
+
choices = getattr(response, "choices", None)
|
|
920
|
+
if not isinstance(choices, (list, tuple)) or not choices:
|
|
921
|
+
return None
|
|
922
|
+
message = getattr(choices[0], "message", None)
|
|
923
|
+
calls = getattr(message, "tool_calls", None)
|
|
924
|
+
if not isinstance(calls, (list, tuple)) or len(calls) != 1:
|
|
925
|
+
return None
|
|
926
|
+
function = getattr(calls[0], "function", None)
|
|
927
|
+
if getattr(function, "name", None) != "end_conversation":
|
|
928
|
+
return None
|
|
929
|
+
return "end_conversation()"
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def _normalize_empty_user_tool_calls(response: Any, *, tools_were_given: bool) -> bool:
|
|
933
|
+
"""Normalize vLLM's empty list to the OpenAI ``null`` no-tool representation."""
|
|
934
|
+
if tools_were_given:
|
|
935
|
+
return False
|
|
936
|
+
choices = getattr(response, "choices", None)
|
|
937
|
+
if not choices:
|
|
938
|
+
return False
|
|
939
|
+
message = getattr(choices[0], "message", None)
|
|
940
|
+
if getattr(message, "tool_calls", None) != []:
|
|
941
|
+
return False
|
|
942
|
+
message.tool_calls = None
|
|
943
|
+
return True
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def _tracked_user(
|
|
947
|
+
config: Mapping[str, Any],
|
|
948
|
+
*,
|
|
949
|
+
seed: int,
|
|
950
|
+
budget: dict[str, int],
|
|
951
|
+
) -> Any:
|
|
952
|
+
from openai import OpenAI
|
|
953
|
+
from tool_sandbox.common.execution_context import RoleType
|
|
954
|
+
from tool_sandbox.common.message_conversion import Message
|
|
955
|
+
from tool_sandbox.roles.openai_api_user import OpenAIAPIUser
|
|
956
|
+
|
|
957
|
+
class TrackedUser(OpenAIAPIUser):
|
|
958
|
+
model_name = config["user_model"]
|
|
959
|
+
|
|
960
|
+
def __init__(self) -> None:
|
|
961
|
+
# OpenAIAPIUser's pinned constructor only creates the default
|
|
962
|
+
# client; no role state is skipped by binding it directly here.
|
|
963
|
+
self.openai_client = OpenAI(
|
|
964
|
+
api_key=os.environ[config["user_key_env"]],
|
|
965
|
+
base_url=config["user_base_url"],
|
|
966
|
+
max_retries=0,
|
|
967
|
+
)
|
|
968
|
+
self.usage = _empty_usage()
|
|
969
|
+
self._base_seed = seed
|
|
970
|
+
self._inference_calls = 0
|
|
971
|
+
self._last_response: Any = None
|
|
972
|
+
self._tools_were_given = False
|
|
973
|
+
self._last_advertised_tool_names: set[str] = set()
|
|
974
|
+
self.compatibility_repairs: list[dict[str, Any]] = []
|
|
975
|
+
|
|
976
|
+
def respond(self, ending_index: int | None = None) -> None:
|
|
977
|
+
if budget["hard_stops"] or budget["model_calls"] >= budget["max_model_calls"]:
|
|
978
|
+
if not budget["hard_stops"]:
|
|
979
|
+
budget["hard_stops"] = 1
|
|
980
|
+
self.add_messages(
|
|
981
|
+
[
|
|
982
|
+
Message(
|
|
983
|
+
sender=RoleType.USER,
|
|
984
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
985
|
+
content="end_conversation()",
|
|
986
|
+
conversation_active=False,
|
|
987
|
+
)
|
|
988
|
+
]
|
|
989
|
+
)
|
|
990
|
+
return
|
|
991
|
+
self._last_response = None
|
|
992
|
+
self._tools_were_given = False
|
|
993
|
+
try:
|
|
994
|
+
super().respond(ending_index=ending_index)
|
|
995
|
+
except AssertionError:
|
|
996
|
+
if _is_empty_length_response(self._last_response):
|
|
997
|
+
budget["hard_stops"] = 1
|
|
998
|
+
replacement = "end_conversation()"
|
|
999
|
+
self.compatibility_repairs.append(
|
|
1000
|
+
{
|
|
1001
|
+
"kind": "empty_length_response_to_user_budget_hard_stop",
|
|
1002
|
+
"replacement": replacement,
|
|
1003
|
+
}
|
|
1004
|
+
)
|
|
1005
|
+
self.add_messages(
|
|
1006
|
+
[
|
|
1007
|
+
Message(
|
|
1008
|
+
sender=RoleType.USER,
|
|
1009
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
1010
|
+
content=replacement,
|
|
1011
|
+
conversation_active=False,
|
|
1012
|
+
)
|
|
1013
|
+
]
|
|
1014
|
+
)
|
|
1015
|
+
return
|
|
1016
|
+
if _is_generated_user_content_invalid(self._last_response):
|
|
1017
|
+
# The upstream ToolSandbox user role asserts that a
|
|
1018
|
+
# no-tool response must contain visible text. vLLM may
|
|
1019
|
+
# legally materialize an empty assistant message instead
|
|
1020
|
+
# (most often after reasoning consumed the generation
|
|
1021
|
+
# allowance). This is simulator infrastructure, not the
|
|
1022
|
+
# evaluated agent, so end the simulated conversation and
|
|
1023
|
+
# preserve the official scorer rather than discarding the
|
|
1024
|
+
# experiment cell as a fictitious tool-schema failure.
|
|
1025
|
+
budget["hard_stops"] = 1
|
|
1026
|
+
replacement = "end_conversation()"
|
|
1027
|
+
self.compatibility_repairs.append(
|
|
1028
|
+
{
|
|
1029
|
+
"kind": "empty_user_response_to_simulator_hard_stop",
|
|
1030
|
+
"replacement": replacement,
|
|
1031
|
+
}
|
|
1032
|
+
)
|
|
1033
|
+
self.add_messages(
|
|
1034
|
+
[
|
|
1035
|
+
Message(
|
|
1036
|
+
sender=RoleType.USER,
|
|
1037
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
1038
|
+
content=replacement,
|
|
1039
|
+
conversation_active=False,
|
|
1040
|
+
)
|
|
1041
|
+
]
|
|
1042
|
+
)
|
|
1043
|
+
return
|
|
1044
|
+
repair = _repair_user_end_conversation(
|
|
1045
|
+
self._last_response,
|
|
1046
|
+
tools_were_given=self._tools_were_given,
|
|
1047
|
+
)
|
|
1048
|
+
if repair is None:
|
|
1049
|
+
raise OfficialArenaError(
|
|
1050
|
+
"ToolSandbox user emitted unsupported tools without an "
|
|
1051
|
+
f"advertised schema: {_response_tool_calls(self._last_response)!r}"
|
|
1052
|
+
) from None
|
|
1053
|
+
self.compatibility_repairs.append(
|
|
1054
|
+
{
|
|
1055
|
+
"kind": "unadvertised_user_end_conversation_to_official_tool",
|
|
1056
|
+
"replacement": repair,
|
|
1057
|
+
}
|
|
1058
|
+
)
|
|
1059
|
+
self.add_messages(
|
|
1060
|
+
[
|
|
1061
|
+
Message(
|
|
1062
|
+
sender=RoleType.USER,
|
|
1063
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
1064
|
+
content=repair,
|
|
1065
|
+
)
|
|
1066
|
+
]
|
|
1067
|
+
)
|
|
1068
|
+
except KeyError:
|
|
1069
|
+
if not _has_unadvertised_agent_tool_call(
|
|
1070
|
+
self._last_response,
|
|
1071
|
+
advertised_tool_names=self._last_advertised_tool_names,
|
|
1072
|
+
):
|
|
1073
|
+
raise
|
|
1074
|
+
# The user role is benchmark infrastructure. Never execute a
|
|
1075
|
+
# hallucinated, unadvertised simulator tool; terminate that
|
|
1076
|
+
# simulator trajectory explicitly so the evaluated agent is
|
|
1077
|
+
# still scored by the official scenario.
|
|
1078
|
+
budget["hard_stops"] = 1
|
|
1079
|
+
replacement = "end_conversation()"
|
|
1080
|
+
self.compatibility_repairs.append(
|
|
1081
|
+
{
|
|
1082
|
+
"kind": "unadvertised_user_tool_to_simulator_hard_stop",
|
|
1083
|
+
"replacement": replacement,
|
|
1084
|
+
}
|
|
1085
|
+
)
|
|
1086
|
+
self.add_messages(
|
|
1087
|
+
[
|
|
1088
|
+
Message(
|
|
1089
|
+
sender=RoleType.USER,
|
|
1090
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
1091
|
+
content=replacement,
|
|
1092
|
+
conversation_active=False,
|
|
1093
|
+
)
|
|
1094
|
+
]
|
|
1095
|
+
)
|
|
1096
|
+
except Exception as error:
|
|
1097
|
+
if not _is_gpt_oss_harmony_parse_failure(error, model_name=self.model_name):
|
|
1098
|
+
raise
|
|
1099
|
+
# This failure belongs to the simulated-user infrastructure,
|
|
1100
|
+
# not the evaluated agent. End the user trajectory explicitly
|
|
1101
|
+
# and retain the official scenario score.
|
|
1102
|
+
budget["model_calls"] += 1
|
|
1103
|
+
self.usage["model_calls"] += 1
|
|
1104
|
+
self.usage["unpriced_model_calls"] += 1
|
|
1105
|
+
budget["hard_stops"] = 1
|
|
1106
|
+
replacement = "end_conversation()"
|
|
1107
|
+
self.compatibility_repairs.append(
|
|
1108
|
+
{
|
|
1109
|
+
"kind": "gpt_oss_harmony_parse_failure_to_simulator_hard_stop",
|
|
1110
|
+
"replacement": replacement,
|
|
1111
|
+
}
|
|
1112
|
+
)
|
|
1113
|
+
self.add_messages(
|
|
1114
|
+
[
|
|
1115
|
+
Message(
|
|
1116
|
+
sender=RoleType.USER,
|
|
1117
|
+
recipient=RoleType.EXECUTION_ENVIRONMENT,
|
|
1118
|
+
content=replacement,
|
|
1119
|
+
conversation_active=False,
|
|
1120
|
+
)
|
|
1121
|
+
]
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
def model_inference(self, openai_messages: Any, openai_tools: Any) -> Any:
|
|
1125
|
+
if (
|
|
1126
|
+
budget["model_calls"] >= budget["max_model_calls"]
|
|
1127
|
+
or budget["output_tokens"] >= budget["max_output_tokens"]
|
|
1128
|
+
):
|
|
1129
|
+
raise OfficialArenaError("ToolSandbox shared user-simulator budget is exhausted")
|
|
1130
|
+
call_seed = self._base_seed + self._inference_calls
|
|
1131
|
+
self._inference_calls += 1
|
|
1132
|
+
tools_were_given = isinstance(openai_tools, (list, tuple))
|
|
1133
|
+
output_cap = min(
|
|
1134
|
+
budget["max_output_tokens_per_call"],
|
|
1135
|
+
budget["max_output_tokens"] - budget["output_tokens"],
|
|
1136
|
+
)
|
|
1137
|
+
messages = [dict(item) for item in openai_messages]
|
|
1138
|
+
if requires_qwen38_initial_system_message(self.model_name):
|
|
1139
|
+
messages = coalesce_chat_system_messages(messages)
|
|
1140
|
+
response = self.openai_client.chat.completions.create(
|
|
1141
|
+
model=self.model_name,
|
|
1142
|
+
messages=messages,
|
|
1143
|
+
tools=openai_tools,
|
|
1144
|
+
tool_choice="auto" if tools_were_given else "none",
|
|
1145
|
+
temperature=0.0,
|
|
1146
|
+
seed=call_seed,
|
|
1147
|
+
max_tokens=output_cap,
|
|
1148
|
+
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
|
1149
|
+
)
|
|
1150
|
+
budget["model_calls"] += 1
|
|
1151
|
+
self._last_response = response
|
|
1152
|
+
self._tools_were_given = tools_were_given
|
|
1153
|
+
self._last_advertised_tool_names = _openai_tool_names(openai_tools)
|
|
1154
|
+
if _normalize_empty_user_tool_calls(
|
|
1155
|
+
response,
|
|
1156
|
+
tools_were_given=tools_were_given,
|
|
1157
|
+
):
|
|
1158
|
+
self.compatibility_repairs.append(
|
|
1159
|
+
{
|
|
1160
|
+
"kind": "empty_user_tool_calls_to_openai_null",
|
|
1161
|
+
"replacement": None,
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
self.compatibility_repairs.extend(_normalize_tool_call_ids(response))
|
|
1165
|
+
_record_openai_usage(self.usage, response)
|
|
1166
|
+
reported = getattr(response, "usage", None)
|
|
1167
|
+
budget["output_tokens"] += int(getattr(reported, "completion_tokens", 0) or 0)
|
|
1168
|
+
return response
|
|
1169
|
+
|
|
1170
|
+
return TrackedUser()
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def _response_tool_calls(response: Any) -> list[tuple[Any, Any]]:
|
|
1174
|
+
choices = getattr(response, "choices", None)
|
|
1175
|
+
if not choices:
|
|
1176
|
+
return []
|
|
1177
|
+
message = getattr(choices[0], "message", None)
|
|
1178
|
+
calls = getattr(message, "tool_calls", None)
|
|
1179
|
+
if not calls:
|
|
1180
|
+
return []
|
|
1181
|
+
return [
|
|
1182
|
+
(
|
|
1183
|
+
getattr(getattr(call, "function", None), "name", None),
|
|
1184
|
+
getattr(getattr(call, "function", None), "arguments", None),
|
|
1185
|
+
)
|
|
1186
|
+
for call in calls
|
|
1187
|
+
]
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def _is_empty_length_response(response: Any) -> bool:
|
|
1191
|
+
"""Recognize a turn that exhausted its fair output cap before acting.
|
|
1192
|
+
|
|
1193
|
+
Reasoning-model APIs may return neither visible content nor tool calls when
|
|
1194
|
+
the shared completion cap is exhausted. ToolSandbox's upstream role asserts
|
|
1195
|
+
on that otherwise valid OpenAI response. Treating it as an explicit hard
|
|
1196
|
+
stop preserves the official failed trajectory and scorer instead of losing
|
|
1197
|
+
the experiment cell to an adapter exception.
|
|
1198
|
+
"""
|
|
1199
|
+
|
|
1200
|
+
choices = getattr(response, "choices", None)
|
|
1201
|
+
if not isinstance(choices, (list, tuple)) or len(choices) != 1:
|
|
1202
|
+
return False
|
|
1203
|
+
choice = choices[0]
|
|
1204
|
+
if getattr(choice, "finish_reason", None) != "length":
|
|
1205
|
+
return False
|
|
1206
|
+
message = getattr(choice, "message", None)
|
|
1207
|
+
if message is None:
|
|
1208
|
+
return False
|
|
1209
|
+
content = getattr(message, "content", None)
|
|
1210
|
+
tool_calls = getattr(message, "tool_calls", None)
|
|
1211
|
+
return not (isinstance(content, str) and content.strip()) and not tool_calls
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def _is_generated_agent_content_invalid(response: Any) -> bool:
|
|
1215
|
+
"""Recognize an otherwise valid response with neither content nor tool calls."""
|
|
1216
|
+
|
|
1217
|
+
choices = getattr(response, "choices", None)
|
|
1218
|
+
if not isinstance(choices, (list, tuple)) or len(choices) != 1:
|
|
1219
|
+
return False
|
|
1220
|
+
message = getattr(choices[0], "message", None)
|
|
1221
|
+
if message is None:
|
|
1222
|
+
return False
|
|
1223
|
+
content = getattr(message, "content", None)
|
|
1224
|
+
tool_calls = getattr(message, "tool_calls", None)
|
|
1225
|
+
return not (isinstance(content, str) and content.strip()) and not tool_calls
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
def _is_generated_user_content_invalid(response: Any) -> bool:
|
|
1229
|
+
"""Recognize the same empty generation at the simulated-user boundary."""
|
|
1230
|
+
|
|
1231
|
+
return _is_generated_agent_content_invalid(response)
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
def _is_gpt_oss_harmony_parse_failure(error: Exception, *, model_name: str) -> bool:
|
|
1235
|
+
"""Recognize only the local GPT-OSS Harmony parser's HTTP-500 failures."""
|
|
1236
|
+
|
|
1237
|
+
if model_name.casefold() != "gpt-oss-20b" or getattr(error, "status_code", None) != 500:
|
|
1238
|
+
return False
|
|
1239
|
+
body = getattr(error, "body", None)
|
|
1240
|
+
message: Any = None
|
|
1241
|
+
if isinstance(body, Mapping):
|
|
1242
|
+
message = body.get("message")
|
|
1243
|
+
nested = body.get("error")
|
|
1244
|
+
if message is None and isinstance(nested, Mapping):
|
|
1245
|
+
message = nested.get("message")
|
|
1246
|
+
if not isinstance(message, str):
|
|
1247
|
+
return False
|
|
1248
|
+
normalized = message.casefold()
|
|
1249
|
+
return normalized.startswith("unexpected tokens remaining in message header") or normalized.startswith(
|
|
1250
|
+
"unexpected token "
|
|
1251
|
+
)
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
def _has_unadvertised_agent_tool_call(
|
|
1255
|
+
response: Any,
|
|
1256
|
+
*,
|
|
1257
|
+
advertised_tool_names: set[str],
|
|
1258
|
+
) -> bool:
|
|
1259
|
+
"""Recognize a generated tool name that the official turn did not expose."""
|
|
1260
|
+
|
|
1261
|
+
calls = _response_tool_calls(response)
|
|
1262
|
+
return bool(calls) and any(
|
|
1263
|
+
not isinstance(name, str) or name not in advertised_tool_names for name, _ in calls
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def _empty_usage() -> dict[str, int | float]:
|
|
1268
|
+
return {
|
|
1269
|
+
"model_calls": 0,
|
|
1270
|
+
"retrieval_calls": 0,
|
|
1271
|
+
"tool_calls": 0,
|
|
1272
|
+
"embedding_calls": 0,
|
|
1273
|
+
"unpriced_model_calls": 0,
|
|
1274
|
+
"input_tokens": 0,
|
|
1275
|
+
"output_tokens": 0,
|
|
1276
|
+
"cpu_seconds": 0.0,
|
|
1277
|
+
"accelerator_seconds": 0.0,
|
|
1278
|
+
"cost_usd": 0.0,
|
|
1279
|
+
"compute_cost_usd": 0.0,
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def _record_openai_usage(usage: dict[str, int | float], response: Any) -> None:
|
|
1284
|
+
usage["model_calls"] += 1
|
|
1285
|
+
reported = getattr(response, "usage", None)
|
|
1286
|
+
if reported is None:
|
|
1287
|
+
usage["unpriced_model_calls"] += 1
|
|
1288
|
+
return
|
|
1289
|
+
usage["input_tokens"] += int(getattr(reported, "prompt_tokens", 0) or 0)
|
|
1290
|
+
usage["output_tokens"] += int(getattr(reported, "completion_tokens", 0) or 0)
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
def _merge_tracked_usage(
|
|
1294
|
+
left: Mapping[str, int | float],
|
|
1295
|
+
right: Mapping[str, int | float],
|
|
1296
|
+
) -> dict[str, int | float]:
|
|
1297
|
+
return {key: left.get(key, 0) + right.get(key, 0) for key in _empty_usage()}
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def _agent_available_tools(context: Any) -> Mapping[str, Any]:
|
|
1301
|
+
"""Return the exact tool names exposed to ToolSandbox's agent role."""
|
|
1302
|
+
return context.get_available_tools(scrambling_allowed=True)
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
def _deterministic_scenarios(
|
|
1306
|
+
factory: Any,
|
|
1307
|
+
*,
|
|
1308
|
+
seed_material: Mapping[str, Any],
|
|
1309
|
+
) -> Any:
|
|
1310
|
+
"""Build upstream scenarios deterministically without leaking global RNG state."""
|
|
1311
|
+
previous_state = random.getstate()
|
|
1312
|
+
try:
|
|
1313
|
+
seed = int(content_hash(seed_material), 16)
|
|
1314
|
+
random.seed(seed)
|
|
1315
|
+
return factory()
|
|
1316
|
+
finally:
|
|
1317
|
+
random.setstate(previous_state)
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def factory() -> ToolSandboxProvider:
|
|
1321
|
+
return ToolSandboxProvider()
|