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,538 @@
|
|
|
1
|
+
"""Tool registry for the agentic diagnosis loop.
|
|
2
|
+
|
|
3
|
+
Each :class:`ToolSpec` wraps one existing stage (M1 probe, M2 stats, M3
|
|
4
|
+
diagnosis, M4 test, ...) behind a name/schema the judge can call. The host —
|
|
5
|
+
not the judge — enforces call caps, preconditions, and the pre-registration
|
|
6
|
+
discipline (no declaring success without a statistically supported,
|
|
7
|
+
protocol-consistent hypothesis).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from evalrx.eval_agent.agentic.actions import Action
|
|
17
|
+
from evalrx.eval_agent.agentic.board import EvidenceBoard
|
|
18
|
+
from evalrx.eval_agent.loop_reports import VLDiagnoseReport
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class ToolSpec:
|
|
23
|
+
"""One tool the judge may call."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
params_schema: dict[str, Any]
|
|
28
|
+
handler: "Callable[[EvidenceBoard, dict[str, Any]], ToolOutcome]"
|
|
29
|
+
max_calls: int = 3
|
|
30
|
+
requires: tuple[str, ...] = ()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ToolOutcome:
|
|
35
|
+
"""What happened when a tool ran — the summary is what re-enters the prompt."""
|
|
36
|
+
|
|
37
|
+
ok: bool
|
|
38
|
+
summary: str
|
|
39
|
+
payload: Any = None
|
|
40
|
+
error: str | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Precondition names -> board attribute truthiness checks.
|
|
44
|
+
_PRECONDITIONS: dict[str, "Callable[[EvidenceBoard], bool]"] = {
|
|
45
|
+
"probe_findings": lambda b: bool(b.probe_findings),
|
|
46
|
+
"stats_findings": lambda b: bool(b.stats_findings),
|
|
47
|
+
"stats_confirmatory": lambda b: bool(b.stats_confirmatory),
|
|
48
|
+
"hypotheses": lambda b: bool(b.hypotheses),
|
|
49
|
+
"supported_hypothesis": lambda b: b.has_supported_hypothesis(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ToolRegistry:
|
|
54
|
+
"""Holds the tools available to the judge for one agentic run."""
|
|
55
|
+
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
self._specs: dict[str, ToolSpec] = {}
|
|
58
|
+
|
|
59
|
+
def register(self, spec: ToolSpec) -> None:
|
|
60
|
+
self._specs[spec.name] = spec
|
|
61
|
+
|
|
62
|
+
def tool_names(self) -> set[str]:
|
|
63
|
+
return set(self._specs)
|
|
64
|
+
|
|
65
|
+
def get(self, name: str) -> ToolSpec:
|
|
66
|
+
return self._specs[name]
|
|
67
|
+
|
|
68
|
+
def catalog_for_prompt(self, board: "EvidenceBoard") -> str:
|
|
69
|
+
lines = []
|
|
70
|
+
for spec in self._specs.values():
|
|
71
|
+
calls_left = (
|
|
72
|
+
spec.max_calls - board.calls_made(spec.name)
|
|
73
|
+
if spec.max_calls > 0
|
|
74
|
+
else None
|
|
75
|
+
)
|
|
76
|
+
unmet = [r for r in spec.requires if not _PRECONDITIONS[r](board)]
|
|
77
|
+
bits = [f"- {spec.name}: {spec.description}"]
|
|
78
|
+
if calls_left is not None:
|
|
79
|
+
bits.append(f"(calls left: {calls_left})")
|
|
80
|
+
if unmet:
|
|
81
|
+
bits.append(f"[BLOCKED until: {', '.join(unmet)}]")
|
|
82
|
+
lines.append(" ".join(bits))
|
|
83
|
+
return "\n".join(lines)
|
|
84
|
+
|
|
85
|
+
def dispatch(self, action: "Action", board: "EvidenceBoard") -> ToolOutcome:
|
|
86
|
+
spec = self._specs.get(action.tool)
|
|
87
|
+
if spec is None:
|
|
88
|
+
return ToolOutcome(False, f"unknown tool {action.tool!r}", error="unknown_tool")
|
|
89
|
+
|
|
90
|
+
if spec.max_calls > 0 and board.calls_made(spec.name) >= spec.max_calls:
|
|
91
|
+
return ToolOutcome(
|
|
92
|
+
False,
|
|
93
|
+
f"{spec.name} has reached its call limit ({spec.max_calls})",
|
|
94
|
+
error="max_calls",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
unmet = [r for r in spec.requires if not _PRECONDITIONS[r](board)]
|
|
98
|
+
if unmet:
|
|
99
|
+
return ToolOutcome(
|
|
100
|
+
False,
|
|
101
|
+
f"{spec.name} requires {unmet} first",
|
|
102
|
+
error="precondition",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if spec.name == "stop" and action.params.get("resolved") and not board.has_supported_hypothesis():
|
|
106
|
+
return ToolOutcome(
|
|
107
|
+
False,
|
|
108
|
+
"cannot declare success: no statistically supported, "
|
|
109
|
+
"protocol-consistent hypothesis yet — call test_hypothesis first",
|
|
110
|
+
error="no_supported_hypothesis",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return spec.handler(board, action.params)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# Default registry: wraps the same M1-M4 stages VLDiagnoseLoop calls directly.
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class _RunState:
|
|
123
|
+
"""Mutable, per-run state the default tool handlers read and update.
|
|
124
|
+
|
|
125
|
+
Mirrors the local variables ``VLDiagnoseLoop.run()`` keeps on its stack —
|
|
126
|
+
an agentic run just spreads the same bookkeeping across tool calls instead
|
|
127
|
+
of a fixed cycle.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
original_data: Any
|
|
131
|
+
data: Any
|
|
132
|
+
cycle: int = 0
|
|
133
|
+
timings: dict[str, float] = field(default_factory=dict)
|
|
134
|
+
all_hypotheses: list[Any] = field(default_factory=list)
|
|
135
|
+
pending_hypotheses: list[Any] = field(default_factory=list)
|
|
136
|
+
all_test_results: list[Any] = field(default_factory=list)
|
|
137
|
+
prior_cycles: list[dict[str, Any]] = field(default_factory=list)
|
|
138
|
+
probe_results: dict[str, Any] = field(default_factory=dict)
|
|
139
|
+
artifact_pngs: list[Any] = field(default_factory=list)
|
|
140
|
+
stats_report: Any = None
|
|
141
|
+
failure_modes: Any = None
|
|
142
|
+
probe_search_result: Any = None
|
|
143
|
+
surgery_outcome: Any = None
|
|
144
|
+
fix_outcome: Any = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _summarize_probe(probe_results: dict[str, Any]) -> list[dict[str, Any]]:
|
|
148
|
+
"""Compact, board-friendly summary of one M1 pass (scalars only, no per-case rows)."""
|
|
149
|
+
out = []
|
|
150
|
+
for name, res in probe_results.items():
|
|
151
|
+
findings = res.findings or {}
|
|
152
|
+
scalars = {k: v for k, v in findings.items() if isinstance(v, (int, float, bool, str))}
|
|
153
|
+
out.append({
|
|
154
|
+
"analyzer": name,
|
|
155
|
+
"scalars": scalars,
|
|
156
|
+
"n_per_case": len(findings.get("per_case", []) or []),
|
|
157
|
+
})
|
|
158
|
+
return out
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _summarize_stats(report: Any) -> list[dict[str, Any]]:
|
|
162
|
+
results = getattr(report, "stats_results", None) or []
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
"tool": r.tool, "ok": r.ok, "effect": r.effect,
|
|
166
|
+
"reject": r.reject, "e_value": r.e_value, "summary": r.summary,
|
|
167
|
+
}
|
|
168
|
+
for r in results
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _current_report(loop: Any, state: _RunState) -> "VLDiagnoseReport":
|
|
173
|
+
"""Snapshot the run so far into the same report shape run_m5/run_fix expect."""
|
|
174
|
+
from evalrx.eval_agent.loop_reports import VLDiagnoseReport
|
|
175
|
+
|
|
176
|
+
verified = loop.hypothesis_tester.best_hypotheses(state.all_test_results)
|
|
177
|
+
return VLDiagnoseReport(
|
|
178
|
+
cycles=state.cycle,
|
|
179
|
+
resolved=bool(verified),
|
|
180
|
+
stopped_by="in_progress",
|
|
181
|
+
verified_hypotheses=verified,
|
|
182
|
+
final_hypotheses=state.all_hypotheses,
|
|
183
|
+
all_test_results=state.all_test_results,
|
|
184
|
+
final_stats_report=state.stats_report,
|
|
185
|
+
store=loop.store,
|
|
186
|
+
_run_id=getattr(loop, "_run_id", ""),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _run_probe(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
191
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
192
|
+
probe_results, artifact_pngs = loop._do_m1(
|
|
193
|
+
state.cycle, state.data, state.all_hypotheses, state.timings
|
|
194
|
+
)
|
|
195
|
+
if not probe_results:
|
|
196
|
+
return ToolOutcome(False, "M1 produced no probe results", error="empty")
|
|
197
|
+
state.probe_results = probe_results
|
|
198
|
+
state.artifact_pngs = artifact_pngs
|
|
199
|
+
board.probe_findings = _summarize_probe(probe_results)
|
|
200
|
+
summary = f"ran {len(probe_results)} analyzer(s): {sorted(probe_results)}"
|
|
201
|
+
state.cycle += 1
|
|
202
|
+
return ToolOutcome(True, summary, payload=probe_results)
|
|
203
|
+
|
|
204
|
+
return handler
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _run_stats(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
208
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
209
|
+
confirmatory = bool(params.get("confirmatory", True))
|
|
210
|
+
stats_report = loop._do_m2(
|
|
211
|
+
state.cycle, state.probe_results, state.data, state.artifact_pngs,
|
|
212
|
+
state.timings, confirmatory=confirmatory,
|
|
213
|
+
)
|
|
214
|
+
state.stats_report = stats_report
|
|
215
|
+
board.stats_findings = _summarize_stats(stats_report)
|
|
216
|
+
board.stats_confirmatory = confirmatory and not getattr(
|
|
217
|
+
stats_report, "descriptive_only", False
|
|
218
|
+
)
|
|
219
|
+
return ToolOutcome(
|
|
220
|
+
True,
|
|
221
|
+
f"severity={stats_report.severity}, confirmatory={board.stats_confirmatory}",
|
|
222
|
+
payload=stats_report,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return handler
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _explore_data(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
229
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
230
|
+
from evalrx.analysis.operationalize import per_case_to_records
|
|
231
|
+
from evalrx.analysis.stats_tools import build_stats_input
|
|
232
|
+
|
|
233
|
+
question = str(
|
|
234
|
+
params.get("question")
|
|
235
|
+
or "Explore this dataset and surface the patterns that matter."
|
|
236
|
+
)
|
|
237
|
+
stats_input = build_stats_input(state.probe_results, state.data)
|
|
238
|
+
records = per_case_to_records(stats_input.per_case, stats_input.labels)
|
|
239
|
+
if not records:
|
|
240
|
+
return ToolOutcome(False, "no per-case records to explore yet", error="empty")
|
|
241
|
+
report = loop.explorer.explore_records(records, question=question)
|
|
242
|
+
board.explore_takeaways = [t.to_dict() for t in getattr(report, "takeaways", [])]
|
|
243
|
+
summary = (
|
|
244
|
+
f"{len(board.explore_takeaways)} takeaway(s)"
|
|
245
|
+
if report.ok else (report.error or "explore failed")
|
|
246
|
+
)
|
|
247
|
+
return ToolOutcome(bool(report.ok), summary, payload=report)
|
|
248
|
+
|
|
249
|
+
return handler
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _cluster_failures(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
253
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
254
|
+
from evalrx.analysis.failure_modes import cluster_failures
|
|
255
|
+
from evalrx.analysis.operationalize import per_case_to_records
|
|
256
|
+
from evalrx.analysis.stats_tools import build_stats_input
|
|
257
|
+
|
|
258
|
+
stats_input = build_stats_input(state.probe_results, state.data)
|
|
259
|
+
records = per_case_to_records(stats_input.per_case, stats_input.labels)
|
|
260
|
+
if not records:
|
|
261
|
+
return ToolOutcome(False, "no per-case records to cluster yet", error="empty")
|
|
262
|
+
report = cluster_failures(
|
|
263
|
+
records, # per_case_to_records writes label="fail"/"pass" — matches the defaults
|
|
264
|
+
min_cluster_size=int(params.get("min_cluster_size", 3)),
|
|
265
|
+
max_clusters=int(params.get("max_clusters", 8)),
|
|
266
|
+
)
|
|
267
|
+
state.failure_modes = report
|
|
268
|
+
board.failure_modes = [c.to_dict() for c in report.clusters]
|
|
269
|
+
run_logger = getattr(loop, "run_logger", None)
|
|
270
|
+
if run_logger is not None:
|
|
271
|
+
run_logger.save_artifact_json("failure_modes.json", report.to_dict())
|
|
272
|
+
return ToolOutcome(
|
|
273
|
+
True, f"{len(report.clusters)} failure mode(s) from {report.n_fail_cases} FAIL case(s)",
|
|
274
|
+
payload=report,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return handler
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _search_probes(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
281
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
282
|
+
from evalrx.eval_agent.stages.probe_search_agent import ProbeSearchAgent
|
|
283
|
+
|
|
284
|
+
seed_pool = state.data
|
|
285
|
+
if not len(seed_pool):
|
|
286
|
+
return ToolOutcome(False, "no seed cases available to probe", error="empty")
|
|
287
|
+
budget = int(params.get("budget", 10))
|
|
288
|
+
agent = ProbeSearchAgent(
|
|
289
|
+
judge=loop.judge, protocol=loop.protocol, budget=budget,
|
|
290
|
+
run_logger=getattr(loop, "run_logger", None),
|
|
291
|
+
)
|
|
292
|
+
result = agent.run(loop.model, seed_pool)
|
|
293
|
+
state.probe_search_result = result
|
|
294
|
+
board.probe_search_findings = [
|
|
295
|
+
{
|
|
296
|
+
"prompt": c.inputs.prompt, "expected": c.expected, "observed": str(c.observed),
|
|
297
|
+
}
|
|
298
|
+
for c in result.failure_cases
|
|
299
|
+
]
|
|
300
|
+
run_logger = getattr(loop, "run_logger", None)
|
|
301
|
+
if run_logger is not None:
|
|
302
|
+
run_logger.save_artifact_json("probe_search_result.json", result.to_dict())
|
|
303
|
+
summary = (
|
|
304
|
+
f"{result.n_simulations} simulation(s) (macro={result.n_macro}, "
|
|
305
|
+
f"micro={result.n_micro}), {len(result.failure_cases)} new failure(s) "
|
|
306
|
+
f"found (error_rate={result.error_rate:.0%})"
|
|
307
|
+
)
|
|
308
|
+
return ToolOutcome(bool(result.n_simulations), summary, payload=result)
|
|
309
|
+
|
|
310
|
+
return handler
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _propose_hypotheses(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
314
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
315
|
+
diag = loop._do_m3(
|
|
316
|
+
state.cycle, state.stats_report, state.prior_cycles, state.timings,
|
|
317
|
+
failure_modes=state.failure_modes,
|
|
318
|
+
)
|
|
319
|
+
if diag is None or not diag.hypotheses:
|
|
320
|
+
return ToolOutcome(False, "M3 produced no hypotheses", error="empty")
|
|
321
|
+
for h in diag.hypotheses:
|
|
322
|
+
loop.store.add_hypothesis(h)
|
|
323
|
+
state.all_hypotheses.extend(diag.hypotheses)
|
|
324
|
+
state.pending_hypotheses = list(diag.hypotheses)
|
|
325
|
+
board.hypotheses = [
|
|
326
|
+
{
|
|
327
|
+
"statement": h.statement,
|
|
328
|
+
"failure_mode": h.predicted_failure_mode,
|
|
329
|
+
"status": h.status.value if h.status else "proposed",
|
|
330
|
+
}
|
|
331
|
+
for h in state.all_hypotheses
|
|
332
|
+
]
|
|
333
|
+
state.prior_cycles.append({
|
|
334
|
+
"cycle": state.cycle,
|
|
335
|
+
"severity": getattr(state.stats_report, "severity", "none"),
|
|
336
|
+
"hypotheses": [
|
|
337
|
+
{
|
|
338
|
+
"statement": h.statement,
|
|
339
|
+
"failure_mode": h.predicted_failure_mode,
|
|
340
|
+
"status": h.status.value if h.status else "pending",
|
|
341
|
+
}
|
|
342
|
+
for h in diag.hypotheses
|
|
343
|
+
],
|
|
344
|
+
})
|
|
345
|
+
return ToolOutcome(True, f"proposed {len(diag.hypotheses)} hypothesis(es)", payload=diag)
|
|
346
|
+
|
|
347
|
+
return handler
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _test_hypothesis(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
351
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
352
|
+
if not state.pending_hypotheses:
|
|
353
|
+
return ToolOutcome(
|
|
354
|
+
False, "no untested hypotheses — call propose_hypotheses first", error="empty"
|
|
355
|
+
)
|
|
356
|
+
loop._finalize_confirmatory_stats(state.stats_report)
|
|
357
|
+
board.stats_confirmatory = True
|
|
358
|
+
test_results = loop._do_m4(
|
|
359
|
+
state.cycle, state.pending_hypotheses, state.stats_report, state.data, state.timings
|
|
360
|
+
)
|
|
361
|
+
for tr in test_results:
|
|
362
|
+
tr.hypothesis.status = tr.status
|
|
363
|
+
state.all_test_results.extend(test_results)
|
|
364
|
+
state.pending_hypotheses = []
|
|
365
|
+
board.hypotheses = [
|
|
366
|
+
{
|
|
367
|
+
"statement": tr.hypothesis.statement,
|
|
368
|
+
"failure_mode": tr.hypothesis.predicted_failure_mode,
|
|
369
|
+
"status": tr.status.value,
|
|
370
|
+
"is_consistent_with_protocol": tr.is_consistent_with_protocol,
|
|
371
|
+
"effect_size": tr.effect_size,
|
|
372
|
+
"confidence": tr.confidence,
|
|
373
|
+
}
|
|
374
|
+
for tr in state.all_test_results
|
|
375
|
+
]
|
|
376
|
+
n_supported = sum(1 for tr in test_results if tr.status.value == "supported")
|
|
377
|
+
return ToolOutcome(
|
|
378
|
+
True, f"tested {len(test_results)} hypothesis(es), {n_supported} supported",
|
|
379
|
+
payload=test_results,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return handler
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _run_surgery(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
386
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
387
|
+
report = _current_report(loop, state)
|
|
388
|
+
iv = loop.run_m5(report, state.original_data)
|
|
389
|
+
state.surgery_outcome = iv
|
|
390
|
+
if iv is None:
|
|
391
|
+
return ToolOutcome(False, "surgery produced no result", error="empty")
|
|
392
|
+
return ToolOutcome(True, f"surgery status={iv.status.value} fixed={iv.fixed}", payload=iv)
|
|
393
|
+
|
|
394
|
+
return handler
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _run_fix(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
398
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
399
|
+
report = _current_report(loop, state)
|
|
400
|
+
outcome = loop.run_fix(report, state.original_data)
|
|
401
|
+
state.fix_outcome = outcome
|
|
402
|
+
if outcome is None:
|
|
403
|
+
return ToolOutcome(False, "fix produced no result", error="empty")
|
|
404
|
+
fixed = bool(getattr(outcome, "fixed", False))
|
|
405
|
+
return ToolOutcome(True, f"fix fixed={fixed}", payload=outcome)
|
|
406
|
+
|
|
407
|
+
return handler
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _stop(loop: Any, state: _RunState) -> "Callable[[EvidenceBoard, dict], ToolOutcome]":
|
|
411
|
+
def handler(board: "EvidenceBoard", params: dict[str, Any]) -> ToolOutcome:
|
|
412
|
+
resolved = bool(params.get("resolved", False))
|
|
413
|
+
reason = str(params.get("reason") or "")
|
|
414
|
+
return ToolOutcome(True, f"stop(resolved={resolved}): {reason}".strip())
|
|
415
|
+
|
|
416
|
+
return handler
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def build_default_registry(loop: Any, state: _RunState) -> ToolRegistry:
|
|
420
|
+
"""Build the default M1-M4 tool registry for one :class:`AgenticDiagnoseLoop` run."""
|
|
421
|
+
registry = ToolRegistry()
|
|
422
|
+
registry.register(ToolSpec(
|
|
423
|
+
name="run_probe",
|
|
424
|
+
description="Run M1 analyzer probing to gather per-case findings on the model.",
|
|
425
|
+
params_schema={"type": "object", "properties": {}},
|
|
426
|
+
handler=_run_probe(loop, state),
|
|
427
|
+
max_calls=3,
|
|
428
|
+
))
|
|
429
|
+
registry.register(ToolSpec(
|
|
430
|
+
name="run_stats",
|
|
431
|
+
description=(
|
|
432
|
+
"Run M2 statistical analysis over the probe findings gathered so far. "
|
|
433
|
+
"params.confirmatory (bool, default true): apply e-BH FDR correction now "
|
|
434
|
+
"vs. defer it (descriptive-only pass)."
|
|
435
|
+
),
|
|
436
|
+
params_schema={
|
|
437
|
+
"type": "object",
|
|
438
|
+
"properties": {"confirmatory": {"type": "boolean"}},
|
|
439
|
+
},
|
|
440
|
+
handler=_run_stats(loop, state),
|
|
441
|
+
max_calls=3,
|
|
442
|
+
requires=("probe_findings",),
|
|
443
|
+
))
|
|
444
|
+
if getattr(loop, "explorer", None) is not None:
|
|
445
|
+
registry.register(ToolSpec(
|
|
446
|
+
name="explore_data",
|
|
447
|
+
description=(
|
|
448
|
+
"Run free-form exploratory data analysis over the per-case signals "
|
|
449
|
+
"gathered so far. params.question (str, optional): what to look for."
|
|
450
|
+
),
|
|
451
|
+
params_schema={
|
|
452
|
+
"type": "object",
|
|
453
|
+
"properties": {"question": {"type": "string"}},
|
|
454
|
+
},
|
|
455
|
+
handler=_explore_data(loop, state),
|
|
456
|
+
max_calls=2,
|
|
457
|
+
requires=("probe_findings",),
|
|
458
|
+
))
|
|
459
|
+
registry.register(ToolSpec(
|
|
460
|
+
name="cluster_failures",
|
|
461
|
+
description=(
|
|
462
|
+
"Cluster FAIL cases into interpretable failure modes from the per-case "
|
|
463
|
+
"signals gathered so far — surfaces patterns propose_hypotheses can draw "
|
|
464
|
+
"on. params.min_cluster_size (int, default 3), params.max_clusters (int, default 8)."
|
|
465
|
+
),
|
|
466
|
+
params_schema={
|
|
467
|
+
"type": "object",
|
|
468
|
+
"properties": {
|
|
469
|
+
"min_cluster_size": {"type": "integer"},
|
|
470
|
+
"max_clusters": {"type": "integer"},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
handler=_cluster_failures(loop, state),
|
|
474
|
+
max_calls=2,
|
|
475
|
+
requires=("probe_findings",),
|
|
476
|
+
))
|
|
477
|
+
registry.register(ToolSpec(
|
|
478
|
+
name="search_probes",
|
|
479
|
+
description=(
|
|
480
|
+
"Run a hierarchical Macro/Micro MCTS probe search (ProbeLLM-style) that "
|
|
481
|
+
"synthesizes and evaluates NEW test cases beyond the loaded dataset, "
|
|
482
|
+
"seeded from it — surfaces failures the original data never showed. "
|
|
483
|
+
"params.budget (int, default 10): total new probes to simulate."
|
|
484
|
+
),
|
|
485
|
+
params_schema={"type": "object", "properties": {"budget": {"type": "integer"}}},
|
|
486
|
+
handler=_search_probes(loop, state),
|
|
487
|
+
max_calls=2,
|
|
488
|
+
))
|
|
489
|
+
registry.register(ToolSpec(
|
|
490
|
+
name="propose_hypotheses",
|
|
491
|
+
description="Run M3 to propose falsifiable hypotheses from the stats findings so far.",
|
|
492
|
+
params_schema={"type": "object", "properties": {}},
|
|
493
|
+
handler=_propose_hypotheses(loop, state),
|
|
494
|
+
max_calls=4,
|
|
495
|
+
requires=("stats_findings",),
|
|
496
|
+
))
|
|
497
|
+
registry.register(ToolSpec(
|
|
498
|
+
name="test_hypothesis",
|
|
499
|
+
description="Run M4 to statistically test the most recently proposed hypotheses.",
|
|
500
|
+
params_schema={"type": "object", "properties": {}},
|
|
501
|
+
handler=_test_hypothesis(loop, state),
|
|
502
|
+
max_calls=4,
|
|
503
|
+
requires=("hypotheses",),
|
|
504
|
+
))
|
|
505
|
+
registry.register(ToolSpec(
|
|
506
|
+
name="run_surgery",
|
|
507
|
+
description="Propose a mechanistic fix (M5) for the best verified hypothesis.",
|
|
508
|
+
params_schema={"type": "object", "properties": {}},
|
|
509
|
+
handler=_run_surgery(loop, state),
|
|
510
|
+
max_calls=1,
|
|
511
|
+
requires=("supported_hypothesis",),
|
|
512
|
+
))
|
|
513
|
+
registry.register(ToolSpec(
|
|
514
|
+
name="run_fix",
|
|
515
|
+
description="Run the tiered, validated fix module (L1-L4) for the best verified hypothesis.",
|
|
516
|
+
params_schema={"type": "object", "properties": {}},
|
|
517
|
+
handler=_run_fix(loop, state),
|
|
518
|
+
max_calls=1,
|
|
519
|
+
requires=("supported_hypothesis",),
|
|
520
|
+
))
|
|
521
|
+
registry.register(ToolSpec(
|
|
522
|
+
name="stop",
|
|
523
|
+
description=(
|
|
524
|
+
"End the investigation. params.resolved (bool): true only once a "
|
|
525
|
+
"hypothesis has been tested and is statistically supported and "
|
|
526
|
+
"protocol-consistent — otherwise it is rejected. params.reason (str)."
|
|
527
|
+
),
|
|
528
|
+
params_schema={
|
|
529
|
+
"type": "object",
|
|
530
|
+
"properties": {"resolved": {"type": "boolean"}, "reason": {"type": "string"}},
|
|
531
|
+
},
|
|
532
|
+
handler=_stop(loop, state),
|
|
533
|
+
# A rejected stop (premature resolved=true, or resolved=false to give up
|
|
534
|
+
# and try again) does no real work, so it shouldn't burn down a scarce
|
|
535
|
+
# quota the way a real M1-M4 stage call does — cap generously.
|
|
536
|
+
max_calls=5,
|
|
537
|
+
))
|
|
538
|
+
return registry
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Checkpoint / heartbeat file I/O for resumable diagnosis runs.
|
|
2
|
+
|
|
3
|
+
Free functions extracted from ``AutoDiagnoseLoop`` (the only current caller,
|
|
4
|
+
in ``legacy.py``) so the atomic-write/read logic is reusable by any future
|
|
5
|
+
loop that wants run_dir-based resume, without depending on the loop class
|
|
6
|
+
itself.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import tempfile
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def write_checkpoint(
|
|
20
|
+
checkpoint_path: Path, *, cycle: int, run_id: str, hypothesis_statuses: list[str],
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Atomic checkpoint write (temp-file + rename)."""
|
|
23
|
+
data = {
|
|
24
|
+
"last_completed_cycle": cycle,
|
|
25
|
+
"run_id": run_id,
|
|
26
|
+
"hypothesis_statuses": hypothesis_statuses,
|
|
27
|
+
"timestamp": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
28
|
+
}
|
|
29
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
30
|
+
dir=checkpoint_path.parent, suffix=".tmp", prefix="checkpoint_"
|
|
31
|
+
)
|
|
32
|
+
os.close(fd)
|
|
33
|
+
try:
|
|
34
|
+
Path(tmp_path).write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
35
|
+
Path(tmp_path).replace(checkpoint_path)
|
|
36
|
+
except BaseException:
|
|
37
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
38
|
+
raise
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def write_heartbeat(heartbeat_path: Path, *, cycle: int, run_id: str) -> None:
|
|
42
|
+
data = {
|
|
43
|
+
"pid": os.getpid(),
|
|
44
|
+
"last_cycle": cycle,
|
|
45
|
+
"run_id": run_id,
|
|
46
|
+
"timestamp": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
47
|
+
}
|
|
48
|
+
heartbeat_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def read_checkpoint(checkpoint_path: "Path | None") -> "dict[str, Any] | None":
|
|
52
|
+
if checkpoint_path is None or not checkpoint_path.exists():
|
|
53
|
+
return None
|
|
54
|
+
try:
|
|
55
|
+
return json.loads(checkpoint_path.read_text(encoding="utf-8"))
|
|
56
|
+
except (json.JSONDecodeError, OSError):
|
|
57
|
+
return None
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Compatibility facade for CLI coding agents and CLI-backed judge models.
|
|
2
|
+
|
|
3
|
+
The implementation lives in ``evalrx.agent_runtime``:
|
|
4
|
+
|
|
5
|
+
- ``evalrx.agent_runtime.providers`` contains CLI coding-provider adapters.
|
|
6
|
+
- ``evalrx.agent_runtime.judges`` contains judge model wrappers.
|
|
7
|
+
- ``evalrx.agent_runtime.cli_types`` contains public config/result types.
|
|
8
|
+
|
|
9
|
+
This module keeps the historical ``evalrx.eval_agent.cli_agent`` import
|
|
10
|
+
path stable.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from evalrx.agent_runtime.cli_types import BINARY_DEFAULTS, CliAgentConfig, CliAgentResult
|
|
16
|
+
from evalrx.agent_runtime.judges.agy import (
|
|
17
|
+
AgyModel,
|
|
18
|
+
)
|
|
19
|
+
from evalrx.agent_runtime.judges.agy import (
|
|
20
|
+
safe_unlink as _safe_unlink,
|
|
21
|
+
)
|
|
22
|
+
from evalrx.agent_runtime.judges.agy import (
|
|
23
|
+
scan_agy_log as _scan_agy_log,
|
|
24
|
+
)
|
|
25
|
+
from evalrx.agent_runtime.judges.claude import ClaudeModel
|
|
26
|
+
from evalrx.agent_runtime.judges.codex import CodexModel
|
|
27
|
+
from evalrx.agent_runtime.providers.antigravity import AntigravityAgent
|
|
28
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase as _CliAgentBase
|
|
29
|
+
from evalrx.agent_runtime.providers.claude_code import ClaudeCodeAgent
|
|
30
|
+
from evalrx.agent_runtime.providers.codex import CodexAgent
|
|
31
|
+
from evalrx.agent_runtime.providers.gemini_cli import GeminiCliAgent
|
|
32
|
+
from evalrx.agent_runtime.providers.kimi_cli import KimiCliAgent
|
|
33
|
+
from evalrx.agent_runtime.providers.opencode import OpenCodeAgent
|
|
34
|
+
from evalrx.agent_runtime.providers.registry import (
|
|
35
|
+
PROVIDER_CLASSES as _PROVIDER_CLASSES,
|
|
36
|
+
)
|
|
37
|
+
from evalrx.agent_runtime.providers.registry import (
|
|
38
|
+
create_cli_agent,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"AgyModel",
|
|
43
|
+
"AntigravityAgent",
|
|
44
|
+
"BINARY_DEFAULTS",
|
|
45
|
+
"ClaudeCodeAgent",
|
|
46
|
+
"ClaudeModel",
|
|
47
|
+
"CodexModel",
|
|
48
|
+
"CliAgentConfig",
|
|
49
|
+
"CliAgentResult",
|
|
50
|
+
"CodexAgent",
|
|
51
|
+
"GeminiCliAgent",
|
|
52
|
+
"KimiCliAgent",
|
|
53
|
+
"OpenCodeAgent",
|
|
54
|
+
"_CliAgentBase",
|
|
55
|
+
"_PROVIDER_CLASSES",
|
|
56
|
+
"_safe_unlink",
|
|
57
|
+
"_scan_agy_log",
|
|
58
|
+
"create_cli_agent",
|
|
59
|
+
]
|