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.
Files changed (339) hide show
  1. evalrx/__init__.py +139 -0
  2. evalrx/agent_assets/__init__.py +2 -0
  3. evalrx/agent_assets/skills/README.md +28 -0
  4. evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
  5. evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
  6. evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
  7. evalrx/agent_assets/skills/nature-figure/README.md +412 -0
  8. evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
  9. evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
  10. evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
  11. evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
  12. evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
  13. evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
  14. evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
  15. evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
  16. evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
  17. evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
  18. evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
  19. evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
  20. evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
  21. evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
  22. evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
  23. evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
  24. evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
  25. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
  26. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
  27. evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
  28. evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
  29. evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
  30. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
  31. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
  32. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
  33. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
  34. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
  35. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
  36. evalrx/agent_assets/skills.py +27 -0
  37. evalrx/agent_runtime/__init__.py +78 -0
  38. evalrx/agent_runtime/_docker_runner.py +89 -0
  39. evalrx/agent_runtime/cli_runtime.py +103 -0
  40. evalrx/agent_runtime/cli_transcript.py +138 -0
  41. evalrx/agent_runtime/cli_types.py +68 -0
  42. evalrx/agent_runtime/codegen/__init__.py +5 -0
  43. evalrx/agent_runtime/codegen/runner.py +94 -0
  44. evalrx/agent_runtime/experiment_harness.py +117 -0
  45. evalrx/agent_runtime/factory.py +102 -0
  46. evalrx/agent_runtime/json_shape.py +44 -0
  47. evalrx/agent_runtime/judges/__init__.py +28 -0
  48. evalrx/agent_runtime/judges/agy.py +179 -0
  49. evalrx/agent_runtime/judges/autodetect.py +135 -0
  50. evalrx/agent_runtime/judges/claude.py +159 -0
  51. evalrx/agent_runtime/judges/codex.py +120 -0
  52. evalrx/agent_runtime/providers/__init__.py +21 -0
  53. evalrx/agent_runtime/providers/antigravity.py +31 -0
  54. evalrx/agent_runtime/providers/base.py +145 -0
  55. evalrx/agent_runtime/providers/claude_code.py +49 -0
  56. evalrx/agent_runtime/providers/codex.py +37 -0
  57. evalrx/agent_runtime/providers/gemini_cli.py +26 -0
  58. evalrx/agent_runtime/providers/kimi_cli.py +27 -0
  59. evalrx/agent_runtime/providers/opencode.py +27 -0
  60. evalrx/agent_runtime/providers/registry.py +58 -0
  61. evalrx/agent_runtime/sandbox.py +517 -0
  62. evalrx/agent_runtime/skill_audit.py +143 -0
  63. evalrx/agent_runtime/skills/__init__.py +19 -0
  64. evalrx/agent_runtime/skills/installer.py +68 -0
  65. evalrx/agent_runtime/skills/prompt_policy.py +86 -0
  66. evalrx/agent_runtime/skills/resolver.py +19 -0
  67. evalrx/analysis/__init__.py +132 -0
  68. evalrx/analysis/adjudicate.py +154 -0
  69. evalrx/analysis/analysis_module.py +361 -0
  70. evalrx/analysis/api.py +171 -0
  71. evalrx/analysis/case_studio.py +651 -0
  72. evalrx/analysis/cli.py +114 -0
  73. evalrx/analysis/dashboard.py +350 -0
  74. evalrx/analysis/eval_case_matrix.py +118 -0
  75. evalrx/analysis/eval_viz_theme.py +833 -0
  76. evalrx/analysis/explore_run.py +333 -0
  77. evalrx/analysis/explorer.py +1276 -0
  78. evalrx/analysis/failure_modes.py +607 -0
  79. evalrx/analysis/fused_pipeline.py +489 -0
  80. evalrx/analysis/holdout.py +300 -0
  81. evalrx/analysis/hypothesis_agent.py +230 -0
  82. evalrx/analysis/narration.py +177 -0
  83. evalrx/analysis/operationalize.py +442 -0
  84. evalrx/analysis/plain_language.py +42 -0
  85. evalrx/analysis/planner.py +283 -0
  86. evalrx/analysis/probe_search.py +203 -0
  87. evalrx/analysis/profile.py +268 -0
  88. evalrx/analysis/prompts/__init__.py +0 -0
  89. evalrx/analysis/prompts/explorer.py +417 -0
  90. evalrx/analysis/prompts/failure_modes.py +33 -0
  91. evalrx/analysis/prompts/holdout.py +27 -0
  92. evalrx/analysis/prompts/hypothesis_agent.py +78 -0
  93. evalrx/analysis/prompts/run_codebase.py +47 -0
  94. evalrx/analysis/prompts/stats_agent.py +72 -0
  95. evalrx/analysis/prompts/stats_tool_generator.py +43 -0
  96. evalrx/analysis/result_marker.py +47 -0
  97. evalrx/analysis/run_codebase.py +242 -0
  98. evalrx/analysis/run_view.py +205 -0
  99. evalrx/analysis/stage_views.py +93 -0
  100. evalrx/analysis/stats_agent.py +944 -0
  101. evalrx/analysis/stats_tool_agent.py +261 -0
  102. evalrx/analysis/stats_tool_generator.py +415 -0
  103. evalrx/analysis/stats_tools.py +1153 -0
  104. evalrx/analysis/trajectory_records.py +193 -0
  105. evalrx/analysis/workbench.py +431 -0
  106. evalrx/analyzers/__init__.py +42 -0
  107. evalrx/analyzers/agent/__init__.py +25 -0
  108. evalrx/analyzers/agent/counterfactual.py +84 -0
  109. evalrx/analyzers/agent/first_error_judge.py +96 -0
  110. evalrx/analyzers/agent/ignored_obs.py +81 -0
  111. evalrx/analyzers/agent/loop_detect.py +79 -0
  112. evalrx/analyzers/agent/reliability.py +165 -0
  113. evalrx/analyzers/agent/tool_shap.py +225 -0
  114. evalrx/analyzers/agent/trajectory_rubric.py +168 -0
  115. evalrx/analyzers/attention/__init__.py +19 -0
  116. evalrx/analyzers/attention/relative_attn.py +610 -0
  117. evalrx/analyzers/attention/rollout.py +73 -0
  118. evalrx/analyzers/attention/sink.py +56 -0
  119. evalrx/analyzers/attention/summary.py +190 -0
  120. evalrx/analyzers/attribution/__init__.py +6 -0
  121. evalrx/analyzers/attribution/generic_attn.py +31 -0
  122. evalrx/analyzers/attribution/gradcam.py +30 -0
  123. evalrx/analyzers/base.py +12 -0
  124. evalrx/analyzers/geometry/__init__.py +6 -0
  125. evalrx/analyzers/geometry/cka.py +70 -0
  126. evalrx/analyzers/geometry/linear_probe.py +157 -0
  127. evalrx/analyzers/hallucination/__init__.py +9 -0
  128. evalrx/analyzers/hallucination/chair.py +78 -0
  129. evalrx/analyzers/hallucination/opera.py +29 -0
  130. evalrx/analyzers/hallucination/pope.py +119 -0
  131. evalrx/analyzers/hallucination/selfcheck.py +155 -0
  132. evalrx/analyzers/hallucination/vcd.py +29 -0
  133. evalrx/analyzers/lens/__init__.py +7 -0
  134. evalrx/analyzers/lens/layer_contrast.py +133 -0
  135. evalrx/analyzers/lens/logit_lens.py +138 -0
  136. evalrx/analyzers/lens/tuned_lens.py +30 -0
  137. evalrx/analyzers/patching/__init__.py +5 -0
  138. evalrx/analyzers/patching/causal_trace.py +30 -0
  139. evalrx/analyzers/perturbation/__init__.py +23 -0
  140. evalrx/analyzers/perturbation/_shapley.py +54 -0
  141. evalrx/analyzers/perturbation/context_shap.py +174 -0
  142. evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
  143. evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
  144. evalrx/analyzers/perturbation/mm_shap.py +146 -0
  145. evalrx/analyzers/perturbation/modality_ablation.py +196 -0
  146. evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
  147. evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
  148. evalrx/analyzers/perturbation/rise.py +94 -0
  149. evalrx/analyzers/perturbation/vl_shap.py +102 -0
  150. evalrx/analyzers/reasoning/__init__.py +33 -0
  151. evalrx/analyzers/reasoning/_text.py +328 -0
  152. evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
  153. evalrx/analyzers/reasoning/arith_audit.py +226 -0
  154. evalrx/analyzers/reasoning/contamination.py +214 -0
  155. evalrx/analyzers/reasoning/knowledge_split.py +253 -0
  156. evalrx/analyzers/reasoning/self_repair.py +246 -0
  157. evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
  158. evalrx/analyzers/reasoning/termination_audit.py +258 -0
  159. evalrx/analyzers/uncertainty/__init__.py +18 -0
  160. evalrx/analyzers/uncertainty/calibration.py +174 -0
  161. evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
  162. evalrx/analyzers/uncertainty/entropy.py +90 -0
  163. evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
  164. evalrx/analyzers/uncertainty/self_consistency.py +204 -0
  165. evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
  166. evalrx/cli.py +411 -0
  167. evalrx/config.py +77 -0
  168. evalrx/contract/__init__.py +179 -0
  169. evalrx/contract/common.py +452 -0
  170. evalrx/contract/emit.py +948 -0
  171. evalrx/contract/export.py +237 -0
  172. evalrx/contract/m1.py +325 -0
  173. evalrx/contract/m2.py +317 -0
  174. evalrx/contract/m3.py +165 -0
  175. evalrx/contract/m4.py +130 -0
  176. evalrx/contract/m5.py +292 -0
  177. evalrx/contract/methodology.py +76 -0
  178. evalrx/contract/pre_m1.py +58 -0
  179. evalrx/contract/typescript.py +140 -0
  180. evalrx/core/__init__.py +85 -0
  181. evalrx/core/analyzer.py +174 -0
  182. evalrx/core/capability.py +54 -0
  183. evalrx/core/case.py +443 -0
  184. evalrx/core/experiment.py +106 -0
  185. evalrx/core/model.py +198 -0
  186. evalrx/core/pipeline.py +42 -0
  187. evalrx/core/registry.py +142 -0
  188. evalrx/core/result.py +64 -0
  189. evalrx/core/spec.py +173 -0
  190. evalrx/core/tokentype.py +165 -0
  191. evalrx/core/tool.py +92 -0
  192. evalrx/datasets/__init__.py +41 -0
  193. evalrx/datasets/base.py +68 -0
  194. evalrx/datasets/gui_os.py +52 -0
  195. evalrx/datasets/llm_qa.py +57 -0
  196. evalrx/datasets/pure_qa.py +12 -0
  197. evalrx/datasets/vlm_qa.py +695 -0
  198. evalrx/datasets/web_search_qa.py +52 -0
  199. evalrx/eval_agent/__init__.py +341 -0
  200. evalrx/eval_agent/_tools.py +81 -0
  201. evalrx/eval_agent/ab_runner.py +50 -0
  202. evalrx/eval_agent/agentic/__init__.py +43 -0
  203. evalrx/eval_agent/agentic/actions.py +216 -0
  204. evalrx/eval_agent/agentic/board.py +107 -0
  205. evalrx/eval_agent/agentic/loop.py +190 -0
  206. evalrx/eval_agent/agentic/tools.py +538 -0
  207. evalrx/eval_agent/checkpoint.py +57 -0
  208. evalrx/eval_agent/cli_agent.py +59 -0
  209. evalrx/eval_agent/cli_skills.py +5 -0
  210. evalrx/eval_agent/evolution.py +396 -0
  211. evalrx/eval_agent/git_manager.py +215 -0
  212. evalrx/eval_agent/hypothesis.py +172 -0
  213. evalrx/eval_agent/label_quarantine.py +209 -0
  214. evalrx/eval_agent/legacy.py +530 -0
  215. evalrx/eval_agent/log_schema.py +497 -0
  216. evalrx/eval_agent/loop.py +2159 -0
  217. evalrx/eval_agent/loop_reports.py +116 -0
  218. evalrx/eval_agent/model_instrumentation.py +282 -0
  219. evalrx/eval_agent/narration.py +193 -0
  220. evalrx/eval_agent/nl_runner.py +460 -0
  221. evalrx/eval_agent/orchestrator.py +61 -0
  222. evalrx/eval_agent/preregister.py +93 -0
  223. evalrx/eval_agent/prompts/__init__.py +1 -0
  224. evalrx/eval_agent/prompts/agentic.py +46 -0
  225. evalrx/eval_agent/prompts/case_discovery.py +25 -0
  226. evalrx/eval_agent/prompts/diagnosis.py +125 -0
  227. evalrx/eval_agent/prompts/experiment_writer.py +265 -0
  228. evalrx/eval_agent/prompts/explore_step.py +37 -0
  229. evalrx/eval_agent/prompts/fix_agent.py +257 -0
  230. evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
  231. evalrx/eval_agent/prompts/nl_runner.py +38 -0
  232. evalrx/eval_agent/prompts/probe_agent.py +25 -0
  233. evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
  234. evalrx/eval_agent/prompts/probe_generator.py +35 -0
  235. evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
  236. evalrx/eval_agent/report.py +58 -0
  237. evalrx/eval_agent/run_context.py +354 -0
  238. evalrx/eval_agent/run_log.schema.json +1215 -0
  239. evalrx/eval_agent/run_logger_v2.py +1764 -0
  240. evalrx/eval_agent/run_metadata.py +208 -0
  241. evalrx/eval_agent/stages/__init__.py +56 -0
  242. evalrx/eval_agent/stages/case_discovery.py +293 -0
  243. evalrx/eval_agent/stages/diagnosis.py +1017 -0
  244. evalrx/eval_agent/stages/experiment_writer.py +1634 -0
  245. evalrx/eval_agent/stages/fix_agent.py +3916 -0
  246. evalrx/eval_agent/stages/fix_internals.py +499 -0
  247. evalrx/eval_agent/stages/fix_pipeline.py +725 -0
  248. evalrx/eval_agent/stages/fix_tiers.py +187 -0
  249. evalrx/eval_agent/stages/fix_tools.py +1034 -0
  250. evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
  251. evalrx/eval_agent/stages/probe.py +439 -0
  252. evalrx/eval_agent/stages/probe_agent.py +1079 -0
  253. evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
  254. evalrx/eval_agent/stages/probe_generator.py +326 -0
  255. evalrx/eval_agent/stages/probe_search_agent.py +106 -0
  256. evalrx/eval_agent/stages/protocol.py +112 -0
  257. evalrx/eval_agent/stages/repair_catalog.py +273 -0
  258. evalrx/eval_agent/stages/surgery.py +524 -0
  259. evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
  260. evalrx/eval_agent/store.py +231 -0
  261. evalrx/logging_utils.py +112 -0
  262. evalrx/models/__init__.py +161 -0
  263. evalrx/models/_discover.py +101 -0
  264. evalrx/models/agent.py +380 -0
  265. evalrx/models/backends/__init__.py +58 -0
  266. evalrx/models/backends/api.py +169 -0
  267. evalrx/models/backends/base.py +57 -0
  268. evalrx/models/backends/gemini_compat.py +579 -0
  269. evalrx/models/backends/hf_local.py +2074 -0
  270. evalrx/models/backends/openai_compat.py +301 -0
  271. evalrx/models/backends/vllm_offline.py +116 -0
  272. evalrx/models/base.py +24 -0
  273. evalrx/models/blackbox/__init__.py +4 -0
  274. evalrx/models/blackbox/agent.py +31 -0
  275. evalrx/models/blackbox/base.py +29 -0
  276. evalrx/models/blackbox/gemini.py +279 -0
  277. evalrx/models/blackbox/llm_api.py +17 -0
  278. evalrx/models/blackbox/vlm_api.py +17 -0
  279. evalrx/models/compose.py +66 -0
  280. evalrx/models/inference.py +88 -0
  281. evalrx/models/paper_methods/__init__.py +8 -0
  282. evalrx/models/paper_methods/aad.py +53 -0
  283. evalrx/models/paper_methods/ifcd.py +204 -0
  284. evalrx/models/paper_methods/pai.py +164 -0
  285. evalrx/models/paper_methods/tcd.py +202 -0
  286. evalrx/models/paper_methods/vcd.py +45 -0
  287. evalrx/models/paper_methods/vicrop.py +137 -0
  288. evalrx/models/toolcodec.py +143 -0
  289. evalrx/models/tools/__init__.py +20 -0
  290. evalrx/models/tools/perception.py +300 -0
  291. evalrx/models/tools/visual.py +174 -0
  292. evalrx/models/whitebox/__init__.py +26 -0
  293. evalrx/models/whitebox/agent.py +31 -0
  294. evalrx/models/whitebox/base.py +24 -0
  295. evalrx/models/whitebox/qwen.py +61 -0
  296. evalrx/models/whitebox/qwen2_5_omni.py +29 -0
  297. evalrx/models/whitebox/qwen2_audio.py +25 -0
  298. evalrx/models/whitebox/qwen_omni.py +53 -0
  299. evalrx/models/whitebox/qwen_vl.py +62 -0
  300. evalrx/observability/__init__.py +21 -0
  301. evalrx/observability/envelope.py +122 -0
  302. evalrx/observability/outbox.py +111 -0
  303. evalrx/observability/tracer.py +882 -0
  304. evalrx/reporting/__init__.py +28 -0
  305. evalrx/reporting/case_study.py +947 -0
  306. evalrx/reporting/compiler.py +587 -0
  307. evalrx/reporting/dynamic.py +1882 -0
  308. evalrx/reporting/html_report.py +2225 -0
  309. evalrx/reporting/langfuse_exporter.py +38 -0
  310. evalrx/reporting/langfuse_source.py +155 -0
  311. evalrx/reporting/model.py +151 -0
  312. evalrx/reporting/run_events.py +184 -0
  313. evalrx/reporting/server.py +557 -0
  314. evalrx/reporting/stages.py +58 -0
  315. evalrx/reporting/static_export.py +142 -0
  316. evalrx/reporting/web_dist/index.html +146 -0
  317. evalrx/specs.py +727 -0
  318. evalrx/stats/__init__.py +47 -0
  319. evalrx/stats/api.py +192 -0
  320. evalrx/stats/bootstrap.py +86 -0
  321. evalrx/stats/ebh.py +27 -0
  322. evalrx/stats/evalue.py +98 -0
  323. evalrx/stats/friedman.py +138 -0
  324. evalrx/stats/mcnemar.py +40 -0
  325. evalrx/stats/multiplicity.py +159 -0
  326. evalrx/stats/subset_sampling.py +55 -0
  327. evalrx/term_links.py +43 -0
  328. evalrx/viz/__init__.py +7 -0
  329. evalrx/viz/labels.py +77 -0
  330. evalrx/viz/prompts.py +39 -0
  331. evalrx/viz/renderer.py +590 -0
  332. evalrx/viz/schema.py +36 -0
  333. evalrx/viz/style.py +134 -0
  334. evalrx-0.1.2.dist-info/METADATA +532 -0
  335. evalrx-0.1.2.dist-info/RECORD +339 -0
  336. evalrx-0.1.2.dist-info/WHEEL +5 -0
  337. evalrx-0.1.2.dist-info/entry_points.txt +3 -0
  338. evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
  339. 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)