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,439 @@
|
|
|
1
|
+
"""M1 — StrategyProbe: select which analyzers to run given a model and a batch.
|
|
2
|
+
|
|
3
|
+
Ranking composes per-SLOT priority lists rather than looking up one per-kind
|
|
4
|
+
list, because a model is not one kind. ``LLM`` / ``VLM`` / ``ALM`` / ``AVLM``
|
|
5
|
+
are four subsets of ``{text, image, audio, video}``, and an enum over the
|
|
6
|
+
subsets grows as 2^n while composing over the members grows as n.
|
|
7
|
+
|
|
8
|
+
Which slots to compose over is decided by the BATCH, not by the model. An omni
|
|
9
|
+
model declares every modality it can consume; evaluated on an audio benchmark it
|
|
10
|
+
still declares image, and ranking on the declaration put image analyzers at the
|
|
11
|
+
top of an audio run. :func:`~evalrx.core.case.probed_modalities` answers
|
|
12
|
+
what is actually under test, and the model's declaration is the fallback for a
|
|
13
|
+
batch that fills no media slot at all — no evidence, rather than evidence of
|
|
14
|
+
absence.
|
|
15
|
+
|
|
16
|
+
Usage::
|
|
17
|
+
|
|
18
|
+
probe = StrategyProbe()
|
|
19
|
+
slots = probe.routed_slots(model, cases) # {"text", "audio"} for an ALM run
|
|
20
|
+
kind = probe.detect_kind(model) # ModelKind.VLM / ALM / AVLM / AGENT / LLM
|
|
21
|
+
names = probe.select(model, max_analyzers=4, data=cases)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import TYPE_CHECKING, Any, Iterable
|
|
28
|
+
|
|
29
|
+
from evalrx.core.capability import Capability
|
|
30
|
+
from evalrx.core.case import MEDIA_SLOTS, probed_modalities
|
|
31
|
+
from evalrx.core.registry import registry
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _carries_trajectories(data: Any) -> bool:
|
|
35
|
+
"""True when *data* (CaseBatch / iterable of cases) has any trajectory-carrying case."""
|
|
36
|
+
try:
|
|
37
|
+
return any(getattr(c, "trajectory", None) is not None for c in data)
|
|
38
|
+
except TypeError:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from evalrx.core.model import Model
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ModelKind(str, Enum):
|
|
46
|
+
"""A coarse label for a model's modality set.
|
|
47
|
+
|
|
48
|
+
Kept for display, logging and the ``priority_override`` API — NOT for
|
|
49
|
+
routing. It is a lossy rendering of a set (an AVLM answering a text-only
|
|
50
|
+
question is still AVLM here), which is exactly why :meth:`StrategyProbe.select`
|
|
51
|
+
composes over :meth:`StrategyProbe.routed_slots` instead of branching on this.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
VLM = "vlm" # image — hallucination + attention analyzers first
|
|
55
|
+
ALM = "alm" # audio, no image — listening / grounding analyzers first
|
|
56
|
+
AVLM = "avlm" # audio AND image — both, audio ranked first
|
|
57
|
+
AGENT = "agent" # trajectories or TOOL_CALLS — behavioral analyzers first
|
|
58
|
+
LLM = "llm" # text only — interpretability analyzers first
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Maps failure-mode tags (from M3 hypotheses) to the analyzers most likely to
|
|
62
|
+
# surface evidence for that mode. Used in cycle 2+ to focus the probe on what
|
|
63
|
+
# the diagnosis agent flagged rather than running the same generic priority list.
|
|
64
|
+
_FAILURE_MODE_TO_ANALYZERS: dict[str, list[str]] = {
|
|
65
|
+
"attention_sink": ["attention_sink"],
|
|
66
|
+
"attention": ["attention", "attention_rollout"],
|
|
67
|
+
"hallucination": ["pope", "chair", "selfcheck_consistency"],
|
|
68
|
+
"low_consistency": ["self_consistency"],
|
|
69
|
+
"unstable_generation": ["self_consistency"],
|
|
70
|
+
"overconfidence": ["verbalized_confidence", "calibration"],
|
|
71
|
+
"miscalibrated_confidence": ["verbalized_confidence", "calibration"],
|
|
72
|
+
"format_bias": ["format_sensitivity"],
|
|
73
|
+
"position_bias": ["format_sensitivity"],
|
|
74
|
+
"unfaithful_reasoning": ["cot_faithfulness"],
|
|
75
|
+
"post_hoc_reasoning": ["cot_faithfulness"],
|
|
76
|
+
"context_ignored": ["context_shap"],
|
|
77
|
+
"premature_layer_divergence": ["layer_contrast"],
|
|
78
|
+
"confident_inconsistency": ["self_consistency", "verbalized_confidence"],
|
|
79
|
+
"loop": ["loop_detect"],
|
|
80
|
+
"ignored_obs": ["ignored_obs"],
|
|
81
|
+
# Decision-layer mechanisms — discriminated by prompt interventions.
|
|
82
|
+
"language_prior_bias": ["prompt_contrast"],
|
|
83
|
+
"negative_bias": ["prompt_contrast", "pope"],
|
|
84
|
+
"threshold_miscalibration": ["prompt_contrast"],
|
|
85
|
+
"prompt_formatting_bias": ["prompt_contrast"],
|
|
86
|
+
"instruction_following": ["prompt_contrast"],
|
|
87
|
+
"entropy": ["token_entropy", "logprob_entropy"],
|
|
88
|
+
"perplexity": ["logprob_entropy"],
|
|
89
|
+
"logit_lens": ["logit_lens"],
|
|
90
|
+
"representational_collapse": ["cka"],
|
|
91
|
+
"numerical_hallucination": ["self_consistency", "verbalized_confidence"],
|
|
92
|
+
# Text-reasoning mechanisms (2026-08). The hygiene pair is listed under the
|
|
93
|
+
# names an M3 hypothesis uses when it blames the harness rather than the model.
|
|
94
|
+
"answer_extraction": ["answer_extraction_audit"],
|
|
95
|
+
"parse_failure": ["answer_extraction_audit"],
|
|
96
|
+
"truncation": ["termination_audit"],
|
|
97
|
+
"degenerate_repetition": ["termination_audit"],
|
|
98
|
+
"premature_termination": ["termination_audit"],
|
|
99
|
+
"arithmetic_error": ["arith_audit"],
|
|
100
|
+
"computation_slip": ["arith_audit"],
|
|
101
|
+
"chain_break": ["arith_audit", "step_rollout_value"],
|
|
102
|
+
"reasoning_break": ["step_rollout_value", "arith_audit"],
|
|
103
|
+
"overthinking": ["cot_faithfulness", "step_rollout_value"],
|
|
104
|
+
"self_correction_failure": ["self_repair"],
|
|
105
|
+
"knowledge_gap": ["knowledge_reasoning_split"],
|
|
106
|
+
"compositionality_gap": ["knowledge_reasoning_split"],
|
|
107
|
+
"selection_failure": ["coverage_verification_gap"],
|
|
108
|
+
"verification_gap": ["coverage_verification_gap"],
|
|
109
|
+
"brittleness": ["perturbation_battery"],
|
|
110
|
+
"surface_form_sensitivity": ["perturbation_battery", "format_sensitivity"],
|
|
111
|
+
"memorization": ["contamination_score", "perturbation_battery"],
|
|
112
|
+
"contamination": ["contamination_score"],
|
|
113
|
+
"semantic_uncertainty": ["self_consistency"],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Per-SLOT ordered priority: high → low diagnostic value for false attribution.
|
|
117
|
+
#
|
|
118
|
+
# Composed, not looked up: ``select`` concatenates the lists for the slots the
|
|
119
|
+
# batch actually fills (agent, then video, audio, image, then always text) and
|
|
120
|
+
# appends the remaining compatible analyzers alphabetically. Adding a modality
|
|
121
|
+
# is one more entry here; it does not multiply the table the way a per-kind
|
|
122
|
+
# table does, and an AVLM needs no entry of its own — it is audio + image.
|
|
123
|
+
_SLOT_PRIORITY: dict[str, list[str]] = {
|
|
124
|
+
"agent": [
|
|
125
|
+
"loop_detect", "ignored_obs", # behavioral heuristics
|
|
126
|
+
"first_error_judge", "trajectory_rubric", # LLM-judge localisation + classification
|
|
127
|
+
"counterfactual", "reliability_probe", # re-run probes: step perturbation, pass@k
|
|
128
|
+
"tool_shap", # re-run probe: tool-subset Shapley
|
|
129
|
+
],
|
|
130
|
+
"video": [
|
|
131
|
+
"modality_ablation", # is the video load-bearing at all?
|
|
132
|
+
"relative_attn", # attention across frames
|
|
133
|
+
"mm_shap", # per-slot reliance
|
|
134
|
+
],
|
|
135
|
+
"audio": [
|
|
136
|
+
# Grounding first, for the same reason the text list leads with hygiene:
|
|
137
|
+
# an ALM that answers from the language prior alone produces failures that
|
|
138
|
+
# mimic every mechanism below, and nothing else here would reveal it.
|
|
139
|
+
"modality_ablation", # does replacing the audio change the answer?
|
|
140
|
+
"mm_shap", # audio vs text reliance
|
|
141
|
+
"answer_extraction_audit", # is the FAIL label real or a parse miss?
|
|
142
|
+
"termination_audit", # truncated / degenerate / gave up?
|
|
143
|
+
"prompt_contrast", # are failures prompt-repairable?
|
|
144
|
+
"selfcheck_consistency", # hallucinated content in the description
|
|
145
|
+
"self_consistency", "logprob_entropy",
|
|
146
|
+
"attention", "attention_sink", # where does it attend among audio tokens?
|
|
147
|
+
"verbalized_confidence", "calibration",
|
|
148
|
+
],
|
|
149
|
+
"image": [
|
|
150
|
+
"pope", "chair", # hallucination metrics
|
|
151
|
+
"modality_ablation", # is the image load-bearing at all?
|
|
152
|
+
"attention", "attention_rollout", # where is the model looking?
|
|
153
|
+
"attention_sink", # sink collapse?
|
|
154
|
+
"prompt_contrast", # are failures prompt-repairable?
|
|
155
|
+
"mm_shap", # text vs image reliance
|
|
156
|
+
"logprob_entropy", "self_consistency",
|
|
157
|
+
],
|
|
158
|
+
"text": [], # filled below from the legacy LLM list
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Back-compat view: ``StrategyProbe(priority_override=...)`` and the docs are
|
|
162
|
+
# written against ModelKind keys. Composition is the default path; an override
|
|
163
|
+
# still selects one list by kind, exactly as before.
|
|
164
|
+
_PRIORITY: dict[str, list[str]] = {
|
|
165
|
+
ModelKind.VLM: [
|
|
166
|
+
"pope", "chair", # hallucination metrics
|
|
167
|
+
"attention", "attention_rollout", # where is the model looking?
|
|
168
|
+
"attention_sink", # sink collapse?
|
|
169
|
+
"prompt_contrast", # are failures prompt-repairable?
|
|
170
|
+
"mm_shap", # text vs image reliance
|
|
171
|
+
"logprob_entropy", "self_consistency",
|
|
172
|
+
],
|
|
173
|
+
ModelKind.AGENT: [
|
|
174
|
+
"loop_detect", "ignored_obs", # behavioral heuristics
|
|
175
|
+
"first_error_judge", "trajectory_rubric", # LLM-judge localisation + classification
|
|
176
|
+
"counterfactual", "reliability_probe", # re-run probes: step perturbation, pass@k
|
|
177
|
+
"tool_shap", # re-run probe: tool-subset Shapley
|
|
178
|
+
],
|
|
179
|
+
ModelKind.LLM: [
|
|
180
|
+
# Hygiene first: both produce confounds that mimic every mechanism
|
|
181
|
+
# column below, so a finding read before them is not interpretable.
|
|
182
|
+
"answer_extraction_audit", # is the FAIL label real or a parse miss?
|
|
183
|
+
"termination_audit", # truncated / degenerate / gave up?
|
|
184
|
+
"arith_audit", # computation slip vs chain break (free)
|
|
185
|
+
"selfcheck_consistency", # text hallucination (black-box)
|
|
186
|
+
"format_sensitivity", # MC position bias vs content-tracking
|
|
187
|
+
"cot_faithfulness", # is the reasoning load-bearing?
|
|
188
|
+
"coverage_verification_gap", # cannot solve vs cannot select
|
|
189
|
+
"perturbation_battery", # invariance breaks / missing sensitivity
|
|
190
|
+
"self_repair", # detect / correct / DAMAGE
|
|
191
|
+
"knowledge_reasoning_split", # missing fact vs broken composition
|
|
192
|
+
"calibration", # ECE / overconfidence vs labels
|
|
193
|
+
"attention", "logit_lens", # interpretability
|
|
194
|
+
"layer_contrast", # DoLa/DeCo divergence signal
|
|
195
|
+
"token_entropy", "logprob_entropy",
|
|
196
|
+
"attention_sink", "attention_rollout",
|
|
197
|
+
"prompt_contrast", # are failures prompt-repairable?
|
|
198
|
+
"context_shap", # RAG context dependence
|
|
199
|
+
"cka", "self_consistency", "verbalized_confidence",
|
|
200
|
+
"step_rollout_value", # where the chain broke (expensive)
|
|
201
|
+
"contamination_score", # is the benchmark measuring recall?
|
|
202
|
+
],
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# text IS the legacy LLM list: every model has a prompt, so this is the floor
|
|
206
|
+
# every composition starts from rather than a peer of the media slots.
|
|
207
|
+
_SLOT_PRIORITY["text"] = _PRIORITY[ModelKind.LLM]
|
|
208
|
+
|
|
209
|
+
# Slots each ModelKind stands for, in composition order. Only the back-compat
|
|
210
|
+
# ``_PRIORITY`` view and ``detect_kind`` read this; ``select`` uses the batch.
|
|
211
|
+
_KIND_SLOTS: dict[str, tuple[str, ...]] = {
|
|
212
|
+
ModelKind.AGENT: ("agent", "text"),
|
|
213
|
+
ModelKind.AVLM: ("audio", "image", "text"),
|
|
214
|
+
ModelKind.ALM: ("audio", "text"),
|
|
215
|
+
ModelKind.VLM: ("image", "text"),
|
|
216
|
+
ModelKind.LLM: ("text",),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _compose(slots: "Iterable[str]") -> "list[str]":
|
|
221
|
+
"""Concatenate the slot lists in ranking order, first occurrence wins.
|
|
222
|
+
|
|
223
|
+
``dict.fromkeys`` rather than a set: an analyzer listed under two slots (a
|
|
224
|
+
VLM run also ranks ``mm_shap`` under image) must keep the position its
|
|
225
|
+
highest-priority slot gave it, and a set would lose the order entirely.
|
|
226
|
+
"""
|
|
227
|
+
ordered: list[str] = []
|
|
228
|
+
for slot in slots:
|
|
229
|
+
ordered.extend(_SLOT_PRIORITY.get(slot, ()))
|
|
230
|
+
return list(dict.fromkeys(ordered))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ALM / AVLM complete the back-compat view; they never had an entry because the
|
|
234
|
+
# enum had no member for them.
|
|
235
|
+
for _kind, _slots in _KIND_SLOTS.items():
|
|
236
|
+
_PRIORITY.setdefault(_kind, _compose(_slots))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_analyzer_catalog(model: "Model") -> dict[str, str]:
|
|
240
|
+
"""Return ``{name: description}`` for all analyzers compatible with *model*.
|
|
241
|
+
|
|
242
|
+
Descriptions come from each analyzer class's ``description`` attribute or,
|
|
243
|
+
if absent, the first non-empty line of its docstring. Used by
|
|
244
|
+
:class:`~evalrx.eval_agent.probe_agent.ProbeAgent` to build the LLM
|
|
245
|
+
selection prompt so the judge understands what each analyzer measures.
|
|
246
|
+
"""
|
|
247
|
+
compatible = set(registry.analyzers.names_compatible_with(model))
|
|
248
|
+
catalog: dict[str, str] = {}
|
|
249
|
+
for name in sorted(compatible):
|
|
250
|
+
cls = registry.analyzers.get(name)
|
|
251
|
+
if cls is None:
|
|
252
|
+
continue
|
|
253
|
+
desc: str | None = getattr(cls, "description", None)
|
|
254
|
+
if not desc and cls.__doc__:
|
|
255
|
+
for line in cls.__doc__.strip().splitlines():
|
|
256
|
+
line = line.strip()
|
|
257
|
+
if line:
|
|
258
|
+
desc = line
|
|
259
|
+
break
|
|
260
|
+
catalog[name] = desc or name
|
|
261
|
+
return catalog
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class StrategyProbe:
|
|
265
|
+
"""Selects analyzers appropriate for a given model.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
priority_override: Replaces the built-in per-kind priority tables
|
|
269
|
+
(keyed by ``ModelKind``). Useful for domain-specific orderings.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(self, priority_override: dict[str, list[str]] | None = None) -> None:
|
|
273
|
+
#: ``None`` selects the composed-per-slot path. A caller-supplied table
|
|
274
|
+
#: is keyed by ModelKind and is looked up, not composed — an override is
|
|
275
|
+
#: an explicit statement about ordering and must not be reordered by the
|
|
276
|
+
#: batch's modality slots.
|
|
277
|
+
self._priority = priority_override
|
|
278
|
+
|
|
279
|
+
@staticmethod
|
|
280
|
+
def slot_starved(names: "set[str]", data: Any = None, model: "Model | None" = None) -> "set[str]":
|
|
281
|
+
"""Analyzers whose required modality slot the batch never fills.
|
|
282
|
+
|
|
283
|
+
The registry match is an intersection against the MODEL's declaration,
|
|
284
|
+
so one shared member (``"text"``, which every model has) is enough to
|
|
285
|
+
make an image analyzer "compatible" with an audio run. It is then
|
|
286
|
+
selected, runs, reads an empty slot and reports a number computed over
|
|
287
|
+
nothing — the same silent-emptiness failure ``requires_trajectories``
|
|
288
|
+
was added to close.
|
|
289
|
+
|
|
290
|
+
A batch that fills NO media slot is ambiguous — media that was never
|
|
291
|
+
persisted looks identical to media that was never there — so nothing is
|
|
292
|
+
dropped, unless *model* is given and declares no media modality either.
|
|
293
|
+
Then there is no ambiguity left to protect: a text-only model cannot have
|
|
294
|
+
had an image, and keeping the gate off would offer a text run every
|
|
295
|
+
media analyzer in the registry.
|
|
296
|
+
|
|
297
|
+
``data=None`` is no evidence at all; nothing is dropped.
|
|
298
|
+
"""
|
|
299
|
+
if data is None:
|
|
300
|
+
return set()
|
|
301
|
+
filled = probed_modalities(data)
|
|
302
|
+
if not (filled & set(MEDIA_SLOTS)):
|
|
303
|
+
declared = set(getattr(model, "modalities", None) or ()) if model is not None else None
|
|
304
|
+
if declared is None or (declared & set(MEDIA_SLOTS)):
|
|
305
|
+
return set()
|
|
306
|
+
starved: set[str] = set()
|
|
307
|
+
for name in names:
|
|
308
|
+
cls = registry.analyzers.get(name)
|
|
309
|
+
required = set(getattr(cls, "requires_modalities", frozenset()) or ())
|
|
310
|
+
if required and not (required & filled):
|
|
311
|
+
starved.add(name)
|
|
312
|
+
return starved
|
|
313
|
+
|
|
314
|
+
# -- what a run is about -------------------------------------------
|
|
315
|
+
@staticmethod
|
|
316
|
+
def is_agent_run(model: "Model", data: Any = None) -> bool:
|
|
317
|
+
"""Whether this run should be ranked as an agent run.
|
|
318
|
+
|
|
319
|
+
Orthogonal to modality: a VLM driving a tool loop is both. Trajectories
|
|
320
|
+
in the batch are definitive; without them, declared TOOL_CALLS is the
|
|
321
|
+
weaker fallback.
|
|
322
|
+
"""
|
|
323
|
+
if data is not None and _carries_trajectories(data):
|
|
324
|
+
return True
|
|
325
|
+
return Capability.TOOL_CALLS in getattr(model, "capabilities", frozenset())
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def routed_slots(model: "Model", data: Any = None) -> set[str]:
|
|
329
|
+
"""The modality slots ranking should compose over.
|
|
330
|
+
|
|
331
|
+
Normally the model's declared modalities intersected with the slots the
|
|
332
|
+
batch actually fills, because both have to hold: an analyzer needs the
|
|
333
|
+
model to accept the modality AND the data to contain it.
|
|
334
|
+
|
|
335
|
+
Falls back to the model's declaration when the batch fills no media slot
|
|
336
|
+
at all. A batch with no images is not evidence that images are not under
|
|
337
|
+
test — it may be a text-only sample of a larger benchmark, or a batch
|
|
338
|
+
whose media never got persisted — and treating "nothing observed" as
|
|
339
|
+
"modality absent" is the same collapse the contract's rule 4 forbids.
|
|
340
|
+
The two are recorded separately in ``AnalyzerSelection`` so a reader can
|
|
341
|
+
see which path was taken.
|
|
342
|
+
"""
|
|
343
|
+
declared = set(getattr(model, "modalities", frozenset({"text"})) or {"text"})
|
|
344
|
+
probed = probed_modalities(data) if data is not None else {"text"}
|
|
345
|
+
if not (probed & set(MEDIA_SLOTS)):
|
|
346
|
+
return declared | {"text"}
|
|
347
|
+
return (declared & probed) | {"text"}
|
|
348
|
+
|
|
349
|
+
def detect_kind(self, model: "Model", data: Any = None) -> ModelKind:
|
|
350
|
+
"""Coarse label for the run — display and ``priority_override`` only.
|
|
351
|
+
|
|
352
|
+
Trajectory-carrying *data* is the definitive agent signal and wins
|
|
353
|
+
outright: a VLM that drove a tool loop should get the AGENT analyzer
|
|
354
|
+
priority (loop detection, first-error attribution), not the VLM one.
|
|
355
|
+
Without trajectories, media modality takes priority over TOOL_CALLS so
|
|
356
|
+
that VLMs that merely *support* tool use (e.g. Qwen3-VL) are treated
|
|
357
|
+
as VLMs, not agents.
|
|
358
|
+
|
|
359
|
+
Reads the MODEL's declaration, not the batch — the label answers "what
|
|
360
|
+
is this model", and narrowing it by the data would make an omni model
|
|
361
|
+
report a different identity per benchmark. :meth:`routed_slots` is the
|
|
362
|
+
one that narrows, and it is what ranking actually uses.
|
|
363
|
+
"""
|
|
364
|
+
if data is not None and _carries_trajectories(data):
|
|
365
|
+
return ModelKind.AGENT
|
|
366
|
+
modalities = set(getattr(model, "modalities", frozenset({"text"})) or {"text"})
|
|
367
|
+
has_image = bool(modalities & {"image", "video"})
|
|
368
|
+
has_audio = "audio" in modalities
|
|
369
|
+
if has_image and has_audio:
|
|
370
|
+
return ModelKind.AVLM
|
|
371
|
+
if has_image:
|
|
372
|
+
return ModelKind.VLM
|
|
373
|
+
if has_audio:
|
|
374
|
+
return ModelKind.ALM
|
|
375
|
+
if Capability.TOOL_CALLS in getattr(model, "capabilities", frozenset()):
|
|
376
|
+
return ModelKind.AGENT
|
|
377
|
+
return ModelKind.LLM
|
|
378
|
+
|
|
379
|
+
def select(
|
|
380
|
+
self,
|
|
381
|
+
model: "Model",
|
|
382
|
+
max_analyzers: int | None = None,
|
|
383
|
+
hint_failure_modes: list[str] | None = None,
|
|
384
|
+
data: Any = None,
|
|
385
|
+
) -> list[str]:
|
|
386
|
+
"""Return compatible analyzer names ranked by diagnostic priority.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
model: The model to analyse.
|
|
390
|
+
max_analyzers: If given, cap the returned list at this length.
|
|
391
|
+
hint_failure_modes: Failure-mode tags from outstanding M3 hypotheses.
|
|
392
|
+
Analyzers that match a hint are promoted to the
|
|
393
|
+
front of the ranked list for focused follow-up.
|
|
394
|
+
data: The case batch about to be probed, when available.
|
|
395
|
+
Trajectory-carrying cases flip the ranking to the
|
|
396
|
+
AGENT priority list (see :meth:`detect_kind`).
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Ordered list of registered analyzer names. Hint-matched items
|
|
400
|
+
come first, then the standard priority-list items, then remaining
|
|
401
|
+
compatible analyzers sorted alphabetically.
|
|
402
|
+
"""
|
|
403
|
+
compatible = set(registry.analyzers.names_compatible_with(model))
|
|
404
|
+
compatible -= self.slot_starved(compatible, data, model)
|
|
405
|
+
|
|
406
|
+
if self._priority is not None:
|
|
407
|
+
# A caller-supplied table is an allowlist as well as an ordering.
|
|
408
|
+
# In particular, benchmark ``pinned`` mode must not silently fill
|
|
409
|
+
# an unsupported pinned slot with an unrelated alphabetical probe.
|
|
410
|
+
priority = self._priority.get(self.detect_kind(model, data), [])
|
|
411
|
+
ranked = [name for name in priority if name in compatible]
|
|
412
|
+
else:
|
|
413
|
+
slots = self.routed_slots(model, data)
|
|
414
|
+
media = [s for s in ("video", "audio", "image") if s in slots]
|
|
415
|
+
# Trajectories in the batch are definitive and lead outright. Merely
|
|
416
|
+
# DECLARING tool support does not: a VLM that supports tools (Qwen3-VL)
|
|
417
|
+
# is still being asked a vision question, so media outranks the
|
|
418
|
+
# capability — the capability only leads when no media slot is in play.
|
|
419
|
+
agent_leads = (data is not None and _carries_trajectories(data)) or (
|
|
420
|
+
not media and self.is_agent_run(model, data)
|
|
421
|
+
)
|
|
422
|
+
priority = _compose((["agent"] if agent_leads else []) + media + ["text"])
|
|
423
|
+
ranked = [name for name in priority if name in compatible]
|
|
424
|
+
ranked += sorted(compatible - set(ranked))
|
|
425
|
+
|
|
426
|
+
if hint_failure_modes:
|
|
427
|
+
# Promote analyzers that map to outstanding failure modes, preserving
|
|
428
|
+
# their relative order and avoiding duplicates.
|
|
429
|
+
boosted = dict.fromkeys(
|
|
430
|
+
a
|
|
431
|
+
for mode in hint_failure_modes
|
|
432
|
+
for a in _FAILURE_MODE_TO_ANALYZERS.get(mode.lower().replace(" ", "_"), [])
|
|
433
|
+
if a in compatible and (self._priority is None or a in ranked)
|
|
434
|
+
)
|
|
435
|
+
ranked = list(boosted) + [a for a in ranked if a not in boosted]
|
|
436
|
+
|
|
437
|
+
if max_analyzers is not None:
|
|
438
|
+
ranked = ranked[:max_analyzers]
|
|
439
|
+
return ranked
|