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,333 @@
|
|
|
1
|
+
"""Single-shot exploratory analysis run (the ``evalrx explore`` entry).
|
|
2
|
+
|
|
3
|
+
One call = one pipeline = one artifact directory. This is the non-interactive
|
|
4
|
+
replacement for the retired chat REPL: the explorer writes EDA code, the host
|
|
5
|
+
adjudicates any host-checkable candidate statistics, charts are rendered
|
|
6
|
+
deterministically, and everything is persisted (optionally a dashboard opens).
|
|
7
|
+
|
|
8
|
+
Flow::
|
|
9
|
+
|
|
10
|
+
ExploratoryAnalysisAgent.explore_path # M2: free-form EDA (a CLI coding agent)
|
|
11
|
+
-> adjudicate_report # host recomputes verdicts (in-sample)
|
|
12
|
+
-> HypothesisAgent.propose # M3: falsifiable hypotheses from M2's takeaways
|
|
13
|
+
-> render_chart_specs # spec + CSV -> PNG, host-side
|
|
14
|
+
-> write_report_artifacts # report.json + figures/ + tables/ + code
|
|
15
|
+
-> launch_dashboard (optional)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import shutil
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from evalrx.analysis.api import explore
|
|
26
|
+
from evalrx.analysis.explorer import RECORDS_FILENAME, load_records_from_path
|
|
27
|
+
from evalrx.analysis.holdout import holdout_confirm as run_holdout_confirm
|
|
28
|
+
from evalrx.analysis.holdout import split_records
|
|
29
|
+
from evalrx.viz.renderer import render_chart_specs
|
|
30
|
+
|
|
31
|
+
# Appended to the analysis question when a held-out confirm phase will follow:
|
|
32
|
+
# recipes must be frozen and threshold-explicit or there is nothing to re-test.
|
|
33
|
+
HOLDOUT_QUESTION_SUFFIX = (
|
|
34
|
+
" This is the EXPLORATION HALF of a held-out design: a disjoint validate "
|
|
35
|
+
"split exists and will re-test whatever you propose. Make every candidate "
|
|
36
|
+
"signal a FROZEN, threshold-explicit recipe (explicit numeric thresholds, "
|
|
37
|
+
"no re-fitting) so it can be re-evaluated verbatim on the held-out half, "
|
|
38
|
+
"and propose hypotheses precise enough to be graded against held-out "
|
|
39
|
+
"statistics."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _build_judge(model: str):
|
|
44
|
+
"""Judge for the held-out confirm phase (separate so tests can stub it)."""
|
|
45
|
+
from evalrx.agent_runtime.judges import ClaudeModel
|
|
46
|
+
|
|
47
|
+
return ClaudeModel(model=model, effort="low")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_explore(
|
|
51
|
+
path: str | Path,
|
|
52
|
+
*,
|
|
53
|
+
question: str = "Explore this dataset and surface the patterns that matter.",
|
|
54
|
+
out: str | Path = "evalrx_explore_output",
|
|
55
|
+
coder_provider: str = "antigravity",
|
|
56
|
+
coder_model: str = "",
|
|
57
|
+
coder_binary: str = "",
|
|
58
|
+
max_rows: int = 2000,
|
|
59
|
+
max_files: int = 200,
|
|
60
|
+
include_tool_calls: bool = False,
|
|
61
|
+
timeout_sec: int = 120,
|
|
62
|
+
max_attempts: int = 2,
|
|
63
|
+
dashboard: bool = False,
|
|
64
|
+
dashboard_port: int | None = None,
|
|
65
|
+
skills: "list[str] | tuple[str, ...]" = (),
|
|
66
|
+
allow_skills: bool = False,
|
|
67
|
+
use_bundled_skills: bool = True,
|
|
68
|
+
outcome_col: str | None = None,
|
|
69
|
+
propose_hypotheses: bool = True,
|
|
70
|
+
holdout_frac: float = 0.0,
|
|
71
|
+
holdout_seed: int = 0,
|
|
72
|
+
holdout_confirm: bool = False,
|
|
73
|
+
judge_model: str = "claude-opus-4-8",
|
|
74
|
+
progress_sink: Any = None,
|
|
75
|
+
) -> int:
|
|
76
|
+
"""Run one exploratory analysis and persist its artifacts.
|
|
77
|
+
|
|
78
|
+
Returns a process exit code: ``0`` if the exploration succeeded, else ``1``.
|
|
79
|
+
When *dashboard* is set, the return value is the dashboard process's code.
|
|
80
|
+
|
|
81
|
+
Skills style the agent-authored figures (e.g. the bundled eval-chart-style
|
|
82
|
+
and nature-figure skills). By default (*use_bundled_skills*) the package's
|
|
83
|
+
bundled skills are applied on the claude/agy/codex backends; *skills* adds
|
|
84
|
+
more dirs and *allow_skills* also enables globally-installed
|
|
85
|
+
(`~/.claude/skills`) skills.
|
|
86
|
+
|
|
87
|
+
*outcome_col* optionally names the target/label column explicitly (M1
|
|
88
|
+
passes ``"label"``); omit it to let the agent auto-detect an outcome by
|
|
89
|
+
name heuristics, or fall back to unsupervised EDA when there is none.
|
|
90
|
+
|
|
91
|
+
*propose_hypotheses* runs M3 (``HypothesisAgent``) on M2's takeaways after
|
|
92
|
+
a successful explore, using the same coding-agent backend; set False to
|
|
93
|
+
skip it (e.g. to save the extra LLM call).
|
|
94
|
+
|
|
95
|
+
*holdout_frac* > 0 splits the loaded records BEFORE exploration
|
|
96
|
+
(deterministic, outcome-stratified; the explorer only sees the explore
|
|
97
|
+
share). With *holdout_confirm* the held-out rows then re-test the frozen
|
|
98
|
+
recipes + hypotheses (``analysis.holdout``) and ``confirm_report.json``
|
|
99
|
+
lands next to the exploratory report — the dashboard's Held-out Verdicts
|
|
100
|
+
tab fills in. Without it the rows are simply reserved on disk.
|
|
101
|
+
"""
|
|
102
|
+
out_dir = Path(out).resolve()
|
|
103
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
source: Any = path
|
|
106
|
+
holdout_rows: "list[dict[str, Any]] | None" = None
|
|
107
|
+
if holdout_frac and holdout_frac > 0:
|
|
108
|
+
records = load_records_from_path(
|
|
109
|
+
path, max_rows=max_rows, max_files=max_files,
|
|
110
|
+
include_tool_calls=include_tool_calls,
|
|
111
|
+
)
|
|
112
|
+
explore_share, holdout_rows = split_records(
|
|
113
|
+
records, holdout_frac, seed=holdout_seed, outcome_col=outcome_col
|
|
114
|
+
)
|
|
115
|
+
if holdout_rows is None:
|
|
116
|
+
print(f"holdout: split impossible (n={len(records)}, frac={holdout_frac}) "
|
|
117
|
+
"— falling back to a plain in-sample run")
|
|
118
|
+
else:
|
|
119
|
+
source = explore_share
|
|
120
|
+
print(f"holdout: exploring {len(explore_share)} rows, "
|
|
121
|
+
f"{len(holdout_rows)} held out "
|
|
122
|
+
+ ("(verification follows)" if holdout_confirm
|
|
123
|
+
else "(reserved, not verified in this mode)"))
|
|
124
|
+
if holdout_confirm:
|
|
125
|
+
question = question.rstrip() + HOLDOUT_QUESTION_SUFFIX
|
|
126
|
+
|
|
127
|
+
result = explore(
|
|
128
|
+
source,
|
|
129
|
+
question=question,
|
|
130
|
+
out=out_dir,
|
|
131
|
+
provider=coder_provider,
|
|
132
|
+
model=coder_model,
|
|
133
|
+
binary=coder_binary,
|
|
134
|
+
outcome_col=outcome_col,
|
|
135
|
+
propose_hypotheses=propose_hypotheses,
|
|
136
|
+
skills=skills,
|
|
137
|
+
allow_skills=allow_skills,
|
|
138
|
+
use_bundled_skills=use_bundled_skills,
|
|
139
|
+
max_rows=max_rows,
|
|
140
|
+
max_files=max_files,
|
|
141
|
+
include_tool_calls=include_tool_calls,
|
|
142
|
+
timeout_sec=timeout_sec,
|
|
143
|
+
max_attempts=max_attempts,
|
|
144
|
+
progress_sink=progress_sink,
|
|
145
|
+
)
|
|
146
|
+
report = result.report
|
|
147
|
+
|
|
148
|
+
print(f"ok: {report.ok}")
|
|
149
|
+
print(f"attempts: {report.attempts}")
|
|
150
|
+
print(f"rows loaded: {report.data_profile.get('loaded_rows', report.data_profile.get('n_rows'))}")
|
|
151
|
+
print(f"output: {out_dir}")
|
|
152
|
+
if report.error:
|
|
153
|
+
print(f"error: {report.error}")
|
|
154
|
+
if report.hypotheses:
|
|
155
|
+
print("\nhypotheses (M3, proposed only — not validated):")
|
|
156
|
+
for h in report.hypotheses[:8]:
|
|
157
|
+
print(f"- {h.get('statement')}")
|
|
158
|
+
if report.observations:
|
|
159
|
+
print("\nobservations:")
|
|
160
|
+
for obs in report.observations[:8]:
|
|
161
|
+
print(f"- {obs}")
|
|
162
|
+
if report.candidate_signals:
|
|
163
|
+
print("\ncandidate signals:")
|
|
164
|
+
for signal in report.candidate_signals[:8]:
|
|
165
|
+
print(f"- {signal.name}: {signal.rationale}{_verdict_suffix(signal)}")
|
|
166
|
+
if report.adjudication:
|
|
167
|
+
adj = report.adjudication
|
|
168
|
+
print(
|
|
169
|
+
f"\nhost adjudication ({adj.get('split')}): "
|
|
170
|
+
f"{adj.get('n_rejected', 0)}/{adj.get('n_host_adjudicated', 0)} reject "
|
|
171
|
+
f"(e-BH family n={adj.get('n_in_family', 0)}, alpha={adj.get('alpha')})"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if holdout_rows is not None:
|
|
175
|
+
# Audit trail either way: the exact rows the explorer never saw.
|
|
176
|
+
(out_dir / "holdout_records.json").write_text(
|
|
177
|
+
json.dumps(holdout_rows, default=str), encoding="utf-8"
|
|
178
|
+
)
|
|
179
|
+
split_meta = {
|
|
180
|
+
"n_explore": len(source) if isinstance(source, list) else None,
|
|
181
|
+
"n_holdout": len(holdout_rows),
|
|
182
|
+
"holdout_frac": holdout_frac,
|
|
183
|
+
"seed": holdout_seed,
|
|
184
|
+
"stratified_by": outcome_col or "label",
|
|
185
|
+
}
|
|
186
|
+
if holdout_confirm:
|
|
187
|
+
judge = None
|
|
188
|
+
judge_meta = None
|
|
189
|
+
try:
|
|
190
|
+
judge = _build_judge(judge_model)
|
|
191
|
+
judge_meta = {"model": judge_model, "effort": "low"}
|
|
192
|
+
except Exception as exc: # noqa: BLE001 — verdicts degrade to not_judged
|
|
193
|
+
print(f"judge unavailable ({exc}) — hypotheses will be not_judged")
|
|
194
|
+
if progress_sink is not None:
|
|
195
|
+
progress_sink.emit("m4", "started", "Verifying hypotheses on the held-out split")
|
|
196
|
+
confirm = run_holdout_confirm(
|
|
197
|
+
report.to_dict(), holdout_rows,
|
|
198
|
+
outcome_col=outcome_col or "label",
|
|
199
|
+
judge=judge, judge_meta=judge_meta,
|
|
200
|
+
)
|
|
201
|
+
confirm["split_meta"] = split_meta
|
|
202
|
+
(out_dir / "confirm_report.json").write_text(
|
|
203
|
+
json.dumps(confirm, indent=1, default=str), encoding="utf-8"
|
|
204
|
+
)
|
|
205
|
+
cadj = confirm.get("adjudication") or {}
|
|
206
|
+
if progress_sink is not None:
|
|
207
|
+
progress_sink.emit(
|
|
208
|
+
"m4", "completed", "Held-out verification complete",
|
|
209
|
+
metrics={
|
|
210
|
+
"n_rows": confirm.get("n_validate_rows"),
|
|
211
|
+
"n_rejected": cadj.get("n_rejected", 0),
|
|
212
|
+
"n_adjudicated": cadj.get("n_host_adjudicated", 0),
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
print(
|
|
216
|
+
f"\nheld-out adjudication: {cadj.get('n_rejected', 0)}/"
|
|
217
|
+
f"{cadj.get('n_host_adjudicated', 0)} reject "
|
|
218
|
+
f"(n={confirm.get('n_validate_rows')} rows, "
|
|
219
|
+
f"{confirm.get('n_validate_fail')} failures)"
|
|
220
|
+
)
|
|
221
|
+
for v in confirm.get("hypothesis_verdicts", []):
|
|
222
|
+
print(f" * [{v.get('verdict')}] {str(v.get('statement', ''))[:90]}")
|
|
223
|
+
else:
|
|
224
|
+
(out_dir / "holdout_split.json").write_text(
|
|
225
|
+
json.dumps(split_meta, indent=1), encoding="utf-8"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# The HTML report is the canonical visualization artifact. Generate it
|
|
229
|
+
# even when the caller does not immediately open a browser so every
|
|
230
|
+
# completed exploratory run is portable and reviewable.
|
|
231
|
+
from evalrx.reporting.html_report import build_html_report
|
|
232
|
+
|
|
233
|
+
build_html_report(out_dir, out_path=out_dir / "report.html")
|
|
234
|
+
|
|
235
|
+
if dashboard:
|
|
236
|
+
from evalrx.analysis.dashboard import launch_dashboard
|
|
237
|
+
|
|
238
|
+
return launch_dashboard(out_dir, port=dashboard_port)
|
|
239
|
+
return 0 if report.ok else 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _verdict_suffix(signal: Any) -> str:
|
|
243
|
+
"""One-line host verdict tag for a candidate signal, or '' if not adjudicated."""
|
|
244
|
+
if not getattr(signal, "host_adjudicated", False):
|
|
245
|
+
return " [descriptive]" if getattr(signal, "sufficient", None) else ""
|
|
246
|
+
verdict = "REJECT H0" if signal.reject else "inconclusive"
|
|
247
|
+
parts = [verdict]
|
|
248
|
+
if signal.e_value is not None:
|
|
249
|
+
# In the e-BH family (has an e-value); reject already reflects survival.
|
|
250
|
+
parts.append(f"e={signal.e_value:.2f}")
|
|
251
|
+
parts.append("e-BH family")
|
|
252
|
+
elif signal.ci is not None:
|
|
253
|
+
parts.append(f"CI={signal.ci[0]:+.3f}..{signal.ci[1]:+.3f} (not FDR-corrected)")
|
|
254
|
+
return " [host: " + ", ".join(parts) + "]"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def write_report_artifacts(
|
|
258
|
+
report: Any, out_dir: Path, *, report_filename: str = "exploratory_report.json"
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Persist one explore run's artifacts: figures/, tables/, rendered charts,
|
|
261
|
+
the report JSON, and the generated code/stdout/stderr.
|
|
262
|
+
|
|
263
|
+
Charts are rendered here (host-side, from spec + CSV) so the persisted
|
|
264
|
+
``exploratory_report.json`` carries each chart's ``figure_path``.
|
|
265
|
+
"""
|
|
266
|
+
out_dir = Path(out_dir)
|
|
267
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
_copy_artifact_dirs(report, out_dir)
|
|
269
|
+
|
|
270
|
+
# Render the explorer's chart specs to PNG from the copied tables/ CSVs.
|
|
271
|
+
try:
|
|
272
|
+
report.charts = render_chart_specs(
|
|
273
|
+
getattr(report, "charts", None), out_dir / "tables", out_dir
|
|
274
|
+
)
|
|
275
|
+
except Exception: # rendering is best-effort, never blocks persistence
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
(out_dir / report_filename).write_text(
|
|
279
|
+
json.dumps(report.to_dict(), indent=2, default=str),
|
|
280
|
+
encoding="utf-8",
|
|
281
|
+
)
|
|
282
|
+
if report.code:
|
|
283
|
+
(out_dir / "analysis.py").write_text(report.code, encoding="utf-8")
|
|
284
|
+
if report.stdout:
|
|
285
|
+
(out_dir / "stdout.txt").write_text(report.stdout, encoding="utf-8")
|
|
286
|
+
if report.stderr:
|
|
287
|
+
(out_dir / "stderr.txt").write_text(report.stderr, encoding="utf-8")
|
|
288
|
+
if report.raw_outputs:
|
|
289
|
+
(out_dir / "agent_raw_output.txt").write_text(
|
|
290
|
+
"\n\n--- attempt ---\n\n".join(report.raw_outputs),
|
|
291
|
+
encoding="utf-8",
|
|
292
|
+
)
|
|
293
|
+
# The UNTRUNCATED raw CLI streams (one per attempt) — the truncated
|
|
294
|
+
# rendering above is for compact UIs; this is the full audit trail.
|
|
295
|
+
raw_streams = getattr(report, "raw_streams", None) or []
|
|
296
|
+
if any(s.strip() for s in raw_streams):
|
|
297
|
+
(out_dir / "agent_raw_streams.txt").write_text(
|
|
298
|
+
"\n\n--- attempt (full stream) ---\n\n".join(raw_streams),
|
|
299
|
+
encoding="utf-8",
|
|
300
|
+
)
|
|
301
|
+
if report.agent_audits:
|
|
302
|
+
(out_dir / "agent_audit.json").write_text(
|
|
303
|
+
json.dumps({"schema_version": 1, "attempts": report.agent_audits}, indent=2),
|
|
304
|
+
encoding="utf-8",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Clean up redundant nested sandbox directory so out_dir is the single clean source of truth
|
|
308
|
+
workdir = Path(getattr(report, "workdir", "") or "")
|
|
309
|
+
try:
|
|
310
|
+
if workdir.exists() and workdir.resolve() != out_dir.resolve() and workdir.is_relative_to(out_dir):
|
|
311
|
+
shutil.rmtree(workdir)
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _copy_artifact_dirs(report: Any, out_dir: Path) -> None:
|
|
317
|
+
workdir = Path(getattr(report, "workdir", "") or "")
|
|
318
|
+
if not workdir.exists():
|
|
319
|
+
return
|
|
320
|
+
for name in ("figures", "tables"):
|
|
321
|
+
src = workdir / name
|
|
322
|
+
dest = out_dir / name
|
|
323
|
+
if src.exists() and src.is_dir():
|
|
324
|
+
if dest.exists():
|
|
325
|
+
shutil.rmtree(dest)
|
|
326
|
+
shutil.copytree(src, dest)
|
|
327
|
+
# Persist the raw loaded records alongside the report so the dashboard can
|
|
328
|
+
# offer a "browse raw data" view — otherwise they only live in the sandbox
|
|
329
|
+
# workdir, which isn't guaranteed to still exist by the time someone opens
|
|
330
|
+
# the dashboard.
|
|
331
|
+
records_src = workdir / RECORDS_FILENAME
|
|
332
|
+
if records_src.exists():
|
|
333
|
+
shutil.copy2(records_src, out_dir / RECORDS_FILENAME)
|