"""Model profile loading and resolution utilities.""" from __future__ import annotations from pathlib import Path from typing import Any import yaml REPO_ROOT = Path(__file__).resolve().parents[2] DEFAULT_MODEL_CONFIG = REPO_ROOT / "configs" / "models.yaml" LEGACY_MODEL_CONFIG = REPO_ROOT / "configs" / "model_profiles.yaml" class ModelConfigError(RuntimeError): pass def load_model_config(path: Path | None = None) -> dict[str, Any]: cfg_path = path or DEFAULT_MODEL_CONFIG if not cfg_path.exists() and LEGACY_MODEL_CONFIG.exists(): cfg_path = LEGACY_MODEL_CONFIG if not cfg_path.exists(): raise ModelConfigError(f"model config not found: {cfg_path}") try: data = yaml.safe_load(cfg_path.read_text(encoding="utf-8")) or {} except Exception as exc: raise ModelConfigError(f"invalid YAML in {cfg_path}: {exc}") from exc if not isinstance(data, dict): raise ModelConfigError(f"invalid model config shape in {cfg_path}") return data def resolve_model_profile( *, profile: str | None = None, overrides: dict[str, str] | None = None, path: Path | None = None, ) -> dict[str, Any]: cfg = load_model_config(path) profiles = cfg.get("profiles") or {} defaults = cfg.get("defaults") or {} selected = profile or defaults.get("profile") if not selected: raise ModelConfigError("no model profile provided and no defaults.profile set") if selected not in profiles: raise ModelConfigError(f"unknown model profile: {selected}") selected_profile = profiles[selected] or {} roles = dict(selected_profile.get("roles") or {}) task_types = dict(selected_profile.get("task_types") or defaults.get("task_types") or {}) if defaults.get("script_models"): for role, model in (defaults.get("script_models") or {}).items(): roles.setdefault(role, model) for role, model in (overrides or {}).items(): roles[role] = model return { "profile": selected, "description": selected_profile.get("description", ""), "roles": roles, "task_types": task_types, } def list_model_profiles(path: Path | None = None) -> list[str]: cfg = load_model_config(path) profiles = cfg.get("profiles") or {} return sorted(profiles.keys()) def parse_model_overrides(items: list[str] | None) -> dict[str, str]: out: dict[str, str] = {} for item in items or []: if "=" not in item: raise ModelConfigError(f"invalid override '{item}', expected role=model") role, model = item.split("=", 1) role = role.strip() model = model.strip() if not role or not model: raise ModelConfigError(f"invalid override '{item}', expected role=model") out[role] = model return out