1010from __future__ import annotations
1111
1212import json
13+ import re
1314from pathlib import Path
1415from typing import Any
1516from typing import Literal
1819from pydantic import BaseModel
1920from pydantic import ConfigDict
2021from pydantic import Field
22+ from pydantic import field_validator
23+ from pydantic import model_validator
24+
25+ _SAFE_ARTIFACT_LABEL = r"^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$"
26+ _WINDOWS_DEVICE_NAMES = {
27+ "aux" ,
28+ "con" ,
29+ "nul" ,
30+ "prn" ,
31+ * (f"com{ index } " for index in range (1 , 10 )),
32+ * (f"lpt{ index } " for index in range (1 , 10 )),
33+ }
34+
35+
36+ def _validate_artifact_label (
37+ value : str ,
38+ * ,
39+ subject : str ,
40+ reserved : set [str ] | None = None ,
41+ ) -> str :
42+ if re .fullmatch (_SAFE_ARTIFACT_LABEL , value ) is None :
43+ raise ValueError (f"{ subject } must be a safe artifact label" )
44+ folded = value .casefold ()
45+ if folded in _WINDOWS_DEVICE_NAMES or folded in (reserved or set ()):
46+ raise ValueError (f"{ subject } is a reserved artifact label: { value !r} " )
47+ return value
48+
49+
50+ def _casefold_duplicates (values : list [str ]) -> list [str ]:
51+ groups : dict [str , list [str ]] = {}
52+ for value in values :
53+ groups .setdefault (value .casefold (), []).append (value )
54+ return sorted (" == " .join (group ) for group in groups .values () if len (group ) > 1 )
2155
2256
2357class _StrictModel (BaseModel ):
@@ -27,9 +61,18 @@ class _StrictModel(BaseModel):
2761class CandidateSource (_StrictModel ):
2862 """A deterministic prompt proposal used by the offline reflection model."""
2963
30- candidate_id : str
64+ candidate_id : str = Field ( pattern = _SAFE_ARTIFACT_LABEL )
3165 path : Path
3266
67+ @field_validator ("candidate_id" )
68+ @classmethod
69+ def _candidate_id_is_artifact_safe (cls , value : str ) -> str :
70+ return _validate_artifact_label (
71+ value ,
72+ subject = "candidate_id" ,
73+ reserved = {"baseline" },
74+ )
75+
3376
3477class PipelineSpec (_StrictModel ):
3578 """All paths and run controls needed by :class:`EvalOptimizePipeline`."""
@@ -40,14 +83,29 @@ class PipelineSpec(_StrictModel):
4083 gate_config : Path
4184 train_dataset : Path
4285 validation_dataset : Path
43- target_prompts : dict [str , Path ]
44- candidate_sources : list [CandidateSource ]
86+ target_prompts : dict [str , Path ] = Field ( min_length = 1 )
87+ candidate_sources : list [CandidateSource ] = Field ( min_length = 1 )
4588 output_dir : Path
4689 seed : int = 91
4790 bootstrap_samples : int = Field (default = 2000 , ge = 100 )
4891 confidence_level : float = Field (default = 0.95 , gt = 0.0 , lt = 1.0 )
4992 apply_if_accepted : bool = False
5093
94+ @model_validator (mode = "after" )
95+ def _validate_artifact_labels (self ) -> "PipelineSpec" :
96+ candidate_ids = [source .candidate_id for source in self .candidate_sources ]
97+ duplicate_ids = _casefold_duplicates (candidate_ids )
98+ if duplicate_ids :
99+ raise ValueError (f"candidate_ids must be case-insensitively unique: { duplicate_ids } " )
100+ prompt_names = list (self .target_prompts )
101+ duplicate_prompt_names = _casefold_duplicates (prompt_names )
102+ if duplicate_prompt_names :
103+ raise ValueError ("target prompt names must be case-insensitively unique: "
104+ f"{ duplicate_prompt_names } " )
105+ for name in prompt_names :
106+ _validate_artifact_label (name , subject = "target prompt name" )
107+ return self
108+
51109 @classmethod
52110 def from_file (
53111 cls ,
@@ -94,8 +152,8 @@ def _resolve(value: str | Path) -> Path:
94152
95153class MetricOutcome (_StrictModel ):
96154 metric_name : str
97- score : Optional [float ] = None
98- threshold : float
155+ score : Optional [float ] = Field ( default = None , allow_inf_nan = False )
156+ threshold : float = Field ( allow_inf_nan = False )
99157 passed : bool
100158 reason : str = ""
101159
@@ -115,7 +173,7 @@ class TrajectoryStep(_StrictModel):
115173class CaseEvaluation (_StrictModel ):
116174 case_id : str
117175 passed : bool
118- score : float
176+ score : float = Field ( allow_inf_nan = False )
119177 key_case : bool = False
120178 hard_fail : bool = False
121179 metrics : list [MetricOutcome ] = Field (default_factory = list )
@@ -128,8 +186,8 @@ class CaseEvaluation(_StrictModel):
128186
129187class SplitEvaluation (_StrictModel ):
130188 split : Literal ["train" , "validation" ]
131- pass_rate : float
132- average_score : float
189+ pass_rate : float = Field ( ge = 0.0 , le = 1.0 , allow_inf_nan = False )
190+ average_score : float = Field ( allow_inf_nan = False )
133191 cases : list [CaseEvaluation ]
134192
135193
@@ -152,24 +210,24 @@ class CaseDelta(_StrictModel):
152210 status : DeltaStatus
153211 baseline_passed : bool
154212 candidate_passed : bool
155- baseline_score : float
156- candidate_score : float
157- score_delta : float
213+ baseline_score : float = Field ( allow_inf_nan = False )
214+ candidate_score : float = Field ( allow_inf_nan = False )
215+ score_delta : float = Field ( allow_inf_nan = False )
158216
159217
160218class PairedConfidenceInterval (_StrictModel ):
161- point_estimate : float
162- lower : float
163- upper : float
164- confidence_level : float
165- bootstrap_samples : int
219+ point_estimate : float = Field ( allow_inf_nan = False )
220+ lower : float = Field ( allow_inf_nan = False )
221+ upper : float = Field ( allow_inf_nan = False )
222+ confidence_level : float = Field ( gt = 0.0 , lt = 1.0 , allow_inf_nan = False )
223+ bootstrap_samples : int = Field ( ge = 1 )
166224 seed : int
167225
168226
169227class SplitDelta (_StrictModel ):
170228 split : Literal ["train" , "validation" ]
171- pass_rate_delta : float
172- average_score_delta : float
229+ pass_rate_delta : float = Field ( allow_inf_nan = False )
230+ average_score_delta : float = Field ( allow_inf_nan = False )
173231 paired_pass_rate_ci : PairedConfidenceInterval
174232 newly_passed : list [str ] = Field (default_factory = list )
175233 newly_failed : list [str ] = Field (default_factory = list )
@@ -200,15 +258,15 @@ class GateDecision(_StrictModel):
200258
201259
202260class ResourceUsage (_StrictModel ):
203- metric_calls : int = 0
204- reflection_calls : int = 0
205- judge_calls : Optional [int ] = None
206- prompt_tokens : int = 0
207- completion_tokens : int = 0
208- total_tokens : int = 0
209- cost_usd : Optional [float ] = None
210- duration_seconds : float = 0.0
211- p95_latency_ms : Optional [float ] = None
261+ metric_calls : int = Field ( default = 0 , ge = 0 )
262+ reflection_calls : int = Field ( default = 0 , ge = 0 )
263+ judge_calls : Optional [int ] = Field ( default = None , ge = 0 )
264+ prompt_tokens : int = Field ( default = 0 , ge = 0 )
265+ completion_tokens : int = Field ( default = 0 , ge = 0 )
266+ total_tokens : int = Field ( default = 0 , ge = 0 )
267+ cost_usd : Optional [float ] = Field ( default = None , ge = 0.0 , allow_inf_nan = False )
268+ duration_seconds : float = Field ( default = 0.0 , ge = 0.0 , allow_inf_nan = False )
269+ p95_latency_ms : Optional [float ] = Field ( default = None , ge = 0.0 , allow_inf_nan = False )
212270 cost_measurement : str = "unavailable"
213271
214272
@@ -236,17 +294,17 @@ class OptimizerAudit(_StrictModel):
236294 status : str
237295 stop_reason : Optional [str ] = None
238296 used_agent_optimizer : bool
239- baseline_pass_rate : float
240- best_pass_rate : float
241- rounds : int
297+ baseline_pass_rate : float = Field ( ge = 0.0 , le = 1.0 , allow_inf_nan = False )
298+ best_pass_rate : float = Field ( ge = 0.0 , le = 1.0 , allow_inf_nan = False )
299+ rounds : int = Field ( ge = 0 )
242300 resources : ResourceUsage
243301 artifact_dir : str
244302
245303
246304class DataQualityAudit (_StrictModel ):
247305 passed : bool
248- train_cases : int
249- validation_cases : int
306+ train_cases : int = Field ( ge = 0 )
307+ validation_cases : int = Field ( ge = 0 )
250308 duplicate_ids : list [str ] = Field (default_factory = list )
251309 cross_split_duplicates : list [str ] = Field (default_factory = list )
252310 near_cross_split_duplicates : list [str ] = Field (default_factory = list )
@@ -257,7 +315,7 @@ class RunAudit(_StrictModel):
257315 run_id : str
258316 started_at : str
259317 finished_at : str
260- duration_seconds : float
318+ duration_seconds : float = Field ( ge = 0.0 , allow_inf_nan = False )
261319 seed : int
262320 config_sha256 : str
263321 train_sha256 : str
@@ -267,9 +325,9 @@ class RunAudit(_StrictModel):
267325
268326
269327class FailureAttributionSummary (_StrictModel ):
270- explained_failed_cases : int
271- total_failed_cases : int
272- coverage_rate : float
328+ explained_failed_cases : int = Field ( ge = 0 )
329+ total_failed_cases : int = Field ( ge = 0 )
330+ coverage_rate : float = Field ( ge = 0.0 , le = 1.0 , allow_inf_nan = False )
273331 category_counts : dict [str , int ] = Field (default_factory = dict )
274332 by_case : dict [str , list [FailureReason ]] = Field (default_factory = dict )
275333
0 commit comments