Files
webber/webber-api/src/shared/config.py
T
jpmschweitzerandClaude Fable 5 6ac1d15591 fix(config): use docker hostnames for tatlock and searxng defaults
The homelab is retiring *.schweitz.internal and will rebind host ports
to 127.0.0.1, so container-to-container traffic must use container
names on the docker-dataplane network. Switch defaults from host
IP:port to http://tatlock:8000 and http://searxng:8080 (SearXNG's
internal port is 8080; 8087 is only the host-published port).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:21:43 +02:00

115 lines
3.4 KiB
Python

"""
Application configuration via Pydantic Settings.
All settings loaded from environment variables or .env file.
Project metadata (name, version, description) sourced from pyproject.toml.
"""
import tomllib
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
@dataclass(frozen=True)
class ProjectMeta:
"""Project metadata from pyproject.toml (single source of truth)."""
name: str
version: str
description: str
def _load_project_meta() -> ProjectMeta:
"""Load project metadata from pyproject.toml."""
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
try:
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
project = data.get("project", {})
return ProjectMeta(
name=str(project.get("name", "webber")).title(),
version=str(project.get("version", "0.0.0")),
description=str(project.get("description", "")),
)
except FileNotFoundError:
return ProjectMeta(name="Webber", version="0.0.0", description="")
PROJECT = _load_project_meta()
__version__ = PROJECT.version
class Settings(BaseSettings):
"""Application settings loaded from environment."""
# Application (from pyproject.toml)
app_name: str = PROJECT.name
app_version: str = PROJECT.version
app_description: str = PROJECT.description
debug: bool = False
# Server
host: str = "0.0.0.0"
port: int = 8086
# Logging
log_level: str = "INFO"
# CORS
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"]
cors_credentials: bool = True
cors_methods: list[str] = ["*"]
cors_headers: list[str] = ["*"]
# LLM - Ollama (always hot in VRAM on tower-of-joy)
ollama_url: str = "http://192.168.86.149:11434"
ollama_agent_model: str = "mistral-nemo-large:latest"
ollama_embed_model: str = "nomic-embed-text:latest"
# Auth - Tatlock integration
tatlock_api_url: str | None = "http://tatlock:8000"
internal_api_key: str | None = None
# Web search - SearXNG (use SEARXNG_URL env var to override)
searxng_url: str = "http://searxng:8080"
searxng_timeout: int = 10
# Tool execution
tool_timeout_seconds: int = 120
sandbox_enabled: bool = True
allowed_paths: list[str] | None = None
# Database
database_url: str = "sqlite+aiosqlite:///./webber.db"
# Sessions & Context
session_ttl_hours: int = 24
max_context_tokens: int = 128000
summarization_threshold: float = 0.8 # Summarize at 80% of max tokens
summarization_target_tokens: int = 500 # Target summary size
keep_recent_messages: int = 6 # Messages to keep unsummarized (3 turns)
# Retry logic
retry_max_attempts: int = 3 # Max retry attempts for transient failures
retry_base_delay: float = 1.0 # Base delay in seconds
retry_max_delay: float = 30.0 # Maximum delay in seconds
model_config = SettingsConfigDict(
env_file=".env",
case_sensitive=False,
extra="ignore",
env_parse_none_str="", # Treat empty string as None
)
@property
def effective_allowed_paths(self) -> list[str]:
"""Return allowed_paths or empty list if None."""
return self.allowed_paths or []
@lru_cache
def get_settings() -> Settings:
"""Cached settings singleton."""
return Settings()