Spaces:
Sleeping
Sleeping
| """Model configuration — secrets-free config for ACE roles. | |
| ``ModelConfig`` describes which model to use and how. No API keys — | |
| those come from the environment (via ``.env`` or exported variables). | |
| ``ACEModelConfig`` maps ACE roles (agent, reflector, skill_manager) | |
| to individual ``ModelConfig`` instances, enabling per-role model selection. | |
| Config is persisted in ``ace.toml`` (committable, no secrets). | |
| Keys are persisted in ``.env`` (gitignored). | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import tomllib | |
| from dataclasses import dataclass, fields | |
| from pathlib import Path | |
| from typing import Any | |
| logger = logging.getLogger(__name__) | |
| CONFIG_FILENAME = "ace.toml" | |
| ENV_FILENAME = ".env" | |
| # --------------------------------------------------------------------------- | |
| # ModelConfig | |
| # --------------------------------------------------------------------------- | |
| class ModelConfig: | |
| """Configuration for a single LLM role. No secrets.""" | |
| model: str | |
| temperature: float = 0.0 | |
| max_tokens: int = 2048 | |
| extra_params: dict[str, Any] | None = None | |
| def to_dict(self) -> dict[str, Any]: | |
| """Serialise to a dict, omitting None/default values.""" | |
| d: dict[str, Any] = {"model": self.model} | |
| if self.temperature != 0.0: | |
| d["temperature"] = self.temperature | |
| if self.max_tokens != 2048: | |
| d["max_tokens"] = self.max_tokens | |
| if self.extra_params: | |
| d["extra_params"] = self.extra_params | |
| return d | |
| def from_dict(cls, d: dict[str, Any]) -> ModelConfig: | |
| known = {f.name for f in fields(cls)} | |
| return cls(**{k: v for k, v in d.items() if k in known}) | |
| # --------------------------------------------------------------------------- | |
| # ACEModelConfig | |
| # --------------------------------------------------------------------------- | |
| class ACEModelConfig: | |
| """Model selection per ACE role. No secrets — keys come from env.""" | |
| default: ModelConfig | |
| agent: ModelConfig | None = None | |
| reflector: ModelConfig | None = None | |
| skill_manager: ModelConfig | None = None | |
| def for_role(self, role: str) -> ModelConfig: | |
| """Return the ModelConfig for *role*, falling back to default.""" | |
| explicit = getattr(self, role, None) | |
| if explicit is not None: | |
| return explicit | |
| return self.default | |
| # -- Serialisation -------------------------------------------------------- | |
| def to_dict(self) -> dict[str, Any]: | |
| d: dict[str, Any] = {"default": self.default.to_dict()} | |
| for role in ("agent", "reflector", "skill_manager"): | |
| cfg = getattr(self, role) | |
| if cfg is not None: | |
| d[role] = cfg.to_dict() | |
| return d | |
| def from_dict(cls, d: dict[str, Any]) -> ACEModelConfig: | |
| default = ModelConfig.from_dict(d["default"]) | |
| agent = ModelConfig.from_dict(d["agent"]) if "agent" in d else None | |
| reflector = ModelConfig.from_dict(d["reflector"]) if "reflector" in d else None | |
| skill_manager = ( | |
| ModelConfig.from_dict(d["skill_manager"]) if "skill_manager" in d else None | |
| ) | |
| return cls( | |
| default=default, | |
| agent=agent, | |
| reflector=reflector, | |
| skill_manager=skill_manager, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # TOML persistence | |
| # --------------------------------------------------------------------------- | |
| def _to_toml(config: ACEModelConfig) -> str: | |
| """Serialise ACEModelConfig to TOML string.""" | |
| lines: list[str] = [] | |
| for section_name in ("default", "agent", "reflector", "skill_manager"): | |
| cfg = getattr(config, section_name) | |
| if cfg is None: | |
| continue | |
| lines.append(f"[{section_name}]") | |
| d = cfg.to_dict() | |
| for key, value in d.items(): | |
| if key == "extra_params": | |
| # Inline table for extra_params | |
| inner = ", ".join(f"{k} = {_toml_value(v)}" for k, v in value.items()) | |
| lines.append(f"extra_params = {{ {inner} }}") | |
| else: | |
| lines.append(f"{key} = {_toml_value(value)}") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def _toml_value(v: Any) -> str: | |
| """Format a Python value as a TOML literal.""" | |
| if isinstance(v, str): | |
| return f'"{v}"' | |
| if isinstance(v, bool): | |
| return "true" if v else "false" | |
| if isinstance(v, float): | |
| return str(v) | |
| if isinstance(v, int): | |
| return str(v) | |
| return repr(v) | |
| def save_config(config: ACEModelConfig, directory: str | Path = ".") -> Path: | |
| """Write ace.toml to *directory*.""" | |
| path = Path(directory) / CONFIG_FILENAME | |
| path.write_text(_to_toml(config), encoding="utf-8") | |
| logger.info("Saved config to %s", path) | |
| return path | |
| def load_config(directory: str | Path = ".") -> ACEModelConfig: | |
| """Load ace.toml from *directory*. | |
| Raises: | |
| FileNotFoundError: If ace.toml does not exist. | |
| """ | |
| path = Path(directory) / CONFIG_FILENAME | |
| if not path.exists(): | |
| raise FileNotFoundError( | |
| f"No {CONFIG_FILENAME} found in {Path(directory).resolve()}. " | |
| "Run `ace setup` to create one." | |
| ) | |
| data = tomllib.loads(path.read_text(encoding="utf-8")) | |
| return ACEModelConfig.from_dict(data) | |
| def find_config(start: str | Path = ".") -> Path | None: | |
| """Walk up from *start* looking for ace.toml. Return path or None.""" | |
| current = Path(start).resolve() | |
| for parent in [current, *current.parents]: | |
| candidate = parent / CONFIG_FILENAME | |
| if candidate.exists(): | |
| return candidate | |
| return None | |
| # --------------------------------------------------------------------------- | |
| # .env helpers | |
| # --------------------------------------------------------------------------- | |
| def load_dotenv() -> bool: | |
| """Load .env if python-dotenv is installed. Return True if loaded.""" | |
| try: | |
| from dotenv import load_dotenv as _load | |
| return _load() | |
| except ImportError: | |
| return False | |
| def save_env_var(key: str, value: str, directory: str | Path = ".") -> None: | |
| """Append or update a key in .env file.""" | |
| path = Path(directory) / ENV_FILENAME | |
| lines: list[str] = [] | |
| found = False | |
| if path.exists(): | |
| for line in path.read_text(encoding="utf-8").splitlines(): | |
| if line.startswith(f"{key}="): | |
| lines.append(f'{key}="{value}"') | |
| found = True | |
| else: | |
| lines.append(line) | |
| if not found: | |
| lines.append(f'{key}="{value}"') | |
| path.write_text("\n".join(lines) + "\n", encoding="utf-8") | |