Files
resume-agent/backend/app/settings.py
T
2026-07-20 14:48:41 +08:00

105 lines
3.5 KiB
Python

from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
BACKEND_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_ENV_FILE = BACKEND_ROOT / ".env"
def _as_bool(value: str | None, default: bool) -> bool:
if value is None:
return default
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
raise ValueError(f"Invalid boolean configuration value: {value!r}")
def _as_int(name: str, value: str | None, default: int) -> int:
if value is None:
return default
parsed = int(value)
if parsed < 0:
raise ValueError(f"{name} must be non-negative")
return parsed
def _as_float(name: str, value: str | None, default: float) -> float:
if value is None:
return default
parsed = float(value)
if parsed <= 0:
raise ValueError(f"{name} must be positive")
return parsed
@dataclass(frozen=True, slots=True)
class Settings:
"""Runtime settings with the API key deliberately hidden from repr output."""
llm_provider: str = "auto"
openai_api_key: str | None = field(default=None, repr=False)
openai_base_url: str | None = None
openai_model: str = "gpt-4o-mini"
openai_timeout_seconds: float = 30.0
openai_max_retries: int = 2
structured_output_retries: int = 1
structured_output_mode: str = "json_schema"
fallback_to_rules: bool = True
@property
def use_openai(self) -> bool:
if self.llm_provider == "openai":
if not self.openai_api_key:
raise ValueError("OPENAI_API_KEY is required when LLM provider is openai")
return True
if self.llm_provider == "rule":
return False
if self.llm_provider != "auto":
raise ValueError("RESUME_AGENT_LLM_PROVIDER must be auto, openai, or rule")
return bool(self.openai_api_key)
def load_settings(env_file: str | Path | None = None) -> Settings:
selected_file = Path(
env_file or os.getenv("RESUME_AGENT_ENV_FILE", str(DEFAULT_ENV_FILE))
)
load_dotenv(selected_file, override=False)
mode = os.getenv("OPENAI_STRUCTURED_OUTPUT_MODE", "json_schema").strip().lower()
if mode not in {"json_schema", "json_object"}:
raise ValueError(
"OPENAI_STRUCTURED_OUTPUT_MODE must be json_schema or json_object"
)
provider = os.getenv("RESUME_AGENT_LLM_PROVIDER", "auto").strip().lower()
settings = Settings(
llm_provider=provider,
openai_api_key=os.getenv("OPENAI_API_KEY") or None,
openai_base_url=os.getenv("OPENAI_BASE_URL") or None,
openai_model=os.getenv("OPENAI_MODEL", "gpt-4o-mini").strip(),
openai_timeout_seconds=_as_float(
"OPENAI_TIMEOUT_SECONDS", os.getenv("OPENAI_TIMEOUT_SECONDS"), 30.0
),
openai_max_retries=_as_int(
"OPENAI_MAX_RETRIES", os.getenv("OPENAI_MAX_RETRIES"), 2
),
structured_output_retries=_as_int(
"OPENAI_STRUCTURED_OUTPUT_RETRIES",
os.getenv("OPENAI_STRUCTURED_OUTPUT_RETRIES"),
1,
),
structured_output_mode=mode,
fallback_to_rules=_as_bool(
os.getenv("RESUME_AGENT_LLM_FALLBACK_TO_RULES"), True
),
)
if not settings.openai_model:
raise ValueError("OPENAI_MODEL cannot be blank")
return settings