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,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
|
+
)
|