murtaza-2007
Aurelius improvement pass: domain-aware recs, finance/research surfaces, 2D graph
658d200 | """ | |
| Aurelius β central configuration. | |
| All tunables live here so they can be adjusted (or env-overridden) without | |
| touching the search/embedding/wiki/server logic. See CLAUDE.md and the | |
| architect plan (Production Refactor + Algorithm + UI Pass, Β§2) for context. | |
| """ | |
| import os | |
| from pathlib import Path | |
| # ββ Embedding model ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Default is all-MiniLM-L6-v2: 80 MB / 384-dim, fits free-tier deployment | |
| # (Render free web service) without OOMing or blowing the cold-start budget. | |
| # Qwen/Qwen3-Embedding-0.6B (plan Β§3) is a better-quality drop-in if you're | |
| # running on a host with more RAM β set AURELIUS_EMBED_MODEL to switch. | |
| EMBED_MODEL_NAME = os.getenv("AURELIUS_EMBED_MODEL", "all-MiniLM-L6-v2") | |
| EMBED_DEVICE = os.getenv("AURELIUS_EMBED_DEVICE", "cpu") | |
| EMBED_BATCH_SIZE = int(os.getenv("AURELIUS_EMBED_BATCH", "64")) # smaller for 0.6B vs 128 for MiniLM | |
| # ββ Wikipedia ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| WIKI_API = "https://en.wikipedia.org/w/api.php" | |
| MAX_FETCH = 500 # max links fetched from Wikipedia (paginated) | |
| MAX_PAGES = 6 # safety cap on pagination pages when hunting for a specific goal link | |
| # Wikimedia's robot policy (https://w.wiki/4wJS) 403s requests whose | |
| # User-Agent has no contact info β this was dropped during the module split | |
| # and the bare "Aurelius-WikiNavigator/7.0" string started getting blocked. | |
| WIKI_HEADERS = { | |
| "User-Agent": "Aurelius-WikiNavigator/7.0 (educational project; contact: murtaza.vali.ug25@plaksha.edu.in)", | |
| "Accept": "application/json", | |
| } | |
| # ββ Search βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| MAX_HOPS = 100 # enforced as expansion-count cap, not path-length cap (see search.py) | |
| TOP_DISPLAY = 35 # max neighbours sent to frontend per step | |
| # ββ Scoring ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Architect plan (Honest Bidirectional Meeting-Check Rewrite): the target's | |
| # backlinks (depth-1) are now the actual goal zone, not just a score hint β | |
| # see search.py's meeting check. Depth-2 frontier, its bonus, and the | |
| # category bonus were removed: none of them could contribute a real, | |
| # verifiable edge, and depth-2 specifically could not be stitched into a | |
| # real path without bridge bookkeeping it never had. The stagnation/ | |
| # frontier-jump escape valve was removed entirely β it fabricated a | |
| # came_from pointer with no corresponding Wikipedia link (see the Marcus | |
| # Aurelius -> A* search algorithm incident), which is no longer needed now | |
| # that the meeting check gives the search an honest way to converge. | |
| PRUNE_TOP_K = 15 # keep this many candidates per expansion after scoring | |
| # NOTE: tried raising this to 0.22 as part of the Semantic Drift fix (see | |
| # search.py docstring) on the theory that richer "{title}. {short_desc}" | |
| # embeddings would make raw cosine trustworthy enough to support a higher | |
| # floor. Verified live against Marcus Aurelius -> A* search algorithm: the | |
| # best of Marcus Aurelius's 500 direct links scores only ~0.186 raw cosine | |
| # to the (also-enriched) target embedding β there is no semantically close | |
| # 1-hop neighbour for a niche CS topic from a Roman-emperor article, full | |
| # stop, regardless of embedding quality. 0.22 killed the search at step 1 | |
| # (0/500 candidates survived). Kept at 0.15. The actual drift fix is the | |
| # embedding enrichment improving *relative* ranking among survivors, plus | |
| # DEPTH_TIEBREAK_EPSILON preventing runaway commitment to one irrelevant | |
| # cluster β not an absolute floor, which hard multi-hop cases can't clear | |
| # early on by construction. | |
| PRUNE_FLOOR = 0.15 # soft floor on raw cosine-to-target; frontier members exempt | |
| PRUNE_MIN_SURVIVORS = 5 # ALWAYS keep at least this many top-ranked candidates per step, | |
| # even if they fall below PRUNE_FLOOR β prevents the search from | |
| # killing itself at step 1 for semantically distant pairs | |
| # (Cleopatra β Time complexity: 0/500 candidates cleared 0.15, | |
| # search died immediately) | |
| FRONTIER_D1_BONUS = 0.30 # candidate is a direct backlink of the target (goal-zone member) | |
| # Depth tie-breaker (Semantic Drift fix): heap priority is h + DEPTH_EPSILON*g | |
| # instead of pure h. This is a deliberate, documented reversal of the prior | |
| # "heap priority = h only, no g-cost" design (see CLAUDE.md) β pure-greedy | |
| # let one noisy high-scoring title (e.g. a proper noun with no domain | |
| # context) drag the search arbitrarily deep into its cluster with nothing | |
| # to prefer a shallower, more recently-improving alternative. 0.01 is small | |
| # enough that it only breaks ties/near-ties; it doesn't override a genuinely | |
| # strong h advantage at any reasonable depth (60 hops -> max 0.6 penalty). | |
| DEPTH_TIEBREAK_EPSILON = 0.01 | |
| # ββ Active Backward Expansion (goal-zone depth-2) βββββββββββββββββββββββ | |
| GOAL_ZONE_EXPAND_INTERVAL = 10 # expand goal zone every N steps | |
| GOAL_ZONE_EXPAND_BATCH = 5 # d1 members to expand per interval | |
| GOAL_ZONE_D2_BACKLINK_LIMIT = 100 # max backlinks fetched per d1 member | |
| FRONTIER_D2_BONUS = 0.15 # score bonus for d2 goal-zone members (half of d1's 0.30) | |
| # ββ Stagnation Detection / Cluster Escape ββββββββββββββββββββββββββββββββ | |
| STAGNATION_WINDOW = 8 # sliding window size for h-improvement tracking | |
| STAGNATION_DELTA = 0.02 # min improvement over window to count as progress | |
| PRUNE_TOP_K_STAGNANT = 25 # widened beam when stagnant (vs 15 normal) | |
| DIVERSITY_WEIGHT = 0.10 # weight for centroid-distance diversity bonus | |
| # ββ Security / abuse limits ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # This backend is public and unauthenticated. None of the limits below | |
| # change search behaviour for a normal user β they only bound what a single | |
| # abusive client can make the server do (open WS handshakes, CPU-bound | |
| # embedding, and Wikipedia API traffic on the shared free tier). | |
| # | |
| # ALLOWED_ORIGINS: browser Origins permitted to call the API (CORS for the | |
| # REST endpoints + an explicit Origin check on the WebSocket handshake). | |
| # Defaults cover the deployed Vercel frontend, localhost dev, and the | |
| # "null" origin used when index.html is opened directly via file:// (the | |
| # documented local workflow). Override in production with the env var, e.g. | |
| # ALLOWED_ORIGINS="https://aurelius-psi.vercel.app". Non-browser clients | |
| # (curl, scripts) send no Origin and are unaffected β Origin checks only | |
| # defend against other websites' JS driving this backend from a user's browser. | |
| ALLOWED_ORIGINS = [ | |
| o.strip() for o in os.getenv( | |
| "ALLOWED_ORIGINS", | |
| "https://aurelius-psi.vercel.app," | |
| "http://localhost:8000,http://127.0.0.1:8000," | |
| "http://localhost:8081,http://127.0.0.1:8081,null", | |
| ).split(",") if o.strip() | |
| ] | |
| MAX_QUERY_LEN = int(os.getenv("AURELIUS_MAX_QUERY_LEN", "200")) # reject absurd start/end strings | |
| MAX_CONCURRENT_SEARCHES = int(os.getenv("AURELIUS_MAX_CONCURRENT", "4")) # parallel searches the box will run | |
| MAX_SEARCH_SECONDS = int(os.getenv("AURELIUS_MAX_SEARCH_SECONDS", "300")) # hard wall-clock cap per connection | |
| # The two knobs above bound a single WS connection; the sliding-window rate | |
| # limit below bounds total request volume per client IP across connections | |
| # (a concurrency cap alone can't stop rapid connect/abandon loops). | |
| RATE_LIMIT_WINDOW_S = int(os.getenv("AURELIUS_RATE_WINDOW", "60")) | |
| RATE_LIMIT_MAX_REQUESTS = int(os.getenv("AURELIUS_RATE_MAX", "30")) # per IP per window | |
| # ββ Files / store ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _THIS_DIR = Path(__file__).resolve().parent | |
| SEED_FILE = _THIS_DIR / "common_wiki_searches.txt" | |
| # Persistent graph + vector store (SQLite; the interface is pgvector-shaped | |
| # so a Postgres swap is config-level β see core/store.py). | |
| AURELIUS_DB = Path(os.getenv("AURELIUS_DB", str(_THIS_DIR / "data" / "aurelius.db"))) | |
| # ββ Adapters βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Contact email: the Wikimedia robot-policy lesson applies to every | |
| # upstream API β identify yourself or get throttled. OpenAlex's "polite | |
| # pool" is explicitly faster with a mailto. | |
| CONTACT_EMAIL = os.getenv("AURELIUS_CONTACT", "murtaza.vali.ug25@plaksha.edu.in") | |
| OPENALEX_API = "https://api.openalex.org" | |
| NEWSAPI_KEY = os.getenv("NEWSAPI_KEY", "") # optional; GDELT fallback when absent | |
| # News Intelligence scheduled refresh: minutes between pipeline runs. | |
| # 0 (default) = off β refresh manually via /api/news/refresh or the CLI. | |
| NEWS_REFRESH_MINUTES = int(os.getenv("NEWS_REFRESH_MINUTES", "0")) | |
| HETIONET_URL = ("https://github.com/hetio/hetionet/raw/main/" | |
| "hetnet/json/hetionet-v1.0.json.bz2") | |
| # ββ Optional LLM layer (Google Gemini via Google AI Studio) ββββββββββββββ | |
| # Purely additive: every AI feature is a lazy call made AFTER the fast | |
| # non-LLM result is already on screen, and the whole app runs with no key. | |
| # The key is a backend-only secret β never committed, never sent to the | |
| # browser (only /api/llm/status's booleans are). See core/llm.py. | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "") | |
| GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash") | |
| GEMINI_RPM = int(os.getenv("GEMINI_RPM", "12")) # soft calls/min budget β stay under the free-tier limit | |
| GEMINI_COOLDOWN_S = int(os.getenv("GEMINI_COOLDOWN_S", "60")) # pause after an upstream 429/5xx | |
| GEMINI_TIMEOUT_S = float(os.getenv("GEMINI_TIMEOUT_S", "12")) | |
| GEMINI_MAX_CONCURRENCY = int(os.getenv("GEMINI_MAX_CONCURRENCY", "3")) | |
| # ββ Structural embeddings (node2vec β core/representation.py) ββββββββββββ | |
| N2V_DIM = int(os.getenv("AURELIUS_N2V_DIM", "64")) | |
| N2V_WALKS_PER_NODE = 10 | |
| N2V_WALK_LENGTH = 20 | |
| N2V_WINDOW = 5 | |
| N2V_EPOCHS = 2 | |
| N2V_NEGATIVES = 5 | |
| # Fusion weight for [text ; Ξ±Β·struct] β how much structure counts relative | |
| # to text, per source. Text dominates for encyclopedic sources; structure | |
| # carries sources whose node text is thin (tickers, genes, file paths). | |
| FUSION_ALPHA = { | |
| "wikipedia": 0.3, "openalex": 0.5, | |
| "biomed": 0.8, "news": 0.5, "finance": 0.8, | |
| } | |