Files
local-translator/local_translator/config.py
T
2026-10-25 19:11:28 +03:00

79 lines
3.3 KiB
Python

from pathlib import Path
import tomllib
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_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"
structured_output: bool = False
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)
@model_validator(mode="before")
@classmethod
def misplaced_top_level_key(cls, data):
if isinstance(data, dict) and "allowed_origins" in data:
raise ValueError("allowed_origins is a top-level setting: move it above the first [section] in config.toml")
return data
@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)
# Web pages allowed to call this API from the browser, e.g. "https://games.example".
# No wildcard: any site could otherwise use your model and read your projects.
allowed_origins: list[str] = Field(default_factory=list, max_length=20)
providers: dict[str, ProviderConfig] = Field(default_factory=lambda: {
"lmstudio": ProviderConfig(prompt_style="hy_mt", structured_output=True, temperature=0.7, top_p=0.6, top_k=20, repetition_penalty=1.05)
})
@field_validator("allowed_origins")
@classmethod
def validate_origins(cls, value: list[str]) -> list[str]:
return list(dict.fromkeys(_check_origin(item) for item in value))
def _check_origin(value: str) -> str:
from urllib.parse import urlsplit
url = urlsplit(value)
if url.scheme not in {"http", "https"} or not url.hostname or url.path not in {"", "/"} or url.query or url.fragment or url.username:
raise ValueError("allowed_origins entries look like https://games.example[:port], without a path")
return f"{url.scheme}://{url.netloc}"
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