Fade0510's picture
Cleaned up code before reimplementing semantic cache
6710fbe
Raw
History Blame Contribute Delete
6.83 kB
from __future__ import annotations
import os
from dataclasses import dataclass, field
from functools import lru_cache
from pathlib import Path
from typing import Any, Dict, List
import yaml
from dotenv import load_dotenv
def _split_csv(value: str) -> List[str]:
return [item.strip() for item in (value or "").split(",") if item.strip()]
@dataclass
class ModelSettings:
"""
Centralized model configuration.
Env overrides:
- FIN_ASSISTANT_ROUTER_MODEL
- FIN_ASSISTANT_AGENT_MODEL
- FIN_ASSISTANT_FORMATTER_MODEL
- FIN_ASSISTANT_EMBEDDING_MODEL
"""
router_model: str = "gpt-4o"
agent_model: str = "gpt-4o"
formatter_model: str = "gpt-4o"
embedding_model: str = "text-embedding-3-large"
@dataclass
class UISettings:
tabs: List[str] = field(default_factory=lambda: ["Chat"])
market_watchlist: List[str] = field(
default_factory=lambda: ["AAPL", "MSFT", "NVDA", "SPY", "QQQ"]
)
portfolio_default_input: str = "10 AAPL, 5 MSFT, 2 VTI"
portfolio_examples: List[str] = field(
default_factory=lambda: [
"10 AAPL, 5 MSFT, 2 VTI",
"100 NVDA, 40 SPY, 25 BND",
"12 SCHD, 8 VEA, 6 VWO",
]
)
@dataclass
class CacheSettings:
response_cache_ttl_days: int = 30
@dataclass
class KBSettings:
"""
Retrieval tuning for the local knowledge base.
Env overrides:
- FIN_ASSISTANT_KB_MIN_SCORE (float, 0-1)
"""
min_score: float = 0.70
@dataclass
class ProfileDefaults:
risk: str = "moderate"
experience: str = "beginner"
@dataclass
class AppSettings:
title: str = "Finance Assistant"
page_title: str = "Finance Assistant"
models: ModelSettings = field(default_factory=ModelSettings)
ui: UISettings = field(default_factory=UISettings)
cache: CacheSettings = field(default_factory=CacheSettings)
kb: KBSettings = field(default_factory=KBSettings)
default_user_profile: ProfileDefaults = field(default_factory=ProfileDefaults)
@classmethod
def load(cls, path: str = "config.yaml") -> "AppSettings":
load_dotenv()
settings = cls()
config_path = Path(path)
if config_path.exists():
with config_path.open("r", encoding="utf-8") as f:
raw = yaml.safe_load(f) or {}
settings = cls.from_dict(raw)
return settings.apply_env_overrides()
@classmethod
def from_dict(cls, raw: Dict[str, Any]) -> "AppSettings":
models_raw = raw.get("models", {}) or {}
ui_raw = raw.get("ui", {}) or {}
cache_raw = raw.get("cache", {}) or {}
kb_raw = raw.get("kb", {}) or {}
profile_raw = raw.get("default_user_profile", {}) or {}
return cls(
title=str(
raw.get("title", raw.get("app", {}).get("title", "Finance Assistant"))
),
page_title=str(
raw.get(
"page_title",
raw.get("app", {}).get("page_title", "Finance Assistant"),
)
),
models=ModelSettings(
router_model=str(models_raw.get("router_model", "gpt-4o")),
agent_model=str(models_raw.get("agent_model", "gpt-4o")),
formatter_model=str(models_raw.get("formatter_model", "gpt-4o")),
embedding_model=str(
models_raw.get("embedding_model", "text-embedding-3-large")
),
),
ui=UISettings(
tabs=list(ui_raw.get("tabs", ["Chat"])),
market_watchlist=list(
ui_raw.get(
"market_watchlist", ["AAPL", "MSFT", "NVDA", "SPY", "QQQ"]
)
),
portfolio_default_input=str(
ui_raw.get("portfolio_default_input", "10 AAPL, 5 MSFT, 2 VTI")
),
portfolio_examples=list(
ui_raw.get(
"portfolio_examples",
[
"10 AAPL, 5 MSFT, 2 VTI",
"100 NVDA, 40 SPY, 25 BND",
"12 SCHD, 8 VEA, 6 VWO",
],
)
),
),
cache=CacheSettings(
response_cache_ttl_days=int(
cache_raw.get("response_cache_ttl_days", 30)
)
),
kb=KBSettings(min_score=float(kb_raw.get("min_score", 0.75))),
default_user_profile=ProfileDefaults(
risk=str(profile_raw.get("risk", "moderate")),
experience=str(profile_raw.get("experience", "beginner")),
),
)
def apply_env_overrides(self) -> "AppSettings":
self.title = os.getenv("FIN_ASSISTANT_TITLE", self.title)
self.page_title = os.getenv("FIN_ASSISTANT_PAGE_TITLE", self.page_title)
self.models.router_model = os.getenv(
"FIN_ASSISTANT_ROUTER_MODEL", self.models.router_model
)
self.models.agent_model = os.getenv(
"FIN_ASSISTANT_AGENT_MODEL", self.models.agent_model
)
self.models.formatter_model = os.getenv(
"FIN_ASSISTANT_FORMATTER_MODEL", self.models.formatter_model
)
self.models.embedding_model = os.getenv(
"FIN_ASSISTANT_EMBEDDING_MODEL", self.models.embedding_model
)
tabs = os.getenv("FIN_ASSISTANT_TABS")
if tabs:
self.ui.tabs = _split_csv(tabs)
watchlist = os.getenv("FIN_ASSISTANT_MARKET_WATCHLIST")
if watchlist:
self.ui.market_watchlist = _split_csv(watchlist)
default_portfolio = os.getenv("FIN_ASSISTANT_PORTFOLIO_DEFAULT_INPUT")
if default_portfolio:
self.ui.portfolio_default_input = default_portfolio
examples = os.getenv("FIN_ASSISTANT_PORTFOLIO_EXAMPLES")
if examples:
self.ui.portfolio_examples = [
item.strip() for item in examples.split(";") if item.strip()
]
ttl_days = os.getenv("FIN_ASSISTANT_RESPONSE_CACHE_TTL_DAYS")
if ttl_days:
self.cache.response_cache_ttl_days = int(ttl_days)
kb_min_score = os.getenv("FIN_ASSISTANT_KB_MIN_SCORE")
if kb_min_score:
self.kb.min_score = float(kb_min_score)
self.default_user_profile.risk = os.getenv(
"FIN_ASSISTANT_DEFAULT_RISK", self.default_user_profile.risk
)
self.default_user_profile.experience = os.getenv(
"FIN_ASSISTANT_DEFAULT_EXPERIENCE", self.default_user_profile.experience
)
return self
@lru_cache(maxsize=1)
def get_settings() -> AppSettings:
return AppSettings.load()