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
evalrx/contract/m5.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""M5 — intervention and repair. Two different jobs under one number.
|
|
2
|
+
|
|
3
|
+
``SurgeryAgent`` INTERVENES: it changes one variable, re-runs, and reads the
|
|
4
|
+
result as causal evidence about *why*. Its output is a verdict, not a deliverable
|
|
5
|
+
-- the modified inputs are discarded.
|
|
6
|
+
|
|
7
|
+
``FixAgent`` REPAIRS: it proposes changes intended to survive, and validates them
|
|
8
|
+
paired against the unmodified baseline on the same cases.
|
|
9
|
+
|
|
10
|
+
Conflating them produces the pipeline's worst failure mode: presenting a
|
|
11
|
+
diagnostic manipulation as a shippable fix.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
from pydantic import Field, model_validator
|
|
19
|
+
|
|
20
|
+
from evalrx.contract.common import (
|
|
21
|
+
CaseBatchRef,
|
|
22
|
+
HypothesisStatus,
|
|
23
|
+
StageEnvelope,
|
|
24
|
+
WireModel,
|
|
25
|
+
)
|
|
26
|
+
from evalrx.contract.methodology import MethodologyWire
|
|
27
|
+
|
|
28
|
+
# ===========================================================================
|
|
29
|
+
# M5a — SurgeryAgent: verify the mechanism
|
|
30
|
+
# ===========================================================================
|
|
31
|
+
|
|
32
|
+
class SurgeryInput(WireModel):
|
|
33
|
+
"""One hypothesis at a time. Surgery is per-claim by construction."""
|
|
34
|
+
|
|
35
|
+
hypothesis_id: str
|
|
36
|
+
results_ref: str = Field(description="Path to the M1 ProbeOutput, for per-case signal extraction.")
|
|
37
|
+
data: CaseBatchRef
|
|
38
|
+
model_name: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InterventionOutput(StageEnvelope):
|
|
42
|
+
"""``SurgeryAgent.operate`` result — causal evidence, not a repair.
|
|
43
|
+
|
|
44
|
+
``fixed`` here means "the intervention completely separated failing from
|
|
45
|
+
passing cases", i.e. the mechanism is confirmed. It does NOT mean a usable
|
|
46
|
+
repair exists; that claim belongs to :class:`FixOutput` alone.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
hypothesis_id: str = Field(min_length=1, description="Points into the M3 DiagnosisOutput.")
|
|
50
|
+
hypothesis_status: HypothesisStatus = Field(
|
|
51
|
+
description="Verdict on the hypothesis. Named apart from the envelope's `status`, "
|
|
52
|
+
"which is this STAGE's execution state — they answer different questions."
|
|
53
|
+
)
|
|
54
|
+
strategy: Literal["verify_fn", "analyzer_params", "experiment_writer", "passive_correlation"] = (
|
|
55
|
+
"passive_correlation"
|
|
56
|
+
)
|
|
57
|
+
fixed: bool = Field(
|
|
58
|
+
default=False, description="Mechanism fully separates the groups. NOT a shippable repair."
|
|
59
|
+
)
|
|
60
|
+
confidence_score: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
61
|
+
evidence_dimensions: dict[str, float] = Field(
|
|
62
|
+
default_factory=dict, description="Breakdown: evidence gap / sample adequacy / control cleanliness."
|
|
63
|
+
)
|
|
64
|
+
evidence: dict[str, Any] = Field(default_factory=dict)
|
|
65
|
+
methodology: MethodologyWire | None = Field(
|
|
66
|
+
default=None,
|
|
67
|
+
description="The manipulation performed: which variable was changed, what was held fixed, "
|
|
68
|
+
"what was compared. This is what makes the causal claim auditable.",
|
|
69
|
+
)
|
|
70
|
+
trial_root: str | None = Field(
|
|
71
|
+
default=None,
|
|
72
|
+
description="Attempt folder when the experiment-writer strategy ran: generated code, "
|
|
73
|
+
"stdout/stderr, workspace and record all sit there.",
|
|
74
|
+
)
|
|
75
|
+
new_data: CaseBatchRef | None = Field(
|
|
76
|
+
default=None,
|
|
77
|
+
description="When SUPPORTED: the cases NOT in the signal group — the refined subset for the "
|
|
78
|
+
"next M1 cycle. null means no refinement, which differs from an empty batch.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def is_intervention(self) -> bool:
|
|
83
|
+
"""Whether this run can ground a causal claim.
|
|
84
|
+
|
|
85
|
+
``passive_correlation`` only observes, so it cannot — which makes this a
|
|
86
|
+
function of ``strategy``, not an independent fact worth storing.
|
|
87
|
+
"""
|
|
88
|
+
return self.strategy != "passive_correlation"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ===========================================================================
|
|
92
|
+
# M5b — FixAgent: propose and validate repairs
|
|
93
|
+
# ===========================================================================
|
|
94
|
+
|
|
95
|
+
class FixInput(WireModel):
|
|
96
|
+
"""Repairs are attempted only on hypotheses M4 verified. ``max_tier`` bounds
|
|
97
|
+
how invasive the search may get and is the caller's decision, never the
|
|
98
|
+
agent's."""
|
|
99
|
+
|
|
100
|
+
hypothesis_ids: list[str]
|
|
101
|
+
data: CaseBatchRef
|
|
102
|
+
model_name: str
|
|
103
|
+
max_tier: Literal["L0", "L1", "L2", "L3a", "L3b", "L4"] = Field(
|
|
104
|
+
description="L0 runtime config / L1 prompt / L2 scaffold / L3a internals-read / "
|
|
105
|
+
"L3b internals-write / L4 params.\n\n"
|
|
106
|
+
"L0 is in this list because `FixTier` has it — decoding settings are a "
|
|
107
|
+
"real, and the least invasive, place to intervene. Omitting it made a "
|
|
108
|
+
"tier the pipeline can produce unrepresentable."
|
|
109
|
+
)
|
|
110
|
+
max_repair_rounds: int = Field(
|
|
111
|
+
default=1, ge=1, description="Feedback-driven propose->validate rounds. 1 = no loop."
|
|
112
|
+
)
|
|
113
|
+
prior_attempts_ref: str | None = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class FixAttemptWire(WireModel):
|
|
117
|
+
"""One candidate validated against the unmodified baseline on the SAME cases.
|
|
118
|
+
|
|
119
|
+
Every count here is a PAIRED flip, never a rate difference. "repaired 18 /
|
|
120
|
+
broke 1" is a statement about individual cases changing sign; a pass-rate
|
|
121
|
+
delta is not the same claim and must never be displayed as if it were.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
tier: Literal["L0", "L1", "L2", "L3a", "L3b", "L4"]
|
|
125
|
+
|
|
126
|
+
ref: str = Field(
|
|
127
|
+
default="",
|
|
128
|
+
description="The reader-facing identifier, e.g. `R3`. Assigned by the producer over the "
|
|
129
|
+
"whole run and STABLE across `selection` and `attempted`, so a reader who "
|
|
130
|
+
"sees R3 in the sweep can find R3's card and know it is the same repair.\n\n"
|
|
131
|
+
"This exists because `name` cannot do the job. Consumers that numbered rows "
|
|
132
|
+
"by list position gave the same repair two different numbers in the two "
|
|
133
|
+
"lists, which is worse than no number at all. Empty means the producer "
|
|
134
|
+
"assigned none; a consumer may then number by position, but only within one "
|
|
135
|
+
"list and never as a cross-list identity.",
|
|
136
|
+
)
|
|
137
|
+
name: str = Field(
|
|
138
|
+
description="Machine slug, e.g. `audio_evidence_then_answer`. It is the JOIN KEY — "
|
|
139
|
+
"`best`, `selected_on_explore` and `ebh_survivors` all name a candidate this "
|
|
140
|
+
"way — and it is what the attempt folder on disk is called.\n\n"
|
|
141
|
+
"It is NOT the label to show a reader. These slugs are chosen by the agent "
|
|
142
|
+
"while naming its log files, so they read as identifiers rather than as "
|
|
143
|
+
"language. Show `ref` to point at a candidate and `headline` to say what it "
|
|
144
|
+
"does.",
|
|
145
|
+
)
|
|
146
|
+
headline: str = Field(
|
|
147
|
+
default="",
|
|
148
|
+
description="One plain sentence saying what this repair changes, written for a reader "
|
|
149
|
+
"with no background in the field: no tier names, no metric names, no jargon, "
|
|
150
|
+
"no slug echoed back. \"Ask the model to describe what it hears before "
|
|
151
|
+
"answering\" — not \"audio-evidence-first prompt scaffold\".\n\n"
|
|
152
|
+
"Empty when the producer had nothing readable to offer, which a consumer "
|
|
153
|
+
"should render as an honest blank rather than by humanising the slug: "
|
|
154
|
+
"`audio_evidence_then_answer` -> \"Audio Evidence Then Answer\" invents "
|
|
155
|
+
"fluency the run never had.",
|
|
156
|
+
)
|
|
157
|
+
kind: str | None = None
|
|
158
|
+
source: str | None = None
|
|
159
|
+
methodology: MethodologyWire | None = Field(
|
|
160
|
+
default=None,
|
|
161
|
+
description="What this candidate DOES, as a graph plus a rendered draw.io diagram. The "
|
|
162
|
+
"payload is the implementation; this is the explanation. Required for any "
|
|
163
|
+
"candidate a reader is asked to accept — a repair nobody can follow cannot "
|
|
164
|
+
"be reviewed, only trusted.",
|
|
165
|
+
)
|
|
166
|
+
trial_root: str | None = Field(
|
|
167
|
+
default=None,
|
|
168
|
+
description="Self-contained attempt folder, RELATIVE TO THE RUN ROOT. Preferred join "
|
|
169
|
+
"key across rounds — name alone is not unique.\n\n"
|
|
170
|
+
"It holds `outputs.jsonl`, one line per validated case: "
|
|
171
|
+
"`{case_id, status, output}` where status is fixed / broken / unchanged. "
|
|
172
|
+
"That is what a reader needs to study a repair case by case — the "
|
|
173
|
+
"candidate's actual answer beside the baseline's — and it is kept out of "
|
|
174
|
+
"this payload because it is one row per case per candidate. Recorded "
|
|
175
|
+
"relative because an absolute producer path resolves nowhere else, which "
|
|
176
|
+
"is the same reason MediaRef paths are relative.",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
n_pairs: int = 0
|
|
180
|
+
n_baseline_correct: int = 0
|
|
181
|
+
n_fixed: int = 0
|
|
182
|
+
n_broken: int = 0
|
|
183
|
+
fixed_cases: list[str] = Field(default_factory=list)
|
|
184
|
+
broken_cases: list[str] = Field(default_factory=list)
|
|
185
|
+
|
|
186
|
+
n_applicable: int = Field(default=0, description="Cases the candidate actually touched.")
|
|
187
|
+
coverage: float | None = Field(default=None, description="applicable FAILs / total FAILs.")
|
|
188
|
+
n_unstable: int = Field(
|
|
189
|
+
default=0, description="Dropped as baseline-unstable, so sampling noise is not read as regression."
|
|
190
|
+
)
|
|
191
|
+
n_model_independent: int = Field(
|
|
192
|
+
default=0,
|
|
193
|
+
description="Cases the scaffold solved by its own computation under frozen_model_control — "
|
|
194
|
+
"excluded from the paired test rather than counted as fixed.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
effect: float | None = None
|
|
198
|
+
e_value: float | None = None
|
|
199
|
+
reject: bool = Field(default=False, description="This candidate's own gate. Not sufficient to win.")
|
|
200
|
+
verdict: Literal[
|
|
201
|
+
"fixed", "partial", "unsafe", "regressed", "no_effect", "not_executed", "model_independent"
|
|
202
|
+
] = Field(
|
|
203
|
+
description="The candidate's outcome. Subsumes a separate `fixed` boolean "
|
|
204
|
+
"(verdict == 'fixed') and a separate exec_error ('not_executed')."
|
|
205
|
+
)
|
|
206
|
+
summary: str = ""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class FixOutput(StageEnvelope):
|
|
210
|
+
"""``FixAgent.propose_and_validate`` result.
|
|
211
|
+
|
|
212
|
+
What each candidate literally applied — the prompt text, the generated code —
|
|
213
|
+
is not here. It is the ``result.json`` under ``trial_root``, which is also
|
|
214
|
+
where the per-case outputs and the execution record sit; inlining a code blob
|
|
215
|
+
would put a second, drifting copy in every report a reader loads.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
max_tier: Literal["L0", "L1", "L2", "L3a", "L3b", "L4"]
|
|
219
|
+
routed: list[dict[str, str]] = Field(
|
|
220
|
+
default_factory=list, description="Which hypothesis went to which tier, and why."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
attempted: list[FixAttemptWire] = Field(
|
|
224
|
+
default_factory=list,
|
|
225
|
+
description="The CONFIRMATION evidence: candidates validated on the held-out split. "
|
|
226
|
+
"With a confirm split in play this is exactly one frozen candidate — "
|
|
227
|
+
"selecting and testing on the same cases is the error the two-stage "
|
|
228
|
+
"protocol exists to prevent.",
|
|
229
|
+
)
|
|
230
|
+
selection: list[FixAttemptWire] = Field(
|
|
231
|
+
default_factory=list,
|
|
232
|
+
description="The candidates tried while CHOOSING one, on the diagnosis split. "
|
|
233
|
+
"Descriptive only — never confirmation evidence, and a reader must not "
|
|
234
|
+
"add these effect sizes to the ones above.\n\n"
|
|
235
|
+
"Carried because omitting it misrepresents the search: a run that tried "
|
|
236
|
+
"seven candidates across L1 and L2 and confirmed one L1 reported a "
|
|
237
|
+
"single L1 row, and every reader concluded L2 was never attempted. "
|
|
238
|
+
"What was ruled out is part of what the run found — an `unsafe` "
|
|
239
|
+
"candidate that repaired 7 cases and broke 8 is a result.",
|
|
240
|
+
)
|
|
241
|
+
selected_on_explore: str | None = Field(
|
|
242
|
+
default=None,
|
|
243
|
+
description="Which candidate the selection phase froze for confirmation. Names a "
|
|
244
|
+
"row in `selection`; `best` names one in `attempted`.",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
best: str | None = Field(
|
|
248
|
+
default=None, description="Winning candidate NAME. The row itself is in `attempted`."
|
|
249
|
+
)
|
|
250
|
+
fixed: bool = False
|
|
251
|
+
ebh_survivors: list[str] = Field(
|
|
252
|
+
default_factory=list,
|
|
253
|
+
description="Candidate names surviving e-BH across the whole tested family. A winner must be "
|
|
254
|
+
"BOTH individually `fixed` AND in this set — best-of-N needs its own correction.",
|
|
255
|
+
)
|
|
256
|
+
repair_rounds: int = Field(default=0, ge=0)
|
|
257
|
+
recommendation: dict[str, Any] | None = Field(
|
|
258
|
+
default=None,
|
|
259
|
+
description="{recommend_tier, reason} when nothing validated. Escalation is NEVER automatic.",
|
|
260
|
+
)
|
|
261
|
+
refine_signal: dict[str, Any] | None = Field(
|
|
262
|
+
default=None,
|
|
263
|
+
description="Set when a candidate helped one subset and hurt another — evidence the hypothesis "
|
|
264
|
+
"should be re-scoped, not that no fix exists. Feedback edge back into M3.",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@model_validator(mode="after")
|
|
268
|
+
def _fixed_requires_confirmed_survivor(self) -> "FixOutput":
|
|
269
|
+
if not self.fixed:
|
|
270
|
+
return self
|
|
271
|
+
if self.best is None:
|
|
272
|
+
raise ValueError("fixed=True requires a `best` candidate")
|
|
273
|
+
row = next((a for a in self.attempted if a.name == self.best), None)
|
|
274
|
+
if row is None:
|
|
275
|
+
raise ValueError(f"best={self.best!r} is not among the validated attempts")
|
|
276
|
+
if row.methodology is None:
|
|
277
|
+
raise ValueError(
|
|
278
|
+
"fixed=True without a methodology on the winning candidate: a shipped repair "
|
|
279
|
+
"must carry an explanation a reviewer can read"
|
|
280
|
+
)
|
|
281
|
+
if self.ebh_survivors and self.best not in self.ebh_survivors:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"best={self.best!r} is not an e-BH survivor; best-of-N selection without "
|
|
284
|
+
"family correction is not a validated fix"
|
|
285
|
+
)
|
|
286
|
+
return self
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
__all__ = [
|
|
290
|
+
"SurgeryInput", "InterventionOutput",
|
|
291
|
+
"FixInput", "FixAttemptWire", "FixOutput",
|
|
292
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Methodology — what an M5 repair or intervention actually DOES.
|
|
2
|
+
|
|
3
|
+
A candidate's method currently survives only as a code blob or a prompt string.
|
|
4
|
+
Neither answers the question a reader has ("what does this do to my model?"), so
|
|
5
|
+
the contract carries a diagram instead.
|
|
6
|
+
|
|
7
|
+
The contract is the draw.io XML and nothing else: a frontend embeds
|
|
8
|
+
``drawio_xml`` directly, with no toolchain and no intermediate format to
|
|
9
|
+
interpret. How a producer arrives at that XML is its own business —
|
|
10
|
+
``examples/contract/drawio_builder.py`` offers a step-graph with automatic
|
|
11
|
+
layout for producers that want one.
|
|
12
|
+
|
|
13
|
+
The XML is still validated on its own terms. A diagram whose edge points at a
|
|
14
|
+
cell that does not exist renders as a perfectly ordinary picture, so nothing
|
|
15
|
+
downstream — and no reader — would ever notice; that check has to happen here or
|
|
16
|
+
not at all.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import xml.etree.ElementTree as ET
|
|
22
|
+
|
|
23
|
+
from pydantic import Field, model_validator
|
|
24
|
+
|
|
25
|
+
from evalrx.contract.common import WireModel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MethodologyWire(WireModel):
|
|
29
|
+
"""A repair or intervention, as an embeddable diagram plus plain language."""
|
|
30
|
+
|
|
31
|
+
title: str = Field(min_length=1, max_length=120)
|
|
32
|
+
summary: str = Field(
|
|
33
|
+
default="", max_length=1000,
|
|
34
|
+
description="Two or three sentences a non-specialist can follow. Not the code.",
|
|
35
|
+
)
|
|
36
|
+
tier: str = Field(default="", description="L1..L4 for a fix; empty for an intervention.")
|
|
37
|
+
drawio_xml: str = Field(
|
|
38
|
+
min_length=1,
|
|
39
|
+
description="Uncompressed mxfile document, embeddable as-is. Compressed diagrams are "
|
|
40
|
+
"rejected: they cannot be diffed, reviewed, or repaired by hand.",
|
|
41
|
+
)
|
|
42
|
+
n_model_calls: int | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Model calls this method makes per case. A large claimed gain alongside few "
|
|
45
|
+
"model calls is the shape of a model-independent result, where the scaffold "
|
|
46
|
+
"rather than the model did the work.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@model_validator(mode="after")
|
|
50
|
+
def _xml_is_renderable(self) -> "MethodologyWire":
|
|
51
|
+
try:
|
|
52
|
+
root = ET.fromstring(self.drawio_xml)
|
|
53
|
+
except ET.ParseError as exc:
|
|
54
|
+
raise ValueError(f"drawio_xml is not well-formed XML: {exc}") from exc
|
|
55
|
+
if root.tag != "mxfile":
|
|
56
|
+
raise ValueError(f"drawio_xml root must be <mxfile>, got <{root.tag}>")
|
|
57
|
+
cells = list(root.iter("mxCell"))
|
|
58
|
+
if not cells:
|
|
59
|
+
raise ValueError("drawio_xml contains no mxCell elements")
|
|
60
|
+
ids = [c.get("id") for c in cells if c.get("id")]
|
|
61
|
+
dupes = {i for i in ids if ids.count(i) > 1}
|
|
62
|
+
if dupes:
|
|
63
|
+
raise ValueError(f"duplicate mxCell ids render unpredictably: {sorted(dupes)}")
|
|
64
|
+
known = set(ids)
|
|
65
|
+
for c in cells:
|
|
66
|
+
for end in ("source", "target"):
|
|
67
|
+
ref = c.get(end)
|
|
68
|
+
if ref and ref not in known:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"edge {c.get('id')!r} has {end}={ref!r}, which is not a cell in this "
|
|
71
|
+
"diagram — it draws as an arrow into empty space"
|
|
72
|
+
)
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = ["MethodologyWire"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Pre-M1 — probe search: synthesize new test cases instead of analyzing old ones.
|
|
2
|
+
|
|
3
|
+
Optional and structurally different from every other stage: its output is DATA,
|
|
4
|
+
not a measurement or a verdict, so it is the one stage whose result can be fed
|
|
5
|
+
back in as another stage's input.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pydantic import Field, model_validator
|
|
11
|
+
|
|
12
|
+
from evalrx.contract.common import CaseBatchRef, StageEnvelope, WireModel
|
|
13
|
+
from evalrx.contract.m1 import ModelRef, ProtocolWire
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProbeSearchInput(WireModel):
|
|
17
|
+
"""Seeds plus a budget. The search anchors both regime trees on the seeds."""
|
|
18
|
+
|
|
19
|
+
model: ModelRef
|
|
20
|
+
seed_pool: CaseBatchRef
|
|
21
|
+
protocol: ProtocolWire | None = None
|
|
22
|
+
budget: int = Field(default=20, ge=1, description="Total simulations (T_max).")
|
|
23
|
+
beta: float = Field(default=1.0, description="UCB exploration constant.")
|
|
24
|
+
w_max: int = Field(default=3, ge=1, description="Max children before progressive widening.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProbeSearchOutput(StageEnvelope):
|
|
28
|
+
"""Discovered cases plus search accounting.
|
|
29
|
+
|
|
30
|
+
``all_cases`` — not ``failure_cases`` — is what M1 should normally receive:
|
|
31
|
+
a batch of nothing but FAILs has no control group, and every downstream
|
|
32
|
+
comparison degenerates.
|
|
33
|
+
|
|
34
|
+
The in-memory result also holds ``macro_root`` / ``micro_root``, two trees
|
|
35
|
+
whose nodes point at both parent and children. That graph is cyclic and
|
|
36
|
+
cannot be serialized naively, which is why the search trajectory is carried
|
|
37
|
+
here as an explicit flat edge list rather than a nested object.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
n_simulations: int = Field(ge=0)
|
|
41
|
+
n_macro: int = Field(ge=0)
|
|
42
|
+
n_micro: int = Field(ge=0)
|
|
43
|
+
|
|
44
|
+
all_cases: CaseBatchRef = Field(description="Every evaluated case, PASS and FAIL. Feed this to M1.")
|
|
45
|
+
failure_cases: CaseBatchRef = Field(description="The FAIL subset. Feed this to cluster_failures.")
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def error_rate(self) -> float:
|
|
49
|
+
return self.failure_cases.n_cases / self.n_simulations if self.n_simulations else 0.0
|
|
50
|
+
|
|
51
|
+
@model_validator(mode="after")
|
|
52
|
+
def _counts_are_consistent(self) -> "ProbeSearchOutput":
|
|
53
|
+
if self.failure_cases.n_cases > self.all_cases.n_cases:
|
|
54
|
+
raise ValueError("failure_cases cannot exceed all_cases")
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = ["ProbeSearchInput", "ProbeSearchOutput"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""JSON Schema -> TypeScript for the stage contract.
|
|
2
|
+
|
|
3
|
+
Written in Python on purpose. ``export.py`` documented ``npx
|
|
4
|
+
json-schema-to-typescript`` as the way to get types, which puts a node toolchain
|
|
5
|
+
and a network fetch between a Python field and the frontend that has to decode
|
|
6
|
+
it — and a generation step that can fail for reasons unrelated to the contract
|
|
7
|
+
is a generation step CI will end up skipping. One language, one command, one
|
|
8
|
+
committed artifact that a diff check can police.
|
|
9
|
+
|
|
10
|
+
The output is a single ``.d.ts``: every stage's payload and every type it
|
|
11
|
+
references, so a frontend imports one file and gets the whole pipeline.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
_SCALARS = {
|
|
19
|
+
"string": "string", "integer": "number", "number": "number",
|
|
20
|
+
"boolean": "boolean", "null": "null",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _ts_type(schema: dict[str, Any]) -> str:
|
|
25
|
+
"""One JSON-Schema node as a TypeScript type expression."""
|
|
26
|
+
if not schema:
|
|
27
|
+
return "unknown"
|
|
28
|
+
if "$ref" in schema:
|
|
29
|
+
return schema["$ref"].rsplit("/", 1)[-1]
|
|
30
|
+
if "const" in schema:
|
|
31
|
+
return _literal(schema["const"])
|
|
32
|
+
if "enum" in schema:
|
|
33
|
+
return " | ".join(_literal(v) for v in schema["enum"]) or "never"
|
|
34
|
+
for key in ("anyOf", "oneOf"):
|
|
35
|
+
if key in schema:
|
|
36
|
+
parts = [_ts_type(s) for s in schema[key]]
|
|
37
|
+
# dict.fromkeys, not set: a stable union order keeps the generated
|
|
38
|
+
# file diffable, and a diff check is the only thing that makes a
|
|
39
|
+
# generated artifact trustworthy.
|
|
40
|
+
return " | ".join(dict.fromkeys(parts)) or "unknown"
|
|
41
|
+
if "allOf" in schema and len(schema["allOf"]) == 1:
|
|
42
|
+
return _ts_type(schema["allOf"][0])
|
|
43
|
+
|
|
44
|
+
kind = schema.get("type")
|
|
45
|
+
if isinstance(kind, list):
|
|
46
|
+
return " | ".join(dict.fromkeys(_SCALARS.get(k, "unknown") for k in kind))
|
|
47
|
+
if kind == "array":
|
|
48
|
+
items = schema.get("items") or schema.get("prefixItems")
|
|
49
|
+
if isinstance(items, list): # tuple, e.g. ci: (float, float)
|
|
50
|
+
return "[" + ", ".join(_ts_type(i) for i in items) + "]"
|
|
51
|
+
return f"({_ts_type(items or {})})[]"
|
|
52
|
+
if kind == "object":
|
|
53
|
+
extra = schema.get("additionalProperties")
|
|
54
|
+
value = _ts_type(extra) if isinstance(extra, dict) else "unknown"
|
|
55
|
+
return f"{{ [key: string]: {value} }}"
|
|
56
|
+
return _SCALARS.get(kind, "unknown")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _literal(value: Any) -> str:
|
|
60
|
+
if isinstance(value, str):
|
|
61
|
+
return '"' + value.replace('"', '\\"') + '"'
|
|
62
|
+
if isinstance(value, bool):
|
|
63
|
+
return "true" if value else "false"
|
|
64
|
+
if value is None:
|
|
65
|
+
return "null"
|
|
66
|
+
return str(value)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _doc(schema: dict[str, Any], indent: str) -> str:
|
|
70
|
+
"""Carry the Python docstring/description across. A generated type with no
|
|
71
|
+
prose sends the reader back to the Python file, which is the drift this
|
|
72
|
+
whole contract exists to avoid."""
|
|
73
|
+
text = (schema.get("description") or "").strip()
|
|
74
|
+
if not text:
|
|
75
|
+
return ""
|
|
76
|
+
lines = [ln.rstrip() for ln in text.splitlines()]
|
|
77
|
+
body = "\n".join(f"{indent} * {ln}".rstrip() for ln in lines)
|
|
78
|
+
return f"{indent}/**\n{body}\n{indent} */\n"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _interface(name: str, schema: dict[str, Any]) -> str:
|
|
82
|
+
if "enum" in schema and "properties" not in schema:
|
|
83
|
+
return f"{_doc(schema, '')}export type {name} = {_ts_type(schema)};\n"
|
|
84
|
+
props = schema.get("properties") or {}
|
|
85
|
+
required = set(schema.get("required") or ())
|
|
86
|
+
out = [_doc(schema, ""), f"export interface {name} {{\n"]
|
|
87
|
+
for field, sub in props.items():
|
|
88
|
+
opt = "" if field in required else "?"
|
|
89
|
+
out.append(_doc(sub, " "))
|
|
90
|
+
out.append(f" {field}{opt}: {_ts_type(sub)};\n")
|
|
91
|
+
if schema.get("additionalProperties") is True:
|
|
92
|
+
out.append(
|
|
93
|
+
" /** Producer-defined keys. The envelope is closed; payload areas are not. */\n"
|
|
94
|
+
" [key: string]: unknown;\n"
|
|
95
|
+
)
|
|
96
|
+
out.append("}\n")
|
|
97
|
+
return "".join(out)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def render(
|
|
101
|
+
schemas: dict[str, dict[str, Any]], *, schema_version: int,
|
|
102
|
+
aliases: "dict[str, tuple[Any, ...]] | None" = None,
|
|
103
|
+
) -> str:
|
|
104
|
+
"""Render every stage schema plus its ``$defs`` into one declaration file.
|
|
105
|
+
|
|
106
|
+
``aliases`` names literal unions that pydantic inlines at every use site
|
|
107
|
+
(``Modality`` is one). A frontend that wants to write ``slot: Modality``
|
|
108
|
+
cannot, unless the name survives generation — and passing them explicitly
|
|
109
|
+
beats inferring which inline unions "look like" a named type.
|
|
110
|
+
"""
|
|
111
|
+
defs: dict[str, dict[str, Any]] = {}
|
|
112
|
+
roots: dict[str, dict[str, Any]] = {}
|
|
113
|
+
for schema in schemas.values():
|
|
114
|
+
for name, sub in (schema.get("$defs") or {}).items():
|
|
115
|
+
defs.setdefault(name, sub)
|
|
116
|
+
root = {k: v for k, v in schema.items() if k != "$defs"}
|
|
117
|
+
roots.setdefault(root.get("title") or "Unnamed", root)
|
|
118
|
+
|
|
119
|
+
parts = [
|
|
120
|
+
"// GENERATED by `python -m evalrx.contract.export --out docs/contract`.\n"
|
|
121
|
+
"// Do not edit: Python is the source of truth (evalrx/contract/).\n"
|
|
122
|
+
f"// schema_version {schema_version}\n\n"
|
|
123
|
+
"/** Bumped only when a field's MEANING changes. Unknown fields must be\n"
|
|
124
|
+
" * ignored by readers, never rejected. */\n"
|
|
125
|
+
f"export const SCHEMA_VERSION = {schema_version};\n\n",
|
|
126
|
+
]
|
|
127
|
+
for name, values in sorted((aliases or {}).items()):
|
|
128
|
+
parts.append(
|
|
129
|
+
f"export type {name} = " + " | ".join(_literal(v) for v in values) + ";\n\n"
|
|
130
|
+
)
|
|
131
|
+
for name in sorted(defs):
|
|
132
|
+
if name in roots:
|
|
133
|
+
continue
|
|
134
|
+
parts.append(_interface(name, defs[name]) + "\n")
|
|
135
|
+
for name in sorted(roots):
|
|
136
|
+
parts.append(_interface(name, roots[name]) + "\n")
|
|
137
|
+
return "".join(parts)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = ["render"]
|
evalrx/core/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""EvalRX core — the sklearn-like substrate.
|
|
2
|
+
|
|
3
|
+
Public contracts every other module builds on:
|
|
4
|
+
|
|
5
|
+
Capability the vocabulary connecting models and analyzers
|
|
6
|
+
Model analyzable model: generate() + forward(capture)->Trace
|
|
7
|
+
Analyzer sklearn-style estimator: Analyzer(**params).run(model, data)->Result
|
|
8
|
+
FailureCase the central data unit; CaseBatch is a collection of them
|
|
9
|
+
Result uniform, agent-readable output (findings + artifacts)
|
|
10
|
+
registry discovery: list/match models and analyzers by capability
|
|
11
|
+
Pipeline compose analyzers
|
|
12
|
+
Experiment declarative spec the agent layer targets
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from evalrx.core.analyzer import Analyzer
|
|
16
|
+
from evalrx.core.capability import Capability, CapabilityError
|
|
17
|
+
from evalrx.core.case import (
|
|
18
|
+
CaseBatch,
|
|
19
|
+
FailureCase,
|
|
20
|
+
Inputs,
|
|
21
|
+
Label,
|
|
22
|
+
Provenance,
|
|
23
|
+
Source,
|
|
24
|
+
Step,
|
|
25
|
+
StepRole,
|
|
26
|
+
Trajectory,
|
|
27
|
+
as_casebatch,
|
|
28
|
+
)
|
|
29
|
+
from evalrx.core.experiment import Experiment, ExperimentRunner
|
|
30
|
+
from evalrx.core.model import CaptureSpec, Model, TokenLogprob, Trace
|
|
31
|
+
from evalrx.core.pipeline import Pipeline
|
|
32
|
+
from evalrx.core.registry import (
|
|
33
|
+
Registry,
|
|
34
|
+
register_analyzer,
|
|
35
|
+
register_model,
|
|
36
|
+
registry,
|
|
37
|
+
)
|
|
38
|
+
from evalrx.core.result import Result
|
|
39
|
+
from evalrx.core.spec import (
|
|
40
|
+
AttnSemantics,
|
|
41
|
+
AudioSpec,
|
|
42
|
+
ModelSpec,
|
|
43
|
+
ModulePaths,
|
|
44
|
+
VisionSpec,
|
|
45
|
+
)
|
|
46
|
+
from evalrx.core.tokentype import TokenTypeMap, build_token_type_map
|
|
47
|
+
from evalrx.core.tool import ChatTurn, Tool, ToolCall
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"Capability",
|
|
51
|
+
"CapabilityError",
|
|
52
|
+
"Model",
|
|
53
|
+
"Trace",
|
|
54
|
+
"TokenLogprob",
|
|
55
|
+
"CaptureSpec",
|
|
56
|
+
"Analyzer",
|
|
57
|
+
"FailureCase",
|
|
58
|
+
"CaseBatch",
|
|
59
|
+
"Inputs",
|
|
60
|
+
"Label",
|
|
61
|
+
"Provenance",
|
|
62
|
+
"Source",
|
|
63
|
+
"Step",
|
|
64
|
+
"StepRole",
|
|
65
|
+
"Trajectory",
|
|
66
|
+
"as_casebatch",
|
|
67
|
+
"ModelSpec",
|
|
68
|
+
"VisionSpec",
|
|
69
|
+
"AudioSpec",
|
|
70
|
+
"ModulePaths",
|
|
71
|
+
"AttnSemantics",
|
|
72
|
+
"Tool",
|
|
73
|
+
"ToolCall",
|
|
74
|
+
"ChatTurn",
|
|
75
|
+
"TokenTypeMap",
|
|
76
|
+
"build_token_type_map",
|
|
77
|
+
"Result",
|
|
78
|
+
"registry",
|
|
79
|
+
"Registry",
|
|
80
|
+
"register_model",
|
|
81
|
+
"register_analyzer",
|
|
82
|
+
"Pipeline",
|
|
83
|
+
"Experiment",
|
|
84
|
+
"ExperimentRunner",
|
|
85
|
+
]
|