Foundation (phase 01, verified): - FastAPI app: /api/health, /api/suggestions, /api/chat (placeholder), static frontend served locally (no CDN) - Postgres 17 + pgvector via db/Containerfile + compose.yaml (podman compose up -d db), Alembic initial migration (documents, chunks with vector(768), query_log) - LLM client targeting https://aipi.reeseapps.com/v1 (turbo/embed); scripts/llm_probe.py verified models + 768-dim embeddings live - Conditional debugpy: imported only when DEBUGPY=1 (attach on demand, :5678); logging config for clean single-line logs - Frontend shell: mobile-first chat + Sources pages, tokens, a11y baselines - Tests: 24 unit+integration (99% coverage on app/), ruff + pyright clean, Playwright smoke E2E (3 tests) against a deterministic mock LLM - Planning: .agent/PLAN.md (architecture + LOCKED decisions), AGENTS.md, 6 user stories, 7 phase files (one story / one phase / one Playwright suite each)
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Application settings.
|
|
|
|
Every setting can be overridden with an environment variable prefixed
|
|
``BOR_`` (or a local gitignored ``.env`` file — see ``.env.example``).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from functools import lru_cache
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="BOR_",
|
|
extra="ignore",
|
|
)
|
|
|
|
# --- App ---
|
|
app_name: str = "Brain of Reese"
|
|
app_version: str = "0.1.0"
|
|
environment: str = "development"
|
|
log_level: str = "INFO"
|
|
static_dir: str = "frontend"
|
|
|
|
# --- Database (PostgreSQL 17 + pgvector) ---
|
|
database_url: str = "postgresql+psycopg://reese:reese@localhost:5432/brain_of_reese"
|
|
|
|
# --- LLM (self-hosted, OpenAI-compatible "aipi" endpoint) ---
|
|
llm_base_url: str = "https://aipi.reeseapps.com/v1"
|
|
llm_api_key: str = ""
|
|
llm_chat_model: str = "turbo"
|
|
llm_embed_model: str = "embed"
|
|
|
|
# --- RAG tuning ---
|
|
embedding_dim: int = 768 # verified against aipi /v1 (embed model)
|
|
top_k_chunks: int = 4
|
|
top_n_docs: int = 2
|
|
relevance_threshold: float = 0.30
|
|
max_context_chars: int = 24_000
|
|
chunk_target_chars: int = 2_000
|
|
chunk_overlap_chars: int = 200
|
|
embed_batch_size: int = 16
|
|
|
|
# Suggested questions (onboarding + empty state).
|
|
suggestions: list[str] = [
|
|
"How is my Kubernetes cluster set up?",
|
|
"What's my backup strategy?",
|
|
"How do I deploy a new service?",
|
|
"What's currently running in the homelab?",
|
|
]
|
|
|
|
@property
|
|
def effective_api_key(self) -> str:
|
|
"""API key for aipi: explicit setting, then $AIPI_KEY, then a placeholder."""
|
|
return self.llm_api_key or os.environ.get("AIPI_KEY", "") or "not-needed"
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
return Settings()
|