"""Runtime configuration for the GLM API and local processing pipeline.""" from __future__ import annotations import os from dataclasses import dataclass def _get_bool(name: str, default: bool) -> bool: """Read a strict boolean environment variable with a safe default.""" raw_value = os.getenv(name) if raw_value is None: return default normalized = raw_value.strip().lower() if normalized in {"1", "true", "yes", "on"}: return True if normalized in {"0", "false", "no", "off"}: return False raise ValueError(f"{name} must be a boolean value, got {raw_value!r}") def _get_int(name: str, default: int, minimum: int = 1) -> int: """Read and validate an integer environment variable.""" value = int(os.getenv(name, str(default))) if value < minimum: raise ValueError(f"{name} must be at least {minimum}, got {value}") return value def _get_float(name: str, default: float, minimum: float = 0.0) -> float: """Read and validate a floating-point environment variable.""" value = float(os.getenv(name, str(default))) if value < minimum: raise ValueError(f"{name} must be at least {minimum}, got {value}") return value @dataclass(frozen=True) class Settings: """Immutable settings used by API clients and command-line workflows.""" api_key: str api_base: str = "https://llm-api.cowin.run" api_path: str = "/v1/chat/completions" model: str = "glm-5.2" timeout_seconds: float = 300.0 max_retries: int = 5 max_tokens: int = 8192 temperature: float = 0.0 reasoning_effort: str = "high" thinking_enabled: bool = True @property def endpoint(self) -> str: """Return the normalized absolute chat-completions URL.""" base = self.api_base.rstrip("/") path = self.api_path if self.api_path.startswith("/") else f"/{self.api_path}" return f"{base}{path}" @classmethod def from_env(cls, *, require_api_key: bool = True) -> Settings: """Construct settings from environment variables. The API key is intentionally loaded only from ``GLM_API_KEY``. The project never reads a committed configuration file containing a key. """ api_key = os.getenv("GLM_API_KEY", "").strip() if require_api_key and not api_key: raise ValueError("GLM_API_KEY is required but was not set") reasoning_effort = os.getenv("GLM_REASONING_EFFORT", "high").strip().lower() if reasoning_effort not in {"low", "medium", "high", "max"}: raise ValueError( "GLM_REASONING_EFFORT must be one of: low, medium, high, max" ) return cls( api_key=api_key, api_base=os.getenv("GLM_API_BASE", "https://llm-api.cowin.run"), api_path=os.getenv("GLM_API_PATH", "/v1/chat/completions"), model=os.getenv("GLM_MODEL", "glm-5.2"), timeout_seconds=_get_float("GLM_TIMEOUT_SECONDS", 300.0, 1.0), max_retries=_get_int("GLM_MAX_RETRIES", 5, 0), max_tokens=_get_int("GLM_MAX_TOKENS", 8192, 1), temperature=_get_float("GLM_TEMPERATURE", 0.0, 0.0), reasoning_effort=reasoning_effort, thinking_enabled=_get_bool("GLM_THINKING_ENABLED", True), )