AstraNexus / server.py
DriptoBhattacharyya's picture
Deploy AstraNexus (Docker: fine-tuned encoder + custom FastAPI frontend)
6e8672b verified
Raw
History Blame Contribute Delete
4.89 kB
import os
os.environ.setdefault("ASTRANEXUS_FINETUNED", "1") # run the SP3 fine-tuned encoder
import astranexus.config # noqa: F401 — loads any HF Space secrets from env
import os.path
import gradio as gr
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
from astranexus.ui.app import build_app, DARK
app = FastAPI(title="AstraNexus")
# 'Sign in with Google' web flow (live mode uses the Gmail REST API over 443,
# because the Space firewalls outbound IMAP). No-op if the OAuth client secrets
# aren't set, so the fixture demo still runs.
from astranexus.web import google_oauth
google_oauth.attach(app)
LANDING = """<!doctype html><html><head><meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>AstraNexus</title>
<style>
*{box-sizing:border-box} body{margin:0;background:#0a0e17;color:#c0caf5;
font-family:system-ui,Segoe UI,Roboto,sans-serif;line-height:1.6}
.wrap{max-width:860px;margin:0 auto;padding:64px 24px}
h1{font-size:44px;margin:0 0 4px;color:#fff;letter-spacing:-.5px}
.tag{color:#7aa2f7;font-size:18px;margin-bottom:28px}
.cta{display:inline-block;margin:18px 0 36px;padding:14px 30px;border-radius:10px;
background:#7aa2f7;color:#0a0e17;font-weight:700;text-decoration:none;font-size:16px}
.cta:hover{background:#9eb8ff}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:24px 0}
.card{background:#0d1320;border:1px solid #29304a;border-radius:12px;padding:16px 18px}
.card b{color:#fff} .k{color:#7aa2f7}
.pills span{display:inline-block;background:#0d1320;border:1px solid #29304a;
border-radius:20px;padding:4px 12px;margin:4px 6px 0 0;font-size:13px}
a{color:#7aa2f7} .foot{margin-top:40px;opacity:.7;font-size:13px}
</style></head><body><div class=wrap>
<h1>&#10022; AstraNexus</h1>
<div class=tag>Your inbox, as a navigable galaxy.</div>
<p>Unsupervised email organizer. A <b>fine-tuned multimodal encoder</b>
(MiniLM&nbsp;text&nbsp;+&nbsp;SigLIP&nbsp;vision, LoRA) embeds every email; HDBSCAN groups them into
<b>constellations</b> that <b>NVIDIA&nbsp;Nemotron-3-Nano-4B</b> names and reasons over;
threads spanning accounts surface as <b>wormholes</b>. No rules, no folders &mdash;
the structure is discovered.</p>
<a class=cta href="app/">Launch the demo &rarr;</a>
<div class=grid>
<div class=card><b>Color</b> &mdash; the <span class=k>constellation</span> (topic the
encoder grouped it into). Each has a Nemotron-named hub star.</div>
<div class=card><b>Size</b> &mdash; unread (big) vs read. <b>Brightness</b> &prop; urgency.</div>
<div class=card><b>Gold halo</b> &mdash; the email carries an image (where the
multimodal model earns its keep).</div>
<div class=card><b>Pink dashed</b> &mdash; a wormhole: one thread across two accounts.</div>
</div>
<p><b>Under the hood:</b> the whole AI pipeline runs on a <b>Modal</b> GPU as a
<b>LangGraph</b> agentic graph &mdash;
fetch&rarr;embed&rarr;cluster&rarr;label&rarr;<b>judge</b>&rarr;serialize. An
<b>LLM-as-judge</b> (Nemotron) scores the labels and loops back to relabel until they
pass, then it writes an open agent trace &mdash;
<a href="trace.json">view the latest trace.json &rarr;</a>. An in-app <b>A/B toggle</b>
pits the fine-tuned encoder against off-the-shelf SigLIP; layered fallbacks
(Modal&nbsp;&rarr;&nbsp;NIM-hosted&nbsp;Nemotron&nbsp;&rarr;&nbsp;keyword heuristic) keep it alive.</p>
<p style="opacity:.75;font-size:14px"><b>About the data:</b> the default
<b>fixture</b> is a <i>synthetic</i> dataset built only for demo/testing &mdash; no real
email. Google restricts reading people's mail, so live Gmail is invite-only
(approved test accounts / my own mailboxes); session-only, never stored.</p>
<div class=pills>
<span>&#129504; Nemotron-3-Nano-4B</span><span>&#9889; Modal GPU</span>
<span>&#128376; LangGraph + judge loop</span><span>&#127919; Fine-tuned encoder + A/B</span>
<span>&#128225; Open agent trace</span>
</div>
<p class=foot>Fine-tuned encoder:
<a href="https://huggingface.co/DriptoBhattacharyya/astranexus-mm-encoder">
DriptoBhattacharyya/astranexus-mm-encoder</a> &middot; built for the Build Small hackathon.</p>
</div></body></html>"""
@app.get("/", response_class=HTMLResponse)
def home():
return LANDING
@app.get("/trace.json")
def trace():
# the LangGraph agent trace from the most recent map (Open Trace)
if os.path.exists("trace.json"):
return FileResponse("trace.json", media_type="application/json")
return JSONResponse({"detail": "Run a map first to generate the agent trace."},
status_code=404)
demo = build_app(theme=DARK) # custom dark Gradio mounted under /app
app = gr.mount_gradio_app(app, demo, path="/app")