from pathlib import Path import tomllib from typing import Literal from pydantic import BaseModel, ConfigDict, Field, field_validator class ProviderConfig(BaseModel): model_config = ConfigDict(extra="forbid") base_url: str = "http://127.0.0.1:1234/v1" model: str = "" api_key_env: str = "LM_STUDIO_API_KEY" timeout: float = Field(default=180, gt=0, le=3600) temperature: float = Field(default=0.2, ge=0, le=2) max_tokens: int = Field(default=4096, ge=1, le=131072) prompt_style: Literal["generic", "hy_mt"] = "generic" top_p: float | None = Field(default=None, gt=0, le=1) top_k: int | None = Field(default=None, ge=0) repetition_penalty: float | None = Field(default=None, gt=0, le=3) autostart: bool = False startup_timeout: float = Field(default=90, gt=0, le=600) load_model: str = "" load_context_length: int = Field(default=8192, ge=512, le=262144) @field_validator("base_url") @classmethod def validate_url(cls, value: str) -> str: from urllib.parse import urlsplit url = urlsplit(value) if url.scheme not in {"http", "https"} or not url.hostname or url.username or url.password or url.query or url.fragment: raise ValueError("Expected an HTTP(S) base URL without credentials, query or fragment") return value.rstrip("/") class Settings(BaseModel): model_config = ConfigDict(extra="forbid") database: str = "data/translations.sqlite3" port: int = Field(default=8787, ge=1, le=65535) providers: dict[str, ProviderConfig] = Field(default_factory=lambda: { "lmstudio": ProviderConfig(prompt_style="hy_mt", temperature=0.7, top_p=0.6, top_k=20, repetition_penalty=1.05) }) def load_settings(path: str = "config.toml") -> Settings: config_path = Path(path).resolve() if config_path.exists(): with config_path.open("rb") as stream: settings = Settings.model_validate(tomllib.load(stream)) else: settings = Settings() db = Path(settings.database) if not db.is_absolute(): settings.database = str(config_path.parent / db) return settings