127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
"""Central configuration — all values from environment, zero hard-coded secrets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
|
|
class LLMConfig(BaseModel):
|
|
"""LLM worker configuration."""
|
|
|
|
base_url: str = Field(..., description="Base URL of the LLM provider (OpenAI-compatible).")
|
|
model: str = Field(..., description="Model identifier to use for LLM calls.")
|
|
max_concurrency: int = Field(default=3, ge=1, description="Max parallel LLM requests.")
|
|
|
|
def get_secret(self) -> str:
|
|
"""Return the raw API key string if present."""
|
|
key = os.environ.get("NSCT_LLM_API_KEY", "")
|
|
return key if key else ""
|
|
|
|
|
|
class VisionConfig(BaseModel):
|
|
"""Vision (image/video) model configuration."""
|
|
|
|
base_url: str = Field(..., description="Base URL of the vision provider endpoint.")
|
|
model: str = Field(..., description="Vision model identifier.")
|
|
|
|
def get_secret(self) -> str:
|
|
key = os.environ.get("NSCT_VISION_API_KEY", "")
|
|
return key if key else ""
|
|
|
|
|
|
class AudioConfig(BaseModel):
|
|
"""Audio / speech model configuration."""
|
|
|
|
base_url: str = Field(..., description="Base URL of the audio provider endpoint.")
|
|
model: str = Field(default="default", description="Audio model identifier.")
|
|
|
|
def get_secret(self) -> str:
|
|
key = os.environ.get("NSCT_AUDIO_API_KEY", "")
|
|
return key if key else ""
|
|
|
|
|
|
class DatabaseConfig(BaseModel):
|
|
"""Database connection configuration."""
|
|
|
|
url: str = Field(
|
|
...,
|
|
description="Database URL (postgresql+asyncpg://...) or a default SQLite fallback.",
|
|
)
|
|
|
|
|
|
class AppSettings(BaseModel):
|
|
"""Top-level application settings."""
|
|
|
|
llm: LLMConfig = Field(default_factory=LLMConfig)
|
|
vision: VisionConfig = Field(default_factory=VisionConfig)
|
|
audio: AudioConfig = Field(default_factory=AudioConfig)
|
|
postgres: DatabaseConfig = Field(default_factory=DatabaseConfig)
|
|
debug: bool = Field(default=False)
|
|
searxng_base_url: str | None = Field(default=None, description="SearXNG instance URL.")
|
|
searxng_engines: tuple[str, ...] = Field(
|
|
default=(),
|
|
description="Optional allow-list of SearXNG engines for web searches.",
|
|
)
|
|
|
|
@staticmethod
|
|
def from_env() -> "AppSettings":
|
|
"""Build AppSettings entirely from environment variables."""
|
|
llm_base_url = os.environ.get("NSCT_LLM_BASE_URL", "")
|
|
llm_model = os.environ.get("NSCT_LLM_MODEL", "")
|
|
llm_concurrency = int(os.environ.get("NSCT_LLM_MAX_CONCURRENCY", "3"))
|
|
|
|
vision_base_url = os.environ.get("NSCT_VISION_BASE_URL", "")
|
|
vision_model = os.environ.get("NSCT_VISION_MODEL", "")
|
|
|
|
audio_base_url = os.environ.get("NSCT_AUDIO_BASE_URL", "")
|
|
audio_model = os.environ.get("NSCT_AUDIO_MODEL", "default")
|
|
|
|
db_url = os.environ.get("NSCT_DB_URL", "")
|
|
|
|
llm_cfg = LLMConfig(
|
|
base_url=llm_base_url,
|
|
model=llm_model,
|
|
max_concurrency=llm_concurrency,
|
|
)
|
|
|
|
vision_cfg = VisionConfig(
|
|
base_url=vision_base_url,
|
|
model=vision_model,
|
|
)
|
|
|
|
audio_cfg = AudioConfig(
|
|
base_url=audio_base_url,
|
|
model=audio_model,
|
|
)
|
|
|
|
postgres_cfg = DatabaseConfig(url=db_url)
|
|
|
|
debug = os.environ.get("NSCT_DEBUG", "false").lower() == "true"
|
|
searxng_url = os.environ.get("NSCT_SEARXNG_BASE_URL", None)
|
|
searxng_engines = tuple(
|
|
engine.strip()
|
|
for engine in os.environ.get("NSCT_SEARXNG_ENGINES", "").split(",")
|
|
if engine.strip()
|
|
)
|
|
|
|
return AppSettings(
|
|
llm=llm_cfg,
|
|
vision=vision_cfg,
|
|
audio=audio_cfg,
|
|
postgres=postgres_cfg,
|
|
debug=debug,
|
|
searxng_base_url=searxng_url,
|
|
searxng_engines=searxng_engines,
|
|
)
|
|
|
|
@property
|
|
def config_dir(self) -> Path:
|
|
"""Return a config directory path for persisting runtime data."""
|
|
data_dir = Path.home() / ".nsct"
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
return data_dir
|