| """briefing-32 — Gradio app entry for Hugging Face Spaces. |
| |
| Build Small Hackathon submission (Backyard AI track): |
| A small-model down-port of ~/ai-news-agent. The production version uses |
| Groq Llama-3.3-70B; this version fits the same workflow under 32B params |
| using Qwen3-32B via Hugging Face Inference Providers. |
| |
| Same pipeline as the every-2-hours cron the maker has running on a laptop: |
| fetch RSS / HN / arXiv / GitHub -> two-pass relevance filter + ranker -> |
| readable digest. Gradio is the delivery surface here instead of WhatsApp. |
| """ |
| from __future__ import annotations |
|
|
| import os |
| import time |
| from typing import Any |
|
|
| import gradio as gr |
| import pandas as pd |
|
|
| from config import ( |
| DEFAULT_BASE_URL, |
| DEFAULT_MODEL, |
| MIN_NEW_ITEMS, |
| PER_SOURCE_CAP, |
| ) |
| from digest import make_digest |
| from fetch import fetch_all |
| from rank import RankerConfig, rank_pipeline |
|
|
|
|
| |
| |
| |
|
|
|
|
| def run_briefing( |
| window_hours: int, |
| enabled_sources: list[str], |
| model: str, |
| hf_token: str, |
| ) -> dict[str, Any]: |
| """Fetch -> filter -> rank -> digest. Returns everything for the UI.""" |
| since_ts = time.time() - window_hours * 3600 |
| enabled = set(enabled_sources) if enabled_sources else {"rss", "hn", "arxiv", "github"} |
|
|
| t0 = time.perf_counter() |
| raw = fetch_all(since_ts, enabled=enabled) |
| fetch_latency = time.perf_counter() - t0 |
|
|
| cfg = RankerConfig( |
| base_url=DEFAULT_BASE_URL, |
| model=model or DEFAULT_MODEL, |
| api_key=hf_token or "", |
| ) |
| result = rank_pipeline(raw, cfg=cfg) |
|
|
| digest = "" |
| if result.after_rank >= MIN_NEW_ITEMS: |
| digest = make_digest(result.items, cfg=cfg) |
| elif result.after_rank > 0: |
| digest = make_digest(result.items, cfg=cfg) |
|
|
| return { |
| "digest": digest or "_(no high-signal items in window)_", |
| "items": result.items, |
| "raw_count": result.raw_count, |
| "after_filter": result.after_filter, |
| "after_rank": result.after_rank, |
| "fetch_latency": fetch_latency, |
| "filter_latency": result.filter_latency, |
| "rank_latency": result.rank_latency, |
| "model": cfg.model, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| def _items_to_df(items: list[dict]) -> pd.DataFrame: |
| if not items: |
| return pd.DataFrame(columns=["score", "source", "title", "reason", "url"]) |
| rows = [ |
| { |
| "score": it.get("score", 0), |
| "source": it.get("source", ""), |
| "title": it.get("title", ""), |
| "reason": it.get("reason", ""), |
| "url": it.get("url", ""), |
| } |
| for it in items |
| ] |
| return pd.DataFrame(rows) |
|
|
|
|
| def _stats_md(result: dict[str, Any]) -> str: |
| return ( |
| f"**Model:** `{result['model']}` \n" |
| f"**Raw items fetched:** {result['raw_count']} \n" |
| f"**Survived filter:** {result['after_filter']} \n" |
| f"**Survived rank (score ≥ 6):** {result['after_rank']} \n" |
| f"**Fetch latency:** {result['fetch_latency']:.1f}s \n" |
| f"**Filter latency:** {result['filter_latency']:.1f}s \n" |
| f"**Rank latency:** {result['rank_latency']:.1f}s \n" |
| f"**Total LLM time:** {result['filter_latency'] + result['rank_latency']:.1f}s" |
| ) |
|
|
|
|
| def _gradio_handler(window_hours, sources, model, hf_token): |
| try: |
| result = run_briefing( |
| window_hours=int(window_hours), |
| enabled_sources=list(sources or []), |
| model=(model or DEFAULT_MODEL).strip(), |
| hf_token=(hf_token or "").strip(), |
| ) |
| except Exception as e: |
| return ( |
| f"**Error:** `{e}`\n\nMake sure `HF_TOKEN` is set in Space secrets " |
| f"or pasted into the sidebar.", |
| pd.DataFrame(), |
| "_no run yet_", |
| ) |
| return result["digest"], _items_to_df(result["items"]), _stats_md(result) |
|
|
|
|
| |
| THEME = gr.themes.Soft( |
| primary_hue="orange", |
| secondary_hue="slate", |
| neutral_hue="zinc", |
| ).set( |
| body_background_fill="#0b1220", |
| body_text_color="#e2e8f0", |
| block_background_fill="#111827", |
| block_border_width="1px", |
| block_border_color="#1f2937", |
| button_primary_background_fill="#f97316", |
| button_primary_text_color="#0b1220", |
| ) |
|
|
|
|
| with gr.Blocks(theme=THEME, title="briefing-32 · Build Small entry") as demo: |
| gr.Markdown( |
| """ |
| # briefing-32 |
| **A 32B-class AI-news briefing the maker runs every 2 hours.** |
| |
| Build Small Hackathon entry (Backyard AI track). Down-ported from the |
| production `ai-news-agent` cron (Groq Llama-3.3-70B → WhatsApp) onto |
| Qwen3-32B served by Hugging Face Inference Providers. |
| |
| Pipeline: RSS + HN + arXiv + GitHub → cheap relevance filter → |
| graded 0–10 ranker → readable digest. Two open-weight model calls, |
| no 70B cloud round-trip required. |
| """ |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### Controls") |
| window_hours = gr.Slider( |
| minimum=1, maximum=72, value=2, step=1, |
| label="Window (hours back)", |
| info="Production runs every 2hr — match that for the authentic story.", |
| ) |
| sources = gr.CheckboxGroup( |
| choices=["rss", "hn", "arxiv", "github"], |
| value=["rss", "hn", "arxiv", "github"], |
| label="Sources", |
| ) |
| model = gr.Textbox( |
| value=DEFAULT_MODEL, |
| label="Model (≤32B params)", |
| info="Default Qwen3-32B. Swap to Qwen3-30B-A3B for faster MoE inference.", |
| ) |
| hf_token = gr.Textbox( |
| label="HF_TOKEN (optional — reads env if blank)", |
| placeholder="hf_…", |
| type="password", |
| ) |
| run_btn = gr.Button("Run briefing", variant="primary") |
|
|
| gr.Markdown("### Run stats") |
| stats = gr.Markdown("_no run yet_") |
|
|
| with gr.Column(scale=2): |
| gr.Markdown("### Digest") |
| digest = gr.Markdown( |
| value="_Click **Run briefing** to fetch the last N hours of AI news, " |
| "rank it on a ≤32B model, and render a readable briefing._" |
| ) |
| gr.Markdown("### Ranked items") |
| items_df = gr.Dataframe( |
| headers=["score", "source", "title", "reason", "url"], |
| value=pd.DataFrame(columns=["score", "source", "title", "reason", "url"]), |
| wrap=True, |
| interactive=False, |
| ) |
|
|
| run_btn.click( |
| _gradio_handler, |
| inputs=[window_hours, sources, model, hf_token], |
| outputs=[digest, items_df, stats], |
| ) |
|
|
| gr.Markdown( |
| """ |
| --- |
| *Build Small Hackathon · Backyard AI track. Apache 2.0.* |
| Code: [github.com/MukundaKatta/briefing-32](https://github.com/MukundaKatta/briefing-32) |
| """ |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.queue(max_size=8).launch( |
| server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"), |
| server_port=int(os.environ.get("PORT", "7860")), |
| ) |
|
|