evalrx 0.1.2__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.
- evalrx/__init__.py +139 -0
- evalrx/agent_assets/__init__.py +2 -0
- evalrx/agent_assets/skills/README.md +28 -0
- evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
- evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
- evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
- evalrx/agent_assets/skills/nature-figure/README.md +412 -0
- evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
- evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
- evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
- evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
- evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
- evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
- evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
- evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
- evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
- evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
- evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
- evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
- evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
- evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
- evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
- evalrx/agent_assets/skills.py +27 -0
- evalrx/agent_runtime/__init__.py +78 -0
- evalrx/agent_runtime/_docker_runner.py +89 -0
- evalrx/agent_runtime/cli_runtime.py +103 -0
- evalrx/agent_runtime/cli_transcript.py +138 -0
- evalrx/agent_runtime/cli_types.py +68 -0
- evalrx/agent_runtime/codegen/__init__.py +5 -0
- evalrx/agent_runtime/codegen/runner.py +94 -0
- evalrx/agent_runtime/experiment_harness.py +117 -0
- evalrx/agent_runtime/factory.py +102 -0
- evalrx/agent_runtime/json_shape.py +44 -0
- evalrx/agent_runtime/judges/__init__.py +28 -0
- evalrx/agent_runtime/judges/agy.py +179 -0
- evalrx/agent_runtime/judges/autodetect.py +135 -0
- evalrx/agent_runtime/judges/claude.py +159 -0
- evalrx/agent_runtime/judges/codex.py +120 -0
- evalrx/agent_runtime/providers/__init__.py +21 -0
- evalrx/agent_runtime/providers/antigravity.py +31 -0
- evalrx/agent_runtime/providers/base.py +145 -0
- evalrx/agent_runtime/providers/claude_code.py +49 -0
- evalrx/agent_runtime/providers/codex.py +37 -0
- evalrx/agent_runtime/providers/gemini_cli.py +26 -0
- evalrx/agent_runtime/providers/kimi_cli.py +27 -0
- evalrx/agent_runtime/providers/opencode.py +27 -0
- evalrx/agent_runtime/providers/registry.py +58 -0
- evalrx/agent_runtime/sandbox.py +517 -0
- evalrx/agent_runtime/skill_audit.py +143 -0
- evalrx/agent_runtime/skills/__init__.py +19 -0
- evalrx/agent_runtime/skills/installer.py +68 -0
- evalrx/agent_runtime/skills/prompt_policy.py +86 -0
- evalrx/agent_runtime/skills/resolver.py +19 -0
- evalrx/analysis/__init__.py +132 -0
- evalrx/analysis/adjudicate.py +154 -0
- evalrx/analysis/analysis_module.py +361 -0
- evalrx/analysis/api.py +171 -0
- evalrx/analysis/case_studio.py +651 -0
- evalrx/analysis/cli.py +114 -0
- evalrx/analysis/dashboard.py +350 -0
- evalrx/analysis/eval_case_matrix.py +118 -0
- evalrx/analysis/eval_viz_theme.py +833 -0
- evalrx/analysis/explore_run.py +333 -0
- evalrx/analysis/explorer.py +1276 -0
- evalrx/analysis/failure_modes.py +607 -0
- evalrx/analysis/fused_pipeline.py +489 -0
- evalrx/analysis/holdout.py +300 -0
- evalrx/analysis/hypothesis_agent.py +230 -0
- evalrx/analysis/narration.py +177 -0
- evalrx/analysis/operationalize.py +442 -0
- evalrx/analysis/plain_language.py +42 -0
- evalrx/analysis/planner.py +283 -0
- evalrx/analysis/probe_search.py +203 -0
- evalrx/analysis/profile.py +268 -0
- evalrx/analysis/prompts/__init__.py +0 -0
- evalrx/analysis/prompts/explorer.py +417 -0
- evalrx/analysis/prompts/failure_modes.py +33 -0
- evalrx/analysis/prompts/holdout.py +27 -0
- evalrx/analysis/prompts/hypothesis_agent.py +78 -0
- evalrx/analysis/prompts/run_codebase.py +47 -0
- evalrx/analysis/prompts/stats_agent.py +72 -0
- evalrx/analysis/prompts/stats_tool_generator.py +43 -0
- evalrx/analysis/result_marker.py +47 -0
- evalrx/analysis/run_codebase.py +242 -0
- evalrx/analysis/run_view.py +205 -0
- evalrx/analysis/stage_views.py +93 -0
- evalrx/analysis/stats_agent.py +944 -0
- evalrx/analysis/stats_tool_agent.py +261 -0
- evalrx/analysis/stats_tool_generator.py +415 -0
- evalrx/analysis/stats_tools.py +1153 -0
- evalrx/analysis/trajectory_records.py +193 -0
- evalrx/analysis/workbench.py +431 -0
- evalrx/analyzers/__init__.py +42 -0
- evalrx/analyzers/agent/__init__.py +25 -0
- evalrx/analyzers/agent/counterfactual.py +84 -0
- evalrx/analyzers/agent/first_error_judge.py +96 -0
- evalrx/analyzers/agent/ignored_obs.py +81 -0
- evalrx/analyzers/agent/loop_detect.py +79 -0
- evalrx/analyzers/agent/reliability.py +165 -0
- evalrx/analyzers/agent/tool_shap.py +225 -0
- evalrx/analyzers/agent/trajectory_rubric.py +168 -0
- evalrx/analyzers/attention/__init__.py +19 -0
- evalrx/analyzers/attention/relative_attn.py +610 -0
- evalrx/analyzers/attention/rollout.py +73 -0
- evalrx/analyzers/attention/sink.py +56 -0
- evalrx/analyzers/attention/summary.py +190 -0
- evalrx/analyzers/attribution/__init__.py +6 -0
- evalrx/analyzers/attribution/generic_attn.py +31 -0
- evalrx/analyzers/attribution/gradcam.py +30 -0
- evalrx/analyzers/base.py +12 -0
- evalrx/analyzers/geometry/__init__.py +6 -0
- evalrx/analyzers/geometry/cka.py +70 -0
- evalrx/analyzers/geometry/linear_probe.py +157 -0
- evalrx/analyzers/hallucination/__init__.py +9 -0
- evalrx/analyzers/hallucination/chair.py +78 -0
- evalrx/analyzers/hallucination/opera.py +29 -0
- evalrx/analyzers/hallucination/pope.py +119 -0
- evalrx/analyzers/hallucination/selfcheck.py +155 -0
- evalrx/analyzers/hallucination/vcd.py +29 -0
- evalrx/analyzers/lens/__init__.py +7 -0
- evalrx/analyzers/lens/layer_contrast.py +133 -0
- evalrx/analyzers/lens/logit_lens.py +138 -0
- evalrx/analyzers/lens/tuned_lens.py +30 -0
- evalrx/analyzers/patching/__init__.py +5 -0
- evalrx/analyzers/patching/causal_trace.py +30 -0
- evalrx/analyzers/perturbation/__init__.py +23 -0
- evalrx/analyzers/perturbation/_shapley.py +54 -0
- evalrx/analyzers/perturbation/context_shap.py +174 -0
- evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
- evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
- evalrx/analyzers/perturbation/mm_shap.py +146 -0
- evalrx/analyzers/perturbation/modality_ablation.py +196 -0
- evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
- evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
- evalrx/analyzers/perturbation/rise.py +94 -0
- evalrx/analyzers/perturbation/vl_shap.py +102 -0
- evalrx/analyzers/reasoning/__init__.py +33 -0
- evalrx/analyzers/reasoning/_text.py +328 -0
- evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
- evalrx/analyzers/reasoning/arith_audit.py +226 -0
- evalrx/analyzers/reasoning/contamination.py +214 -0
- evalrx/analyzers/reasoning/knowledge_split.py +253 -0
- evalrx/analyzers/reasoning/self_repair.py +246 -0
- evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
- evalrx/analyzers/reasoning/termination_audit.py +258 -0
- evalrx/analyzers/uncertainty/__init__.py +18 -0
- evalrx/analyzers/uncertainty/calibration.py +174 -0
- evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
- evalrx/analyzers/uncertainty/entropy.py +90 -0
- evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
- evalrx/analyzers/uncertainty/self_consistency.py +204 -0
- evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
- evalrx/cli.py +411 -0
- evalrx/config.py +77 -0
- evalrx/contract/__init__.py +179 -0
- evalrx/contract/common.py +452 -0
- evalrx/contract/emit.py +948 -0
- evalrx/contract/export.py +237 -0
- evalrx/contract/m1.py +325 -0
- evalrx/contract/m2.py +317 -0
- evalrx/contract/m3.py +165 -0
- evalrx/contract/m4.py +130 -0
- evalrx/contract/m5.py +292 -0
- evalrx/contract/methodology.py +76 -0
- evalrx/contract/pre_m1.py +58 -0
- evalrx/contract/typescript.py +140 -0
- evalrx/core/__init__.py +85 -0
- evalrx/core/analyzer.py +174 -0
- evalrx/core/capability.py +54 -0
- evalrx/core/case.py +443 -0
- evalrx/core/experiment.py +106 -0
- evalrx/core/model.py +198 -0
- evalrx/core/pipeline.py +42 -0
- evalrx/core/registry.py +142 -0
- evalrx/core/result.py +64 -0
- evalrx/core/spec.py +173 -0
- evalrx/core/tokentype.py +165 -0
- evalrx/core/tool.py +92 -0
- evalrx/datasets/__init__.py +41 -0
- evalrx/datasets/base.py +68 -0
- evalrx/datasets/gui_os.py +52 -0
- evalrx/datasets/llm_qa.py +57 -0
- evalrx/datasets/pure_qa.py +12 -0
- evalrx/datasets/vlm_qa.py +695 -0
- evalrx/datasets/web_search_qa.py +52 -0
- evalrx/eval_agent/__init__.py +341 -0
- evalrx/eval_agent/_tools.py +81 -0
- evalrx/eval_agent/ab_runner.py +50 -0
- evalrx/eval_agent/agentic/__init__.py +43 -0
- evalrx/eval_agent/agentic/actions.py +216 -0
- evalrx/eval_agent/agentic/board.py +107 -0
- evalrx/eval_agent/agentic/loop.py +190 -0
- evalrx/eval_agent/agentic/tools.py +538 -0
- evalrx/eval_agent/checkpoint.py +57 -0
- evalrx/eval_agent/cli_agent.py +59 -0
- evalrx/eval_agent/cli_skills.py +5 -0
- evalrx/eval_agent/evolution.py +396 -0
- evalrx/eval_agent/git_manager.py +215 -0
- evalrx/eval_agent/hypothesis.py +172 -0
- evalrx/eval_agent/label_quarantine.py +209 -0
- evalrx/eval_agent/legacy.py +530 -0
- evalrx/eval_agent/log_schema.py +497 -0
- evalrx/eval_agent/loop.py +2159 -0
- evalrx/eval_agent/loop_reports.py +116 -0
- evalrx/eval_agent/model_instrumentation.py +282 -0
- evalrx/eval_agent/narration.py +193 -0
- evalrx/eval_agent/nl_runner.py +460 -0
- evalrx/eval_agent/orchestrator.py +61 -0
- evalrx/eval_agent/preregister.py +93 -0
- evalrx/eval_agent/prompts/__init__.py +1 -0
- evalrx/eval_agent/prompts/agentic.py +46 -0
- evalrx/eval_agent/prompts/case_discovery.py +25 -0
- evalrx/eval_agent/prompts/diagnosis.py +125 -0
- evalrx/eval_agent/prompts/experiment_writer.py +265 -0
- evalrx/eval_agent/prompts/explore_step.py +37 -0
- evalrx/eval_agent/prompts/fix_agent.py +257 -0
- evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
- evalrx/eval_agent/prompts/nl_runner.py +38 -0
- evalrx/eval_agent/prompts/probe_agent.py +25 -0
- evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
- evalrx/eval_agent/prompts/probe_generator.py +35 -0
- evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
- evalrx/eval_agent/report.py +58 -0
- evalrx/eval_agent/run_context.py +354 -0
- evalrx/eval_agent/run_log.schema.json +1215 -0
- evalrx/eval_agent/run_logger_v2.py +1764 -0
- evalrx/eval_agent/run_metadata.py +208 -0
- evalrx/eval_agent/stages/__init__.py +56 -0
- evalrx/eval_agent/stages/case_discovery.py +293 -0
- evalrx/eval_agent/stages/diagnosis.py +1017 -0
- evalrx/eval_agent/stages/experiment_writer.py +1634 -0
- evalrx/eval_agent/stages/fix_agent.py +3916 -0
- evalrx/eval_agent/stages/fix_internals.py +499 -0
- evalrx/eval_agent/stages/fix_pipeline.py +725 -0
- evalrx/eval_agent/stages/fix_tiers.py +187 -0
- evalrx/eval_agent/stages/fix_tools.py +1034 -0
- evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
- evalrx/eval_agent/stages/probe.py +439 -0
- evalrx/eval_agent/stages/probe_agent.py +1079 -0
- evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
- evalrx/eval_agent/stages/probe_generator.py +326 -0
- evalrx/eval_agent/stages/probe_search_agent.py +106 -0
- evalrx/eval_agent/stages/protocol.py +112 -0
- evalrx/eval_agent/stages/repair_catalog.py +273 -0
- evalrx/eval_agent/stages/surgery.py +524 -0
- evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
- evalrx/eval_agent/store.py +231 -0
- evalrx/logging_utils.py +112 -0
- evalrx/models/__init__.py +161 -0
- evalrx/models/_discover.py +101 -0
- evalrx/models/agent.py +380 -0
- evalrx/models/backends/__init__.py +58 -0
- evalrx/models/backends/api.py +169 -0
- evalrx/models/backends/base.py +57 -0
- evalrx/models/backends/gemini_compat.py +579 -0
- evalrx/models/backends/hf_local.py +2074 -0
- evalrx/models/backends/openai_compat.py +301 -0
- evalrx/models/backends/vllm_offline.py +116 -0
- evalrx/models/base.py +24 -0
- evalrx/models/blackbox/__init__.py +4 -0
- evalrx/models/blackbox/agent.py +31 -0
- evalrx/models/blackbox/base.py +29 -0
- evalrx/models/blackbox/gemini.py +279 -0
- evalrx/models/blackbox/llm_api.py +17 -0
- evalrx/models/blackbox/vlm_api.py +17 -0
- evalrx/models/compose.py +66 -0
- evalrx/models/inference.py +88 -0
- evalrx/models/paper_methods/__init__.py +8 -0
- evalrx/models/paper_methods/aad.py +53 -0
- evalrx/models/paper_methods/ifcd.py +204 -0
- evalrx/models/paper_methods/pai.py +164 -0
- evalrx/models/paper_methods/tcd.py +202 -0
- evalrx/models/paper_methods/vcd.py +45 -0
- evalrx/models/paper_methods/vicrop.py +137 -0
- evalrx/models/toolcodec.py +143 -0
- evalrx/models/tools/__init__.py +20 -0
- evalrx/models/tools/perception.py +300 -0
- evalrx/models/tools/visual.py +174 -0
- evalrx/models/whitebox/__init__.py +26 -0
- evalrx/models/whitebox/agent.py +31 -0
- evalrx/models/whitebox/base.py +24 -0
- evalrx/models/whitebox/qwen.py +61 -0
- evalrx/models/whitebox/qwen2_5_omni.py +29 -0
- evalrx/models/whitebox/qwen2_audio.py +25 -0
- evalrx/models/whitebox/qwen_omni.py +53 -0
- evalrx/models/whitebox/qwen_vl.py +62 -0
- evalrx/observability/__init__.py +21 -0
- evalrx/observability/envelope.py +122 -0
- evalrx/observability/outbox.py +111 -0
- evalrx/observability/tracer.py +882 -0
- evalrx/reporting/__init__.py +28 -0
- evalrx/reporting/case_study.py +947 -0
- evalrx/reporting/compiler.py +587 -0
- evalrx/reporting/dynamic.py +1882 -0
- evalrx/reporting/html_report.py +2225 -0
- evalrx/reporting/langfuse_exporter.py +38 -0
- evalrx/reporting/langfuse_source.py +155 -0
- evalrx/reporting/model.py +151 -0
- evalrx/reporting/run_events.py +184 -0
- evalrx/reporting/server.py +557 -0
- evalrx/reporting/stages.py +58 -0
- evalrx/reporting/static_export.py +142 -0
- evalrx/reporting/web_dist/index.html +146 -0
- evalrx/specs.py +727 -0
- evalrx/stats/__init__.py +47 -0
- evalrx/stats/api.py +192 -0
- evalrx/stats/bootstrap.py +86 -0
- evalrx/stats/ebh.py +27 -0
- evalrx/stats/evalue.py +98 -0
- evalrx/stats/friedman.py +138 -0
- evalrx/stats/mcnemar.py +40 -0
- evalrx/stats/multiplicity.py +159 -0
- evalrx/stats/subset_sampling.py +55 -0
- evalrx/term_links.py +43 -0
- evalrx/viz/__init__.py +7 -0
- evalrx/viz/labels.py +77 -0
- evalrx/viz/prompts.py +39 -0
- evalrx/viz/renderer.py +590 -0
- evalrx/viz/schema.py +36 -0
- evalrx/viz/style.py +134 -0
- evalrx-0.1.2.dist-info/METADATA +532 -0
- evalrx-0.1.2.dist-info/RECORD +339 -0
- evalrx-0.1.2.dist-info/WHEEL +5 -0
- evalrx-0.1.2.dist-info/entry_points.txt +3 -0
- evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
- evalrx-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Contamination probe — is this benchmark measuring reasoning or recall?
|
|
2
|
+
|
|
3
|
+
Every accuracy number in M1/M2 assumes the model is solving the item, not
|
|
4
|
+
remembering it. When a benchmark leaked into pre-training, PASS/FAIL stops
|
|
5
|
+
tracking capability, the mechanism columns correlate with nothing real, and any
|
|
6
|
+
fix validated on it will not transfer. Contamination is therefore a
|
|
7
|
+
**confound column**, not a headline: it is measured per case, carried into M2
|
|
8
|
+
alongside the mechanism columns, and used to disqualify a finding whose effect
|
|
9
|
+
lives entirely on memorised items.
|
|
10
|
+
|
|
11
|
+
Needs no labels and no reference corpus. The guided-vs-general design of
|
|
12
|
+
Golchin & Surdeanu isolates the effect: both arms ask the model to complete the
|
|
13
|
+
second half of the item, but only the guided arm names the benchmark and split.
|
|
14
|
+
A model that reconstructs the continuation *better when told which benchmark it
|
|
15
|
+
came from* is retrieving, not generalising.
|
|
16
|
+
|
|
17
|
+
Cost: 2 generations per case (3 with ``ask_provenance=True``).
|
|
18
|
+
|
|
19
|
+
References:
|
|
20
|
+
- Time Travel in LLMs: Tracing Data Contamination in Large Language Models —
|
|
21
|
+
Golchin & Surdeanu, ICLR 2024 — arXiv:2308.08493 (guided vs general prompting)
|
|
22
|
+
- Investigating Data Contamination in Modern Benchmarks for LLMs —
|
|
23
|
+
Deng et al., NAACL 2024 — arXiv:2311.09783
|
|
24
|
+
- Rethinking Benchmark and Contamination for Language Models with
|
|
25
|
+
Rephrased Samples — Yang et al., 2023 — arXiv:2311.04850
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import dataclasses
|
|
31
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
32
|
+
|
|
33
|
+
from evalrx.analyzers.reasoning._text import word_ngrams
|
|
34
|
+
from evalrx.core.analyzer import Analyzer
|
|
35
|
+
from evalrx.core.capability import Capability
|
|
36
|
+
from evalrx.core.registry import register_analyzer
|
|
37
|
+
from evalrx.core.result import Result
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from evalrx.core.case import CaseBatch, FailureCase
|
|
41
|
+
from evalrx.core.model import Model
|
|
42
|
+
|
|
43
|
+
_GENERAL = (
|
|
44
|
+
"Complete the rest of this text exactly as it continues. Output only the "
|
|
45
|
+
"continuation.\n\nText so far:\n{first_half}"
|
|
46
|
+
)
|
|
47
|
+
_GUIDED = (
|
|
48
|
+
"The text below is the FIRST PIECE of an instance from the {split} split of "
|
|
49
|
+
"the {dataset} dataset. Finish the second piece exactly as it appears in the "
|
|
50
|
+
"dataset. Output only the continuation.\n\nFirst piece:\n{first_half}"
|
|
51
|
+
)
|
|
52
|
+
_PROVENANCE = (
|
|
53
|
+
"Which public benchmark or dataset is the following item from? Answer with "
|
|
54
|
+
"the dataset name only, or 'unknown'.\n\n{text}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def overlap_score(candidate: Any, reference: Any, n: int = 4) -> float:
|
|
59
|
+
"""Fraction of the reference's word ``n``-grams that the candidate reproduces.
|
|
60
|
+
|
|
61
|
+
Recall-oriented on purpose: a verbose completion that contains the reference
|
|
62
|
+
span has still reproduced it, and length-penalising that would hide exactly
|
|
63
|
+
the memorisation this probe is looking for. Falls back to unigrams for
|
|
64
|
+
references too short to have ``n``-grams.
|
|
65
|
+
"""
|
|
66
|
+
ref_grams = word_ngrams(reference, n) or word_ngrams(reference, 1)
|
|
67
|
+
if not ref_grams:
|
|
68
|
+
return 0.0
|
|
69
|
+
width = len(ref_grams[0])
|
|
70
|
+
cand_grams = set(word_ngrams(candidate, width))
|
|
71
|
+
if not cand_grams:
|
|
72
|
+
return 0.0
|
|
73
|
+
hits = sum(1 for g in ref_grams if g in cand_grams)
|
|
74
|
+
return round(hits / len(ref_grams), 4)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@register_analyzer("contamination_score")
|
|
78
|
+
class ContaminationProbe(Analyzer):
|
|
79
|
+
"""Guided-vs-general completion overlap — a per-case memorisation signal.
|
|
80
|
+
|
|
81
|
+
Hyper-parameters:
|
|
82
|
+
dataset_name: benchmark name used by the guided arm (REQUIRED to be
|
|
83
|
+
meaningful — the guided/general contrast IS the design).
|
|
84
|
+
split: split name used by the guided arm.
|
|
85
|
+
split_frac: fraction of the item shown as the first half.
|
|
86
|
+
ngram: n-gram width for the overlap score.
|
|
87
|
+
flag_threshold: guided overlap above this ⇒ ``verbatim_flag``.
|
|
88
|
+
ask_provenance: +1 generation asking the model to name the benchmark.
|
|
89
|
+
max_cases: label-stratified cap; 0 (the default) = every case.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name = "contamination_score"
|
|
93
|
+
requires = frozenset({Capability.GENERATE})
|
|
94
|
+
applies_to_modalities = frozenset({"text", "image"})
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
dataset_name: str = "the evaluation",
|
|
99
|
+
split: str = "test",
|
|
100
|
+
split_frac: float = 0.5,
|
|
101
|
+
ngram: int = 4,
|
|
102
|
+
flag_threshold: float = 0.6,
|
|
103
|
+
ask_provenance: bool = False,
|
|
104
|
+
max_cases: int = 0,
|
|
105
|
+
) -> None:
|
|
106
|
+
super().__init__(
|
|
107
|
+
dataset_name=dataset_name,
|
|
108
|
+
split=split,
|
|
109
|
+
split_frac=min(max(split_frac, 0.1), 0.9),
|
|
110
|
+
ngram=ngram,
|
|
111
|
+
flag_threshold=flag_threshold,
|
|
112
|
+
ask_provenance=ask_provenance,
|
|
113
|
+
max_cases=max_cases,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _run(self, model: "Model", cases: "CaseBatch") -> Result:
|
|
117
|
+
per_case: list[dict[str, Any]] = []
|
|
118
|
+
for case in cases.stratified_head(self.max_cases):
|
|
119
|
+
per_case.append(self._probe_case(model, case))
|
|
120
|
+
|
|
121
|
+
scored = [c for c in per_case if "guided_overlap" in c]
|
|
122
|
+
flagged = [c for c in scored if c["verbatim_flag"] == 1]
|
|
123
|
+
labelled = [c for c in scored if c.get("labelled_pass") is not None]
|
|
124
|
+
findings: dict[str, Any] = {
|
|
125
|
+
"n_cases": len(per_case),
|
|
126
|
+
"n_scored": len(scored),
|
|
127
|
+
"dataset_name": self.dataset_name,
|
|
128
|
+
"mean_general_overlap": _mean([c["general_overlap"] for c in scored]),
|
|
129
|
+
"mean_guided_overlap": _mean([c["guided_overlap"] for c in scored]),
|
|
130
|
+
"mean_guided_gain": _mean([c["guided_gain"] for c in scored]),
|
|
131
|
+
"verbatim_flag_rate": (
|
|
132
|
+
round(len(flagged) / len(scored), 4) if scored else None
|
|
133
|
+
),
|
|
134
|
+
# the number that actually matters: does memorisation buy accuracy?
|
|
135
|
+
"accuracy_on_flagged": _mean([c["labelled_pass"] for c in labelled
|
|
136
|
+
if c["verbatim_flag"] == 1]),
|
|
137
|
+
"accuracy_on_unflagged": _mean([c["labelled_pass"] for c in labelled
|
|
138
|
+
if c["verbatim_flag"] == 0]),
|
|
139
|
+
"per_case": per_case,
|
|
140
|
+
"_caveat": (
|
|
141
|
+
"This is a CONFOUND column, never a headline. A guided_gain > 0 "
|
|
142
|
+
"says the model reconstructs the item better when told which "
|
|
143
|
+
"benchmark it is from — evidence of exposure, not proof, and it "
|
|
144
|
+
"is confounded by items whose continuation is guessable from "
|
|
145
|
+
"format alone (templated benchmarks score high with no leak, so "
|
|
146
|
+
"read general_overlap as the floor). The usable test is the "
|
|
147
|
+
"SPLIT: when accuracy_on_flagged far exceeds "
|
|
148
|
+
"accuracy_on_unflagged, the benchmark's PASS mass is partly "
|
|
149
|
+
"recall and any M3 hypothesis resting on it must be re-run on "
|
|
150
|
+
"unflagged cases. Set dataset_name — with the default the "
|
|
151
|
+
"guided arm names nothing and the contrast is void."
|
|
152
|
+
),
|
|
153
|
+
}
|
|
154
|
+
return Result(analyzer=self.name, model=repr(model), cases=cases, findings=findings)
|
|
155
|
+
|
|
156
|
+
# ------------------------------------------------------------------
|
|
157
|
+
def _probe_case(self, model: "Model", case: "FailureCase") -> dict[str, Any]:
|
|
158
|
+
from evalrx.core.case import Label
|
|
159
|
+
|
|
160
|
+
text = str(case.inputs.prompt or "")
|
|
161
|
+
words = text.split()
|
|
162
|
+
entry: dict[str, Any] = {"sample_id": case.id, "n_words": len(words)}
|
|
163
|
+
if len(words) < 2 * self.ngram:
|
|
164
|
+
entry["skipped"] = "prompt too short to split into two halves"
|
|
165
|
+
return entry
|
|
166
|
+
|
|
167
|
+
cut = max(1, int(len(words) * self.split_frac))
|
|
168
|
+
first_half, second_half = " ".join(words[:cut]), " ".join(words[cut:])
|
|
169
|
+
|
|
170
|
+
general = str(model.generate(_bare(case, _GENERAL.format(first_half=first_half))))
|
|
171
|
+
guided = str(
|
|
172
|
+
model.generate(
|
|
173
|
+
_bare(
|
|
174
|
+
case,
|
|
175
|
+
_GUIDED.format(
|
|
176
|
+
first_half=first_half,
|
|
177
|
+
dataset=self.dataset_name,
|
|
178
|
+
split=self.split,
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
general_overlap = overlap_score(general, second_half, self.ngram)
|
|
184
|
+
guided_overlap = overlap_score(guided, second_half, self.ngram)
|
|
185
|
+
entry.update(
|
|
186
|
+
{
|
|
187
|
+
"general_overlap": general_overlap,
|
|
188
|
+
"guided_overlap": guided_overlap,
|
|
189
|
+
"guided_gain": round(guided_overlap - general_overlap, 4),
|
|
190
|
+
"verbatim_flag": int(guided_overlap >= self.flag_threshold),
|
|
191
|
+
"labelled_pass": (
|
|
192
|
+
int(case.label == Label.PASS)
|
|
193
|
+
if case.label in (Label.PASS, Label.FAIL)
|
|
194
|
+
else None
|
|
195
|
+
),
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
if self.ask_provenance:
|
|
199
|
+
named = str(model.generate(_bare(case, _PROVENANCE.format(text=text[:1500]))))
|
|
200
|
+
entry["named_dataset"] = named.strip()[:80]
|
|
201
|
+
entry["named_correct"] = int(
|
|
202
|
+
self.dataset_name.lower().split()[0] in named.lower()
|
|
203
|
+
)
|
|
204
|
+
return entry
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _bare(case: "FailureCase", prompt: str):
|
|
208
|
+
"""Inputs carrying *prompt* alone — media slots are preserved, the task is not."""
|
|
209
|
+
return dataclasses.replace(case.inputs, prompt=prompt)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _mean(values: list) -> Optional[float]:
|
|
213
|
+
clean = [v for v in values if v is not None]
|
|
214
|
+
return round(sum(clean) / len(clean), 4) if clean else None
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Knowledge/reasoning split — is the fact missing, or is the chaining broken?
|
|
2
|
+
|
|
3
|
+
A wrong multi-hop answer has two incompatible causes. Either the model never
|
|
4
|
+
knew the bridging fact (a *knowledge* deficit — retrieval, tools, or a bigger
|
|
5
|
+
model), or it knew every fact and still failed to combine them (a *reasoning*
|
|
6
|
+
deficit — decomposition scaffolds, which do nothing for missing knowledge).
|
|
7
|
+
Picking the wrong one costs a whole M5 tier.
|
|
8
|
+
|
|
9
|
+
The discriminating move needs no dataset annotation: ask the model to state the
|
|
10
|
+
facts it needs, then hand *its own* stated facts back and ask again. If it now
|
|
11
|
+
answers correctly, the knowledge was present all along and the composition
|
|
12
|
+
failed. When the case carries gold context (``metadata['context']`` /
|
|
13
|
+
``'facts'``) an open-book arm is added, which separates "does not know" from
|
|
14
|
+
"cannot retrieve from its own weights".
|
|
15
|
+
|
|
16
|
+
Cost: 4 generations per case, 5 with gold context.
|
|
17
|
+
|
|
18
|
+
References:
|
|
19
|
+
- MuSiQue: Multihop Questions via Single-hop Question Composition — Trivedi et
|
|
20
|
+
al., TACL 2022 — arXiv:2108.00573 (composability vs single-hop knowledge)
|
|
21
|
+
- Measuring and Narrowing the Compositionality Gap in Language Models — Press et
|
|
22
|
+
al., EMNLP Findings 2023 — arXiv:2210.03350
|
|
23
|
+
- Chain-of-Thought Prompting Elicits Reasoning — Wei et al., NeurIPS 2022 —
|
|
24
|
+
arXiv:2201.11903 (decomposition arm)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import dataclasses
|
|
30
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
31
|
+
|
|
32
|
+
from evalrx.analyzers.reasoning._text import answer_equal, extract_answer
|
|
33
|
+
from evalrx.core.analyzer import Analyzer
|
|
34
|
+
from evalrx.core.capability import Capability
|
|
35
|
+
from evalrx.core.registry import register_analyzer
|
|
36
|
+
from evalrx.core.result import Result
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from evalrx.core.case import CaseBatch, FailureCase
|
|
40
|
+
from evalrx.core.model import Model
|
|
41
|
+
|
|
42
|
+
_ANSWER_FORMAT = "Give the final answer on its own last line as 'Answer: <answer>'."
|
|
43
|
+
_BASELINE = _ANSWER_FORMAT
|
|
44
|
+
_DECOMPOSE = (
|
|
45
|
+
"Break this into the sub-questions you must answer first, answer each in "
|
|
46
|
+
f"turn, then combine them. {_ANSWER_FORMAT}"
|
|
47
|
+
)
|
|
48
|
+
_RECALL = (
|
|
49
|
+
"Do NOT answer the question yet. List only the specific facts you would "
|
|
50
|
+
"need in order to answer it, one per line."
|
|
51
|
+
)
|
|
52
|
+
_FROM_FACTS = (
|
|
53
|
+
"Facts you listed:\n{facts}\n\nUsing only these facts, answer the question. "
|
|
54
|
+
f"{_ANSWER_FORMAT}"
|
|
55
|
+
)
|
|
56
|
+
_OPEN_BOOK = (
|
|
57
|
+
"Reference material:\n{context}\n\nUsing this material, answer the question. "
|
|
58
|
+
f"{_ANSWER_FORMAT}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
KNOWLEDGE = "knowledge"
|
|
62
|
+
REASONING = "reasoning"
|
|
63
|
+
BOTH = "both"
|
|
64
|
+
NONE = "none"
|
|
65
|
+
UNKNOWN = "unknown"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@register_analyzer("knowledge_reasoning_split")
|
|
69
|
+
class KnowledgeReasoningSplit(Analyzer):
|
|
70
|
+
"""Attribute each failure to a missing fact or a broken composition.
|
|
71
|
+
|
|
72
|
+
Hyper-parameters:
|
|
73
|
+
use_context: add the open-book arm when the case carries gold context.
|
|
74
|
+
context_keys: metadata keys searched for that context, in order.
|
|
75
|
+
max_cases: label-stratified cap (4–5 generations each); 0 (the default) = every case.
|
|
76
|
+
grader: ``callable(prediction, case) -> bool | None``.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
name = "knowledge_reasoning_split"
|
|
80
|
+
requires = frozenset({Capability.GENERATE})
|
|
81
|
+
applies_to_modalities = frozenset({"text", "image"})
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
use_context: bool = True,
|
|
86
|
+
context_keys: tuple = ("context", "facts", "supporting_facts", "passage"),
|
|
87
|
+
max_cases: int = 0,
|
|
88
|
+
grader: Optional[Callable[[Any, "FailureCase"], Optional[bool]]] = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
super().__init__(
|
|
91
|
+
use_context=use_context,
|
|
92
|
+
context_keys=tuple(context_keys),
|
|
93
|
+
max_cases=max_cases,
|
|
94
|
+
)
|
|
95
|
+
self.grader = grader or _default_grader
|
|
96
|
+
|
|
97
|
+
def _run(self, model: "Model", cases: "CaseBatch") -> Result:
|
|
98
|
+
per_case: list[dict[str, Any]] = []
|
|
99
|
+
for case in cases.stratified_head(self.max_cases):
|
|
100
|
+
if case.expected is None:
|
|
101
|
+
per_case.append({"sample_id": case.id, "skipped": "no gold answer"})
|
|
102
|
+
continue
|
|
103
|
+
per_case.append(self._probe_case(model, case))
|
|
104
|
+
|
|
105
|
+
scored = [c for c in per_case if "deficit_class" in c]
|
|
106
|
+
failed = [c for c in scored if c["baseline_correct"] == 0]
|
|
107
|
+
|
|
108
|
+
# UNKNOWN is a residual, not a class: with no gold context the open-book
|
|
109
|
+
# arm never runs and every failure the scaffolds did not rescue lands
|
|
110
|
+
# there. Dividing by all failures would then report a MEASURED 0.0 for
|
|
111
|
+
# knowledge_deficit_share when nothing was measured at all — the same
|
|
112
|
+
# trap arith_audit avoids by excluding its own UNKNOWN from the
|
|
113
|
+
# denominator.
|
|
114
|
+
classified = [c for c in failed if c["deficit_class"] != UNKNOWN]
|
|
115
|
+
|
|
116
|
+
def _share(cls: str) -> Optional[float]:
|
|
117
|
+
return (
|
|
118
|
+
round(sum(1 for c in classified if c["deficit_class"] == cls)
|
|
119
|
+
/ len(classified), 4)
|
|
120
|
+
if classified
|
|
121
|
+
else None
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
findings: dict[str, Any] = {
|
|
125
|
+
"n_cases": len(per_case),
|
|
126
|
+
"n_scored": len(scored),
|
|
127
|
+
"n_baseline_fail": len(failed),
|
|
128
|
+
"n_classified": len(classified),
|
|
129
|
+
"n_unclassified": len(failed) - len(classified),
|
|
130
|
+
"unclassified_share": (
|
|
131
|
+
round((len(failed) - len(classified)) / len(failed), 4) if failed else None
|
|
132
|
+
),
|
|
133
|
+
"knowledge_deficit_share": _share(KNOWLEDGE),
|
|
134
|
+
"reasoning_deficit_share": _share(REASONING),
|
|
135
|
+
"both_deficit_share": _share(BOTH),
|
|
136
|
+
"decomposition_gain": _gain(scored, "decomposed_correct"),
|
|
137
|
+
"own_facts_gain": _gain(scored, "own_facts_correct"),
|
|
138
|
+
"open_book_gain": _gain(scored, "open_book_correct"),
|
|
139
|
+
"per_case": per_case,
|
|
140
|
+
"_caveat": (
|
|
141
|
+
"The three deficit shares are over CLASSIFIED failures only — "
|
|
142
|
+
"read n_classified and unclassified_share first. Separating "
|
|
143
|
+
"'knowledge' from 'both' REQUIRES gold context in "
|
|
144
|
+
"case.metadata; without it the open-book arm never runs, every "
|
|
145
|
+
"failure the scaffolds did not rescue is unclassified, and "
|
|
146
|
+
"knowledge_deficit_share is None (not 0.0). A 0.0 here means "
|
|
147
|
+
"'measured, none were knowledge deficits'; None means 'this "
|
|
148
|
+
"batch cannot answer the question'. "
|
|
149
|
+
"The arms differ in PROMPT as well as in information, so a gain "
|
|
150
|
+
"is 'this scaffold helps', not proof of where the knowledge "
|
|
151
|
+
"lives — own_facts_correct in particular re-states the question "
|
|
152
|
+
"with a cleaner context, which alone helps some models. Read "
|
|
153
|
+
"reasoning_deficit_share against a same-length no-op scaffold "
|
|
154
|
+
"before promoting a decomposition fix. Cases where the model "
|
|
155
|
+
"lists NO usable facts fall out as own_facts_parsed=0 and are "
|
|
156
|
+
"excluded from own_facts_gain rather than counted as failures. "
|
|
157
|
+
"open_book_* exists only for cases carrying gold context; its "
|
|
158
|
+
"absence is 'not measured'. INTERVENTIONAL — re-run to confirm."
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
return Result(analyzer=self.name, model=repr(model), cases=cases, findings=findings)
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
def _probe_case(self, model: "Model", case: "FailureCase") -> dict[str, Any]:
|
|
165
|
+
entry: dict[str, Any] = {"sample_id": case.id}
|
|
166
|
+
baseline = self.grader(model.generate(self._ask(case, _BASELINE)), case)
|
|
167
|
+
decomposed = self.grader(model.generate(self._ask(case, _DECOMPOSE)), case)
|
|
168
|
+
|
|
169
|
+
facts = str(model.generate(self._ask(case, _RECALL)))
|
|
170
|
+
fact_lines = [ln.strip() for ln in facts.splitlines() if ln.strip()]
|
|
171
|
+
entry["n_listed_facts"] = len(fact_lines)
|
|
172
|
+
entry["own_facts_parsed"] = int(bool(fact_lines))
|
|
173
|
+
own_facts = None
|
|
174
|
+
if fact_lines:
|
|
175
|
+
own_facts = self.grader(
|
|
176
|
+
model.generate(
|
|
177
|
+
self._ask(case, _FROM_FACTS.format(facts="\n".join(fact_lines)))
|
|
178
|
+
),
|
|
179
|
+
case,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
context = self._context_for(case)
|
|
183
|
+
open_book = None
|
|
184
|
+
if context:
|
|
185
|
+
open_book = self.grader(
|
|
186
|
+
model.generate(self._ask(case, _OPEN_BOOK.format(context=context))), case
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if baseline is None:
|
|
190
|
+
entry["skipped"] = "ungradable baseline"
|
|
191
|
+
return entry
|
|
192
|
+
entry["baseline_correct"] = int(baseline)
|
|
193
|
+
for key, value in (
|
|
194
|
+
("decomposed_correct", decomposed),
|
|
195
|
+
("own_facts_correct", own_facts),
|
|
196
|
+
("open_book_correct", open_book),
|
|
197
|
+
):
|
|
198
|
+
if value is not None:
|
|
199
|
+
entry[key] = int(value)
|
|
200
|
+
|
|
201
|
+
entry["deficit_class"] = _classify(baseline, own_facts, decomposed, open_book)
|
|
202
|
+
return entry
|
|
203
|
+
|
|
204
|
+
def _ask(self, case: "FailureCase", instruction: str):
|
|
205
|
+
prompt = f"{case.inputs.prompt or ''}\n\n{instruction}"
|
|
206
|
+
return dataclasses.replace(case.inputs, prompt=prompt)
|
|
207
|
+
|
|
208
|
+
def _context_for(self, case: "FailureCase") -> str:
|
|
209
|
+
if not self.use_context:
|
|
210
|
+
return ""
|
|
211
|
+
for key in self.context_keys:
|
|
212
|
+
value = case.metadata.get(key)
|
|
213
|
+
if isinstance(value, (list, tuple)):
|
|
214
|
+
value = "\n".join(str(v) for v in value)
|
|
215
|
+
if value and str(value).strip():
|
|
216
|
+
return str(value).strip()
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _classify(
|
|
221
|
+
baseline: bool,
|
|
222
|
+
own_facts: Optional[bool],
|
|
223
|
+
decomposed: Optional[bool],
|
|
224
|
+
open_book: Optional[bool],
|
|
225
|
+
) -> str:
|
|
226
|
+
"""Which deficit does the arm pattern imply for a failing baseline?"""
|
|
227
|
+
if baseline:
|
|
228
|
+
return NONE
|
|
229
|
+
# Scaffolding its own knowledge fixed it ⇒ the facts were there, the
|
|
230
|
+
# composition was not.
|
|
231
|
+
if own_facts or decomposed:
|
|
232
|
+
return REASONING
|
|
233
|
+
# Only the gold context fixed it ⇒ the facts were genuinely missing.
|
|
234
|
+
if open_book:
|
|
235
|
+
return KNOWLEDGE
|
|
236
|
+
if open_book is False:
|
|
237
|
+
# even with the facts handed over it still fails: both halves are broken
|
|
238
|
+
return BOTH
|
|
239
|
+
return UNKNOWN
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _gain(entries: list[dict[str, Any]], key: str) -> Optional[float]:
|
|
243
|
+
"""Mean arm-minus-baseline accuracy over the cases where the arm ran."""
|
|
244
|
+
pairs = [(c["baseline_correct"], c[key]) for c in entries if key in c]
|
|
245
|
+
if not pairs:
|
|
246
|
+
return None
|
|
247
|
+
return round(sum(arm - base for base, arm in pairs) / len(pairs), 4)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _default_grader(prediction: Any, case: "FailureCase") -> Optional[bool]:
|
|
251
|
+
if case.expected is None:
|
|
252
|
+
return None
|
|
253
|
+
return answer_equal(extract_answer(prediction), case.expected)
|