94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
"""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),
|
|
)
|