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,497 @@
|
|
|
1
|
+
"""Published, machine-consumable schema for a run's logged events.
|
|
2
|
+
|
|
3
|
+
A run's events (``run_start``, ``probe``, ``diagnosis``, ...) are the
|
|
4
|
+
contract between a diagnosis run and everything downstream (dashboards,
|
|
5
|
+
parsers, the agent's own memory) — RunLoggerV2 stores them bucketed into
|
|
6
|
+
``run.json`` + per-stage ``M<n>/log.json`` rather than a flat JSONL file, but
|
|
7
|
+
each individual event has this same shape wherever it lives. Until this
|
|
8
|
+
module existed that contract lived only in docstrings + a table in
|
|
9
|
+
``docs/extending.md`` and an opaque ``schema_version`` int — a consumer had
|
|
10
|
+
nothing to *validate* against.
|
|
11
|
+
|
|
12
|
+
This module is that contract, as code:
|
|
13
|
+
|
|
14
|
+
* :func:`build_schema` returns a JSON Schema (Draft 2020-12) describing every
|
|
15
|
+
event type, built purely from the stdlib so importing it adds **no runtime
|
|
16
|
+
dependency** (the package's light-install promise is preserved — see
|
|
17
|
+
pyproject ``dependencies``).
|
|
18
|
+
* The rendered schema is committed next to this file as
|
|
19
|
+
``run_log.schema.json`` (shipped as package data) so non-Python consumers can
|
|
20
|
+
validate too; :func:`load_schema` reads it and a drift test keeps the two in
|
|
21
|
+
lock-step.
|
|
22
|
+
* :func:`validate_event` / :func:`iter_log_errors` validate real log lines.
|
|
23
|
+
They need the ``jsonschema`` package (a *dev*/optional dep, not core) and
|
|
24
|
+
raise a clear, actionable error if it is missing rather than failing obscurely.
|
|
25
|
+
|
|
26
|
+
Design note — the schema is intentionally **permissive**: it pins the common
|
|
27
|
+
envelope, the event discriminator, required fields and core types, but does
|
|
28
|
+
*not* forbid extra properties. That matches ``RUN_LOG_SCHEMA_VERSION``'s rule
|
|
29
|
+
that *additive* fields don't bump the version, so a newer producer emitting an
|
|
30
|
+
extra field still validates against an older schema. What it catches is the
|
|
31
|
+
breakage that actually matters: a missing required field, a wrong type, a
|
|
32
|
+
malformed timestamp, or an unknown event name.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
# Bump when an existing event's fields are renamed, removed, or change meaning,
|
|
42
|
+
# or when a new event TYPE is added (additive fields on an existing event don't
|
|
43
|
+
# need a bump). A downstream parser can branch on this instead of guessing
|
|
44
|
+
# from `evalrx_version`.
|
|
45
|
+
# v2: `analysis`'s stats_tool_results/stats_results/stats_plan/
|
|
46
|
+
# corrected_rejections are now conditionally externalized (see
|
|
47
|
+
# _externalize_if_large) — a {path, n_items, bytes} summary instead of
|
|
48
|
+
# the raw value once it exceeds _INLINE_MAX_BYTES.
|
|
49
|
+
# v3: two new event types for AgenticDiagnoseLoop — `agent_decision` (one judge
|
|
50
|
+
# decision turn: chosen tool + rationale, keyed by `step` not `cycle`) and
|
|
51
|
+
# `agent_tool` (the dispatch layer's accept/reject outcome for that tool
|
|
52
|
+
# call). VLDiagnoseLoop/AutoDiagnoseLoop's events are unchanged.
|
|
53
|
+
# v5: the EvalVitals → EvalRX rename renamed the `evalvitals_version` field to
|
|
54
|
+
# `evalrx_version` on every event and moved the published schema's `$id`
|
|
55
|
+
# to https://evalrx.dev/schemas/run_log.schema.json. Old logs still parse
|
|
56
|
+
# (the schema is permissive on unknown fields) but a strict
|
|
57
|
+
# `evalrx_version`-keyed reader needs this bump to tell them apart from
|
|
58
|
+
# pre-rename logs.
|
|
59
|
+
# v6: folded in the bucketed-storage events (`model_call`, `diagnose_report`,
|
|
60
|
+
# `unrouted`) and the `stage`/`event_seq`/`span_id` envelope fields that
|
|
61
|
+
# were previously a separate, RunLoggerV2-only schema layered on top of
|
|
62
|
+
# this one — there is now one event schema, not a base plus an overlay.
|
|
63
|
+
RUN_LOG_SCHEMA_VERSION = 6
|
|
64
|
+
|
|
65
|
+
#: The event types a run logs.
|
|
66
|
+
EVENT_TYPES: tuple[str, ...] = (
|
|
67
|
+
"run_start",
|
|
68
|
+
"probe",
|
|
69
|
+
"explore",
|
|
70
|
+
"analysis",
|
|
71
|
+
"diagnosis",
|
|
72
|
+
"surgery",
|
|
73
|
+
"experiment",
|
|
74
|
+
"fix",
|
|
75
|
+
"loop_end",
|
|
76
|
+
"tool_codegen",
|
|
77
|
+
"tool_registry",
|
|
78
|
+
"agent_decision",
|
|
79
|
+
"agent_tool",
|
|
80
|
+
"case_record",
|
|
81
|
+
"report_published",
|
|
82
|
+
"stage_skipped",
|
|
83
|
+
"model_call",
|
|
84
|
+
"diagnose_report",
|
|
85
|
+
"unrouted",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
#: Path to the committed, rendered schema shipped as package data.
|
|
89
|
+
SCHEMA_PATH = Path(__file__).with_name("run_log.schema.json")
|
|
90
|
+
|
|
91
|
+
# Reusable sub-schemas -------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
_HYPOTHESIS = {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"properties": {
|
|
96
|
+
"statement": {"type": "string"},
|
|
97
|
+
"plain_statement": {"type": ["string", "null"]},
|
|
98
|
+
"failure_mode": {"type": ["string", "null"]},
|
|
99
|
+
"status": {"type": ["string", "null"]},
|
|
100
|
+
"test_design": {"type": ["string", "null"]},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_JUDGE_IO = {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"prompt_path": {"type": "string"},
|
|
108
|
+
"prompt_chars": {"type": "integer"},
|
|
109
|
+
"raw_path": {"type": "string"},
|
|
110
|
+
"raw_chars": {"type": "integer"},
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# An M2 stats field is either its raw value (object/array) or, once it exceeds
|
|
115
|
+
# the inline-size threshold, an externalized {path, n_items, bytes} pointer.
|
|
116
|
+
_INLINE_OR_EXTERNAL = {"type": ["object", "array"]}
|
|
117
|
+
|
|
118
|
+
# Per-event field schemas + the fields each event always carries -------------
|
|
119
|
+
# (event, schema_version, ts, trace_id come from the shared envelope.)
|
|
120
|
+
|
|
121
|
+
_EVENTS: dict[str, dict[str, Any]] = {
|
|
122
|
+
"run_start": {
|
|
123
|
+
"required": [],
|
|
124
|
+
"properties": {
|
|
125
|
+
"evalrx_version": {"type": "string"},
|
|
126
|
+
"python_version": {"type": "string"},
|
|
127
|
+
"git_commit": {"type": ["string", "null"]},
|
|
128
|
+
"data_fingerprint": {"type": "string"},
|
|
129
|
+
"label_distribution": {"type": "object"},
|
|
130
|
+
"n_cases": {"type": "integer"},
|
|
131
|
+
"model": {"type": "string"},
|
|
132
|
+
# AgenticDiagnoseLoop only: the decision judge (distinct from the M3
|
|
133
|
+
# stage judge already covered by "judge") and its action-turn budget.
|
|
134
|
+
"decision_judge": {"type": "string"},
|
|
135
|
+
"max_actions": {"type": "integer"},
|
|
136
|
+
# In-cycle explore step configured (provider:model of its coder).
|
|
137
|
+
"explorer": {"type": "string"},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
"probe": {
|
|
141
|
+
"required": ["cycle", "analyzers", "findings", "artifact_paths"],
|
|
142
|
+
"properties": {
|
|
143
|
+
"analyzers": {"type": "array", "items": {"type": "string"}},
|
|
144
|
+
"findings": {"type": "object"},
|
|
145
|
+
"artifact_paths": {"type": "object"},
|
|
146
|
+
# Additive M1 fields (permissive schema): result_paths (per-analyzer
|
|
147
|
+
# complete result json), failed_analyzers (selected-but-errored),
|
|
148
|
+
# selected_analyzers — see RunLoggerV2.log_probe.
|
|
149
|
+
"selection_rationale": {"type": "string"},
|
|
150
|
+
"judge_io": _JUDGE_IO,
|
|
151
|
+
"duration_sec": {"type": "number"},
|
|
152
|
+
# Every generate/forward/logprobs/chat call an analyzer made against
|
|
153
|
+
# the target model this cycle (see model_instrumentation.py); full
|
|
154
|
+
# verbatim records live in the sibling model_calls.jsonl this points
|
|
155
|
+
# to, kept out of this event so an analyzer making dozens of calls
|
|
156
|
+
# per case doesn't drown out the cycle-level narrative.
|
|
157
|
+
"n_model_calls": {"type": "integer"},
|
|
158
|
+
"model_calls_path": {"type": "string"},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
"analysis": {
|
|
162
|
+
"required": ["cycle", "severity", "n_findings", "findings", "narrative"],
|
|
163
|
+
"properties": {
|
|
164
|
+
"severity": {"type": "string"},
|
|
165
|
+
"n_findings": {"type": "integer"},
|
|
166
|
+
"findings": {"type": "array", "items": {"type": "string"}},
|
|
167
|
+
"narrative": {"type": "string"},
|
|
168
|
+
"conclusion": {"type": "string"},
|
|
169
|
+
"evidence_chain": {"type": "array"},
|
|
170
|
+
"stats_tool_results": _INLINE_OR_EXTERNAL,
|
|
171
|
+
"stats_plan": _INLINE_OR_EXTERNAL,
|
|
172
|
+
"stats_results": _INLINE_OR_EXTERNAL,
|
|
173
|
+
"corrected_rejections": _INLINE_OR_EXTERNAL,
|
|
174
|
+
"visualizations": {"type": "array"},
|
|
175
|
+
"figures": {"type": "array"},
|
|
176
|
+
"judge_io": _JUDGE_IO,
|
|
177
|
+
"duration_sec": {"type": "number"},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
"explore": {
|
|
181
|
+
# Optional in-cycle explore step (VLDiagnoseLoop(explorer=...)): a
|
|
182
|
+
# free-form EDA pass over M1's per-case table between M1 and M2.
|
|
183
|
+
# DESCRIPTIVE ONLY — counts, observations, rendered figure paths and
|
|
184
|
+
# where the report/tables/figures were persisted; the explorer's
|
|
185
|
+
# in-sample candidate verdicts appear only as counts under
|
|
186
|
+
# `adjudication` and never enter the confirmatory M2 family.
|
|
187
|
+
"required": ["cycle", "ok", "n_observations", "n_charts", "n_tables"],
|
|
188
|
+
"properties": {
|
|
189
|
+
"ok": {"type": "boolean"},
|
|
190
|
+
"n_observations": {"type": "integer"},
|
|
191
|
+
"n_charts": {"type": "integer"},
|
|
192
|
+
"n_charts_rendered": {"type": "integer"},
|
|
193
|
+
"n_tables": {"type": "integer"},
|
|
194
|
+
"n_candidate_signals": {"type": "integer"},
|
|
195
|
+
"n_hypotheses": {"type": "integer"},
|
|
196
|
+
"adjudication": {"type": "object"},
|
|
197
|
+
"observations": {"type": "array", "items": {"type": "string"}},
|
|
198
|
+
"caveats": {"type": "array", "items": {"type": "string"}},
|
|
199
|
+
"figures": {"type": "array", "items": {"type": "string"}},
|
|
200
|
+
"attempts": {"type": "integer"},
|
|
201
|
+
"error": {"type": "string"},
|
|
202
|
+
"out_dir": {"type": "string"},
|
|
203
|
+
"report_path": {"type": "string"},
|
|
204
|
+
"duration_sec": {"type": "number"},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
"diagnosis": {
|
|
208
|
+
"required": ["cycle", "model_name", "n_hypotheses", "hypotheses", "raw_judge_output"],
|
|
209
|
+
"properties": {
|
|
210
|
+
"model_name": {"type": ["string", "null"]},
|
|
211
|
+
"n_hypotheses": {"type": "integer"},
|
|
212
|
+
"hypotheses": {"type": "array", "items": _HYPOTHESIS},
|
|
213
|
+
"raw_judge_output": {"type": ["string", "null"]},
|
|
214
|
+
# Provenance of the (UNCONFIRMED) explorer mechanism notes M3 was shown.
|
|
215
|
+
# Descriptive/display-only — never enters M2/M4/fix.
|
|
216
|
+
"referenced_charts": {"type": "array", "items": {"type": "string"}},
|
|
217
|
+
"explore_context_used": {"type": "boolean"},
|
|
218
|
+
"explore_figures": {"type": "array", "items": {"type": "string"}},
|
|
219
|
+
# Whether a (clustered, descriptive-only) FailureModeReport was
|
|
220
|
+
# shown to M3 — additive field, no schema version bump needed.
|
|
221
|
+
"failure_modes_used": {"type": "boolean"},
|
|
222
|
+
"judge_io": _JUDGE_IO,
|
|
223
|
+
"duration_sec": {"type": "number"},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
"surgery": {
|
|
227
|
+
"required": ["cycle", "module", "hypothesis", "failure_mode", "status", "fixed"],
|
|
228
|
+
"properties": {
|
|
229
|
+
"module": {"type": "string"},
|
|
230
|
+
"hypothesis": {"type": "string"},
|
|
231
|
+
"failure_mode": {"type": ["string", "null"]},
|
|
232
|
+
"status": {"type": "string"},
|
|
233
|
+
"fixed": {"type": "boolean"},
|
|
234
|
+
"confidence_score": {"type": ["number", "null"]},
|
|
235
|
+
"evidence": {"type": ["object", "null"]},
|
|
236
|
+
"n_refocused_cases": {"type": ["integer", "null"]},
|
|
237
|
+
"duration_sec": {"type": "number"},
|
|
238
|
+
# Additive: M4 protocol-consistency judge prompt/response, persisted
|
|
239
|
+
# under prompts/ (same pattern as probe/analysis/diagnosis).
|
|
240
|
+
"judge_io": _JUDGE_IO,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
"experiment": {
|
|
244
|
+
"required": ["cycle", "module", "hypothesis", "failure_mode", "status", "fixed"],
|
|
245
|
+
"properties": {
|
|
246
|
+
"module": {"type": "string"},
|
|
247
|
+
"hypothesis": {"type": "string"},
|
|
248
|
+
"failure_mode": {"type": ["string", "null"]},
|
|
249
|
+
"status": {"type": ["string", "null"]},
|
|
250
|
+
"fixed": {"type": "boolean"},
|
|
251
|
+
"provider": {"type": ["string", "null"]},
|
|
252
|
+
"metrics": {"type": ["object", "null"]},
|
|
253
|
+
"returncode": {"type": ["integer", "null"]},
|
|
254
|
+
"timed_out": {"type": ["boolean", "null"]},
|
|
255
|
+
"llm_calls": {"type": ["integer", "null"]},
|
|
256
|
+
"code_paths": {"type": "object"},
|
|
257
|
+
"output_paths": {"type": "object"},
|
|
258
|
+
"workspace_snapshot": {"type": ["object", "null"]},
|
|
259
|
+
"record": {"type": "string"},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
"fix": {
|
|
263
|
+
"required": ["cycle", "module"],
|
|
264
|
+
"properties": {
|
|
265
|
+
"module": {"type": "string"},
|
|
266
|
+
"record": {"type": "string"},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
"loop_end": {
|
|
270
|
+
"required": ["cycles"],
|
|
271
|
+
"properties": {
|
|
272
|
+
"cycles": {"type": "integer"},
|
|
273
|
+
"tokens_used": {"type": ["integer", "null"]},
|
|
274
|
+
"timings_sec": {"type": "object"},
|
|
275
|
+
"total_duration_sec": {"type": "number"},
|
|
276
|
+
"resolved": {"type": "boolean"},
|
|
277
|
+
"n_hypotheses": {"type": "integer"},
|
|
278
|
+
"final_hypotheses": {"type": "array", "items": _HYPOTHESIS},
|
|
279
|
+
"stopped_by": {"type": ["string", "null"]},
|
|
280
|
+
"n_verified": {"type": "integer"},
|
|
281
|
+
"verified_hypotheses": {"type": "array"},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
"tool_codegen": {
|
|
285
|
+
"required": ["cycle", "module", "tool_name", "need", "source", "ok"],
|
|
286
|
+
"properties": {
|
|
287
|
+
"module": {"type": "string"},
|
|
288
|
+
"tool_name": {"type": "string"},
|
|
289
|
+
"need": {"type": "string"},
|
|
290
|
+
"source": {"type": "string"},
|
|
291
|
+
"ok": {"type": "boolean"},
|
|
292
|
+
"error": {"type": ["string", "null"]},
|
|
293
|
+
"code_paths": {"type": "object"},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
"tool_registry": {
|
|
297
|
+
"required": ["cycle", "module", "n_tools", "tools"],
|
|
298
|
+
"properties": {
|
|
299
|
+
"module": {"type": "string"},
|
|
300
|
+
"n_tools": {"type": "integer"},
|
|
301
|
+
"tools": {"type": "array"},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
"agent_decision": {
|
|
305
|
+
"required": ["step", "action", "valid"],
|
|
306
|
+
"properties": {
|
|
307
|
+
"step": {"type": "integer"},
|
|
308
|
+
"action": {"type": "string"},
|
|
309
|
+
"params": {"type": "object"},
|
|
310
|
+
"rationale": {"type": "string"},
|
|
311
|
+
"valid": {"type": "boolean"},
|
|
312
|
+
"repair_attempts": {"type": "integer"},
|
|
313
|
+
"fallback_used": {"type": "boolean"},
|
|
314
|
+
"judge_io": _JUDGE_IO,
|
|
315
|
+
"duration_sec": {"type": "number"},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
"agent_tool": {
|
|
319
|
+
"required": ["step", "tool", "ok"],
|
|
320
|
+
"properties": {
|
|
321
|
+
"step": {"type": "integer"},
|
|
322
|
+
"tool": {"type": "string"},
|
|
323
|
+
"ok": {"type": "boolean"},
|
|
324
|
+
"summary": {"type": "string"},
|
|
325
|
+
"error": {"type": ["string", "null"]},
|
|
326
|
+
"duration_sec": {"type": "number"},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
"case_record": {
|
|
330
|
+
"required": ["case_id", "case", "media_paths"],
|
|
331
|
+
"properties": {
|
|
332
|
+
"case_id": {"type": "string"},
|
|
333
|
+
"case": {"type": "object"},
|
|
334
|
+
"media_paths": {"type": "array", "items": {"type": "string"}},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
"report_published": {
|
|
338
|
+
"required": ["report_schema_version", "catalog_version", "sha256", "generated_by"],
|
|
339
|
+
"properties": {
|
|
340
|
+
"report_schema_version": {"type": "integer"},
|
|
341
|
+
"catalog_version": {"type": "string"},
|
|
342
|
+
"json_render_version": {"type": "string"},
|
|
343
|
+
"source_event_seq": {"type": "integer"},
|
|
344
|
+
"sha256": {"type": "string"},
|
|
345
|
+
"generated_by": {"type": "object"},
|
|
346
|
+
"report_paths": {"type": "array", "items": {"type": "string"}},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
"stage_skipped": {
|
|
350
|
+
"required": ["stage", "cycle", "reason_code"],
|
|
351
|
+
"properties": {
|
|
352
|
+
"stage": {"type": "string"},
|
|
353
|
+
"reason_code": {"type": "string"},
|
|
354
|
+
"detail": {"type": "string"},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
"model_call": {
|
|
358
|
+
# A single generate/forward/logprobs/chat call against the target
|
|
359
|
+
# model, recorded immediately (see model_instrumentation.py). Either
|
|
360
|
+
# an analyzer's direct call (analyzer/method/call_index) or a
|
|
361
|
+
# judge/coder exchange (role/operation).
|
|
362
|
+
"required": ["cycle", "inputs", "output"],
|
|
363
|
+
"properties": {
|
|
364
|
+
"duration_sec": {"type": "number"},
|
|
365
|
+
"error": {"type": ["string", "null"]},
|
|
366
|
+
},
|
|
367
|
+
"anyOf": [{"required": ["analyzer", "method", "call_index"]},
|
|
368
|
+
{"required": ["role", "operation"]}],
|
|
369
|
+
},
|
|
370
|
+
"diagnose_report": {
|
|
371
|
+
"required": ["cycles", "hypotheses", "m4_results", "discovery"],
|
|
372
|
+
"properties": {key: {"type": "array"} for key in
|
|
373
|
+
("hypotheses", "m4_results", "discovery")},
|
|
374
|
+
},
|
|
375
|
+
"unrouted": {
|
|
376
|
+
# A caller passed a tag _resolve_stage couldn't map to M1-M5 — filed
|
|
377
|
+
# here instead of being lost.
|
|
378
|
+
"required": ["key", "tag"],
|
|
379
|
+
"properties": {
|
|
380
|
+
"key": {"type": "string"},
|
|
381
|
+
"tag": {"type": ["string", "null"]},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def build_schema() -> dict[str, Any]:
|
|
388
|
+
"""Return the JSON Schema (Draft 2020-12) for a run's logged events.
|
|
389
|
+
|
|
390
|
+
Built from the stdlib only — no third-party import — so this stays callable
|
|
391
|
+
in the light install. The single source of truth for the version pin is
|
|
392
|
+
:data:`RUN_LOG_SCHEMA_VERSION`. Validates one event at a time regardless of
|
|
393
|
+
where it's stored — a line in a flat log, or a record inside a bucketed
|
|
394
|
+
``run.json``/``M<n>/log.json`` array.
|
|
395
|
+
"""
|
|
396
|
+
envelope = {
|
|
397
|
+
"type": "object",
|
|
398
|
+
"required": ["event", "schema_version", "ts", "trace_id", "event_seq", "stage", "span_id"],
|
|
399
|
+
"properties": {
|
|
400
|
+
"event": {"type": "string", "enum": list(EVENT_TYPES)},
|
|
401
|
+
"schema_version": {"const": RUN_LOG_SCHEMA_VERSION},
|
|
402
|
+
# `format` is for documentation/interop; jsonschema only enforces it
|
|
403
|
+
# when the optional rfc3339-validator is installed, so a `pattern`
|
|
404
|
+
# pins the ISO-8601 shape unconditionally (it matches the producer's
|
|
405
|
+
# datetime.now(UTC).isoformat(timespec="microseconds")).
|
|
406
|
+
"ts": {
|
|
407
|
+
"type": "string",
|
|
408
|
+
"format": "date-time",
|
|
409
|
+
"pattern": r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$",
|
|
410
|
+
},
|
|
411
|
+
"trace_id": {"type": "string"},
|
|
412
|
+
"event_seq": {"type": "integer", "minimum": 1},
|
|
413
|
+
"span_id": {"type": "string"},
|
|
414
|
+
"stage": {"enum": ["RUN", "M1", "M2", "M3", "M4", "M5"]},
|
|
415
|
+
"cycle": {"type": "integer"},
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
defs: dict[str, Any] = {"envelope": envelope}
|
|
420
|
+
branches: list[dict[str, Any]] = []
|
|
421
|
+
for name, spec in _EVENTS.items():
|
|
422
|
+
variant: dict[str, Any] = {
|
|
423
|
+
"type": "object",
|
|
424
|
+
"properties": {"event": {"const": name}, **spec["properties"]},
|
|
425
|
+
"required": ["event", *spec["required"]],
|
|
426
|
+
}
|
|
427
|
+
if "anyOf" in spec:
|
|
428
|
+
variant["anyOf"] = spec["anyOf"]
|
|
429
|
+
branch = {"allOf": [{"$ref": "#/$defs/envelope"}, variant]}
|
|
430
|
+
defs[name] = branch
|
|
431
|
+
branches.append({"$ref": f"#/$defs/{name}"})
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
435
|
+
"$id": "https://evalrx.dev/schemas/run_log.schema.json",
|
|
436
|
+
"title": "EvalRX run event",
|
|
437
|
+
"description": (
|
|
438
|
+
"One logged event, wherever a run stores it (run.json / "
|
|
439
|
+
"M<n>/log.json). Discriminated on `event`; matches schema_version "
|
|
440
|
+
f"{RUN_LOG_SCHEMA_VERSION}. Permissive: extra (additive) fields "
|
|
441
|
+
"are allowed."
|
|
442
|
+
),
|
|
443
|
+
"$defs": defs,
|
|
444
|
+
"oneOf": branches,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def load_schema() -> dict[str, Any]:
|
|
449
|
+
"""Load the committed, rendered :data:`SCHEMA_PATH` (the shipped artifact)."""
|
|
450
|
+
return json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _validator(schema: dict[str, Any] | None = None):
|
|
454
|
+
"""Build a ``jsonschema`` validator with the date-time format checker on.
|
|
455
|
+
|
|
456
|
+
Raises a clear :class:`ImportError` if ``jsonschema`` (an optional dep) is
|
|
457
|
+
not installed, so the failure points at the fix instead of an obscure
|
|
458
|
+
``ModuleNotFoundError`` deep in a call stack.
|
|
459
|
+
"""
|
|
460
|
+
try:
|
|
461
|
+
import jsonschema
|
|
462
|
+
except ImportError as exc: # pragma: no cover - exercised via message only
|
|
463
|
+
raise ImportError(
|
|
464
|
+
"validating run_log.jsonl needs the 'jsonschema' package — "
|
|
465
|
+
"install it with `pip install jsonschema` or `pip install evalrx[dev]`."
|
|
466
|
+
) from exc
|
|
467
|
+
schema = schema if schema is not None else build_schema()
|
|
468
|
+
cls = jsonschema.validators.validator_for(schema)
|
|
469
|
+
cls.check_schema(schema)
|
|
470
|
+
return cls(schema, format_checker=cls.FORMAT_CHECKER)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def validate_event(event: dict[str, Any], *, schema: dict[str, Any] | None = None) -> None:
|
|
474
|
+
"""Validate a single decoded event; raise ``jsonschema.ValidationError`` if bad."""
|
|
475
|
+
_validator(schema).validate(event)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def iter_log_errors(path: str | Path, *, schema: dict[str, Any] | None = None):
|
|
479
|
+
"""Yield ``(line_number, message)`` for every invalid line in a JSONL log.
|
|
480
|
+
|
|
481
|
+
A convenience for CI / downstream consumers: parse-and-validate a whole
|
|
482
|
+
``run_log.jsonl`` in one call. Lines that aren't valid JSON are reported
|
|
483
|
+
too. An empty iterator means the file fully conforms.
|
|
484
|
+
"""
|
|
485
|
+
validator = _validator(schema)
|
|
486
|
+
with open(path, encoding="utf-8") as fh:
|
|
487
|
+
for i, line in enumerate(fh, start=1):
|
|
488
|
+
line = line.strip()
|
|
489
|
+
if not line:
|
|
490
|
+
continue
|
|
491
|
+
try:
|
|
492
|
+
event = json.loads(line)
|
|
493
|
+
except json.JSONDecodeError as exc:
|
|
494
|
+
yield i, f"invalid JSON: {exc}"
|
|
495
|
+
continue
|
|
496
|
+
for err in validator.iter_errors(event):
|
|
497
|
+
yield i, err.message
|