chore: import upstream snapshot with attribution
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
"""Config loading, merging, and validation for the eval framework.
|
||||
|
||||
Loads model and mode YAML configs from eval/configs/, merges them with
|
||||
mode overriding model on key conflicts, and validates required fields.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
# Resolve configs directory relative to this file (eval/configs/)
|
||||
_CONFIGS_DIR = Path(__file__).resolve().parent / "configs"
|
||||
|
||||
|
||||
def load_model_config(name: str) -> dict[str, Any]:
|
||||
"""Load a model config from configs/models/{name}.yaml."""
|
||||
path = _CONFIGS_DIR / "models" / f"{name}.yaml"
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Model config not found: {path}")
|
||||
with open(path) as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
|
||||
|
||||
def load_mode_config(name: str) -> dict[str, Any]:
|
||||
"""Load a mode config from configs/modes/{name}.yaml."""
|
||||
path = _CONFIGS_DIR / "modes" / f"{name}.yaml"
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Mode config not found: {path}")
|
||||
with open(path) as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
|
||||
|
||||
def merge_configs(model_config: dict[str, Any], mode_config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Deep-merge two config dicts. Mode values override model on key conflicts.
|
||||
|
||||
For nested dicts, merges recursively. For scalar values, mode wins.
|
||||
"""
|
||||
return _deep_merge(model_config, mode_config)
|
||||
|
||||
|
||||
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Recursively merge override into base. Override wins on conflicts."""
|
||||
result = dict(base)
|
||||
for key, value in override.items():
|
||||
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||
result[key] = _deep_merge(result[key], value)
|
||||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
_REQUIRED_FIELDS = [
|
||||
"model.model_name",
|
||||
"agent.agent_class",
|
||||
"environment.environment_class",
|
||||
]
|
||||
|
||||
|
||||
def validate_config(merged: dict[str, Any]) -> None:
|
||||
"""Validate that all required fields are present in the merged config.
|
||||
|
||||
Required fields use dot notation: 'model.model_name' means
|
||||
merged["model"]["model_name"].
|
||||
|
||||
Raises ValueError listing all missing fields if any are absent.
|
||||
"""
|
||||
missing = []
|
||||
for field in _REQUIRED_FIELDS:
|
||||
parts = field.split(".")
|
||||
current = merged
|
||||
found = True
|
||||
for part in parts:
|
||||
if not isinstance(current, dict) or part not in current:
|
||||
found = False
|
||||
break
|
||||
current = current[part]
|
||||
if not found:
|
||||
missing.append(field)
|
||||
|
||||
if missing:
|
||||
raise ValueError(f"Missing required config fields: {', '.join(missing)}")
|
||||
|
||||
|
||||
def list_configs() -> dict[str, list[str]]:
|
||||
"""Discover all YAML config files in configs/models/ and configs/modes/.
|
||||
|
||||
Returns a dict with 'models' and 'modes' keys, each containing a list
|
||||
of config names (without the .yaml extension).
|
||||
"""
|
||||
result: dict[str, list[str]] = {"models": [], "modes": []}
|
||||
for category in ("models", "modes"):
|
||||
directory = _CONFIGS_DIR / category
|
||||
if directory.is_dir():
|
||||
result[category] = sorted(
|
||||
p.stem for p in directory.iterdir() if p.suffix in (".yaml", ".yml") and p.is_file()
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user