"""Configuration helpers for the GCMD classifier MVP.""" from __future__ import annotations import os from collections.abc import Mapping from pydantic import BaseModel, ConfigDict, Field class ModelSettings(BaseModel): """Model and prompt settings loaded from environment-compatible mappings.""" model_config = ConfigDict(extra="forbid", frozen=True) provider: str = "fake" model_name: str = "fake-model" temperature: float = Field(default=0.0, ge=0.0) timeout_seconds: float = Field(default=30.0, gt=0.0) max_retries: int = Field(default=2, ge=0) prompt_version_topic: str = "topic-v1" prompt_version_term: str = "term-v1" prompt_version_variable: str = "variable-v1" api_key_env_var: str = "OPENAI_API_KEY" include_cost_metadata: bool = True @classmethod def from_environment(cls, env: Mapping[str, str] | None = None) -> ModelSettings: """Load model settings from an environment mapping without reading secrets.""" source = os.environ if env is None else env return cls( provider=source.get("MODEL_PROVIDER", cls.model_fields["provider"].default), model_name=source.get("MODEL_NAME", cls.model_fields["model_name"].default), temperature=_float_env( source, "MODEL_TEMPERATURE", cls.model_fields["temperature"].default, ), timeout_seconds=_float_env( source, "MODEL_TIMEOUT_SECONDS", cls.model_fields["timeout_seconds"].default, ), max_retries=_int_env( source, "MODEL_MAX_RETRIES", cls.model_fields["max_retries"].default, ), prompt_version_topic=source.get( "PROMPT_VERSION_TOPIC", cls.model_fields["prompt_version_topic"].default, ), prompt_version_term=source.get( "PROMPT_VERSION_TERM", cls.model_fields["prompt_version_term"].default, ), prompt_version_variable=source.get( "PROMPT_VERSION_VARIABLE", cls.model_fields["prompt_version_variable"].default, ), api_key_env_var=source.get( "MODEL_API_KEY_ENV_VAR", cls.model_fields["api_key_env_var"].default, ), include_cost_metadata=_bool_env( source, "MODEL_INCLUDE_COST_METADATA", cls.model_fields["include_cost_metadata"].default, ), ) def prompt_version_for_stage(self, stage: str) -> str: """Return the configured prompt version for a model-call stage.""" if stage == "topic": return self.prompt_version_topic if stage == "term": return self.prompt_version_term if stage == "variable": return self.prompt_version_variable raise ValueError(f"Unknown prompt stage: {stage}") def _float_env(source: Mapping[str, str], name: str, default: float) -> float: value = source.get(name) return default if value is None else float(value) def _int_env(source: Mapping[str, str], name: str, default: int) -> int: value = source.get(name) return default if value is None else int(value) def _bool_env(source: Mapping[str, str], name: str, default: bool) -> bool: value = source.get(name) if value is None: return default return value.strip().lower() in {"1", "true", "yes", "on"}