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,90 @@
1
+ """Token-level uncertainty from the logit distribution.
2
+
3
+ A small, high-ROI white-box analyzer: from a forward pass it summarises the
4
+ next-token predictive distribution (entropy + top-k) — a cheap signal for
5
+ "where was the model unsure?" that needs only the ``LOGITS`` capability, so it
6
+ runs on any model that provides it (and, unlike attention, does not need eager).
7
+
8
+ ``torch`` is imported inside ``_run`` so this module imports torch-free.
9
+
10
+ References:
11
+ - Uncertainty Estimation in Autoregressive Structured Prediction (predictive entropy)
12
+ Malinin & Gales, ICLR 2021 — arXiv:2002.07650
13
+ - Semantic Uncertainty: ... for Natural Language Generation
14
+ Kuhn, Gal & Farquhar, ICLR 2023 — arXiv:2302.09664
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass, field
20
+ from typing import TYPE_CHECKING
21
+
22
+ from evalrx.core.analyzer import Analyzer
23
+ from evalrx.core.capability import Capability
24
+ from evalrx.core.registry import register_analyzer
25
+ from evalrx.core.result import Result
26
+
27
+ if TYPE_CHECKING:
28
+ from evalrx.core.case import CaseBatch
29
+ from evalrx.core.model import Model
30
+
31
+
32
+ @dataclass
33
+ class UncertaintyResult(Result):
34
+ """Per-position predictive entropy (artifact) + agent-readable summary."""
35
+
36
+ tokens: list[str] = field(default_factory=list)
37
+
38
+ @property
39
+ def entropy(self): # (seq_len,)
40
+ return self.artifacts["entropy"]
41
+
42
+
43
+ @register_analyzer("token_entropy")
44
+ class TokenEntropyAnalyzer(Analyzer):
45
+ """Summarise next-token predictive entropy from a model's logits.
46
+
47
+ Hyper-parameters:
48
+ top_k: number of top next-token candidates to report at the final position.
49
+ """
50
+
51
+ name = "token_entropy"
52
+ requires = frozenset({Capability.LOGITS})
53
+
54
+ def __init__(self, top_k: int = 5) -> None:
55
+ super().__init__(top_k=top_k)
56
+
57
+ def _run(self, model: "Model", cases: "CaseBatch") -> UncertaintyResult:
58
+ import torch
59
+
60
+ case = cases[0]
61
+ trace = model.forward(case.inputs, capture={Capability.LOGITS})
62
+ logits = trace.require(Capability.LOGITS) # (seq, vocab)
63
+
64
+ logprobs = torch.log_softmax(logits.float(), dim=-1)
65
+ probs = logprobs.exp()
66
+ entropy = -(probs * logprobs).sum(dim=-1) # (seq,)
67
+
68
+ last = logprobs[-1]
69
+ k = min(self.top_k, last.shape[-1])
70
+ topk = torch.topk(last, k)
71
+ top_next = [
72
+ {"token_id": int(i), "logprob": round(float(v), 4)}
73
+ for v, i in zip(topk.values, topk.indices)
74
+ ]
75
+
76
+ result = UncertaintyResult(
77
+ analyzer=self.name,
78
+ model=repr(model),
79
+ cases=cases,
80
+ tokens=trace.tokens,
81
+ artifacts={"entropy": entropy, "logits": logits},
82
+ )
83
+ result.findings = {
84
+ "seq_len": len(trace.tokens),
85
+ "mean_entropy": round(float(entropy.mean()), 4),
86
+ "max_entropy": round(float(entropy.max()), 4),
87
+ "final_token_entropy": round(float(entropy[-1]), 4),
88
+ "top_next_tokens": top_next,
89
+ }
90
+ return result
@@ -0,0 +1,69 @@
1
+ """Perplexity + predictive entropy from output-token logprobs (black-box).
2
+
3
+ Unlike :class:`TokenEntropyAnalyzer` (which needs the full LOGITS tensor from a
4
+ white-box forward), this works from ``model.logprobs()`` — i.e. OpenAI-style
5
+ ``logprobs`` — so it runs on an API model. ``requires=LOGPROBS``.
6
+
7
+ Perplexity is the exponential of mean negative log-likelihood — lower is better.
8
+ Token-level predictive entropy (mean over top-k distribution) measures local
9
+ uncertainty; high entropy on early tokens signals broad uncertainty.
10
+
11
+ Predictive entropy for uncertainty estimation:
12
+ "Dropout as a Bayesian Approximation: Representing Model Uncertainty in
13
+ Deep Learning" — Gal & Ghahramani, ICML 2016 — https://arxiv.org/abs/1506.02142
14
+
15
+ LLM self-knowledge via logprobs:
16
+ "Language Models (Mostly) Know What They Know"
17
+ Kadavath et al., 2022 — https://arxiv.org/abs/2207.05221
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import math
23
+ from typing import TYPE_CHECKING
24
+
25
+ from evalrx.core.analyzer import Analyzer
26
+ from evalrx.core.capability import Capability
27
+ from evalrx.core.registry import register_analyzer
28
+ from evalrx.core.result import Result
29
+
30
+ if TYPE_CHECKING:
31
+ from evalrx.core.case import CaseBatch
32
+ from evalrx.core.model import Model
33
+
34
+
35
+ @register_analyzer("logprob_entropy")
36
+ class LogprobEntropyAnalyzer(Analyzer):
37
+ """Sequence perplexity + mean top-k predictive entropy from output logprobs."""
38
+
39
+ name = "logprob_entropy"
40
+ requires = frozenset({Capability.LOGPROBS})
41
+ applies_to_modalities = frozenset({"text", "image"})
42
+
43
+ def _run(self, model: "Model", cases: "CaseBatch") -> Result:
44
+ case = cases[0]
45
+ toks = model.logprobs(case.inputs) # list[TokenLogprob]
46
+ lps = [t.logprob for t in toks]
47
+ mean_lp = sum(lps) / len(lps) if lps else 0.0
48
+ perplexity = math.exp(-mean_lp) if lps else float("inf")
49
+
50
+ entropies = []
51
+ for t in toks:
52
+ if not t.top:
53
+ continue
54
+ ps = [math.exp(v) for v in t.top.values()]
55
+ z = sum(ps) or 1.0
56
+ ps = [p / z for p in ps]
57
+ entropies.append(-sum(p * math.log(p) for p in ps if p > 0))
58
+
59
+ return Result(
60
+ analyzer=self.name, model=repr(model), cases=cases,
61
+ artifacts={"token_logprobs": toks},
62
+ findings={
63
+ "n_tokens": len(toks),
64
+ "mean_logprob": round(mean_lp, 4),
65
+ "perplexity": round(perplexity, 4) if math.isfinite(perplexity) else None,
66
+ "min_token_logprob": round(min(lps), 4) if lps else None,
67
+ "mean_top_entropy": round(sum(entropies) / len(entropies), 4) if entropies else None,
68
+ },
69
+ )
@@ -0,0 +1,204 @@
1
+ """Self-consistency — sample N generations and measure agreement.
2
+
3
+ A near-free black-box uncertainty signal (needs only ``GENERATE``): low agreement
4
+ across samples flags brittle/uncertain answers. Runs on API models too.
5
+
6
+ Surface agreement is a lower bound, though: "18", "18 apples" and "the answer is
7
+ eighteen" are three strings and one answer, so string-level consistency reports
8
+ uncertainty the model does not have. **Semantic entropy** fixes that by
9
+ clustering the SAME samples into meaning classes before measuring — no extra
10
+ generations, so it is strictly additive over the existing score. The default
11
+ clustering is a lexical proxy (bidirectional content-word containment); inject
12
+ ``entailment_fn`` to use a real NLI model where it matters.
13
+
14
+ References:
15
+ - Self-Consistency Improves Chain of Thought Reasoning in Language Models
16
+ Wang et al., ICLR 2023 — arXiv:2203.11171
17
+ - Detecting Hallucinations in Large Language Models Using Semantic Entropy
18
+ Farquhar et al., Nature 630 (2024) — bidirectional-entailment clustering
19
+ - Semantic Uncertainty: Linguistic Invariances for Uncertainty Estimation
20
+ Kuhn et al., ICLR 2023 — arXiv:2302.09664
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ from collections import Counter
27
+ from typing import TYPE_CHECKING, Any, Callable, Optional
28
+
29
+ from evalrx.analyzers.hallucination.selfcheck import containment
30
+ from evalrx.core.analyzer import Analyzer
31
+ from evalrx.core.capability import Capability
32
+ from evalrx.core.registry import register_analyzer
33
+ from evalrx.core.result import Result
34
+
35
+ if TYPE_CHECKING:
36
+ from evalrx.core.case import CaseBatch
37
+ from evalrx.core.model import Model
38
+
39
+
40
+ def lexical_equivalent(a: str, b: str, threshold: float = 0.8) -> bool:
41
+ """Bidirectional content-word containment ≥ *threshold* — the default proxy.
42
+
43
+ Bidirectional on purpose: one-way containment makes every short answer a
44
+ member of every long one ("18" ⊂ "18 apples were left"), which merges
45
+ clusters that disagree and understates entropy.
46
+ """
47
+ if not str(a or "").strip() or not str(b or "").strip():
48
+ return not str(a or "").strip() and not str(b or "").strip()
49
+ return min(containment(a, b), containment(b, a)) >= threshold
50
+
51
+
52
+ def cluster_by_equivalence(
53
+ samples: list[str], equivalent: Callable[[str, str], bool]
54
+ ) -> list[list[int]]:
55
+ """Greedy single-link clustering against each cluster's first member.
56
+
57
+ Greedy rather than transitive-closure: chaining through near-misses collapses
58
+ genuinely different answers into one cluster and silently drives the entropy
59
+ to zero, which is the failure this metric exists to avoid.
60
+ """
61
+ clusters: list[list[int]] = []
62
+ for idx, sample in enumerate(samples):
63
+ for cluster in clusters:
64
+ if equivalent(sample, samples[cluster[0]]):
65
+ cluster.append(idx)
66
+ break
67
+ else:
68
+ clusters.append([idx])
69
+ return clusters
70
+
71
+
72
+ def discrete_entropy(sizes: list[int]) -> float:
73
+ """Shannon entropy (nats) of the cluster-size distribution."""
74
+ total = sum(sizes)
75
+ if total <= 0:
76
+ return 0.0
77
+ return round(
78
+ -sum((n / total) * math.log(n / total) for n in sizes if n > 0), 4
79
+ )
80
+
81
+
82
+ @register_analyzer("self_consistency")
83
+ class SelfConsistencyAnalyzer(Analyzer):
84
+ """Sample ``n`` generations and report the modal-answer fraction (consistency).
85
+
86
+ Hyper-parameters:
87
+ n: number of samples.
88
+ gen_kwargs: passed to ``model.generate`` (e.g. ``{"temperature": 0.7}``).
89
+ semantic: also cluster the same samples by meaning and report
90
+ semantic entropy (no extra generations).
91
+ entailment_fn: ``callable(a, b) -> bool`` equivalence test used for that
92
+ clustering; defaults to :func:`lexical_equivalent`.
93
+ answer_fn: ``callable(text) -> str`` applied BEFORE comparing.
94
+
95
+ ``answer_fn`` is what makes this usable on a reasoning model. Without it the
96
+ comparison is over the whole generation, and two samples of a 3,000-token
97
+ chain of thought are never byte-identical even when they reach the same
98
+ answer — so ``consistency`` reads ``1/n`` by construction and ``n_unique``
99
+ reads ``n``, regardless of whether the model actually agreed with itself.
100
+ That is not a lower bound, it is a constant. Pass
101
+ ``answer_fn=extract_answer`` and the metric measures answers again; the
102
+ raw-text number is still reported alongside as ``raw_text_consistency`` so
103
+ the two are never confused.
104
+ """
105
+
106
+ name = "self_consistency"
107
+ requires = frozenset({Capability.GENERATE})
108
+ applies_to_modalities = frozenset({"text", "image"})
109
+ signal_docs = {
110
+ 'consistency': ('Answer stability', 'How often the model gave the same answer when asked the same question repeatedly. Low means unstable reasoning.'),
111
+ 'modal_answer': 'The answer the model gave most often.',
112
+ 'n_samples': 'How many times each question was re-asked.',
113
+ 'n_semantic_clusters': 'How many genuinely different answers there were, after wording differences are ignored.',
114
+ 'n_unique': ('Different answers given', 'How many different answers came back for the same question.'),
115
+ 'normalized_semantic_entropy': ('Answer spread', 'How spread out the answers were. High means the model has no settled view.'),
116
+ }
117
+
118
+ def __init__(
119
+ self,
120
+ n: int = 5,
121
+ gen_kwargs: dict | None = None,
122
+ semantic: bool = True,
123
+ entailment_fn: Optional[Callable[[str, str], bool]] = None,
124
+ answer_fn: Optional[Callable[[str], str]] = None,
125
+ ) -> None:
126
+ super().__init__(n=n, gen_kwargs=gen_kwargs or {}, semantic=semantic)
127
+ # ctor name, so sklearn-style get_params() reflection works
128
+ self.entailment_fn = entailment_fn or lexical_equivalent
129
+ self.answer_fn = answer_fn
130
+
131
+ def _run(self, model: "Model", cases: "CaseBatch") -> Result:
132
+ case = cases[0]
133
+ samples = [model.generate(case.inputs, **self.gen_kwargs) for _ in range(self.n)]
134
+ raw = [str(s).strip().lower() for s in samples]
135
+ if self.answer_fn is not None:
136
+ norm = [str(self.answer_fn(s) or "").strip().lower() for s in samples]
137
+ else:
138
+ norm = raw
139
+ counts = Counter(norm)
140
+ modal, modal_n = counts.most_common(1)[0]
141
+ raw_counts = Counter(raw)
142
+ findings: dict[str, Any] = {
143
+ "n_samples": self.n,
144
+ "consistency": round(modal_n / max(len(samples), 1), 4),
145
+ "n_unique": len(counts),
146
+ "compared_on": "answer" if self.answer_fn is not None else "raw_text",
147
+ # Kept alongside so the two are never mistaken for each other. On a
148
+ # reasoning model this one is ~1/n by construction; a downstream
149
+ # stage that sees only the headline number cannot tell.
150
+ "raw_text_consistency": round(
151
+ raw_counts.most_common(1)[0][1] / max(len(samples), 1), 4),
152
+ "modal_answer": (modal if self.answer_fn is not None
153
+ else samples[norm.index(modal)]),
154
+ # The consistency score is meaningless without the sampling
155
+ # config that produced it (temperature above all): a low score
156
+ # at temperature 0 is a real defect, the same score at 1.0 is
157
+ # expected. Empty dict == the model's own generate() defaults.
158
+ "gen_kwargs": dict(self.gen_kwargs),
159
+ }
160
+ if self.semantic:
161
+ findings.update(self._semantic_findings([str(s) for s in samples]))
162
+ return Result(
163
+ analyzer=self.name,
164
+ model=repr(model),
165
+ cases=cases,
166
+ artifacts={"samples": samples},
167
+ findings=findings,
168
+ )
169
+
170
+ # ------------------------------------------------------------------
171
+ def _semantic_findings(self, samples: list[str]) -> dict[str, Any]:
172
+ clusters = cluster_by_equivalence(samples, self.entailment_fn)
173
+ sizes = sorted((len(c) for c in clusters), reverse=True)
174
+ entropy = discrete_entropy(sizes)
175
+ max_entropy = math.log(len(samples)) if len(samples) > 1 else 0.0
176
+ return {
177
+ "n_semantic_clusters": len(clusters),
178
+ "semantic_entropy": entropy,
179
+ # normalised so batches with different n stay comparable
180
+ "normalized_semantic_entropy": (
181
+ round(entropy / max_entropy, 4) if max_entropy > 0 else 0.0
182
+ ),
183
+ "semantic_consistency": round(sizes[0] / len(samples), 4) if sizes else 0.0,
184
+ "cluster_sizes": sizes,
185
+ "cluster_representatives": [samples[c[0]][:120] for c in clusters[:5]],
186
+ "entailment": (
187
+ "lexical_proxy"
188
+ if self.entailment_fn is lexical_equivalent
189
+ else getattr(self.entailment_fn, "__name__", "injected")
190
+ ),
191
+ "_caveat": (
192
+ "semantic_* uses the SAME samples as consistency — it costs no "
193
+ "extra generations, and the two are meant to be read together: "
194
+ "consistency << semantic_consistency means the disagreement was "
195
+ "only wording. With the default lexical_proxy, clustering is "
196
+ "content-word overlap, NOT entailment: it merges answers that "
197
+ "share vocabulary while contradicting each other ('the claim "
198
+ "holds' / 'the claim does not hold') and splits correct "
199
+ "paraphrases with no shared words. Inject an NLI entailment_fn "
200
+ "before reading semantic_entropy as the Farquhar et al. metric. "
201
+ "At temperature 0 every sample is identical and both scores are "
202
+ "1 by construction, not by measurement."
203
+ ),
204
+ }
@@ -0,0 +1,64 @@
1
+ """Verbalized confidence — elicit and parse the model's stated confidence.
2
+
3
+ Black-box (``GENERATE`` only): ask the model to state a 0–100 confidence and parse
4
+ it. A cheap, well-studied (often miscalibrated) uncertainty signal.
5
+
6
+ References:
7
+ - Teaching Models to Express Their Uncertainty in Words
8
+ Lin, Hilton & Evans, TMLR 2022 — arXiv:2205.14334
9
+ - Just Ask for Calibration: ... Verbalized Confidence (RLHF LMs)
10
+ Tian et al., EMNLP 2023 — arXiv:2305.14975
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from typing import TYPE_CHECKING, Optional
17
+
18
+ from evalrx.core.analyzer import Analyzer
19
+ from evalrx.core.capability import Capability
20
+ from evalrx.core.registry import register_analyzer
21
+ from evalrx.core.result import Result
22
+
23
+ if TYPE_CHECKING:
24
+ from evalrx.core.case import CaseBatch
25
+ from evalrx.core.model import Model
26
+
27
+ _DEFAULT_ELICIT = (
28
+ "\n\nAfter answering, state your confidence on a 0-100 scale "
29
+ "on its own line as: Confidence: <number>"
30
+ )
31
+ _CONF = re.compile(r"confidence[:=]?\s*(\d{1,3}(?:\.\d+)?)\s*%?", re.IGNORECASE)
32
+ _PCT = re.compile(r"(\d{1,3}(?:\.\d+)?)\s*%")
33
+
34
+
35
+ @register_analyzer("verbalized_confidence")
36
+ class VerbalizedConfidenceAnalyzer(Analyzer):
37
+ """Elicit a stated confidence and parse it to a 0–1 float (None if unparseable)."""
38
+
39
+ name = "verbalized_confidence"
40
+ requires = frozenset({Capability.GENERATE})
41
+ applies_to_modalities = frozenset({"text", "image"})
42
+
43
+ def __init__(self, elicit: str = _DEFAULT_ELICIT) -> None:
44
+ super().__init__(elicit=elicit)
45
+
46
+ @staticmethod
47
+ def _parse(text: str) -> Optional[float]:
48
+ m = _CONF.search(text) or _PCT.search(text)
49
+ if not m:
50
+ return None
51
+ val = float(m.group(1))
52
+ return max(0.0, min(1.0, val / 100.0 if val > 1.0 else val))
53
+
54
+ def _run(self, model: "Model", cases: "CaseBatch") -> Result:
55
+ case = cases[0]
56
+ raw = model.generate(str(case.inputs) + self.elicit)
57
+ conf = self._parse(raw)
58
+ return Result(
59
+ analyzer=self.name,
60
+ model=repr(model),
61
+ cases=cases,
62
+ artifacts={"raw": raw},
63
+ findings={"verbalized_confidence": conf, "parsed": conf is not None, "raw_tail": raw[-120:]},
64
+ )