cricket-rag / app.py
Piyushdash94's picture
Fix runs-needed in chase swings and capped commentary; network graph; fonts; 2 suggestions
3c2e63d verified
Raw History Blame Contribute Delete
4.01 kB
"""cricket-rag on a Hugging Face Gradio Space (CPU basic): the match centre, Pandit and the API on one port.
/ the match centre (a pre-built copy of frontend/ui, served as static files)
/api/... the same API as `cricrag serve`: Pandit's chat stream, archive, match pages, stats
/pandit a plain Gradio chat with Pandit (also callable through the Gradio API)
Nothing here needs a GPU: Pandit's LLM calls go to Groq (GROQ_API_KEY secret) and the rest is DuckDB and
LanceDB on CPU. Run locally with `python app.py` (port 7860, or PORT).
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE / "src")) # the cricrag and bundled cricbot packages
os.chdir(HERE) # data/ and .env are read relative to this folder
try: # present on ZeroGPU hardware; elsewhere the app runs without it
import spaces
except Exception:
spaces = None
import gradio as gr # noqa: E402 (after the sys.path line)
import uvicorn # noqa: E402 (after the sys.path line)
from fastapi.staticfiles import StaticFiles # noqa: E402 (after the sys.path line)
from cricrag import server # noqa: E402 (after the sys.path line)
api = server.create_app()
if spaces is not None:
@spaces.GPU(duration=5)
def gpu_check() -> str:
"""Not used by the app. ZeroGPU expects a Space to declare one GPU function; Pandit runs on CPU and Groq."""
return "ok"
def _text(content) -> str:
if isinstance(content, str):
return content
if isinstance(content, list): # [{"type": "text", "text": ...}, ...]
return " ".join(part.get("text", "") for part in content if isinstance(part, dict))
return str(content or "")
def ask(message, history):
"""One Pandit answer; history is the chat so far (follow-ups like "and in 2017?" use it)."""
past = [{"role": m["role"], "content": _text(m["content"])} for m in history or [] if isinstance(m, dict)]
pandit = api.state.agent() # built on first use (takes the lock itself)
with api.state.lock: # one question at a time: Pandit holds DuckDB connections
answer = pandit.ask(_text(message), past)
return answer.text
with gr.Blocks(title="Pandit") as demo:
gr.Markdown(
"### Pandit\nIPL questions answered from 19 seasons of ball-by-ball data. "
"The full match centre is at [the Space's home page](/)."
)
gr.ChatInterface(
ask,
examples=[
"Where did the 2025 final turn?",
"Who is Rohit Sharma's nemesis?",
],
)
# Spaces set GRADIO_SSR_MODE=true. With SSR on, mount_gradio_app starts a Node server on the first free port from
# 7860, which then blocks uvicorn below ("address already in use"). The chat needs no SSR, so keep it off.
app = gr.mount_gradio_app(api, demo, path="/pandit", ssr_mode=False)
app.mount("/", StaticFiles(directory=HERE / "ui", html=True), name="ui") # last: every other path is the UI
def zerogpu_startup() -> None:
"""On ZeroGPU, report the GPU function to the scheduler, as `demo.launch()` would.
The `spaces` package runs this report from a patched `gr.Blocks.launch`. This app serves Gradio inside its
own FastAPI app and never calls launch(), so without this ZeroGPU stops the Space with
"No @spaces.GPU function detected during startup". Elsewhere (CPU basic, your computer) it does nothing.
"""
if spaces is None or os.environ.get("SPACES_ZERO_GPU", "").lower() not in ("1", "true"):
return
from spaces import zero
startup = getattr(zero, "startup", None)
if startup is None: # a `spaces` release that moved the hook: say so instead of failing silently
print("ZeroGPU: spaces.zero.startup not found; choose CPU basic hardware for this Space.", flush=True)
return
startup()
if __name__ == "__main__":
zerogpu_startup()
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860")))