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,452 @@
|
|
|
1
|
+
"""Shared wire types for the M1-M4 stage contract.
|
|
2
|
+
|
|
3
|
+
These models describe the **serialized** shape crossing a stage boundary — what
|
|
4
|
+
lands on disk and what a frontend reads — not the in-memory Python objects.
|
|
5
|
+
The two are deliberately different: ``AnalysisReport.to_dict()`` drops
|
|
6
|
+
``raw_results`` and flattens ``findings`` to strings, so a contract written
|
|
7
|
+
against the in-memory dataclass would not describe what a reader actually gets.
|
|
8
|
+
|
|
9
|
+
Design rules enforced here (see ``docs/stage_io.md``):
|
|
10
|
+
|
|
11
|
+
1. **Envelope strict, payload free.** Fields the pipeline joins/aggregates on are
|
|
12
|
+
required and typed; analyzer- and dataset-specific content stays open
|
|
13
|
+
(``extra="allow"``) so a new analyzer never requires a contract change.
|
|
14
|
+
2. **One join key.** Every per-case row carries ``sample_id`` and it equals
|
|
15
|
+
:attr:`FailureCaseWire.id`. A broken join is reported, never silently
|
|
16
|
+
degraded into "no effect found" (see :class:`JoinReport`).
|
|
17
|
+
3. **Modalities are slots + sets, never combination enums.** Adding a modality
|
|
18
|
+
must not multiply the type space.
|
|
19
|
+
4. **Absent / null / empty / zero / false are five different things.** Every
|
|
20
|
+
optional field is explicitly ``| None`` so a reader can tell them apart; never
|
|
21
|
+
collapse them with a truthiness fallback.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import Any, Literal, TypeVar, Union
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
30
|
+
|
|
31
|
+
#: Bumped only when a field's *meaning* changes. Adding fields is additive and
|
|
32
|
+
#: does NOT bump it — readers must ignore unknown fields, not fail on them.
|
|
33
|
+
#:
|
|
34
|
+
#: 4: ``AnalyzerSelection.model_kind`` (a combination enum) replaced by modality
|
|
35
|
+
#: slots + ``is_agent``; ``StepWire.role`` realigned to ``core.case.StepRole``.
|
|
36
|
+
#: 5: stage ids M4 and M5 swapped so numbering follows execution order —
|
|
37
|
+
#: hypothesis verification is now ``m4`` (was ``m5``), surgery + fix are now
|
|
38
|
+
#: ``m5_surgery``/``m5_fix`` (were ``m4_surgery``/``m4_fix``).
|
|
39
|
+
SCHEMA_VERSION = 5
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WireModel(BaseModel):
|
|
43
|
+
"""Base for every model in this contract: strict envelope, no coercion."""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(
|
|
46
|
+
extra="forbid", # envelope fields are closed by default
|
|
47
|
+
strict=False, # allow int->float, but not str->int
|
|
48
|
+
validate_assignment=True,
|
|
49
|
+
frozen=False,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OpenWireModel(WireModel):
|
|
54
|
+
"""Base for payload areas whose content is producer-defined.
|
|
55
|
+
|
|
56
|
+
Unknown keys are kept (not dropped) so the raw inspector can show them and a
|
|
57
|
+
newer producer never loses data against an older reader.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(extra="allow")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# Enums — closed value sets the UI branches on
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
class Label(str, Enum):
|
|
68
|
+
"""Per-case outcome. Not a statistical verdict."""
|
|
69
|
+
|
|
70
|
+
PASS = "pass"
|
|
71
|
+
FAIL = "fail"
|
|
72
|
+
UNKNOWN = "unknown"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Source(str, Enum):
|
|
76
|
+
HUMAN = "human"
|
|
77
|
+
DATASET = "dataset"
|
|
78
|
+
AGENT = "agent"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class HypothesisStatus(str, Enum):
|
|
82
|
+
PROPOSED = "proposed"
|
|
83
|
+
TESTING = "testing"
|
|
84
|
+
SUPPORTED = "supported"
|
|
85
|
+
REFUTED = "refuted"
|
|
86
|
+
INCONCLUSIVE = "inconclusive"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EvidenceGrade(str, Enum):
|
|
90
|
+
"""Strength tier of the deciding evidence.
|
|
91
|
+
|
|
92
|
+
``INTERVENTION`` beats ``OBSERVATIONAL``: a paired contrast changed one
|
|
93
|
+
variable and re-ran, so it carries causal weight an association does not.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
INTERVENTION = "intervention"
|
|
97
|
+
OBSERVATIONAL = "observational"
|
|
98
|
+
NONE = "none"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class StageState(str, Enum):
|
|
102
|
+
"""Per-stage UI state. A single generic "N/A" loses information the reader
|
|
103
|
+
needs — in particular ``EMPTY`` (ran, produced nothing) and ``FAILED``
|
|
104
|
+
(did not run correctly) must never render the same."""
|
|
105
|
+
|
|
106
|
+
NOT_STARTED = "not_started"
|
|
107
|
+
RUNNING = "running"
|
|
108
|
+
SUCCEEDED = "succeeded"
|
|
109
|
+
EMPTY = "empty"
|
|
110
|
+
ABSTAINED = "abstained"
|
|
111
|
+
PARTIAL = "partial"
|
|
112
|
+
FAILED = "failed"
|
|
113
|
+
SKIPPED = "skipped"
|
|
114
|
+
UNAVAILABLE = "unavailable"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#: Open string set on purpose — a new modality must be one more member of a set,
|
|
118
|
+
#: never a new combination enum (VA / TIA / ... is 2^n and does not compose).
|
|
119
|
+
#:
|
|
120
|
+
#: LLM / VLM / ALM / AVLM are not four types in this contract; they are four
|
|
121
|
+
#: subsets of this set. An AVLM is ``{"text", "image", "audio"}`` and needs no
|
|
122
|
+
#: schema change to exist.
|
|
123
|
+
Modality = Literal["text", "image", "audio", "video"]
|
|
124
|
+
|
|
125
|
+
#: Every modality slot other than ``text``. Text is always present (a prompt is
|
|
126
|
+
#: mandatory), so it is never a *slot* — it is the floor.
|
|
127
|
+
MEDIA_SLOTS: tuple[Modality, ...] = ("image", "audio", "video")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# References — things that are NOT inline payload
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
class ExternalRef(WireModel):
|
|
135
|
+
"""Pointer replacing an inline value that exceeded the size budget.
|
|
136
|
+
|
|
137
|
+
Detect by shape (``kind == "external_ref"``), lazy-load ``path``, and keep
|
|
138
|
+
``n_items`` visible while loading. Never render the pointer as one item.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
kind: Literal["external_ref"] = "external_ref"
|
|
142
|
+
path: str = Field(description="Relative to runRoot. Never an absolute host path.")
|
|
143
|
+
n_items: int | None = Field(default=None, description="Item count in the target, when countable.")
|
|
144
|
+
bytes: int = Field(description="Serialized size of the externalized value.")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
T = TypeVar("T")
|
|
148
|
+
#: An inline value that may have been swapped for a pointer. Readers must handle
|
|
149
|
+
#: both arms; the discriminator is ``kind == "external_ref"``.
|
|
150
|
+
Externalizable = Union[T, ExternalRef]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class MediaRef(WireModel):
|
|
154
|
+
"""A media *reference*, never media bytes.
|
|
155
|
+
|
|
156
|
+
``kind`` tells the reader whether a preview is even possible: an in-memory
|
|
157
|
+
PIL image that was never persisted degrades to ``descriptor`` (e.g.
|
|
158
|
+
``"<image 640x480>"``), and offering a broken preview for it is a bug.
|
|
159
|
+
|
|
160
|
+
A ``path`` must be RELATIVE TO THE RUN ROOT, which means the run has to carry
|
|
161
|
+
the file: the media a case was evaluated on is part of the evidence, not an
|
|
162
|
+
external dependency. An absolute path is only meaningful on the machine that
|
|
163
|
+
produced it, so a run recording one is readable nowhere else — every audio
|
|
164
|
+
and video preview 404s and an audio-visual report silently becomes text. The
|
|
165
|
+
run logger copies case media under ``artifacts/case_media/`` for exactly this
|
|
166
|
+
reason; point at that copy, and keep the producer's original path (if it is
|
|
167
|
+
worth keeping at all) as provenance that nothing resolves against.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
kind: Literal["path", "url", "descriptor"]
|
|
171
|
+
value: str = Field(
|
|
172
|
+
description="Relative to runRoot when kind is 'path'. Never an absolute host path.",
|
|
173
|
+
)
|
|
174
|
+
mime: str | None = None
|
|
175
|
+
n_bytes: int | None = None
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def coerce(cls, value: Any) -> "MediaRef | None":
|
|
179
|
+
"""Build a ref from whatever a producer put in a modality slot.
|
|
180
|
+
|
|
181
|
+
``FailureCase.to_dict()`` runs every slot through ``_json_safe``, which
|
|
182
|
+
yields a bare string: a path/URL when the slot held one, or a descriptor
|
|
183
|
+
like ``"<image 640x480>"`` for a PIL object that was never persisted.
|
|
184
|
+
Both are legitimate producer output, so the contract classifies them here
|
|
185
|
+
rather than rejecting the case — a case is not invalid because its image
|
|
186
|
+
lived in memory.
|
|
187
|
+
"""
|
|
188
|
+
if value is None or isinstance(value, MediaRef):
|
|
189
|
+
return value
|
|
190
|
+
if isinstance(value, dict):
|
|
191
|
+
return cls.model_validate(value)
|
|
192
|
+
text = str(value).strip()
|
|
193
|
+
if not text:
|
|
194
|
+
return None
|
|
195
|
+
if text.startswith("<") and text.endswith(">"):
|
|
196
|
+
return cls(kind="descriptor", value=text)
|
|
197
|
+
if text.startswith(("http://", "https://", "data:", "s3://", "gs://")):
|
|
198
|
+
return cls(kind="url", value=text)
|
|
199
|
+
return cls(kind="path", value=text)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ArtifactRef(WireModel):
|
|
203
|
+
"""A heavy artifact (``.npy``, ``.png``, workspace dir) left on disk.
|
|
204
|
+
|
|
205
|
+
Heavy arrays never go inline: a browser must not parse a ``.npy``, and a
|
|
206
|
+
JSONL event must not carry an attention map.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
path: str = Field(description="Relative to runRoot.")
|
|
210
|
+
media_type: Literal["npy", "png", "jpg", "json", "text", "dir", "other"] = "other"
|
|
211
|
+
bytes: int | None = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# Case data — the raw rows every stage joins back to
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
class InputsWire(OpenWireModel):
|
|
219
|
+
"""The question, plus optional modality slots.
|
|
220
|
+
|
|
221
|
+
Slots are independent and may be filled in any combination. There is
|
|
222
|
+
deliberately no "modality" discriminator field: ``prompt + image`` and
|
|
223
|
+
``prompt + image + audio`` are the same type with different slots filled.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
prompt: str
|
|
227
|
+
image: MediaRef | None = None
|
|
228
|
+
audio: MediaRef | None = None
|
|
229
|
+
video: MediaRef | None = None
|
|
230
|
+
|
|
231
|
+
@field_validator("image", "audio", "video", mode="before")
|
|
232
|
+
@classmethod
|
|
233
|
+
def _coerce_media(cls, v: Any) -> Any:
|
|
234
|
+
return MediaRef.coerce(v)
|
|
235
|
+
|
|
236
|
+
def modalities(self) -> set[Modality]:
|
|
237
|
+
"""Modalities actually present on this case."""
|
|
238
|
+
present: set[Modality] = {"text"}
|
|
239
|
+
for slot in MEDIA_SLOTS:
|
|
240
|
+
if getattr(self, slot) is not None:
|
|
241
|
+
present.add(slot)
|
|
242
|
+
return present
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
#: Legacy role names this contract shipped before it was aligned to
|
|
246
|
+
#: :class:`evalrx.core.case.StepRole`. Mapped rather than rejected so a
|
|
247
|
+
#: trajectory written by an older producer still validates.
|
|
248
|
+
_LEGACY_STEP_ROLES = {"env": "tool", "critic": "verifier"}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class StepWire(OpenWireModel):
|
|
252
|
+
"""One step of an agent trajectory.
|
|
253
|
+
|
|
254
|
+
``role`` is exactly :class:`evalrx.core.case.StepRole`. It was not, and
|
|
255
|
+
the divergence was not cosmetic: ``planner`` / ``verifier`` / ``memory`` /
|
|
256
|
+
``system`` are values the producer emits every multi-agent run, and every one
|
|
257
|
+
of them failed validation here, while ``env`` / ``critic`` named steps no
|
|
258
|
+
producer can emit.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
idx: int
|
|
262
|
+
role: Literal[
|
|
263
|
+
"user", "planner", "actor", "tool", "verifier", "memory", "system"
|
|
264
|
+
] = "actor"
|
|
265
|
+
content: Any | None = None
|
|
266
|
+
agent_id: str = "main"
|
|
267
|
+
tool_call: dict[str, Any] | None = None
|
|
268
|
+
observation: Any | None = None
|
|
269
|
+
span: dict[str, Any] = Field(default_factory=dict, description="tokens / latency_ms / cost / model")
|
|
270
|
+
is_first_error: bool | None = None
|
|
271
|
+
failure_mode: str | None = None
|
|
272
|
+
judge_confidence: float | None = None
|
|
273
|
+
|
|
274
|
+
@field_validator("role", mode="before")
|
|
275
|
+
@classmethod
|
|
276
|
+
def _map_legacy_role(cls, v: Any) -> Any:
|
|
277
|
+
if isinstance(v, str):
|
|
278
|
+
return _LEGACY_STEP_ROLES.get(v, v)
|
|
279
|
+
return getattr(v, "value", v)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class TrajectoryWire(OpenWireModel):
|
|
283
|
+
"""Multi-step agent run. Present only for agent cases.
|
|
284
|
+
|
|
285
|
+
A trajectory is a *data* property, not a model capability: trajectory
|
|
286
|
+
analyzers run with ``model=None`` on rows loaded from disk, so eligibility
|
|
287
|
+
is gated on the batch in hand, not on what the model can do.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
sample_id: str = Field(description="Must equal the owning FailureCaseWire.id.")
|
|
291
|
+
goal: str = ""
|
|
292
|
+
steps: list[StepWire] = Field(default_factory=list)
|
|
293
|
+
final_answer: Any | None = None
|
|
294
|
+
ground_truth: Any | None = None
|
|
295
|
+
outcome: Label = Label.UNKNOWN
|
|
296
|
+
metrics: dict[str, Any] = Field(default_factory=dict)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class ProvenanceWire(OpenWireModel):
|
|
300
|
+
source: Source = Source.HUMAN
|
|
301
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class FailureCaseWire(OpenWireModel):
|
|
305
|
+
"""One evaluated case. ``id`` is the pipeline's single join key.
|
|
306
|
+
|
|
307
|
+
``expected`` / ``observed`` are intentionally ``Any``: a gold answer may be a
|
|
308
|
+
string, a bounding box, or a structured object depending on the benchmark.
|
|
309
|
+
Render strings directly and objects in a structured/raw view.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
id: str = Field(min_length=1, description="Stable join key. Every per-case row points here.")
|
|
313
|
+
inputs: InputsWire
|
|
314
|
+
expected: Any | None = None
|
|
315
|
+
observed: Any | None = None
|
|
316
|
+
trajectory: TrajectoryWire | None = None
|
|
317
|
+
label: Label = Label.UNKNOWN
|
|
318
|
+
tags: list[str] = Field(default_factory=list, description="Order is not semantically meaningful.")
|
|
319
|
+
provenance: ProvenanceWire = Field(default_factory=ProvenanceWire)
|
|
320
|
+
metadata: dict[str, Any] = Field(
|
|
321
|
+
default_factory=dict,
|
|
322
|
+
description="Benchmark-specific columns. Never assume a fixed schema here.",
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
@field_validator("trajectory")
|
|
326
|
+
@classmethod
|
|
327
|
+
def _trajectory_id_matches(cls, v: TrajectoryWire | None, info) -> TrajectoryWire | None:
|
|
328
|
+
# A trajectory whose sample_id differs from the case id gets registered
|
|
329
|
+
# under BOTH keys downstream, which double-counts that case in every
|
|
330
|
+
# group comparison. Reject the divergence at the boundary instead.
|
|
331
|
+
case_id = (info.data or {}).get("id")
|
|
332
|
+
if v is not None and case_id and v.sample_id != case_id:
|
|
333
|
+
raise ValueError(
|
|
334
|
+
f"trajectory.sample_id={v.sample_id!r} != case id={case_id!r}; "
|
|
335
|
+
"a divergent id double-counts this case in stats group splits"
|
|
336
|
+
)
|
|
337
|
+
return v
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class CaseBatchWire(WireModel):
|
|
341
|
+
"""Serialized ``CaseBatch``. The in-memory class has no ``to_dict()``, so
|
|
342
|
+
this shape is the contract for anything that crosses a process boundary."""
|
|
343
|
+
|
|
344
|
+
schema_version: int = SCHEMA_VERSION
|
|
345
|
+
n_cases: int
|
|
346
|
+
cases: list[FailureCaseWire]
|
|
347
|
+
|
|
348
|
+
@field_validator("cases")
|
|
349
|
+
@classmethod
|
|
350
|
+
def _ids_unique(cls, v: list[FailureCaseWire]) -> list[FailureCaseWire]:
|
|
351
|
+
ids = [c.id for c in v]
|
|
352
|
+
dupes = {i for i in ids if ids.count(i) > 1}
|
|
353
|
+
if dupes:
|
|
354
|
+
raise ValueError(f"duplicate case ids break every downstream join: {sorted(dupes)[:5]}")
|
|
355
|
+
return v
|
|
356
|
+
|
|
357
|
+
def probed_modalities(self) -> set[Modality]:
|
|
358
|
+
"""Slots this batch actually fills — the union over its cases.
|
|
359
|
+
|
|
360
|
+
Distinct from what the model *declares*, and it is the distinction that
|
|
361
|
+
decides analyzer routing: an omni model evaluated on an audio benchmark
|
|
362
|
+
declares image too, and routing on the declaration sends image analyzers
|
|
363
|
+
at a batch with no images in it.
|
|
364
|
+
"""
|
|
365
|
+
present: set[Modality] = set()
|
|
366
|
+
for case in self.cases:
|
|
367
|
+
present |= case.inputs.modalities()
|
|
368
|
+
return present or {"text"}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class CaseBatchRef(WireModel):
|
|
372
|
+
"""A batch referenced by file rather than inlined (the normal case)."""
|
|
373
|
+
|
|
374
|
+
path: str
|
|
375
|
+
n_cases: int
|
|
376
|
+
split: Literal["all", "explore", "confirm", "discovery"] | None = None
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
# Cross-cutting diagnostics
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
class JoinReport(WireModel):
|
|
384
|
+
"""Health of one ``sample_id`` <-> ``FailureCase.id`` join.
|
|
385
|
+
|
|
386
|
+
This exists because a broken join and a real null result are indistinguishable
|
|
387
|
+
downstream: unmatched ids are skipped, both groups come out empty, and the
|
|
388
|
+
tool reports "no significant association" — the same output as a model with
|
|
389
|
+
no defect. Emitting coverage makes "the pipeline is broken" a separate,
|
|
390
|
+
visible state from "the model is fine".
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
left: str = Field(description="e.g. 'attention.findings.per_case'")
|
|
394
|
+
right: str = Field(description="e.g. 'CaseBatch(explore)'")
|
|
395
|
+
n_left: int
|
|
396
|
+
n_right: int
|
|
397
|
+
n_matched: int
|
|
398
|
+
coverage: float = Field(ge=0.0, le=1.0, description="n_matched / n_left")
|
|
399
|
+
unmatched_sample: list[str] = Field(
|
|
400
|
+
default_factory=list, max_length=10, description="First few unmatched ids, for triage."
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def is_broken(self) -> bool:
|
|
405
|
+
"""Zero overlap is a plumbing failure, never a scientific finding."""
|
|
406
|
+
return self.n_left > 0 and self.n_matched == 0
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class StageStatus(WireModel):
|
|
410
|
+
"""Terminal state of one stage in one cycle.
|
|
411
|
+
|
|
412
|
+
``EMPTY`` and ``ABSTAINED`` are successes with nothing to show; ``FAILED``
|
|
413
|
+
and ``UNAVAILABLE`` are not. Collapsing them loses the only signal a reader
|
|
414
|
+
has for whether to trust the absence of a result.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
stage: Literal["pre_m1", "m1", "explore", "m2", "m3", "m5_surgery", "m5_fix", "m4"]
|
|
418
|
+
state: StageState
|
|
419
|
+
cycle: int = Field(description="Normal cycles start at 0; the post-loop fix uses -1.")
|
|
420
|
+
reason: str | None = Field(
|
|
421
|
+
default=None,
|
|
422
|
+
description="Why, for any state that is not plain success — the skip rule, the error, "
|
|
423
|
+
"the reason for abstaining. One field, because a reader wants one answer.",
|
|
424
|
+
)
|
|
425
|
+
duration_sec: float | None = None
|
|
426
|
+
joins: list[JoinReport] = Field(default_factory=list)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class StageEnvelope(WireModel):
|
|
430
|
+
"""Common header on every persisted stage output.
|
|
431
|
+
|
|
432
|
+
Which stage this is comes from ``status.stage`` — carrying it twice invites
|
|
433
|
+
the two copies to disagree, and the reader has no way to tell which is right.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
schema_version: int = SCHEMA_VERSION
|
|
437
|
+
trace_id: str = Field(description="Run-level correlation id.")
|
|
438
|
+
span_id: str | None = Field(default=None, description="e.g. 'c0.m1'.")
|
|
439
|
+
cycle: int = 0
|
|
440
|
+
produced_at: str = Field(description="ISO-8601 UTC.")
|
|
441
|
+
status: StageStatus
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
__all__ = [
|
|
445
|
+
"SCHEMA_VERSION", "WireModel", "OpenWireModel",
|
|
446
|
+
"Label", "Source", "HypothesisStatus", "EvidenceGrade", "StageState",
|
|
447
|
+
"Modality", "MEDIA_SLOTS",
|
|
448
|
+
"ExternalRef", "Externalizable", "MediaRef", "ArtifactRef",
|
|
449
|
+
"InputsWire", "StepWire", "TrajectoryWire", "ProvenanceWire",
|
|
450
|
+
"FailureCaseWire", "CaseBatchWire", "CaseBatchRef",
|
|
451
|
+
"JoinReport", "StageStatus", "StageEnvelope",
|
|
452
|
+
]
|