Files
webber/webber-api/src/shared/config.py
T
jpmschweitzerandClaude 9d58df85f3 fix(config): default agent model to gemma4:e2b
mistral-nemo-large holds ~9.2 GB of the 11 GB card it shares with Speaches,
which starves Whisper and breaks voice transcription. gemma4:e2b holds
1.9 GB and is faster. The deployed stack already overrides this via
OLLAMA_AGENT_MODEL; this aligns the default so a deployment without that
override does not reintroduce the contention.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 14:58:57 +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 = "gemma4:e2b"
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()