Files
NSCT---Neutral-Search-Crawl…/src/nsct/config.py
2026-09-07 12:46:06 +02:00

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