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>
115 lines
3.4 KiB
Python
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()
|