Spaces:
Sleeping
Sleeping
| 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()] | |
| 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" | |
| 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", | |
| ] | |
| ) | |
| class CacheSettings: | |
| response_cache_ttl_days: int = 30 | |
| class KBSettings: | |
| """ | |
| Retrieval tuning for the local knowledge base. | |
| Env overrides: | |
| - FIN_ASSISTANT_KB_MIN_SCORE (float, 0-1) | |
| """ | |
| min_score: float = 0.70 | |
| class ProfileDefaults: | |
| risk: str = "moderate" | |
| experience: str = "beginner" | |
| 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) | |
| 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() | |
| 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 | |
| def get_settings() -> AppSettings: | |
| return AppSettings.load() | |