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,963 @@
|
|
|
1
|
+
"""Prepared question-answering adapter with evaluator-only references.
|
|
2
|
+
|
|
3
|
+
The normalized input schema is intentionally small and dataset-neutral. A
|
|
4
|
+
visible row contains the user question plus optional context blocks, while its
|
|
5
|
+
matching gold row contains either one reference group or a set of annotation-
|
|
6
|
+
aligned reference groups. Keeping those files separate prevents an
|
|
7
|
+
intervention or prediction callback from observing answers during execution.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import string
|
|
15
|
+
from collections.abc import Mapping, Sequence
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from agentinputlab.contracts.errors import ArtifactError, ValidationError
|
|
21
|
+
from agentinputlab.contracts.serialization import canonical_json, freeze_mapping
|
|
22
|
+
from agentinputlab.contracts.state import ContextBlock
|
|
23
|
+
from agentinputlab.metrics import (
|
|
24
|
+
best_reference_score,
|
|
25
|
+
english_qa_exact_match,
|
|
26
|
+
english_qa_f1,
|
|
27
|
+
normalize_english_answer,
|
|
28
|
+
set_f1,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from .base import BenchmarkExample
|
|
32
|
+
from .preprocess import PreparedBenchmark
|
|
33
|
+
|
|
34
|
+
_INPUT_FIELDS = {"id", "query", "instruction", "visible_context", "visible_metadata"}
|
|
35
|
+
_CONTEXT_FIELDS = {"block_id", "text", "source", "score", "metadata"}
|
|
36
|
+
_REFERENCE_FIELDS = {
|
|
37
|
+
"answer_type",
|
|
38
|
+
"answers",
|
|
39
|
+
"support_ids",
|
|
40
|
+
"evidence_ids",
|
|
41
|
+
"evidence_texts",
|
|
42
|
+
}
|
|
43
|
+
_BENCHMARK_PROFILES = {
|
|
44
|
+
"musique.answerable": "musique",
|
|
45
|
+
"hotpotqa.distractor": "hotpotqa",
|
|
46
|
+
"2wiki.multihop": "2wiki",
|
|
47
|
+
"qasper.v0_3_dev": "qasper",
|
|
48
|
+
}
|
|
49
|
+
_NAMED_PROFILE_IDENTITIES = {
|
|
50
|
+
"musique.answerable": ("musique.answerable.dev", "musique_zip_v1", "dev"),
|
|
51
|
+
"hotpotqa.distractor": (
|
|
52
|
+
"hotpotqa.distractor.validation",
|
|
53
|
+
"hotpotqa_parquet_v1",
|
|
54
|
+
"validation",
|
|
55
|
+
),
|
|
56
|
+
"2wiki.multihop": ("2wiki.multihop.dev", "two_wiki_zip_v1", "dev"),
|
|
57
|
+
"qasper.v0_3_dev": ("qasper.dev.official", "qasper_tgz_v1", "dev"),
|
|
58
|
+
}
|
|
59
|
+
_CATEGORICAL_ANSWERS = frozenset({"yes", "no", "noanswer"})
|
|
60
|
+
_QASPER_ANSWER_TYPES = frozenset({"extractive", "abstractive", "boolean", "none"})
|
|
61
|
+
_PROFILE_METRIC_KEYS = {
|
|
62
|
+
"musique": ("answer_em", "answer_f1", "support_f1"),
|
|
63
|
+
"hotpotqa": (
|
|
64
|
+
"answer_em",
|
|
65
|
+
"answer_f1",
|
|
66
|
+
"answer_precision",
|
|
67
|
+
"answer_recall",
|
|
68
|
+
"support_em",
|
|
69
|
+
"support_f1",
|
|
70
|
+
"support_precision",
|
|
71
|
+
"support_recall",
|
|
72
|
+
"joint_em",
|
|
73
|
+
"joint_f1",
|
|
74
|
+
"joint_precision",
|
|
75
|
+
"joint_recall",
|
|
76
|
+
),
|
|
77
|
+
"2wiki": (
|
|
78
|
+
"answer_em",
|
|
79
|
+
"answer_f1",
|
|
80
|
+
"answer_precision",
|
|
81
|
+
"answer_recall",
|
|
82
|
+
"support_em",
|
|
83
|
+
"support_f1",
|
|
84
|
+
"support_precision",
|
|
85
|
+
"support_recall",
|
|
86
|
+
"evidence_em",
|
|
87
|
+
"evidence_f1",
|
|
88
|
+
"evidence_precision",
|
|
89
|
+
"evidence_recall",
|
|
90
|
+
"joint_em",
|
|
91
|
+
"joint_f1",
|
|
92
|
+
"joint_precision",
|
|
93
|
+
"joint_recall",
|
|
94
|
+
),
|
|
95
|
+
"qasper": ("answer_em", "answer_f1", "evidence_f1"),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True, slots=True)
|
|
100
|
+
class QAPrediction:
|
|
101
|
+
"""One answer with optional support IDs and ID- or text-based evidence."""
|
|
102
|
+
|
|
103
|
+
answer: str
|
|
104
|
+
support_ids: tuple[str, ...] = ()
|
|
105
|
+
evidence_ids: tuple[str, ...] = ()
|
|
106
|
+
evidence_texts: tuple[str, ...] = ()
|
|
107
|
+
|
|
108
|
+
def __post_init__(self) -> None:
|
|
109
|
+
if not isinstance(self.answer, str):
|
|
110
|
+
raise ValidationError("QA prediction answer must be a string")
|
|
111
|
+
object.__setattr__(
|
|
112
|
+
self,
|
|
113
|
+
"support_ids",
|
|
114
|
+
_prediction_identifiers(self.support_ids, label="QA prediction support_ids"),
|
|
115
|
+
)
|
|
116
|
+
object.__setattr__(
|
|
117
|
+
self,
|
|
118
|
+
"evidence_ids",
|
|
119
|
+
_prediction_identifiers(self.evidence_ids, label="QA prediction evidence_ids"),
|
|
120
|
+
)
|
|
121
|
+
object.__setattr__(
|
|
122
|
+
self,
|
|
123
|
+
"evidence_texts",
|
|
124
|
+
_prediction_identifiers(self.evidence_texts, label="QA prediction evidence_texts"),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class PreparedQABenchmarkAdapter:
|
|
129
|
+
"""Production adapter for a verified, normalized QA benchmark.
|
|
130
|
+
|
|
131
|
+
Input rows must use this exact schema::
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
"id": "...",
|
|
135
|
+
"query": "...",
|
|
136
|
+
"instruction": "...",
|
|
137
|
+
"visible_context": [
|
|
138
|
+
{"block_id": "...", "text": "...", "source": "...",
|
|
139
|
+
"score": null, "metadata": {}}
|
|
140
|
+
],
|
|
141
|
+
"visible_metadata": {
|
|
142
|
+
"benchmark_cell": "...",
|
|
143
|
+
"source_split": "..."
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
A gold row uses either ``answers`` with optional ``support_ids`` and one of
|
|
148
|
+
``evidence_ids``/``evidence_texts``, or ``reference_groups`` containing
|
|
149
|
+
those same fields. The latter preserves annotation alignment for datasets
|
|
150
|
+
such as QASPER; its official evidence score uses the text-based variant.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
pairing_mode = "stateless_score_twice"
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
prepared: PreparedBenchmark,
|
|
158
|
+
*,
|
|
159
|
+
benchmark_id: str,
|
|
160
|
+
split: str,
|
|
161
|
+
) -> None:
|
|
162
|
+
if not isinstance(prepared, PreparedBenchmark):
|
|
163
|
+
raise TypeError("prepared must be a PreparedBenchmark")
|
|
164
|
+
_validated_identity(benchmark_id, label="benchmark_id")
|
|
165
|
+
_validated_identity(split, label="split")
|
|
166
|
+
_validate_prepared_identity(
|
|
167
|
+
prepared,
|
|
168
|
+
benchmark_id=benchmark_id,
|
|
169
|
+
split=split,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
input_rows = _records_by_id(prepared.inputs_path, label="prepared QA inputs")
|
|
173
|
+
gold_rows = _records_by_id(prepared.gold_path, label="prepared QA gold")
|
|
174
|
+
if set(input_rows) != set(gold_rows):
|
|
175
|
+
raise ArtifactError("prepared QA input and gold IDs do not match")
|
|
176
|
+
|
|
177
|
+
examples: list[BenchmarkExample] = []
|
|
178
|
+
benchmark_profile = _BENCHMARK_PROFILES.get(benchmark_id, "generic")
|
|
179
|
+
metric_profile: tuple[bool, str | None] | None = None
|
|
180
|
+
for example_id in sorted(input_rows):
|
|
181
|
+
visible = input_rows[example_id]
|
|
182
|
+
evaluator = gold_rows[example_id]
|
|
183
|
+
_require_exact_fields(visible, _INPUT_FIELDS, label="prepared QA input")
|
|
184
|
+
query = _required_text(visible, "query", label="prepared QA input")
|
|
185
|
+
instruction = _required_text(visible, "instruction", label="prepared QA input")
|
|
186
|
+
context = _visible_context(visible["visible_context"], example_id=example_id)
|
|
187
|
+
metadata = _visible_metadata(
|
|
188
|
+
visible["visible_metadata"],
|
|
189
|
+
benchmark_id=benchmark_id,
|
|
190
|
+
split=split,
|
|
191
|
+
)
|
|
192
|
+
references, evaluator_metadata, profile = _reference_groups(evaluator)
|
|
193
|
+
_validate_benchmark_profile(
|
|
194
|
+
benchmark_profile,
|
|
195
|
+
references=references,
|
|
196
|
+
evaluator_metadata=evaluator_metadata,
|
|
197
|
+
)
|
|
198
|
+
if metric_profile is None:
|
|
199
|
+
metric_profile = profile
|
|
200
|
+
elif metric_profile != profile:
|
|
201
|
+
raise ArtifactError(
|
|
202
|
+
"prepared QA gold rows must expose one consistent support/evidence schema"
|
|
203
|
+
)
|
|
204
|
+
examples.append(
|
|
205
|
+
BenchmarkExample(
|
|
206
|
+
example_id=example_id,
|
|
207
|
+
query=query,
|
|
208
|
+
instruction=instruction,
|
|
209
|
+
visible_context=context,
|
|
210
|
+
visible_metadata=metadata,
|
|
211
|
+
evaluator_gold=_frozen_canonical_mapping(
|
|
212
|
+
{
|
|
213
|
+
"reference_groups": references,
|
|
214
|
+
"evaluator_metadata": evaluator_metadata,
|
|
215
|
+
},
|
|
216
|
+
label="prepared QA evaluator gold",
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self.benchmark_id = benchmark_id
|
|
222
|
+
self.split = split
|
|
223
|
+
self.prepared = prepared
|
|
224
|
+
self.metric_profile = benchmark_profile
|
|
225
|
+
self._examples = tuple(examples)
|
|
226
|
+
self._examples_by_id = {example.example_id: example for example in examples}
|
|
227
|
+
self._has_support, self._evidence_field = metric_profile or (False, None)
|
|
228
|
+
self.metric_keys = _PROFILE_METRIC_KEYS.get(
|
|
229
|
+
benchmark_profile,
|
|
230
|
+
(
|
|
231
|
+
"exact_match",
|
|
232
|
+
"answer_f1",
|
|
233
|
+
*(("support_f1",) if self._has_support else ()),
|
|
234
|
+
*(("evidence_f1",) if self._evidence_field is not None else ()),
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def examples(self, split: str) -> tuple[BenchmarkExample, ...]:
|
|
239
|
+
if split != self.split:
|
|
240
|
+
raise ValidationError(
|
|
241
|
+
f"prepared QA adapter exposes split {self.split!r}, not {split!r}"
|
|
242
|
+
)
|
|
243
|
+
return self._examples
|
|
244
|
+
|
|
245
|
+
def example(self, split: str, example_id: str) -> BenchmarkExample:
|
|
246
|
+
"""Resolve one verified unit without rescanning the full split."""
|
|
247
|
+
|
|
248
|
+
if split != self.split:
|
|
249
|
+
raise ValidationError(
|
|
250
|
+
f"prepared QA adapter exposes split {self.split!r}, not {split!r}"
|
|
251
|
+
)
|
|
252
|
+
if not isinstance(example_id, str) or not example_id.strip():
|
|
253
|
+
raise ValidationError("prepared QA example_id must be non-empty")
|
|
254
|
+
try:
|
|
255
|
+
return self._examples_by_id[example_id]
|
|
256
|
+
except KeyError as exc:
|
|
257
|
+
raise ValidationError(
|
|
258
|
+
f"prepared QA split does not contain example {example_id!r}"
|
|
259
|
+
) from exc
|
|
260
|
+
|
|
261
|
+
def score(
|
|
262
|
+
self,
|
|
263
|
+
example: BenchmarkExample,
|
|
264
|
+
prediction: QAPrediction | Mapping[str, Any],
|
|
265
|
+
) -> Mapping[str, float]:
|
|
266
|
+
if not isinstance(example, BenchmarkExample):
|
|
267
|
+
raise TypeError("example must be a BenchmarkExample")
|
|
268
|
+
if self._examples_by_id.get(example.example_id) is not example:
|
|
269
|
+
raise ValidationError("benchmark example does not belong to this prepared QA adapter")
|
|
270
|
+
normalized = (
|
|
271
|
+
prediction
|
|
272
|
+
if isinstance(prediction, QAPrediction)
|
|
273
|
+
else _prediction_from_mapping(prediction)
|
|
274
|
+
)
|
|
275
|
+
raw_groups = example.evaluator_gold.get("reference_groups")
|
|
276
|
+
if not isinstance(raw_groups, tuple) or not raw_groups:
|
|
277
|
+
raise ArtifactError("benchmark example does not contain QA reference groups")
|
|
278
|
+
|
|
279
|
+
groups = _validated_runtime_groups(raw_groups)
|
|
280
|
+
evaluator_metadata = example.evaluator_gold.get("evaluator_metadata")
|
|
281
|
+
if not isinstance(evaluator_metadata, Mapping): # pragma: no cover - invariant
|
|
282
|
+
raise ArtifactError("benchmark example does not contain QA evaluator metadata")
|
|
283
|
+
|
|
284
|
+
if self.metric_profile == "musique":
|
|
285
|
+
return _score_musique(normalized, groups)
|
|
286
|
+
if self.metric_profile == "hotpotqa":
|
|
287
|
+
return _score_hotpotqa(normalized, groups)
|
|
288
|
+
if self.metric_profile == "2wiki":
|
|
289
|
+
return _score_two_wiki(normalized, groups, evaluator_metadata)
|
|
290
|
+
if self.metric_profile == "qasper":
|
|
291
|
+
return _score_qasper(normalized, groups)
|
|
292
|
+
return _score_generic(
|
|
293
|
+
normalized,
|
|
294
|
+
groups,
|
|
295
|
+
has_support=self._has_support,
|
|
296
|
+
evidence_field=self._evidence_field,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _score_generic(
|
|
301
|
+
prediction: QAPrediction,
|
|
302
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
303
|
+
*,
|
|
304
|
+
has_support: bool,
|
|
305
|
+
evidence_field: str | None,
|
|
306
|
+
) -> Mapping[str, float]:
|
|
307
|
+
answer_exact: list[float] = []
|
|
308
|
+
answer_f1: list[float] = []
|
|
309
|
+
support_f1: list[float] = []
|
|
310
|
+
evidence_f1: list[float] = []
|
|
311
|
+
for group in groups:
|
|
312
|
+
answers = group["answers"]
|
|
313
|
+
answer_exact.append(
|
|
314
|
+
best_reference_score(
|
|
315
|
+
prediction.answer,
|
|
316
|
+
answers,
|
|
317
|
+
scorer=english_qa_exact_match,
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
answer_f1.append(best_reference_score(prediction.answer, answers, scorer=english_qa_f1))
|
|
321
|
+
if has_support:
|
|
322
|
+
support_f1.append(set_f1(prediction.support_ids, group["support_ids"]))
|
|
323
|
+
if evidence_field == "evidence_ids":
|
|
324
|
+
evidence_f1.append(set_f1(prediction.evidence_ids, group["evidence_ids"]))
|
|
325
|
+
elif evidence_field == "evidence_texts":
|
|
326
|
+
evidence_f1.append(set_f1(prediction.evidence_texts, group["evidence_texts"]))
|
|
327
|
+
|
|
328
|
+
metrics = {
|
|
329
|
+
"exact_match": max(answer_exact),
|
|
330
|
+
"answer_f1": max(answer_f1),
|
|
331
|
+
}
|
|
332
|
+
if has_support:
|
|
333
|
+
metrics["support_f1"] = max(support_f1)
|
|
334
|
+
if evidence_field is not None:
|
|
335
|
+
metrics["evidence_f1"] = max(evidence_f1)
|
|
336
|
+
return metrics
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _score_musique(
|
|
340
|
+
prediction: QAPrediction,
|
|
341
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
342
|
+
) -> Mapping[str, float]:
|
|
343
|
+
"""Match the official answerable-split answer and paragraph metrics."""
|
|
344
|
+
|
|
345
|
+
answer_em = max(
|
|
346
|
+
best_reference_score(
|
|
347
|
+
prediction.answer,
|
|
348
|
+
group["answers"],
|
|
349
|
+
scorer=english_qa_exact_match,
|
|
350
|
+
)
|
|
351
|
+
for group in groups
|
|
352
|
+
)
|
|
353
|
+
answer_f1 = max(
|
|
354
|
+
best_reference_score(prediction.answer, group["answers"], scorer=english_qa_f1)
|
|
355
|
+
for group in groups
|
|
356
|
+
)
|
|
357
|
+
support_f1 = max(set_f1(prediction.support_ids, group["support_ids"]) for group in groups)
|
|
358
|
+
return {
|
|
359
|
+
"answer_em": answer_em,
|
|
360
|
+
"answer_f1": answer_f1,
|
|
361
|
+
"support_f1": support_f1,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _score_hotpotqa(
|
|
366
|
+
prediction: QAPrediction,
|
|
367
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
368
|
+
) -> Mapping[str, float]:
|
|
369
|
+
answer = _best_answer_scores(prediction.answer, groups, categorical_guard=True)
|
|
370
|
+
support = _best_support_scores(prediction.support_ids, groups, lowercase_titles=False)
|
|
371
|
+
return _joint_metrics(answer, support)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _score_two_wiki(
|
|
375
|
+
prediction: QAPrediction,
|
|
376
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
377
|
+
evaluator_metadata: Mapping[str, Any],
|
|
378
|
+
) -> Mapping[str, float]:
|
|
379
|
+
answer = _best_answer_scores(prediction.answer, groups, categorical_guard=True)
|
|
380
|
+
support = _best_support_scores(prediction.support_ids, groups, lowercase_titles=True)
|
|
381
|
+
evidence = _two_wiki_evidence_scores(
|
|
382
|
+
prediction.evidence_ids,
|
|
383
|
+
evaluator_metadata.get("evidence_options"),
|
|
384
|
+
)
|
|
385
|
+
return _joint_metrics(answer, support, evidence)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _score_qasper(
|
|
389
|
+
prediction: QAPrediction,
|
|
390
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
391
|
+
) -> Mapping[str, float]:
|
|
392
|
+
"""Match QASPER's independent max-over-annotations scoring rule."""
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"answer_em": max(
|
|
396
|
+
best_reference_score(
|
|
397
|
+
prediction.answer,
|
|
398
|
+
group["answers"],
|
|
399
|
+
scorer=english_qa_exact_match,
|
|
400
|
+
)
|
|
401
|
+
for group in groups
|
|
402
|
+
),
|
|
403
|
+
"answer_f1": max(
|
|
404
|
+
best_reference_score(prediction.answer, group["answers"], scorer=english_qa_f1)
|
|
405
|
+
for group in groups
|
|
406
|
+
),
|
|
407
|
+
"evidence_f1": max(
|
|
408
|
+
set_f1(prediction.evidence_texts, group["evidence_texts"]) for group in groups
|
|
409
|
+
),
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _best_answer_scores(
|
|
414
|
+
prediction: str,
|
|
415
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
416
|
+
*,
|
|
417
|
+
categorical_guard: bool,
|
|
418
|
+
) -> Mapping[str, float]:
|
|
419
|
+
scores = [
|
|
420
|
+
_answer_scores(prediction, answer, categorical_guard=categorical_guard)
|
|
421
|
+
for group in groups
|
|
422
|
+
for answer in group["answers"]
|
|
423
|
+
]
|
|
424
|
+
return {key: max(score[key] for score in scores) for key in ("em", "f1", "precision", "recall")}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _answer_scores(
|
|
428
|
+
prediction: str,
|
|
429
|
+
reference: str,
|
|
430
|
+
*,
|
|
431
|
+
categorical_guard: bool,
|
|
432
|
+
) -> Mapping[str, float]:
|
|
433
|
+
normalized_prediction = _normalized_answer(prediction)
|
|
434
|
+
normalized_reference = _normalized_answer(reference)
|
|
435
|
+
exact_match = float(normalized_prediction == normalized_reference)
|
|
436
|
+
if categorical_guard and (
|
|
437
|
+
normalized_prediction != normalized_reference
|
|
438
|
+
and (
|
|
439
|
+
normalized_prediction in _CATEGORICAL_ANSWERS
|
|
440
|
+
or normalized_reference in _CATEGORICAL_ANSWERS
|
|
441
|
+
)
|
|
442
|
+
):
|
|
443
|
+
return {"em": exact_match, "f1": 0.0, "precision": 0.0, "recall": 0.0}
|
|
444
|
+
|
|
445
|
+
prediction_tokens = normalized_prediction.split()
|
|
446
|
+
reference_tokens = normalized_reference.split()
|
|
447
|
+
common = set(prediction_tokens) & set(reference_tokens)
|
|
448
|
+
# Token multiplicity matters in the official SQuAD/Hotpot scorer.
|
|
449
|
+
matched = sum(
|
|
450
|
+
min(prediction_tokens.count(token), reference_tokens.count(token)) for token in common
|
|
451
|
+
)
|
|
452
|
+
if matched == 0:
|
|
453
|
+
return {"em": exact_match, "f1": 0.0, "precision": 0.0, "recall": 0.0}
|
|
454
|
+
precision = matched / len(prediction_tokens)
|
|
455
|
+
recall = matched / len(reference_tokens)
|
|
456
|
+
return {
|
|
457
|
+
"em": exact_match,
|
|
458
|
+
"f1": _harmonic_mean(precision, recall),
|
|
459
|
+
"precision": precision,
|
|
460
|
+
"recall": recall,
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _best_support_scores(
|
|
465
|
+
prediction_ids: tuple[str, ...],
|
|
466
|
+
groups: tuple[Mapping[str, Any], ...],
|
|
467
|
+
*,
|
|
468
|
+
lowercase_titles: bool,
|
|
469
|
+
) -> Mapping[str, float]:
|
|
470
|
+
predicted = _support_pairs(
|
|
471
|
+
prediction_ids,
|
|
472
|
+
lowercase_titles=lowercase_titles,
|
|
473
|
+
prediction=True,
|
|
474
|
+
)
|
|
475
|
+
scores = [
|
|
476
|
+
_set_scores(
|
|
477
|
+
predicted,
|
|
478
|
+
_support_pairs(
|
|
479
|
+
group["support_ids"],
|
|
480
|
+
lowercase_titles=lowercase_titles,
|
|
481
|
+
prediction=False,
|
|
482
|
+
),
|
|
483
|
+
)
|
|
484
|
+
for group in groups
|
|
485
|
+
]
|
|
486
|
+
return {key: max(score[key] for score in scores) for key in ("em", "f1", "precision", "recall")}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _support_pairs(
|
|
490
|
+
identifiers: Sequence[str],
|
|
491
|
+
*,
|
|
492
|
+
lowercase_titles: bool,
|
|
493
|
+
prediction: bool,
|
|
494
|
+
) -> set[tuple[str, int]]:
|
|
495
|
+
pairs: set[tuple[str, int]] = set()
|
|
496
|
+
for identifier in identifiers:
|
|
497
|
+
value = _canonical_identifier(
|
|
498
|
+
identifier,
|
|
499
|
+
expected_length=2,
|
|
500
|
+
label="support ID",
|
|
501
|
+
prediction=prediction,
|
|
502
|
+
)
|
|
503
|
+
title, sentence_index = value
|
|
504
|
+
if not isinstance(title, str) or not title.strip():
|
|
505
|
+
_identifier_error("support ID title must be a non-empty string", prediction)
|
|
506
|
+
if (
|
|
507
|
+
isinstance(sentence_index, bool)
|
|
508
|
+
or not isinstance(sentence_index, int)
|
|
509
|
+
or sentence_index < 0
|
|
510
|
+
):
|
|
511
|
+
_identifier_error("support ID index must be a non-negative integer", prediction)
|
|
512
|
+
pairs.add((title.lower() if lowercase_titles else title, sentence_index))
|
|
513
|
+
return pairs
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _two_wiki_evidence_scores(
|
|
517
|
+
prediction_ids: tuple[str, ...],
|
|
518
|
+
raw_options: Any,
|
|
519
|
+
) -> Mapping[str, float]:
|
|
520
|
+
if not isinstance(raw_options, tuple) or not raw_options:
|
|
521
|
+
raise ArtifactError("2Wiki evaluator metadata requires non-empty evidence_options")
|
|
522
|
+
accepted_groups: list[set[tuple[str, str, str]]] = []
|
|
523
|
+
for group_index, raw_group in enumerate(raw_options):
|
|
524
|
+
if not isinstance(raw_group, tuple) or not raw_group:
|
|
525
|
+
raise ArtifactError(f"2Wiki evidence_options group {group_index} must be non-empty")
|
|
526
|
+
accepted_groups.append(
|
|
527
|
+
{
|
|
528
|
+
_normalized_evidence_triple(
|
|
529
|
+
option,
|
|
530
|
+
prediction=False,
|
|
531
|
+
)
|
|
532
|
+
for option in raw_group
|
|
533
|
+
}
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# The official v1.1 evaluator normalizes first and then converts predictions
|
|
537
|
+
# to a set. Without that second deduplication, case/punctuation variants of
|
|
538
|
+
# one triple can be counted more than once and push recall/F1 above 1.
|
|
539
|
+
predicted = {
|
|
540
|
+
_normalized_evidence_triple(identifier, prediction=True) for identifier in prediction_ids
|
|
541
|
+
}
|
|
542
|
+
matched = sum(
|
|
543
|
+
1 for candidate in predicted if any(candidate in group for group in accepted_groups)
|
|
544
|
+
)
|
|
545
|
+
precision = matched / len(predicted) if predicted else 0.0
|
|
546
|
+
recall = matched / len(accepted_groups) if accepted_groups else 0.0
|
|
547
|
+
return {
|
|
548
|
+
"em": float(matched == len(predicted) == len(accepted_groups)),
|
|
549
|
+
"f1": _harmonic_mean(precision, recall),
|
|
550
|
+
"precision": precision,
|
|
551
|
+
"recall": recall,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _normalized_evidence_triple(
|
|
556
|
+
identifier: str,
|
|
557
|
+
*,
|
|
558
|
+
prediction: bool,
|
|
559
|
+
) -> tuple[str, str, str]:
|
|
560
|
+
value = _canonical_identifier(
|
|
561
|
+
identifier,
|
|
562
|
+
expected_length=3,
|
|
563
|
+
label="evidence ID",
|
|
564
|
+
prediction=prediction,
|
|
565
|
+
)
|
|
566
|
+
if not all(isinstance(item, str) and item.strip() for item in value):
|
|
567
|
+
_identifier_error("evidence ID must contain three non-empty strings", prediction)
|
|
568
|
+
subject, relation, object_ = value
|
|
569
|
+
return (
|
|
570
|
+
_normalize_evidence_component(subject),
|
|
571
|
+
_normalize_evidence_component(relation),
|
|
572
|
+
_normalize_evidence_component(object_),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _canonical_identifier(
|
|
577
|
+
identifier: str,
|
|
578
|
+
*,
|
|
579
|
+
expected_length: int,
|
|
580
|
+
label: str,
|
|
581
|
+
prediction: bool,
|
|
582
|
+
) -> list[Any]:
|
|
583
|
+
try:
|
|
584
|
+
value = json.loads(identifier)
|
|
585
|
+
except json.JSONDecodeError:
|
|
586
|
+
_identifier_error(f"{label} must be canonical JSON", prediction)
|
|
587
|
+
if (
|
|
588
|
+
not isinstance(value, list)
|
|
589
|
+
or len(value) != expected_length
|
|
590
|
+
or canonical_json(value) != identifier
|
|
591
|
+
):
|
|
592
|
+
_identifier_error(
|
|
593
|
+
f"{label} must be a canonical JSON array of length {expected_length}",
|
|
594
|
+
prediction,
|
|
595
|
+
)
|
|
596
|
+
return value
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def _identifier_error(message: str, prediction: bool) -> None:
|
|
600
|
+
if prediction:
|
|
601
|
+
raise ValidationError(f"QA prediction {message}")
|
|
602
|
+
raise ArtifactError(f"prepared QA gold {message}")
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _set_scores(prediction: set[Any], reference: set[Any]) -> Mapping[str, float]:
|
|
606
|
+
matched = len(prediction & reference)
|
|
607
|
+
precision = matched / len(prediction) if prediction else 0.0
|
|
608
|
+
recall = matched / len(reference) if reference else 0.0
|
|
609
|
+
return {
|
|
610
|
+
"em": float(prediction == reference),
|
|
611
|
+
"f1": _harmonic_mean(precision, recall),
|
|
612
|
+
"precision": precision,
|
|
613
|
+
"recall": recall,
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _joint_metrics(*components: Mapping[str, float]) -> Mapping[str, float]:
|
|
618
|
+
prefixes = ("answer", "support", "evidence")
|
|
619
|
+
metrics: dict[str, float] = {}
|
|
620
|
+
for prefix, component in zip(prefixes[: len(components)], components, strict=True):
|
|
621
|
+
for metric in ("em", "f1", "precision", "recall"):
|
|
622
|
+
metrics[f"{prefix}_{metric}"] = component[metric]
|
|
623
|
+
joint_em = math.prod(component["em"] for component in components)
|
|
624
|
+
joint_precision = math.prod(component["precision"] for component in components)
|
|
625
|
+
joint_recall = math.prod(component["recall"] for component in components)
|
|
626
|
+
metrics.update(
|
|
627
|
+
{
|
|
628
|
+
"joint_em": joint_em,
|
|
629
|
+
"joint_f1": _harmonic_mean(joint_precision, joint_recall),
|
|
630
|
+
"joint_precision": joint_precision,
|
|
631
|
+
"joint_recall": joint_recall,
|
|
632
|
+
}
|
|
633
|
+
)
|
|
634
|
+
return metrics
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _harmonic_mean(precision: float, recall: float) -> float:
|
|
638
|
+
return 2 * precision * recall / (precision + recall) if precision + recall else 0.0
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _normalized_answer(value: str) -> str:
|
|
642
|
+
# Keep one source of truth for SQuAD/Hotpot answer normalization.
|
|
643
|
+
return normalize_english_answer(value)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _normalize_evidence_component(value: str) -> str:
|
|
647
|
+
without_punctuation = "".join(
|
|
648
|
+
character for character in value.lower() if character not in string.punctuation
|
|
649
|
+
)
|
|
650
|
+
return " ".join(without_punctuation.split())
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _validated_runtime_groups(value: tuple[Any, ...]) -> tuple[Mapping[str, Any], ...]:
|
|
654
|
+
groups: list[Mapping[str, Any]] = []
|
|
655
|
+
for group in value:
|
|
656
|
+
if not isinstance(group, Mapping): # pragma: no cover - construction invariant
|
|
657
|
+
raise ArtifactError("benchmark QA reference group is invalid")
|
|
658
|
+
groups.append(group)
|
|
659
|
+
return tuple(groups)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _validate_prepared_identity(
|
|
663
|
+
prepared: PreparedBenchmark,
|
|
664
|
+
*,
|
|
665
|
+
benchmark_id: str,
|
|
666
|
+
split: str,
|
|
667
|
+
) -> None:
|
|
668
|
+
expected = _NAMED_PROFILE_IDENTITIES.get(benchmark_id)
|
|
669
|
+
if expected is None:
|
|
670
|
+
# Direct construction remains available for custom datasets. Preserve
|
|
671
|
+
# the original compatibility contract: custom identities may be absent,
|
|
672
|
+
# but identities that are supplied must agree with the adapter request.
|
|
673
|
+
if prepared.cell_id and prepared.cell_id != benchmark_id:
|
|
674
|
+
raise ArtifactError("prepared QA cell_id does not match adapter benchmark_id")
|
|
675
|
+
if prepared.split and prepared.split != split:
|
|
676
|
+
raise ArtifactError("prepared QA split does not match adapter split")
|
|
677
|
+
return
|
|
678
|
+
|
|
679
|
+
expected_asset_id, expected_processor, expected_split = expected
|
|
680
|
+
if split != expected_split:
|
|
681
|
+
raise ArtifactError(
|
|
682
|
+
f"named QA profile {benchmark_id!r} requires adapter split {expected_split!r}"
|
|
683
|
+
)
|
|
684
|
+
expected_fields = {
|
|
685
|
+
"asset_id": expected_asset_id,
|
|
686
|
+
"processor": expected_processor,
|
|
687
|
+
"cell_id": benchmark_id,
|
|
688
|
+
"split": expected_split,
|
|
689
|
+
}
|
|
690
|
+
for field, expected_value in expected_fields.items():
|
|
691
|
+
if getattr(prepared, field) != expected_value:
|
|
692
|
+
raise ArtifactError(
|
|
693
|
+
f"prepared QA {field} does not match named profile {benchmark_id!r}"
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _validate_benchmark_profile(
|
|
698
|
+
profile: str,
|
|
699
|
+
*,
|
|
700
|
+
references: tuple[Mapping[str, Any], ...],
|
|
701
|
+
evaluator_metadata: Mapping[str, Any],
|
|
702
|
+
) -> None:
|
|
703
|
+
if profile in {"musique", "hotpotqa"}:
|
|
704
|
+
if any(
|
|
705
|
+
"support_ids" not in group or "evidence_ids" in group or "evidence_texts" in group
|
|
706
|
+
for group in references
|
|
707
|
+
):
|
|
708
|
+
raise ArtifactError(f"{profile} gold requires support_ids and no evidence")
|
|
709
|
+
elif profile == "2wiki":
|
|
710
|
+
if any("support_ids" not in group or "evidence_ids" not in group for group in references):
|
|
711
|
+
raise ArtifactError("2Wiki gold requires support_ids and evidence_ids")
|
|
712
|
+
options = evaluator_metadata.get("evidence_options")
|
|
713
|
+
if not isinstance(options, tuple) or not options:
|
|
714
|
+
raise ArtifactError("2Wiki evaluator metadata requires non-empty evidence_options")
|
|
715
|
+
if any(len(options) != len(group["evidence_ids"]) for group in references):
|
|
716
|
+
raise ArtifactError("2Wiki evidence_options must align with evidence_ids")
|
|
717
|
+
elif profile == "qasper" and any(
|
|
718
|
+
group.get("answer_type") not in _QASPER_ANSWER_TYPES
|
|
719
|
+
or "evidence_texts" not in group
|
|
720
|
+
or "support_ids" in group
|
|
721
|
+
for group in references
|
|
722
|
+
):
|
|
723
|
+
raise ArtifactError(
|
|
724
|
+
"QASPER gold requires a valid answer_type and evidence_texts per annotation"
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _records_by_id(path: Path, *, label: str) -> dict[str, dict[str, Any]]:
|
|
729
|
+
records: dict[str, dict[str, Any]] = {}
|
|
730
|
+
try:
|
|
731
|
+
with path.open(encoding="utf-8") as handle:
|
|
732
|
+
for line_number, raw in enumerate(handle, start=1):
|
|
733
|
+
if not raw.strip():
|
|
734
|
+
raise ArtifactError(f"{label} contains a blank line at {line_number}")
|
|
735
|
+
value = json.loads(raw)
|
|
736
|
+
if not isinstance(value, dict):
|
|
737
|
+
raise ArtifactError(f"{label} row {line_number} must be an object")
|
|
738
|
+
row_id = _required_text(value, "id", label=label)
|
|
739
|
+
if row_id in records:
|
|
740
|
+
raise ArtifactError(f"{label} contains duplicate ID {row_id!r}")
|
|
741
|
+
records[row_id] = value
|
|
742
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
743
|
+
raise ArtifactError(f"{label} is not valid UTF-8 JSONL") from exc
|
|
744
|
+
if not records:
|
|
745
|
+
raise ArtifactError(f"{label} contains no records")
|
|
746
|
+
return records
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _visible_context(value: Any, *, example_id: str) -> tuple[ContextBlock, ...]:
|
|
750
|
+
if not isinstance(value, list):
|
|
751
|
+
raise ArtifactError("prepared QA visible_context must be a JSON array")
|
|
752
|
+
blocks: list[ContextBlock] = []
|
|
753
|
+
block_ids: set[str] = set()
|
|
754
|
+
for index, item in enumerate(value):
|
|
755
|
+
label = f"prepared QA context {example_id!r} item {index}"
|
|
756
|
+
if not isinstance(item, dict):
|
|
757
|
+
raise ArtifactError(f"{label} must be an object")
|
|
758
|
+
unknown = set(item) - _CONTEXT_FIELDS
|
|
759
|
+
if unknown:
|
|
760
|
+
raise ArtifactError(f"{label} has unknown fields: {sorted(unknown)!r}")
|
|
761
|
+
if not {"block_id", "text"} <= set(item):
|
|
762
|
+
raise ArtifactError(f"{label} requires block_id and text")
|
|
763
|
+
block_id = _required_text(item, "block_id", label=label)
|
|
764
|
+
if block_id in block_ids:
|
|
765
|
+
raise ArtifactError(f"prepared QA context contains duplicate block ID {block_id!r}")
|
|
766
|
+
block_ids.add(block_id)
|
|
767
|
+
text = _required_text(item, "text", label=label)
|
|
768
|
+
source = item.get("source", "")
|
|
769
|
+
if not isinstance(source, str):
|
|
770
|
+
raise ArtifactError(f"{label} field 'source' must be a string")
|
|
771
|
+
score = item.get("score")
|
|
772
|
+
numeric_score: float | None = None
|
|
773
|
+
if score is not None:
|
|
774
|
+
if isinstance(score, bool) or not isinstance(score, (int, float)):
|
|
775
|
+
raise ArtifactError(f"{label} field 'score' must be finite numeric or null")
|
|
776
|
+
try:
|
|
777
|
+
numeric_score = float(score)
|
|
778
|
+
except OverflowError as exc:
|
|
779
|
+
raise ArtifactError(
|
|
780
|
+
f"{label} field 'score' must be finite numeric or null"
|
|
781
|
+
) from exc
|
|
782
|
+
if not math.isfinite(numeric_score):
|
|
783
|
+
raise ArtifactError(f"{label} field 'score' must be finite numeric or null")
|
|
784
|
+
metadata = item.get("metadata", {})
|
|
785
|
+
if not isinstance(metadata, Mapping):
|
|
786
|
+
raise ArtifactError(f"{label} field 'metadata' must be an object")
|
|
787
|
+
try:
|
|
788
|
+
block = ContextBlock(
|
|
789
|
+
block_id=block_id,
|
|
790
|
+
text=text,
|
|
791
|
+
source=source,
|
|
792
|
+
score=numeric_score,
|
|
793
|
+
metadata=_frozen_canonical_mapping(metadata, label=f"{label} metadata"),
|
|
794
|
+
)
|
|
795
|
+
except (TypeError, ValidationError) as exc:
|
|
796
|
+
raise ArtifactError(f"{label} is invalid: {exc}") from exc
|
|
797
|
+
blocks.append(block)
|
|
798
|
+
return tuple(blocks)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _visible_metadata(
|
|
802
|
+
value: Any,
|
|
803
|
+
*,
|
|
804
|
+
benchmark_id: str,
|
|
805
|
+
split: str,
|
|
806
|
+
) -> Mapping[str, Any]:
|
|
807
|
+
if not isinstance(value, Mapping):
|
|
808
|
+
raise ArtifactError("prepared QA visible_metadata must be an object")
|
|
809
|
+
if value.get("benchmark_cell") != benchmark_id:
|
|
810
|
+
raise ArtifactError("prepared QA benchmark_cell does not match adapter benchmark_id")
|
|
811
|
+
if value.get("source_split") != split:
|
|
812
|
+
raise ArtifactError("prepared QA source_split does not match adapter split")
|
|
813
|
+
return _frozen_canonical_mapping(value, label="prepared QA visible_metadata")
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _reference_groups(
|
|
817
|
+
evaluator: Mapping[str, Any],
|
|
818
|
+
) -> tuple[tuple[Mapping[str, Any], ...], Mapping[str, Any], tuple[bool, str | None]]:
|
|
819
|
+
raw_evaluator_metadata = evaluator.get("evaluator_metadata", {})
|
|
820
|
+
if not isinstance(raw_evaluator_metadata, Mapping):
|
|
821
|
+
raise ArtifactError("prepared QA evaluator_metadata must be an object")
|
|
822
|
+
evaluator_metadata = _frozen_canonical_mapping(
|
|
823
|
+
raw_evaluator_metadata,
|
|
824
|
+
label="prepared QA evaluator_metadata",
|
|
825
|
+
)
|
|
826
|
+
if "reference_groups" in evaluator:
|
|
827
|
+
if not set(evaluator) <= {"id", "reference_groups", "evaluator_metadata"}:
|
|
828
|
+
raise ArtifactError(
|
|
829
|
+
"prepared QA grouped gold permits only id, reference_groups, and evaluator_metadata"
|
|
830
|
+
)
|
|
831
|
+
raw_groups = evaluator["reference_groups"]
|
|
832
|
+
if not isinstance(raw_groups, list) or not raw_groups:
|
|
833
|
+
raise ArtifactError("prepared QA reference_groups must be a non-empty JSON array")
|
|
834
|
+
else:
|
|
835
|
+
fields = set(evaluator)
|
|
836
|
+
allowed = {"id", "evaluator_metadata"} | _REFERENCE_FIELDS
|
|
837
|
+
if not {"id", "answers"} <= fields or not fields <= allowed:
|
|
838
|
+
raise ArtifactError("prepared QA gold has an invalid flat reference schema")
|
|
839
|
+
raw_groups = [
|
|
840
|
+
{key: evaluator[key] for key in fields if key not in {"id", "evaluator_metadata"}}
|
|
841
|
+
]
|
|
842
|
+
|
|
843
|
+
groups: list[Mapping[str, Any]] = []
|
|
844
|
+
profile: tuple[bool, str | None] | None = None
|
|
845
|
+
for index, raw_group in enumerate(raw_groups):
|
|
846
|
+
label = f"prepared QA reference group {index}"
|
|
847
|
+
if not isinstance(raw_group, dict):
|
|
848
|
+
raise ArtifactError(f"{label} must be an object")
|
|
849
|
+
fields = set(raw_group)
|
|
850
|
+
if "answers" not in fields or not fields <= _REFERENCE_FIELDS:
|
|
851
|
+
raise ArtifactError(f"{label} has an invalid schema")
|
|
852
|
+
answers = _answers(raw_group["answers"], label=label)
|
|
853
|
+
normalized: dict[str, Any] = {"answers": answers}
|
|
854
|
+
if "answer_type" in fields:
|
|
855
|
+
normalized["answer_type"] = _required_text(
|
|
856
|
+
raw_group,
|
|
857
|
+
"answer_type",
|
|
858
|
+
label=label,
|
|
859
|
+
)
|
|
860
|
+
has_support = "support_ids" in fields
|
|
861
|
+
evidence_fields = fields & {"evidence_ids", "evidence_texts"}
|
|
862
|
+
if len(evidence_fields) > 1:
|
|
863
|
+
raise ArtifactError(f"{label} cannot mix evidence_ids and evidence_texts")
|
|
864
|
+
evidence_field = next(iter(evidence_fields), None)
|
|
865
|
+
if has_support:
|
|
866
|
+
normalized["support_ids"] = _gold_identifiers(
|
|
867
|
+
raw_group["support_ids"], label=f"{label} support_ids"
|
|
868
|
+
)
|
|
869
|
+
if evidence_field is not None:
|
|
870
|
+
normalized[evidence_field] = _gold_identifiers(
|
|
871
|
+
raw_group[evidence_field], label=f"{label} {evidence_field}"
|
|
872
|
+
)
|
|
873
|
+
current_profile = (has_support, evidence_field)
|
|
874
|
+
if profile is None:
|
|
875
|
+
profile = current_profile
|
|
876
|
+
elif profile != current_profile:
|
|
877
|
+
raise ArtifactError("prepared QA reference groups must use one support/evidence schema")
|
|
878
|
+
groups.append(_frozen_canonical_mapping(normalized, label=label))
|
|
879
|
+
return tuple(groups), evaluator_metadata, profile or (False, None)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def _answers(value: Any, *, label: str) -> tuple[str, ...]:
|
|
883
|
+
if not isinstance(value, list) or not value:
|
|
884
|
+
raise ArtifactError(f"{label} answers must be a non-empty JSON array")
|
|
885
|
+
if not all(isinstance(item, str) and item.strip() for item in value):
|
|
886
|
+
raise ArtifactError(f"{label} answers must contain non-empty strings")
|
|
887
|
+
if len(value) != len(set(value)):
|
|
888
|
+
raise ArtifactError(f"{label} answers must not contain duplicates")
|
|
889
|
+
return tuple(value)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def _gold_identifiers(value: Any, *, label: str) -> tuple[str, ...]:
|
|
893
|
+
if not isinstance(value, list):
|
|
894
|
+
raise ArtifactError(f"{label} must be a JSON array")
|
|
895
|
+
try:
|
|
896
|
+
return _identifier_tuple(value, label=label)
|
|
897
|
+
except ValidationError as exc:
|
|
898
|
+
raise ArtifactError(str(exc)) from exc
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def _prediction_identifiers(value: Any, *, label: str) -> tuple[str, ...]:
|
|
902
|
+
if isinstance(value, str) or not isinstance(value, Sequence):
|
|
903
|
+
raise ValidationError(f"{label} must be a sequence, not one string")
|
|
904
|
+
return _identifier_tuple(value, label=label)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _identifier_tuple(value: Sequence[Any], *, label: str) -> tuple[str, ...]:
|
|
908
|
+
identifiers = tuple(value)
|
|
909
|
+
if not all(isinstance(item, str) and item.strip() for item in identifiers):
|
|
910
|
+
raise ValidationError(f"{label} must contain non-empty strings")
|
|
911
|
+
if len(identifiers) != len(set(identifiers)):
|
|
912
|
+
raise ValidationError(f"{label} must not contain duplicates")
|
|
913
|
+
return identifiers
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _prediction_from_mapping(value: Mapping[str, Any]) -> QAPrediction:
|
|
917
|
+
if not isinstance(value, Mapping):
|
|
918
|
+
raise TypeError("QA prediction must be QAPrediction or a mapping")
|
|
919
|
+
fields = set(value)
|
|
920
|
+
if "answer" not in fields or not fields <= {
|
|
921
|
+
"answer",
|
|
922
|
+
"support_ids",
|
|
923
|
+
"evidence_ids",
|
|
924
|
+
"evidence_texts",
|
|
925
|
+
}:
|
|
926
|
+
raise ValidationError("QA prediction mapping has an invalid schema")
|
|
927
|
+
return QAPrediction(
|
|
928
|
+
answer=value["answer"],
|
|
929
|
+
support_ids=value.get("support_ids", ()),
|
|
930
|
+
evidence_ids=value.get("evidence_ids", ()),
|
|
931
|
+
evidence_texts=value.get("evidence_texts", ()),
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def _require_exact_fields(record: Mapping[str, Any], fields: set[str], *, label: str) -> None:
|
|
936
|
+
if set(record) != fields:
|
|
937
|
+
missing = sorted(fields - set(record))
|
|
938
|
+
unknown = sorted(set(record) - fields)
|
|
939
|
+
raise ArtifactError(f"{label} schema mismatch: missing={missing!r}, unknown={unknown!r}")
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def _required_text(record: Mapping[str, Any], key: str, *, label: str) -> str:
|
|
943
|
+
value = record.get(key)
|
|
944
|
+
if not isinstance(value, str) or not value.strip():
|
|
945
|
+
raise ArtifactError(f"{label} field {key!r} must be a non-empty string")
|
|
946
|
+
return value
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def _validated_identity(value: str, *, label: str) -> None:
|
|
950
|
+
if not isinstance(value, str) or not value.strip():
|
|
951
|
+
raise ValidationError(f"{label} must be non-empty")
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _frozen_canonical_mapping(value: Mapping[str, Any], *, label: str) -> Mapping[str, Any]:
|
|
955
|
+
try:
|
|
956
|
+
frozen = freeze_mapping(value)
|
|
957
|
+
canonical_json(frozen)
|
|
958
|
+
except (TypeError, ValueError) as exc:
|
|
959
|
+
raise ArtifactError(f"{label} is not canonical: {exc}") from exc
|
|
960
|
+
return frozen
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
__all__ = ["PreparedQABenchmarkAdapter", "QAPrediction"]
|