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,460 @@
|
|
|
1
|
+
"""Natural-language scaffold for the EvalRX diagnosis pipeline.
|
|
2
|
+
|
|
3
|
+
Generates a self-contained Docker experiment from a plain-text description
|
|
4
|
+
of a model failure. The resulting directory mirrors the ``examples/`` layout
|
|
5
|
+
and can be launched immediately::
|
|
6
|
+
|
|
7
|
+
python -m evalrx.eval_agent.nl_runner \\
|
|
8
|
+
--description "My VLM confuses left and right in spatial questions" \\
|
|
9
|
+
--model qwen2.5-vl-7b-instruct \\
|
|
10
|
+
--out ./my_experiment
|
|
11
|
+
|
|
12
|
+
cd my_experiment && docker compose up
|
|
13
|
+
|
|
14
|
+
Two generation modes
|
|
15
|
+
--------------------
|
|
16
|
+
CLI agent (``--provider claude_code / gemini_cli / …``)
|
|
17
|
+
A coding agent writes a bespoke ``run.py`` tailored to the description.
|
|
18
|
+
Requires the corresponding binary and API key.
|
|
19
|
+
|
|
20
|
+
Template (default, no extra binary needed)
|
|
21
|
+
A parametrised template is filled with the description and model key.
|
|
22
|
+
The produced ``run.py`` is a complete, working script that users can
|
|
23
|
+
customise further.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from evalrx.eval_agent.prompts.nl_runner import _AGENT_PROMPT_TEMPLATE
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# File templates
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
_RUN_PY_TEMPLATE = '''\
|
|
38
|
+
"""EvalRX diagnosis experiment — generated from description.
|
|
39
|
+
|
|
40
|
+
Description
|
|
41
|
+
-----------
|
|
42
|
+
{description}
|
|
43
|
+
|
|
44
|
+
Model : {model_key}
|
|
45
|
+
|
|
46
|
+
Usage (via Docker — preferred)
|
|
47
|
+
docker compose up
|
|
48
|
+
|
|
49
|
+
Usage (direct)
|
|
50
|
+
python run.py --smoke-test # quick wiring check, no GPU/API key
|
|
51
|
+
python run.py # full run
|
|
52
|
+
|
|
53
|
+
Edit the CASES list below to add your own failure examples.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
from __future__ import annotations
|
|
57
|
+
|
|
58
|
+
import argparse
|
|
59
|
+
import sys
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
|
|
62
|
+
_OUTPUTS_DIR = Path(__file__).parent / "outputs"
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Failure cases — EDIT THESE to match your scenario
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Each FailureCase has:
|
|
68
|
+
# id : unique string key
|
|
69
|
+
# inputs : Inputs(prompt=..., image=...) — image is optional
|
|
70
|
+
# expected : what the correct answer looks like (string or rubric dict)
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _build_cases():
|
|
74
|
+
from evalrx.core.case import CaseBatch, FailureCase, Inputs
|
|
75
|
+
|
|
76
|
+
return CaseBatch([
|
|
77
|
+
# Replace these with real failure cases from your evaluation.
|
|
78
|
+
FailureCase(
|
|
79
|
+
id="example_0",
|
|
80
|
+
inputs=Inputs(prompt="[TODO: replace with a prompt that exposes the failure]"),
|
|
81
|
+
expected="[expected answer]",
|
|
82
|
+
),
|
|
83
|
+
FailureCase(
|
|
84
|
+
id="example_1",
|
|
85
|
+
inputs=Inputs(prompt="[TODO: replace with a passing control prompt]"),
|
|
86
|
+
expected="[expected answer]",
|
|
87
|
+
),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Smoke-test stand-in (no model / GPU required)
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
class _SmokeModel:
|
|
96
|
+
def __init__(self):
|
|
97
|
+
from evalrx.core.capability import Capability
|
|
98
|
+
self.capabilities = frozenset({{Capability.GENERATE}})
|
|
99
|
+
self.modalities = frozenset({{"text"}})
|
|
100
|
+
|
|
101
|
+
def generate(self, inputs, **kwargs) -> str:
|
|
102
|
+
return "smoke-test answer"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Main
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def main() -> None:
|
|
110
|
+
parser = argparse.ArgumentParser()
|
|
111
|
+
parser.add_argument("--model", default="{model_key}")
|
|
112
|
+
parser.add_argument("--device", default="cpu")
|
|
113
|
+
parser.add_argument("--dtype", default="float32")
|
|
114
|
+
parser.add_argument("--max-cycles", type=int, default=2)
|
|
115
|
+
parser.add_argument("--max-analyzers", type=int, default=2)
|
|
116
|
+
parser.add_argument("--smoke-test", action="store_true",
|
|
117
|
+
help="Run with a tiny stand-in model (no GPU/API key needed)")
|
|
118
|
+
parser.add_argument("--run-dir", default=str(_OUTPUTS_DIR))
|
|
119
|
+
args = parser.parse_args()
|
|
120
|
+
|
|
121
|
+
import evalrx # noqa: F401 — registers all analyzers
|
|
122
|
+
|
|
123
|
+
from evalrx.eval_agent import AgyModel, RunContext, VLDiagnoseLoop
|
|
124
|
+
from evalrx.eval_agent.stages.diagnosis import DiagnosisAgent
|
|
125
|
+
from evalrx.eval_agent.stages.probe_agent import ProbeAgent
|
|
126
|
+
from evalrx.eval_agent.stages.protocol import ExperimentProtocol
|
|
127
|
+
from evalrx.analysis.stats_agent import StatsAnalysisAgent
|
|
128
|
+
|
|
129
|
+
protocol = ExperimentProtocol(
|
|
130
|
+
description=(
|
|
131
|
+
"{description}"
|
|
132
|
+
),
|
|
133
|
+
task_domain="{task_domain}",
|
|
134
|
+
success_criteria="The model answer matches the expected output.",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if args.smoke_test:
|
|
138
|
+
model = _SmokeModel()
|
|
139
|
+
print("[smoke-test] using stand-in model — skipping real inference")
|
|
140
|
+
else:
|
|
141
|
+
from evalrx import compose
|
|
142
|
+
from evalrx.core.capability import Capability
|
|
143
|
+
model = compose(args.model, "hf_local",
|
|
144
|
+
want={{Capability.GENERATE, Capability.ATTENTION}},
|
|
145
|
+
device=args.device, dtype=args.dtype)
|
|
146
|
+
|
|
147
|
+
cases = _build_cases()
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
judge = AgyModel()
|
|
151
|
+
except RuntimeError:
|
|
152
|
+
judge = None
|
|
153
|
+
print("[warn] agy binary not found — M3/M4 will be skipped")
|
|
154
|
+
|
|
155
|
+
ctx = RunContext(args.run_dir, verbose=True)
|
|
156
|
+
print(f"Logging to: {{ctx.root}}")
|
|
157
|
+
|
|
158
|
+
loop = VLDiagnoseLoop(
|
|
159
|
+
model=model,
|
|
160
|
+
probe_agent=ProbeAgent(max_analyzers=args.max_analyzers),
|
|
161
|
+
stats_agent=StatsAnalysisAgent(judge=judge, figure_dir=str(ctx.figures_dir)),
|
|
162
|
+
diagnosis_agent=DiagnosisAgent(judge=judge),
|
|
163
|
+
max_cycles=args.max_cycles,
|
|
164
|
+
protocol=protocol,
|
|
165
|
+
run_logger=ctx.logger,
|
|
166
|
+
)
|
|
167
|
+
report = loop.run(cases)
|
|
168
|
+
ctx.write_diagnose_report(report, cases)
|
|
169
|
+
ctx.finalize()
|
|
170
|
+
|
|
171
|
+
print(f"\\n[VLDiagnoseLoop] cycles={{report.cycles}} resolved={{report.resolved}}")
|
|
172
|
+
for h in report.final_hypotheses:
|
|
173
|
+
print(f" hypothesis : {{h.statement}}")
|
|
174
|
+
print(f" status : {{h.status}}")
|
|
175
|
+
print(f"\\nRun record -> {{ctx.root / 'run.json'}}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
main()
|
|
180
|
+
'''
|
|
181
|
+
|
|
182
|
+
_DOCKERFILE_TEMPLATE = """\
|
|
183
|
+
FROM python:3.11-slim
|
|
184
|
+
|
|
185
|
+
WORKDIR /app
|
|
186
|
+
|
|
187
|
+
# Install torch with CUDA 12.4 wheels first so [local] won't reinstall it
|
|
188
|
+
RUN pip install --no-cache-dir \\
|
|
189
|
+
torch torchvision --index-url https://download.pytorch.org/whl/cu124
|
|
190
|
+
|
|
191
|
+
# Install evalrx from local source
|
|
192
|
+
COPY . /evalrx
|
|
193
|
+
RUN pip install --no-cache-dir "/evalrx[local,gemini,data,viz]"
|
|
194
|
+
|
|
195
|
+
WORKDIR /app/example
|
|
196
|
+
COPY {rel_dir}/ .
|
|
197
|
+
|
|
198
|
+
ENV PYTHONUNBUFFERED=1
|
|
199
|
+
|
|
200
|
+
CMD ["python", "run.py"]
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
_COMPOSE_TEMPLATE = """\
|
|
204
|
+
services:
|
|
205
|
+
evalrx_experiment:
|
|
206
|
+
build:
|
|
207
|
+
context: {build_context}
|
|
208
|
+
dockerfile: {rel_dir}/Dockerfile
|
|
209
|
+
image: evalrx-{slug}
|
|
210
|
+
environment:
|
|
211
|
+
- CUDA_DEVICE_ORDER=PCI_BUS_ID
|
|
212
|
+
- CUDA_VISIBLE_DEVICES=${{CUDA_VISIBLE_DEVICES:-0}}
|
|
213
|
+
- PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
|
214
|
+
- HF_HOME=/hf_cache
|
|
215
|
+
- HOME=/home/user
|
|
216
|
+
volumes:
|
|
217
|
+
- ${{HF_HOME:-~/.cache/huggingface}}:/hf_cache
|
|
218
|
+
- ./outputs:/app/example/outputs
|
|
219
|
+
- ${{AGY_PATH:-~/.local/bin/agy}}:/usr/local/bin/agy:ro
|
|
220
|
+
- ~/.gemini:/home/user/.gemini
|
|
221
|
+
- ~/.cache/antigravity:/home/user/.cache/antigravity
|
|
222
|
+
deploy:
|
|
223
|
+
resources:
|
|
224
|
+
reservations:
|
|
225
|
+
devices:
|
|
226
|
+
- driver: nvidia
|
|
227
|
+
count: all
|
|
228
|
+
capabilities: [gpu]
|
|
229
|
+
command: >
|
|
230
|
+
python run.py
|
|
231
|
+
--model {model_key}
|
|
232
|
+
--device cuda
|
|
233
|
+
--dtype bfloat16
|
|
234
|
+
--max-cycles 2
|
|
235
|
+
--max-analyzers 2
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
_GITIGNORE = "outputs/\n__pycache__/\n"
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
# Public API
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
def scaffold_from_description(
|
|
245
|
+
description: str,
|
|
246
|
+
model_key: str = "qwen2.5-vl-7b-instruct",
|
|
247
|
+
output_dir: str | Path = "./my_evalrx_experiment",
|
|
248
|
+
provider: str = "",
|
|
249
|
+
cli_binary: str = "",
|
|
250
|
+
cli_model: str = "",
|
|
251
|
+
cli_timeout: int = 180,
|
|
252
|
+
task_domain: str = "",
|
|
253
|
+
) -> Path:
|
|
254
|
+
"""Scaffold a Docker experiment from a natural-language failure description.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
description: Plain-text description of the failure to diagnose.
|
|
258
|
+
model_key: EvalRX model key (from ``evalrx.list_specs()``).
|
|
259
|
+
output_dir: Where to write the generated scaffold.
|
|
260
|
+
provider: CLI agent provider (``"claude_code"``, ``"gemini_cli"``,
|
|
261
|
+
``"codex"``, …). Leave empty to use the template path.
|
|
262
|
+
cli_binary: Explicit path to the CLI binary (auto-detected when empty).
|
|
263
|
+
cli_model: Model flag forwarded to the CLI binary (e.g. ``"sonnet"``).
|
|
264
|
+
cli_timeout: Seconds allowed for the CLI agent to write ``run.py``.
|
|
265
|
+
task_domain: Short label for the experiment (e.g. ``"spatial reasoning"``).
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
``Path`` to the generated scaffold directory.
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
RuntimeError: When ``provider`` is set but the binary is not found.
|
|
272
|
+
"""
|
|
273
|
+
out = Path(output_dir).expanduser().resolve()
|
|
274
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
|
|
276
|
+
run_py_path = out / "run.py"
|
|
277
|
+
|
|
278
|
+
if provider:
|
|
279
|
+
_write_run_py_via_agent(
|
|
280
|
+
run_py_path,
|
|
281
|
+
description=description,
|
|
282
|
+
model_key=model_key,
|
|
283
|
+
provider=provider,
|
|
284
|
+
binary=cli_binary,
|
|
285
|
+
model=cli_model,
|
|
286
|
+
timeout=cli_timeout,
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
_write_run_py_template(
|
|
290
|
+
run_py_path,
|
|
291
|
+
description=description,
|
|
292
|
+
model_key=model_key,
|
|
293
|
+
task_domain=task_domain or _infer_domain(description),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
_write_docker_files(out, model_key=model_key)
|
|
297
|
+
return out
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
# Internal helpers
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
def _infer_domain(description: str) -> str:
|
|
305
|
+
"""Return a short domain label heuristically extracted from the description."""
|
|
306
|
+
desc_lower = description.lower()
|
|
307
|
+
for keyword in ("spatial", "temporal", "count", "ocr", "grounding",
|
|
308
|
+
"hallucination", "reasoning", "chart", "navigation"):
|
|
309
|
+
if keyword in desc_lower:
|
|
310
|
+
return keyword + " understanding"
|
|
311
|
+
return "visual understanding"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _write_run_py_template(
|
|
315
|
+
path: Path,
|
|
316
|
+
description: str,
|
|
317
|
+
model_key: str,
|
|
318
|
+
task_domain: str,
|
|
319
|
+
) -> None:
|
|
320
|
+
escaped_desc = description.replace('"', '\\"')
|
|
321
|
+
content = _RUN_PY_TEMPLATE.format(
|
|
322
|
+
description=escaped_desc,
|
|
323
|
+
model_key=model_key,
|
|
324
|
+
task_domain=task_domain,
|
|
325
|
+
)
|
|
326
|
+
path.write_text(content, encoding="utf-8")
|
|
327
|
+
print(f"[nl_runner] wrote {path}")
|
|
328
|
+
print("[nl_runner] edit CASES in run.py to add your failure examples")
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _write_run_py_via_agent(
|
|
332
|
+
path: Path,
|
|
333
|
+
*,
|
|
334
|
+
description: str,
|
|
335
|
+
model_key: str,
|
|
336
|
+
provider: str,
|
|
337
|
+
binary: str,
|
|
338
|
+
model: str,
|
|
339
|
+
timeout: int,
|
|
340
|
+
) -> None:
|
|
341
|
+
from evalrx.agent_runtime.cli_types import CliAgentConfig
|
|
342
|
+
from evalrx.agent_runtime.codegen import CodegenRunner
|
|
343
|
+
|
|
344
|
+
prompt = _AGENT_PROMPT_TEMPLATE.format(
|
|
345
|
+
description=description,
|
|
346
|
+
model_key=model_key,
|
|
347
|
+
)
|
|
348
|
+
cfg = CliAgentConfig(
|
|
349
|
+
provider=provider,
|
|
350
|
+
binary_path=binary,
|
|
351
|
+
model=model,
|
|
352
|
+
timeout_sec=timeout,
|
|
353
|
+
)
|
|
354
|
+
result = CodegenRunner(cfg).write_code(
|
|
355
|
+
prompt,
|
|
356
|
+
workdir=path.parent,
|
|
357
|
+
preferred_filenames=("run.py",),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if not result.ok:
|
|
361
|
+
raise RuntimeError(
|
|
362
|
+
f"CLI agent ({provider}) failed to generate run.py: {result.error}"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
generated = result.code
|
|
366
|
+
if generated is None:
|
|
367
|
+
raise RuntimeError(
|
|
368
|
+
f"CLI agent ({provider}) produced no .py files in {path.parent}"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
path.write_text(generated, encoding="utf-8")
|
|
372
|
+
print(f"[nl_runner] {provider} wrote {path} ({len(result.files)} file(s))")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _write_docker_files(out: Path, *, model_key: str) -> None:
|
|
376
|
+
slug = model_key.replace(".", "-").replace("/", "-")
|
|
377
|
+
|
|
378
|
+
# Detect whether out is inside the repo (for a relative build context)
|
|
379
|
+
try:
|
|
380
|
+
rel = out.relative_to(Path.cwd())
|
|
381
|
+
rel_dir = str(rel).replace("\\", "/")
|
|
382
|
+
build_context = str(Path.cwd()).replace("\\", "/")
|
|
383
|
+
except ValueError:
|
|
384
|
+
rel_dir = out.name
|
|
385
|
+
build_context = str(out.parent).replace("\\", "/")
|
|
386
|
+
|
|
387
|
+
dockerfile = _DOCKERFILE_TEMPLATE.format(rel_dir=rel_dir)
|
|
388
|
+
compose = _COMPOSE_TEMPLATE.format(
|
|
389
|
+
build_context=build_context,
|
|
390
|
+
rel_dir=rel_dir,
|
|
391
|
+
slug=slug,
|
|
392
|
+
model_key=model_key,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
(out / "Dockerfile").write_text(dockerfile, encoding="utf-8")
|
|
396
|
+
(out / "docker-compose.yml").write_text(compose, encoding="utf-8")
|
|
397
|
+
(out / ".gitignore").write_text(_GITIGNORE, encoding="utf-8")
|
|
398
|
+
print(f"[nl_runner] scaffold ready in {out}")
|
|
399
|
+
print(f"[nl_runner] cd {out} && docker compose up")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# ---------------------------------------------------------------------------
|
|
403
|
+
# CLI entry point
|
|
404
|
+
# ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
def _parse_args() -> argparse.Namespace:
|
|
407
|
+
p = argparse.ArgumentParser(
|
|
408
|
+
description=(
|
|
409
|
+
"Scaffold an EvalRX Docker experiment from a natural-language "
|
|
410
|
+
"description of a model failure."
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
p.add_argument(
|
|
414
|
+
"--description", "-d", required=True,
|
|
415
|
+
help="Plain-text description of the failure to diagnose.",
|
|
416
|
+
)
|
|
417
|
+
p.add_argument(
|
|
418
|
+
"--model", "-m", default="qwen2.5-vl-7b-instruct",
|
|
419
|
+
metavar="MODEL_KEY",
|
|
420
|
+
help="EvalRX model key (see evalrx.list_specs()).",
|
|
421
|
+
)
|
|
422
|
+
p.add_argument(
|
|
423
|
+
"--out", "-o", default="./my_evalrx_experiment",
|
|
424
|
+
metavar="DIR",
|
|
425
|
+
help="Output directory for the generated scaffold.",
|
|
426
|
+
)
|
|
427
|
+
p.add_argument(
|
|
428
|
+
"--provider", default="",
|
|
429
|
+
metavar="PROVIDER",
|
|
430
|
+
help=(
|
|
431
|
+
"CLI agent provider for run.py generation "
|
|
432
|
+
"(claude_code / gemini_cli / codex / opencode / kimi_cli). "
|
|
433
|
+
"Omit to use the built-in template."
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
p.add_argument("--cli-model", default="", help="Model flag forwarded to the CLI binary.")
|
|
437
|
+
p.add_argument("--cli-binary", default="", help="Explicit path to the CLI binary.")
|
|
438
|
+
p.add_argument("--cli-timeout", type=int, default=180, help="CLI agent timeout (seconds).")
|
|
439
|
+
p.add_argument("--task-domain", default="", help='Short domain label, e.g. "spatial reasoning".')
|
|
440
|
+
return p.parse_args()
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def main() -> None:
|
|
444
|
+
args = _parse_args()
|
|
445
|
+
out = scaffold_from_description(
|
|
446
|
+
description=args.description,
|
|
447
|
+
model_key=args.model,
|
|
448
|
+
output_dir=args.out,
|
|
449
|
+
provider=args.provider,
|
|
450
|
+
cli_binary=args.cli_binary,
|
|
451
|
+
cli_model=args.cli_model,
|
|
452
|
+
cli_timeout=args.cli_timeout,
|
|
453
|
+
task_domain=args.task_domain,
|
|
454
|
+
)
|
|
455
|
+
print(f"\nScaffold created at: {out}")
|
|
456
|
+
print(f" cd {out} && docker compose up")
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
if __name__ == "__main__":
|
|
460
|
+
main()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Closed-loop orchestrator: define → split → pre-register → test → report.
|
|
2
|
+
|
|
3
|
+
Implements the selective-inference-safe path:
|
|
4
|
+
1. partition cases into explore/validate/confirm (DataSplit),
|
|
5
|
+
2. register the hypothesis (hash + timestamp) BEFORE unblinding its test split,
|
|
6
|
+
3. run the A/B on that split,
|
|
7
|
+
4. emit a DiagnosticReport carrying the pre-registration proof.
|
|
8
|
+
|
|
9
|
+
The exploratory generation that *produces* hypotheses (LLM-backed) lives in the
|
|
10
|
+
SelfEvolveLoop; this orchestrator enforces the discipline around testing them.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Callable, Optional
|
|
16
|
+
|
|
17
|
+
from evalrx.core.experiment import ExperimentRunner
|
|
18
|
+
from evalrx.eval_agent.ab_runner import ABRunner
|
|
19
|
+
from evalrx.eval_agent.legacy import SelfEvolveLoop
|
|
20
|
+
from evalrx.eval_agent.preregister import (
|
|
21
|
+
DataSplit,
|
|
22
|
+
PreregisteredHypothesis,
|
|
23
|
+
PreregistrationLog,
|
|
24
|
+
Split,
|
|
25
|
+
)
|
|
26
|
+
from evalrx.eval_agent.report import DiagnosticReport
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EvalOrchestrator:
|
|
30
|
+
"""Drive a pre-registered A/B comparison end-to-end."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, loop: Optional[SelfEvolveLoop] = None, split: Optional[DataSplit] = None) -> None:
|
|
33
|
+
self.loop = loop or SelfEvolveLoop()
|
|
34
|
+
self.runner: ExperimentRunner = self.loop.runner
|
|
35
|
+
self.split = split or DataSplit()
|
|
36
|
+
self.prereg_log = PreregistrationLog()
|
|
37
|
+
|
|
38
|
+
def run(
|
|
39
|
+
self,
|
|
40
|
+
cases,
|
|
41
|
+
hypothesis: PreregisteredHypothesis,
|
|
42
|
+
strategy_a: Callable,
|
|
43
|
+
strategy_b: Callable,
|
|
44
|
+
*,
|
|
45
|
+
cluster_fn: Optional[Callable] = None,
|
|
46
|
+
timestamp: Optional[str] = None,
|
|
47
|
+
) -> dict:
|
|
48
|
+
"""Register *hypothesis*, test it on its split, return a DiagnosticReport dict."""
|
|
49
|
+
parts = self.split.partition(cases)
|
|
50
|
+
prereg_hash = self.prereg_log.register(hypothesis, timestamp) # BEFORE unblinding
|
|
51
|
+
test_cases = parts[Split(hypothesis.split)]
|
|
52
|
+
ab = ABRunner(strategy_a, strategy_b, cluster_fn=cluster_fn).run(
|
|
53
|
+
test_cases, alpha=hypothesis.alpha, min_effect=hypothesis.min_effect
|
|
54
|
+
)
|
|
55
|
+
return DiagnosticReport.from_comparison(
|
|
56
|
+
ab.stat,
|
|
57
|
+
title=hypothesis.statement or hypothesis.predicate,
|
|
58
|
+
hypothesis=hypothesis,
|
|
59
|
+
prereg_hash=prereg_hash,
|
|
60
|
+
split=hypothesis.split,
|
|
61
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Pre-registration + data splitting — the selective-inference safety machine.
|
|
2
|
+
|
|
3
|
+
The closed loop (mine results → propose hypothesis → test) is textbook data
|
|
4
|
+
dredging if the hypothesis is tested on the same data that suggested it. This
|
|
5
|
+
module enforces the discipline that makes the loop's verdicts trustworthy:
|
|
6
|
+
|
|
7
|
+
* :class:`DataSplit` deterministically partitions cases into **explore /
|
|
8
|
+
validate / confirm** (by hash of case id) — mine freely on explore, test a
|
|
9
|
+
pre-registered hypothesis once on validate, lock confirm for the final report.
|
|
10
|
+
* :class:`PreregisteredHypothesis` is a falsifiable, operationalised contract
|
|
11
|
+
{predicate, metric, direction, test, alpha, min_effect, split} fixed BEFORE
|
|
12
|
+
unblinding the test split.
|
|
13
|
+
* :class:`PreregistrationLog` hashes + timestamps the contract so a report can
|
|
14
|
+
prove the hypothesis preceded the test (and counts the denominator for FDR).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
from dataclasses import asdict, dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import Iterable, Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Split(str, Enum):
|
|
27
|
+
EXPLORE = "explore"
|
|
28
|
+
VALIDATE = "validate"
|
|
29
|
+
CONFIRM = "confirm"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DataSplit:
|
|
34
|
+
"""Deterministic explore/validate/confirm assignment by hash of case id."""
|
|
35
|
+
|
|
36
|
+
explore_frac: float = 0.5
|
|
37
|
+
validate_frac: float = 0.3
|
|
38
|
+
seed: int = 0
|
|
39
|
+
|
|
40
|
+
def assign(self, case_id: str) -> Split:
|
|
41
|
+
h = int(hashlib.sha1(f"{self.seed}:{case_id}".encode()).hexdigest(), 16)
|
|
42
|
+
u = (h % 1_000_000) / 1_000_000
|
|
43
|
+
if u < self.explore_frac:
|
|
44
|
+
return Split.EXPLORE
|
|
45
|
+
if u < self.explore_frac + self.validate_frac:
|
|
46
|
+
return Split.VALIDATE
|
|
47
|
+
return Split.CONFIRM
|
|
48
|
+
|
|
49
|
+
def partition(self, cases: Iterable) -> dict:
|
|
50
|
+
out: dict = {Split.EXPLORE: [], Split.VALIDATE: [], Split.CONFIRM: []}
|
|
51
|
+
for c in cases:
|
|
52
|
+
cid = getattr(c, "id", str(c))
|
|
53
|
+
out[self.assign(cid)].append(c)
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class PreregisteredHypothesis:
|
|
59
|
+
"""A falsifiable contract fixed before unblinding the test split."""
|
|
60
|
+
|
|
61
|
+
predicate: str # operationalised filter over cases (what subset)
|
|
62
|
+
statement: str = "" # human-readable claim
|
|
63
|
+
metric: str = "success"
|
|
64
|
+
direction: str = "B>A" # expected effect sign
|
|
65
|
+
test: str = "mcnemar+evalue"
|
|
66
|
+
alpha: float = 0.05
|
|
67
|
+
min_effect: float = 0.0
|
|
68
|
+
split: str = Split.VALIDATE.value
|
|
69
|
+
|
|
70
|
+
def contract_hash(self) -> str:
|
|
71
|
+
return hashlib.sha1(json.dumps(asdict(self), sort_keys=True).encode()).hexdigest()[:16]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class PreregistrationLog:
|
|
76
|
+
"""Append-only proof that hypotheses were registered before testing."""
|
|
77
|
+
|
|
78
|
+
entries: list = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
def register(self, hyp: PreregisteredHypothesis, timestamp: Optional[str] = None) -> str:
|
|
81
|
+
if timestamp is None:
|
|
82
|
+
from datetime import datetime
|
|
83
|
+
|
|
84
|
+
timestamp = datetime.now().isoformat(timespec="seconds")
|
|
85
|
+
h = hyp.contract_hash()
|
|
86
|
+
self.entries.append({"hash": h, "timestamp": timestamp, "contract": asdict(hyp)})
|
|
87
|
+
return h
|
|
88
|
+
|
|
89
|
+
def is_registered(self, hyp: PreregisteredHypothesis) -> bool:
|
|
90
|
+
return any(e["hash"] == hyp.contract_hash() for e in self.entries)
|
|
91
|
+
|
|
92
|
+
def __len__(self) -> int:
|
|
93
|
+
return len(self.entries)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Prompt builders and prompt policies for EvalRX agents."""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Prompt templates for the agentic diagnosis loop's decision judge."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
DECISION_PROMPT = """\
|
|
6
|
+
You are the diagnostician driving an automated model-failure investigation.
|
|
7
|
+
Unlike a fixed pipeline, you choose which step to run next based on the
|
|
8
|
+
evidence gathered so far — probe the model, run statistics, explore the raw
|
|
9
|
+
data, propose hypotheses, test a hypothesis, or (once one is statistically
|
|
10
|
+
supported) stop.
|
|
11
|
+
|
|
12
|
+
{evidence}
|
|
13
|
+
|
|
14
|
+
Reply with EXACTLY ONE JSON object choosing your next action — no prose
|
|
15
|
+
outside it:
|
|
16
|
+
{{"tool": "<tool name from AVAILABLE TOOLS>", "params": {{...}}, "rationale": "<one sentence: why this tool, why now>"}}
|
|
17
|
+
|
|
18
|
+
Rules:
|
|
19
|
+
- You may only call "stop" with params.resolved=true once a hypothesis has
|
|
20
|
+
actually been tested and is statistically supported and protocol-consistent
|
|
21
|
+
— declaring success earlier will be rejected and you'll be asked again.
|
|
22
|
+
- Do not repeat a tool past its stated call limit.
|
|
23
|
+
- Do not call a tool that is marked BLOCKED until its listed precondition(s)
|
|
24
|
+
are met — run the blocking step first.
|
|
25
|
+
- Prefer the order that makes evidentiary sense (e.g. you need probe findings
|
|
26
|
+
before stats, stats before hypotheses, hypotheses before testing one) but
|
|
27
|
+
you decide the sequence and when to stop, not a fixed script.
|
|
28
|
+
|
|
29
|
+
Return ONLY the JSON object."""
|
|
30
|
+
|
|
31
|
+
ACTION_REPAIR_PROMPT = """\
|
|
32
|
+
Your previous response could not be used as an action.
|
|
33
|
+
|
|
34
|
+
Previous response:
|
|
35
|
+
{raw}
|
|
36
|
+
|
|
37
|
+
Problem(s):
|
|
38
|
+
{errors}
|
|
39
|
+
|
|
40
|
+
{evidence}
|
|
41
|
+
|
|
42
|
+
Reply with EXACTLY ONE JSON object choosing your next action — no prose
|
|
43
|
+
outside it:
|
|
44
|
+
{{"tool": "<tool name from AVAILABLE TOOLS>", "params": {{...}}, "rationale": "<one sentence>"}}
|
|
45
|
+
|
|
46
|
+
Return ONLY the JSON object."""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Prompt templates for case discovery and labeling."""
|
|
2
|
+
|
|
3
|
+
_JUDGE_PROMPT = """\
|
|
4
|
+
You are scoring one model answer for an evaluation case.
|
|
5
|
+
|
|
6
|
+
Experiment protocol:
|
|
7
|
+
{protocol}
|
|
8
|
+
|
|
9
|
+
Success criteria:
|
|
10
|
+
{success_criteria}
|
|
11
|
+
|
|
12
|
+
Prompt:
|
|
13
|
+
{prompt}
|
|
14
|
+
|
|
15
|
+
Expected answer or rubric:
|
|
16
|
+
{expected}
|
|
17
|
+
|
|
18
|
+
Observed model answer:
|
|
19
|
+
{observed}
|
|
20
|
+
|
|
21
|
+
Return a JSON object:
|
|
22
|
+
{{"label": "PASS|FAIL|UNKNOWN", "reason": "one concise sentence"}}
|
|
23
|
+
|
|
24
|
+
Use PASS only when the observed answer satisfies the expected answer/rubric under
|
|
25
|
+
the success criteria."""
|